The main entry point to Oak security is the SecurityProvider, which is registered to the Oak repository upon creation. The provider is in charge of collecting and exposing all security related modules present in a given Oak repository.
Each security module comes with one or multiple SecurityConfiguration(s) that are registered with the provider, identified (and possibly aggregated) by their name.
Currently Oak comes with the following built-in security modules, each defined by a dedicated sub-interfaces of SecurityConfiguration:
The package org.apache.jackrabbit.oak.spi.security defines the following interfaces and base implementations:
The SecurityProvider is the key to Oak security by providing access to the individual security modules and the configurations associated. Since version 1.3.7 Oak provides two implementations of the SecurityProvider suited for OSGi and non-OSGi setup, respectively.
Since Oak 1.3.7 the core bundle will install a dedicated OSGi component (SecurityProviderRegistration, labeled “Apache Jackrabbit Oak SecurityProvider”), which registers the SecurityProvider once all mandatory references have successfully been resolved. This new approach addresses issues present with the initial security provider implementation and has been backported to existing branches (see OAK-3201 and OAK-3441).
While optional configuration setting can be changed or extended at runtime, modules and extensions considered required for a functional security setup, need to be listed in the “Required Service PIDs” property. This asserts both reliable security setup and proper initialization of the individual modules. See also sections Configuration and Pluggability below.
In a non-OSGi setup the SecurityProvider (be it the default or a custom implementation) gets passed to the repository constructor. See section pluggability for details wrt module initialization.
The following example has been extracted from the basic test setup:
NodeStore nodeStore = ... ConfigurationParameters params = ... // TODO: provide config options SecurityProvider sp = new SecurityProviderImpl(params); // Optional: bind additional/custom implementations of the supported `SecurityConfiguration`s Repository repository = new Jcr(nodeStore).with(sp).createRepository();
The SecurityConfiguration interface defines functionality common to all security related modules. Apart from simple configuration parameters the basic interface defines the means to properly initialize a given security module and provide Oak internal mechanism to asserts proper validation and data consistency for all security relevant operations.
Please note, that RepositoryInitializer and WorkspaceInitializer as exposed by the base interface are only invoked upon repository|workspace initialization and consequently impact the pluggability of a given security module. Examples includes modules that require modifications to the global repository content such as node types, namespaces and privileges or require the installation of initial content or index definitions for proper execution.
The following subinterfaces of SecurityConfiguration are currently defined by Oak:
While Oak ships default implementation for all security configurations listed above, only authentication and authorization are mandatory for a functional Oak repository.
This is compliant with the security requirements defined by JSR 283 which defines API to login into the repository and mandates minimal permission evaluation, be it implementation specific of imposed by the optional access control management.
The minimal security setup may consequently be reduced to a setup as defined by the following imaginary, custom SecurityProvider (see also OpenSecurityProvider)
public class MySecurityProvider implements SecurityProvider { [...] public <T> T getConfiguration(Class<T> configClass) { if (AuthenticationConfiguration.class == configClass) { return (T) new MyAuthentication(); } else if (AuthorizationConfiguration.class == configClass) { return (T) new MyAuthorization(); } else { throw new IllegalArgumentException(); } } private final class MyAuthentication extends SecurityConfiguration.Default implements AuthenticationConfiguration { [...] } private final class MyAuthorization extends SecurityConfiguration.Default implements AuthorizationConfiguration { public AccessControlManager getAccessControlManager(Root root, NamePathMapper namePathMapper) { throw new UnsupportedOperationException(); } public RestrictionProvider getRestrictionProvider() { throw new UnsupportedOperationException(); } public PermissionProvider getPermissionProvider(Root root, String workspaceName, Set<Principal> principals) { return MyPermissionProvider.getInstance(principals); } } }
All other security modules can be considered optional from an Oak repository point of view. Please note the following dependencies and special cases:
The configuration parameters of individual security modules are described in the corresponding sections. The following paragraphs describe the configuration of SecurityProvider and CompositeConfiguration in an OSGi-base setup.
Parameter | Type | Default | Description |
---|---|---|---|
Required Services | String[] | see below | Service references mandatory for the SecurityProvider registration. |
The value of the individual configuration entries can be one of:
By default the SecurityProviderRegistration defines the following mandatory services. As long as these required references are not resolved the SecurityProviderRegistration will not register the SecurityProvider service and ultimately prevent premature initialization of the Oak repository instance.
The value of this configuration parameter needs to be adjusted for any additional module or functionality that is considered required for a successful security setup. See section pluggability below.
Parameter | Type | Default | Description | |
---|---|---|---|---|
Authorization Composition Type | String (AND | OR) | AND | The Composite Authorization model uses this flag to determine what type of logic to apply to the existing providers |
Given a set of permission providers, the composite model can aggregate the results by applying an AND logic (for example all providers must allow a specific privilege in order to be granted), or an OR (for example any provider can allow a privilege). By default the AND version is used.
Parameter | Type | Default | Description |
---|---|---|---|
PARAM_RANKING | int | NO_RANKING (Integer.MIN_VALUE) | Optional configuration parameter to define the ranking within the aggregation. |
Note: Security modules that allow for multiple configurations may choose to expose the PARAM_RANKING option in order to allow for explicit ordering of the individual implementations. If the ranking parameter is omitted the CompositeConfiguration will try to use the SERVICE_RANKING to define the order. If neither is available (or set to NO_RANKING) the new entry will be appended to the list.
In a default setup Oak allows to plug custom or additional implementations of the various SecurityConfiguration described before. Similarly it would be possible to provide a custom SecurityProvider.
Please note: this is only recommended for experts having in-depth understanding of Oak internals and which understand the security risk associated with custom replacements or extensions.
The default SecurityProvider service could be replaced by deploying a custom service to the OSGi container. In a non-OSGi setup the JCR|Oak repository needs to be created with the custom implementation:
SecurityProvider sp = new MySecurityProvider(); Repository repository = new Jcr().with(sp).createRepository();
The default Oak security setup distinguishes between the following types of modules:
Plugging an implementation of an unary module will replace the default provided by Oak. As far as the multiple modules are concerned a custom implementation plugged into the repository will result in the creation of a CompositeConfiguration. The aggregated modules are kept in a list while the insertion order is defined by the PARAM_RANKING or by the OSGi service ranking in case the explicit ranking parameter is missing.
The following steps are required to replace an existing unary security component or add an additional implementation (for multiple configurations only):
In this case the individual security modules get “manually” bound/unbound to the SecurityProvider instance. The provider itself might be the default or a custom implementation of the interface. If and how security modules can be added to the provider remains an implementation detail and is not part of the SecurityProvider interface definition.
Extend the default SecurityProvider with a custom PrincipalConfiguration. See also oak-exercise module for an example.
SecurityProvider sp = new SecurityProviderImpl(); sp.bindPrincipalConfiguration(new MyPrincipalConfiguration()); Repository repository = new Jcr().with(sp).createRepository();
If a given security modules mandates repository and|or workspace initialization steps such as e.g. node type registration or persisting new index definitions, the deployment of the module requires a reload of the SecurityProvider.
In the default OSGi-based setup this is achieved by adding the PID of corresponding service to the Required Service PIDs property mentioned above ultimately forcing the re-registration of the SecurityProvider service with the bundle context.
Other setup scenarios would need to recreate the ContentRepository object or adjust the repository ‘initial’ content before binding the new configuration to the SecurityProvider in order to avoid inconsistencies.