The concept of restriction has been created as extension to JCR access control management in order to refine the effect of individual access control entries.
Quoting from JSR 283 section 16.6.2 Permissions:
[…] the permissions encompass the restrictions imposed by privileges, but also include any additional policy-internal refinements with effects too fine-grained to be exposed through privilege discovery. A common case may be to provide finer-grained access restrictions to individual properties or child nodes of the node to which the policy applies.
Furthermore the restriction concept is aimed to allow for custom extensions of the default access control implementation to meet project specific needs without having to implement the common functionality provided by JCR.
Existing and potential examples of restrictions limiting the effect of a given access control entry during permission evaluation include:
The set of built-in restrictions present with Jackrabbit 2.x has extended as of Oak 1.0 along with some extensions of the Jackrabbit API. This covers the public facing usage of restrictions i.e. access control management.
In addition Oak provides it’s own restriction API that adds support for internal validation and permission evaluation.
The Jackrabbit API add the following extensions to JCR access control management to read and create entries with restrictions:
The following public interfaces are provided by Oak in the package org.apache.jackrabbit.oak.spi.security.authorization.restriction and provide support for pluggable restrictions both for access control management and the repository internal permission evaluation:
Oak 1.0 provides the following base implementations:
Apart from the fact that the internal Jackrabbit extension has been replaced by a public API, the restriction implementation in Oak differs from Jackrabbit 2.x as follows:
The default implementations of the Restriction interface are present with Oak 1.0 access control management:
For a nodePath /foo the following results can be expected for the different values of rep:glob.
rep:glob | Result |
---|---|
"" | matches node /foo only |
/cat | the node /foo/cat and all it’s children |
/cat/ | the descendants of the node /foo/cat |
cat | the node /foocat and all it’s children |
cat/ | all descendants of the node /foocat |
* | foo, siblings of foo and their descendants |
/*cat | all children of /foo whose path ends with ‘cat’ |
/*/cat | all non-direct descendants of /foo named ‘cat’ |
/cat* | all descendant path of /foo that have the direct foo-descendant segment starting with ‘cat’ |
*cat | all siblings and descendants of foo that have a name ending with ‘cat’ |
*/cat | all descendants of /foo and foo’s siblings that have a name segment ‘cat’ |
cat/* | all descendants of ‘/foocat’ |
/cat/* | all descendants of ‘/foo/cat’ |
*cat/* | all descendants of /foo that have an intermediate segment ending with ‘cat’ |
All restrictions defined by default in a Oak repository are stored as properties in a dedicated rep:restriction child node of the target access control entry node. Similarly, they are represented with the corresponding permission entry. The node type definition used to represent restriction content is as follows:
[rep:ACE] - rep:principalName (STRING) protected mandatory - rep:privileges (NAME) protected mandatory multiple - rep:nodePath (PATH) protected /* deprecated in favor of restrictions */ - rep:glob (STRING) protected /* deprecated in favor of restrictions */ - * (UNDEFINED) protected /* deprecated in favor of restrictions */ + rep:restrictions (rep:Restrictions) = rep:Restrictions protected /* since oak 1.0 */ /** * @since oak 1.0 */ [rep:Restrictions] - * (UNDEFINED) protected - * (UNDEFINED) protected multiple
The default security setup as present with Oak 1.0 is able to provide custom RestrictionProvider implementations and will automatically combine the different implementations using the CompositeRestrictionProvider.
In an OSGi setup the following steps are required in order to add a action provider implementation:
Please make sure to consider the following recommendations when implementing a custom RestrictionProvider: - restrictions are part of the overall permission evaluation and thus may heavily impact overall read/write performance - the hashCode generation of the base implementation (RestrictionImpl.hashCode) relies on PropertyStateValue.hashCode, which includes the internal String representation, which is not optimal for binaries (see also OAK-5784)
Simple example of a RestrictionProvider that defines a single time-based Restriction, which is expected to have 2 values defining a start and end date, which can then be used to allow or deny access within the given time frame.
@Component @Service(RestrictionProvider.class) public class MyRestrictionProvider extends AbstractRestrictionProvider { public MyRestrictionProvider() { super(supportedRestrictions()); } private static Map<String, RestrictionDefinition> supportedRestrictions() { RestrictionDefinition dates = new RestrictionDefinitionImpl("dates", Type.DATES, false); return Collections.singletonMap(dates.getName(), dates); } //------------------------------------------------< RestrictionProvider >--- @Override public RestrictionPattern getPattern(String oakPath, Tree tree) { if (oakPath != null) { PropertyState property = tree.getProperty("dates"); if (property != null) { return DatePattern.create(property); } } return RestrictionPattern.EMPTY; } @Nonnull @Override public RestrictionPattern getPattern(@Nullable String oakPath, @Nonnull Set<Restriction> restrictions) { if (oakPath != null) { for (Restriction r : restrictions) { String name = r.getDefinition().getName(); if ("dates".equals(name)) { return DatePattern.create(r.getProperty()); } } } return RestrictionPattern.EMPTY; } // TODO: implementing 'validateRestrictions(String oakPath, Tree aceTree)' would allow to make sure the property contains 2 date values. }
The time-based RestrictionPattern used by the example provider above.
class DatePattern implements RestrictionPattern { private final Date start; private final Date end; private DatePattern(@Nonnull Calendar start, @Nonnull Calendar end) { this.start = start.getTime(); this.end = end.getTime(); } static RestrictionPattern create(PropertyState timeProperty) { if (timeProperty.count() == 2) { return new DatePattern( Conversions.convert(timeProperty.getValue(Type.DATE, 0), Type.DATE).toCalendar(), Conversions.convert(timeProperty.getValue(Type.DATE, 1), Type.DATE).toCalendar() ); } else { return RestrictionPattern.EMPTY; } } @Override public boolean matches(@Nonnull Tree tree, @Nullable PropertyState property) { return matches(); } @Override public boolean matches(@Nonnull String path) { return matches(); } @Override public boolean matches() { Date d = new Date(); return d.after(start) && d.before(end); } };
RestrictionProvider rProvider = CompositeRestrictionProvider.newInstance(new MyRestrictionProvider(), ...); Map<String, RestrictionProvider> authorizMap = ImmutableMap.of(PARAM_RESTRICTION_PROVIDER, rProvider); ConfigurationParameters config = ConfigurationParameters.of(ImmutableMap.of(AuthorizationConfiguration.NAME, ConfigurationParameters.of(authorizMap))); SecurityProvider securityProvider = new SecurityProviderImpl(config)); Repository repo = new Jcr(new Oak()).with(securityProvider).createRepository();