Oak 1.0 comes with a extension to the Jackrabbit user management API that allows to perform additional actions or validations upon common user management tasks such as
Similar functionality has been present in Jackrabbit 2.x as internal interface. Compared to the Jackrabbit interface the new AuthorizableAction has been slightly adjusted to match Oak requirements operate directly on the Oak API, which eases the handling of implementation specific tasks such as writing protected items.
The following public interfaces are provided by Oak in the package org.apache.jackrabbit.oak.spi.security.user.action:
The AuthorizableAction interface itself allows to perform validations or write additional application specific content while executing user management related write operations. Therefore these actions are executed as part of the transient user management modifications. This contrasts to org.apache.jackrabbit.oak.spi.commit.CommitHooks which in turn are only triggered once modifications are persisted.
Consequently, implementations of the AuthorizableAction interface are expected to adhere to this rule and perform transient repository operation or validation. They must not force changes to be persisted by calling org.apache.jackrabbit.oak.api.Root.commit().
See section Group Actions for a related extension to monitor group specific operations.
Oak 1.0 provides the following base implementations:
The following implementations of the AuthorizableAction interface are provided:
As in Jackrabbit 2.x the actions are executed with the editing session and the target operation will fail if any of the configured actions fails (e.g. due to insufficient permissions by the editing Oak ContentSession).
The default security setup as present with Oak 1.0 is able to provide custom AuthorizableActionProvider implementations and will automatically combine the different implementations using the CompositeActionProvider.
In an OSGi setup the following steps are required in order to add an action provider implementation:
@Component() @Service(AuthorizableActionProvider.class) public class MyAuthorizableActionProvider implements AuthorizableActionProvider { private static final String PUBLIC_PROFILE_NAME = "publicProfileName"; private static final String PRIVATE_PROFILE_NAME = "privateProfileName"; private static final String FRIENDS_PROFILE_NAME = "friendsProfileName"; @Property(name = PUBLIC_PROFILE_NAME, value = "publicProfile") private String publicName; @Property(name = PRIVATE_PROFILE_NAME, value = "privateProfile") private String privateName; @Property(name = FRIENDS_PROFILE_NAME, value = "friendsProfile") private String friendsName; private ConfigurationParameters config = ConfigurationParameters.EMPTY; public MyAuthorizableActionProvider() {} public MyAuthorizableActionProvider(ConfigurationParameters config) { this.config = config; } //-----------------------------------------< AuthorizableActionProvider >--- @Override public List<? extends AuthorizableAction> getAuthorizableActions(SecurityProvider securityProvider) { AuthorizableAction action = new ProfileAction(publicName, privateName, friendsName); action.init(securityProvider, config); return Collections.singletonList(action); } //----------------------------------------------------< SCR Integration >--- @Activate private void activate(Map<String, Object> properties) { config = ConfigurationParameters.of(properties); } }
This example action generates additional child nodes upon user/group creation that will later be used to store various target-specific profile information:
class ProfileAction extends AbstractAuthorizableAction { private final String publicName; private final String privateName; private final String friendsName; ProfileAction(@Nullable String publicName, @Nullable String privateName, @Nullable String friendsName) { this.publicName = publicName; this.privateName = privateName; this.friendsName = friendsName; } @Override public void onCreate(Group group, Root root, NamePathMapper namePathMapper) throws RepositoryException { createProfileNodes(group.getPath(), root); } @Override public void onCreate(User user, String password, Root root, NamePathMapper namePathMapper) throws RepositoryException { createProfileNodes(user.getPath(), root); } private void createProfileNodes(@Nonnull String authorizablePath, @Nonnull Root root) throws AccessDeniedException { Tree tree = root.getTree(authorizablePath); if (tree.exists()) { NodeUtil authorizableNode = new NodeUtil(tree); if (publicName != null) { authorizableNode.addChild(publicName, NodeTypeConstants.NT_OAK_UNSTRUCTURED); } if (privateName != null) { authorizableNode.addChild(privateName, NodeTypeConstants.NT_OAK_UNSTRUCTURED); } if (friendsName != null) { authorizableNode.addChild(friendsName, NodeTypeConstants.NT_OAK_UNSTRUCTURED); } } }
Map<String, Object> userParams = new HashMap<String, Object>(); userParams.put(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, new MyAuthorizableActionProvider()); ConfigurationParameters config = ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, ConfigurationParameters.of(userParams))); SecurityProvider securityProvider = new SecurityProviderImpl(config)); Repository repo = new Jcr(new Oak()).with(securityProvider).createRepository();