Oak 1.6 comes with an extension to the Jackrabbit user management API that allows to perform additional actions or validations upon group member management tasks such as
The following public interface is provided by Oak in the package org.apache.jackrabbit.oak.spi.security.user.action:
The GroupAction interface extends from AuthorizableAction and itself allows to perform validations or write additional application specific content while executing group member 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 GroupAction interface are expected to adhere to this rule and perform transient repository operations or validation. They must not force changes to be persisted by calling org.apache.jackrabbit.oak.api.Root.commit().
Any group 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).
Oak 1.5 provides the following base implementation for GroupAction implementations to build upon:
Refer to Authorizable Actions | Pluggability for details on how to plug a new group action into the system.
During import the group actions are called in the same fashion as for regular groups as long as the member reference can be resolved to an existing authorizable. Member IDs of authorizables that do not exist at group import time or failed member IDs are passed to the group actions if ImportBehavior.BESTEFFORT is set for the import.
This example action creates or removes asset home directories for members added to or removed from a specific group:
public class CreateHomeForMemberGroupAction extends AbstractGroupAction { private static final String GROUP_ID = "asset-editors"; private static final String ASSET_ROOT = "/content/assets"; private SecurityProvider securityProvider; @Override public void init(SecurityProvider securityProvider, ConfigurationParameters config) { this.securityProvider = securityProvider; } @Override public void onMemberAdded(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException { createHome(group, root, member.getID(), namePathMapper); } @Override public void onMembersAdded(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException { createHome(group, root, memberIds, failedIds, namePathMapper); } @Override public void onMemberRemoved(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException { removeHome(group, root, member.getID(), namePathMapper); } @Override public void onMembersRemoved(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException { removeHome(group, root, memberIds, failedIds, namePathMapper); } private void createHome(Group group, Root root, String memberId, NamePathMapper namePathMapper) throws RepositoryException { createHome(group, root, Lists.newArrayList(memberId), Lists.<String>newArrayList(), namePathMapper); } private void createHome(Group group, Root root, Iterable<String> memberIds, Iterable<String> failedIds, NamePathMapper namePathMapper) throws RepositoryException { if (GROUP_ID.equals(group.getID())) { UserManager userManager = securityProvider.getConfiguration(UserConfiguration.class).getUserManager(root, namePathMapper); for (String memberId : memberIds) { Authorizable authorizable = userManager.getAuthorizable(memberId); if (authorizable != null && !authorizable.isGroup()) { // Note: this is done with the editing session of the group modification and may not // be the desired session / privilege level with which to perform these actions. NodeUtil assetRoot = new NodeUtil(root.getTree(ASSET_ROOT)); NodeUtil home = assetRoot.addChild(memberId, NodeTypeConstants.NT_OAK_UNSTRUCTURED); // ... } } } } private void removeHome(Group group, Root root, String memberId, NamePathMapper namePathMapper) { removeHome(group, root, Lists.newArrayList(memberId), Lists.<String>newArrayList(), namePathMapper); } private void removeHome(Group group, Root root, Iterable<String> memberIds, Iterable<String> failedIds, NamePathMapper namePathMapper) { } }