Fork me on GitHub

Authorizable Actions

Overview

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

  • create authorizables
  • remove authorizables
  • change a user's password

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.

AuthorizableAction API

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 and User Actions for user specific operations.

Default Implementations

Oak 1.0 provides the following base implementations:

  • AbstractAuthorizableAction: abstract base implementation that doesn't perform any action.
  • DefaultAuthorizableActionProvider: default action provider service that allows to enable the built-in actions provided with oak.
  • CompositeActionProvider: Allows to aggregate multiple provider implementations.

Changes wrt Jackrabbit 2.x

  • actions no longer operate on JCR API but rather on the Oak API direct.
  • provider interface simplifies pluggability

Built-in AuthorizableAction Implementations

The following implementations of the AuthorizableAction interface are provided:

  • AccessControlAction: set up permission for new authorizables
  • PasswordValidationAction: simplistic password verification upon user creation and password modification
  • PasswordChangeAction: verifies that the new password is different from the old one
  • ClearMembershipAction: clear group membership upon removal of an authorizable. Note, that this will only remove those membership references that are visible to the editing session.

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).

Pluggability

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:

  • implement AuthorizableActionProvider interface exposing your custom action(s).
  • make the provider implementation an OSGi service and make it available to the Oak repository.
  • make sure the AuthorizableActionProvider is listed as required service with the SecurityProvider (see also Introduction)
Examples
Example Action Provider
@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);
    }
}
Example Action

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);
            }
        }
    }
Example Non-OSGI Setup
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 = SecurityProviderBuilder.newBuilder().with(config).build();
Repository repo = new Jcr(new Oak()).with(securityProvider).createRepository();