You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2011/10/21 11:38:37 UTC

svn commit: r1187257 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/config/ main/java/org/apache/jackrabbit/core/security/user/ main/java/org/apache/jackrabbit/core/security/user/a...

Author: angela
Date: Fri Oct 21 09:38:37 2011
New Revision: 1187257

URL: http://svn.apache.org/viewvc?rev=1187257&view=rev
Log:
JCR-3118 : Configurable actions upon authorizable creation and removal

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AccessControlAction.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/ClearMembershipAction.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/UserManagerConfig.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/config/SecurityConfigTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java Fri Oct 21 09:38:37 2011
@@ -63,6 +63,7 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl;
 import org.apache.jackrabbit.core.security.user.MembershipCache;
 import org.apache.jackrabbit.core.security.user.UserManagerImpl;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -427,19 +428,20 @@ public class DefaultSecurityManager impl
     }   
 
     /**
-     * @param workspaceName
+     * @param workspaceName The name of the target workspace.
      * @return The system user manager. Since this implementation stores users
      * in a dedicated workspace the system user manager is the same for all
      * sessions irrespective of the workspace.
+     * @throws javax.jcr.RepositoryException If an error occurs.
      */
     protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException {
         return systemUserManager;
     }
 
     /**
-     * @param session
-     * @return
-     * @throws RepositoryException
+     * @param session The session for which to retrieve the membership cache.
+     * @return The membership cache.
+     * @throws RepositoryException If an error occurs.
      */
     protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException {
         if (session == systemSession || session instanceof SystemSession) {
@@ -474,14 +476,18 @@ public class DefaultSecurityManager impl
                     Properties.class,
                     MembershipCache.class};
             um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class,
-                    paramTypes, (SessionImpl) session, adminId, params,
-                    getMembershipCache(session));
+                    paramTypes, session, adminId, params, getMembershipCache(session));
             // TODO: should we make sure the implementation doesn't allow
             // TODO: to change the autosave behavior? since the user manager
             // TODO: writes to a separate workspace this would cause troubles.
         } else {
             um = new UserManagerImpl(session, adminId, params, getMembershipCache(session));
         }
+
+        if (umc != null && !(session instanceof SystemSession)) {
+            AuthorizableAction[] actions = umc.getAuthorizableActions();
+            um.setAuthorizableActions(actions);
+        }
         return um;
     }
 
@@ -592,7 +598,7 @@ public class DefaultSecurityManager impl
      * Make sure the system users (admin and anonymous) exist.
      *
      * @param userManager Manager to create users/groups.
-     * @param session
+     * @param session The editing session.
      * @param adminId UserID of the administrator.
      * @param anonymousId UserID of the anonymous user.
      * @throws RepositoryException If an error occurs.
@@ -602,11 +608,11 @@ public class DefaultSecurityManager impl
                                   String adminId,
                                   String anonymousId) throws RepositoryException {
 
-        Authorizable admin = null;
+        Authorizable admin;
         if (adminId != null) {
             admin = userManager.getAuthorizable(adminId);
             if (admin == null) {
-                admin = userManager.createUser(adminId, adminId);
+                userManager.createUser(adminId, adminId);
                 if (!userManager.isAutoSave()) {
                     session.save();
                 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java Fri Oct 21 09:38:37 2011
@@ -29,6 +29,7 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.user.MembershipCache;
 import org.apache.jackrabbit.core.security.user.UserPerWorkspaceUserManager;
 import org.apache.jackrabbit.core.security.user.UserManagerImpl;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
 
 import javax.jcr.Credentials;
 import javax.jcr.Repository;
@@ -219,7 +220,8 @@ public class UserPerWorkspaceSecurityMan
     protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException {
         UserManagerConfig umc = getConfig().getUserManagerConfig();
         Properties params = (umc == null) ? null : umc.getParameters();
-        
+
+        UserManagerImpl umgr;
         // in contrast to the DefaultSecurityManager users are not retrieved
         // from a dedicated workspace: the system session of each workspace must
         // get a system user manager that asserts the existence of the admin user.
@@ -229,11 +231,17 @@ public class UserPerWorkspaceSecurityMan
                     String.class,
                     Properties.class,
                     MembershipCache.class};
-            return (UserPerWorkspaceUserManager) umc.getUserManager(UserPerWorkspaceUserManager.class,
-                    paramTypes, (SessionImpl) session, adminId, params, getMembershipCache(session));
+            umgr = (UserPerWorkspaceUserManager) umc.getUserManager(UserPerWorkspaceUserManager.class,
+                    paramTypes, session, adminId, params, getMembershipCache(session));
         } else {
-            return new UserPerWorkspaceUserManager(session, adminId, params, getMembershipCache(session));
+            umgr = new UserPerWorkspaceUserManager(session, adminId, params, getMembershipCache(session));
+        }
+
+        if (umc != null && !(session instanceof SystemSession)) {
+            AuthorizableAction[] actions = umc.getAuthorizableActions();
+            umgr.setAuthorizableActions(actions);
         }
+        return umgr;
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java Fri Oct 21 09:38:37 2011
@@ -32,6 +32,8 @@ import javax.xml.parsers.DocumentBuilder
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
 /**
@@ -350,6 +352,37 @@ public class ConfigurationParser {
     }
 
     /**
+     * Returns the named child of the given parent element.
+     *
+     * @param parent parent element
+     * @param name name of the child element
+     * @param required indicates if the child element is required
+     * @return named child element, or <code>null</code> if not found and
+     *         <code>required</code> is <code>false</code>.
+     * @throws ConfigurationException if the child element is not found and
+     *         <code>required</code> is <code>true</code>;
+     *         or if more than one element with this name exists.
+     */
+    protected Element[] getElements(Element parent, String name, boolean required)
+            throws ConfigurationException {
+        NodeList children = parent.getChildNodes();
+        List<Element> found = new ArrayList<Element>();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() == Node.ELEMENT_NODE
+                    && name.equals(child.getNodeName())) {
+                found.add((Element) child);
+            }
+        }
+        if (required && found.isEmpty()) {
+            throw new ConfigurationException(
+                    "Configuration element " + name + " not found in "
+                    + parent.getNodeName() + ".");
+        }
+        return found.toArray(new Element[found.size()]);
+    }
+
+    /**
      * Returns the value of the named attribute of the given element.
      *
      * @param element element

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java Fri Oct 21 09:38:37 2011
@@ -193,6 +193,12 @@ public class RepositoryConfigurationPars
     private static final String AC_PROVIDER_ELEMENT = "AccessControlProvider";
 
     /**
+     * Optional configuration elements with the user manager configuration.
+     * @see org.apache.jackrabbit.core.security.user.action.AuthorizableAction
+     */
+    private static final String AUTHORIZABLE_ACTION = "AuthorizableAction";
+
+    /**
      * The repositories {@link ConnectionFactory}. 
      */
     protected final ConnectionFactory connectionFactory;
@@ -419,7 +425,12 @@ public class RepositoryConfigurationPars
             UserManagerConfig umc = null;
             element = getElement(smElement, USER_MANAGER_ELEMENT, false);
             if (element != null) {
-                umc = new UserManagerConfig(parseBeanConfig(smElement, USER_MANAGER_ELEMENT));
+                Element[] acElements = getElements(element, AUTHORIZABLE_ACTION, false);
+                BeanConfig[] aaConfig = new BeanConfig[acElements.length];
+                for (int i = 0; i < acElements.length; i++) {
+                    aaConfig[i] = parseBeanConfig(acElements[i]);
+                }
+                umc = new UserManagerConfig(parseBeanConfig(element), aaConfig);
             }
 
             BeanConfig uidcc = null;
@@ -531,7 +542,8 @@ public class RepositoryConfigurationPars
      * Parse workspace config.
      *
      * @param root root element of the workspace configuration
-     *
+     * @return The workspace configuration
+     * @throws ConfigurationException
      * @see #parseWorkspaceConfig(InputSource)
      */
     protected WorkspaceConfig parseWorkspaceConfig(Element root)

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/UserManagerConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/UserManagerConfig.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/UserManagerConfig.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/UserManagerConfig.java Fri Oct 21 09:38:37 2011
@@ -16,11 +16,14 @@
  */
 package org.apache.jackrabbit.core.config;
 
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.jackrabbit.api.security.user.UserManager;
 
 import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * User manager configuration. This bean configuration class is used to
@@ -39,9 +42,16 @@ public class UserManagerConfig extends B
 
     private Constructor<?> constr;
 
+    private final BeanConfig[] actionConfig;
+
     public UserManagerConfig(BeanConfig config) {
+        this(config, null);
+    }
+
+    public UserManagerConfig(BeanConfig config, BeanConfig[] actionConfig) {
         super(config);
         setValidate(false); // omit validation of the config properties
+        this.actionConfig = actionConfig;
     }
 
     /**
@@ -89,4 +99,17 @@ public class UserManagerConfig extends B
             throw new ConfigurationException("Invalid UserManager implementation '" + getClassName() + "'.", e);
         }
     }
+
+    public AuthorizableAction[] getAuthorizableActions() throws ConfigurationException {
+        if (actionConfig == null || actionConfig.length == 0) {
+            return new AuthorizableAction[0];
+        } else {
+            List<AuthorizableAction> actions = new ArrayList<AuthorizableAction>(actionConfig.length);
+            for (BeanConfig c : actionConfig) {
+                AuthorizableAction action = c.newInstance(AuthorizableAction.class);
+                actions.add(action);
+            }
+            return actions.toArray(new AuthorizableAction[actions.size()]);
+        }
+    }
 }
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java Fri Oct 21 09:38:37 2011
@@ -266,6 +266,7 @@ abstract class AuthorizableImpl implemen
             throw new RepositoryException("The administrator cannot be removed.");
         }
         Session s = getSession();
+        userManager.onRemove(this);
         node.remove();
         if (userManager.isAutoSave()) {
             s.save();

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Fri Oct 21 09:38:37 2011
@@ -31,6 +31,7 @@ import org.apache.jackrabbit.core.Sessio
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
 import org.apache.jackrabbit.core.session.SessionOperation;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
@@ -256,9 +257,20 @@ public class UserManagerImpl extends Pro
      */
     private final int groupMembershipSplitSize;
 
+    /**
+     * The membership cache.
+     */
     private final MembershipCache membershipCache;
 
     /**
+     * Authorizable actions that will all be executed upon creation and removal
+     * of authorizables in the order they are contained in the array.<p/>
+     * Note, that if {@link #isAutoSave() autosave} is turned on, the configured
+     * actions are executed before persisting the creation or removal.
+     */
+    private AuthorizableAction[] authorizableActions = new AuthorizableAction[0];
+
+    /**
      * Create a new <code>UserManager</code> with the default configuration.
      *
      * @param session The editing/reading session.
@@ -380,6 +392,18 @@ public class UserManagerImpl extends Pro
         return groupMembershipSplitSize;
     }
 
+    /**
+     * Set the authorizable actions that will be invoked upon authorizable
+     * creation and removal.
+     *
+     * @param authorizableActions An array of authorizable actions.
+     */
+    public void setAuthorizableActions(AuthorizableAction[] authorizableActions) {
+        if (authorizableActions != null) {
+            this.authorizableActions = authorizableActions;
+        }
+    }
+
     //--------------------------------------------------------< UserManager >---
     /**
      * @see UserManager#getAuthorizable(String)
@@ -535,6 +559,7 @@ public class UserManagerImpl extends Pro
             setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
 
             User user = createUser(userNode);
+            onCreate(user);
             if (isAutoSave()) {
                 session.save();
             }
@@ -616,6 +641,7 @@ public class UserManagerImpl extends Pro
             }
 
             Group group = createGroup(groupNode);
+            onCreate(group);
             if (isAutoSave()) {
                 session.save();
             }
@@ -1004,6 +1030,35 @@ public class UserManagerImpl extends Pro
         return n;
     }
 
+    //--------------------------------------------------------------------------
+    /**
+     * Let the configured <code>AuthorizableAction</code>s perform additional
+     * tasks associated with the creation of the new authorizable before the
+     * corresponding new node is persisted.
+     *
+     * @param authorizable The new authorizable.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void onCreate(Authorizable authorizable) throws RepositoryException {
+        for (AuthorizableAction action : authorizableActions) {
+            action.onCreate(authorizable, session);
+        }
+    }
+
+    /**
+     * Let the configured <code>AuthorizableAction</code>s perform any clean
+     * up tasks related to the authorizable removal (before the corresponding
+     * node gets removed).
+     *
+     * @param authorizable The authorizable to be removed.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void onRemove(Authorizable authorizable) throws RepositoryException {
+        for (AuthorizableAction action : authorizableActions) {
+            action.onRemove(authorizable, session);
+        }
+    }
+
     //----------------------------------------------------< SessionListener >---
     /**
      * @see SessionListener#loggingOut(org.apache.jackrabbit.core.SessionImpl)

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AccessControlAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AccessControlAction.java?rev=1187257&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AccessControlAction.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AccessControlAction.java Fri Oct 21 09:38:37 2011
@@ -0,0 +1,156 @@
+/*
+ *
+ *  * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  * contributor license agreements.  See the NOTICE file distributed with
+ *  * this work for additional information regarding copyright ownership.
+ *  * The ASF licenses this file to You under the Apache License, Version 2.0
+ *  * (the "License"); you may not use this file except in compliance with
+ *  * the License.  You may obtain a copy of the License at
+ *  *
+ *  *      http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+package org.apache.jackrabbit.core.security.user.action;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.core.security.principal.UnknownPrincipal;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <code>AccessControlAction</code>
+ */
+public class AccessControlAction implements AuthorizableAction {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(AccessControlAction.class);
+
+    private String[] groupPrivilegeNames = new String[0];
+    private String[] userPrivilegeNames = new String[0];
+
+    /**
+     * Create a new instance.
+     */
+    public AccessControlAction() {}
+
+    //-------------------------------------------------< AuthorizableAction >---
+    /**
+     * @see AuthorizableAction#onCreate(org.apache.jackrabbit.api.security.user.Authorizable, javax.jcr.Session)
+     */
+    public void onCreate(Authorizable authorizable, Session session) throws RepositoryException {
+        Node aNode;
+        String path = authorizable.getPath();
+
+        JackrabbitAccessControlList acl = null;
+        AccessControlManager acMgr = session.getAccessControlManager();
+        for (AccessControlPolicyIterator it = acMgr.getApplicablePolicies(path); it.hasNext();) {
+            AccessControlPolicy plc = it.nextAccessControlPolicy();
+            if (plc instanceof JackrabbitAccessControlList) {
+                acl = (JackrabbitAccessControlList) plc;
+                break;
+            }
+        }
+
+        if (acl == null) {
+            log.warn("Cannot process AccessControlAction: no applicable ACL at " + path);
+        } else {
+            // setup acl according to configuration.
+            Principal principal = new UnknownPrincipal(authorizable.getPrincipal().getName());
+            boolean modified = false;
+            if (authorizable.isGroup()) {
+                // new authorizable is a Group
+                if (groupPrivilegeNames.length > 0) {
+                    modified = acl.addAccessControlEntry(principal, getPrivileges(groupPrivilegeNames, acMgr));
+                }
+            } else {
+                // new authorizable is a User
+                if (userPrivilegeNames.length > 0) {
+                    modified = acl.addAccessControlEntry(principal, getPrivileges(userPrivilegeNames, acMgr));
+                }
+            }
+            if (modified) {
+                acMgr.setPolicy(path, acl);
+            }
+        }
+    }
+
+    /**
+     * @see AuthorizableAction#onRemove(org.apache.jackrabbit.api.security.user.Authorizable, javax.jcr.Session)
+     */
+    public void onRemove(Authorizable authorizable, Session session) throws RepositoryException {
+        // nothing to do.
+    }
+
+    //--------------------------------------------------------< Bean Config >---
+
+    public void setGroupPrivilegeNames(String privilegeNames) {
+        if (privilegeNames != null && privilegeNames.length() > 0) {
+            groupPrivilegeNames = split(privilegeNames);
+        }
+
+    }
+
+    public void setUserPrivilegeNames(String privilegeNames) {
+        if (privilegeNames != null && privilegeNames.length() > 0) {
+            userPrivilegeNames = split(privilegeNames);
+        }
+    }
+
+    //------------------------------------------------------------< private >---
+    /**
+     * Retrieve privileges for the specified privilege names.
+     *
+     * @param privNames
+     * @param acMgr
+     * @return Array of <code>Privilege</code>
+     * @throws javax.jcr.RepositoryException If a privilege name cannot be
+     * resolved to a valid privilege.
+     */
+    private static Privilege[] getPrivileges(String[] privNames, AccessControlManager acMgr) throws RepositoryException {
+        if (privNames == null || privNames.length == 0) {
+            return new Privilege[0];
+        }
+        Privilege[] privileges = new Privilege[privNames.length];
+        for (int i = 0; i < privNames.length; i++) {
+            privileges[i] = acMgr.privilegeFromName(privNames[i]);
+        }
+        return privileges;
+    }
+
+    /**
+     *
+     * @param configParam
+     * @return
+     */
+    private static String[] split(String configParam) {
+        List<String> nameList = new ArrayList<String>();
+        for (String pn : Text.explode(configParam, ',', false)) {
+            String privName = pn.trim();
+            if (privName.length()  > 0) {
+                nameList.add(privName);
+            }
+        }
+        return nameList.toArray(new String[nameList.size()]);
+    }
+}
\ No newline at end of file

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java?rev=1187257&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java Fri Oct 21 09:38:37 2011
@@ -0,0 +1,54 @@
+/*
+ *
+ *  * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  * contributor license agreements.  See the NOTICE file distributed with
+ *  * this work for additional information regarding copyright ownership.
+ *  * The ASF licenses this file to You under the Apache License, Version 2.0
+ *  * (the "License"); you may not use this file except in compliance with
+ *  * the License.  You may obtain a copy of the License at
+ *  *
+ *  *      http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+package org.apache.jackrabbit.core.security.user.action;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * <code>AuthorizableAction</code>...
+ */
+public interface AuthorizableAction {
+
+    /**
+     * Allows to add application specific modifications associated with the
+     * creation of a new authorizable. Note, that this method is called
+     * <strong>before</strong> any <code>Session.save</code> call.
+     *
+     * @param authorizable The new authorizable that has not yet been persisted;
+     * e.g. the associated node is still 'NEW'.
+     * @param session The editing session associated with the user manager.
+     * @throws RepositoryException If an error occurs.
+     */
+    void onCreate(Authorizable authorizable, Session session) throws RepositoryException;
+
+    /**
+     * Allows to add application specific behavior associated with the removal
+     * of an authorizable. Note, that this method is called <strong>before</strong>
+     * {@link Authorizable#remove} is executed (and persisted); thus the
+     * target authorizable still exists.
+     *
+     * @param authorizable The authorizable to be removed.
+     * @param session The editing session associated with the user manager.
+     * @throws RepositoryException If an error occurs.
+     */
+    void onRemove(Authorizable authorizable, Session session) throws RepositoryException;
+}
\ No newline at end of file

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/ClearMembershipAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/ClearMembershipAction.java?rev=1187257&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/ClearMembershipAction.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/ClearMembershipAction.java Fri Oct 21 09:38:37 2011
@@ -0,0 +1,62 @@
+/*
+ *
+ *  * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  * contributor license agreements.  See the NOTICE file distributed with
+ *  * this work for additional information regarding copyright ownership.
+ *  * The ASF licenses this file to You under the Apache License, Version 2.0
+ *  * (the "License"); you may not use this file except in compliance with
+ *  * the License.  You may obtain a copy of the License at
+ *  *
+ *  *      http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+package org.apache.jackrabbit.core.security.user.action;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import java.util.Iterator;
+
+/**
+ * <code>ClearMembershipAction</code>...
+ */
+public class ClearMembershipAction implements AuthorizableAction {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(ClearMembershipAction.class);
+
+    /**
+     * Create a new instance.
+     */
+    public ClearMembershipAction() {}
+
+    //-------------------------------------------------< AuthorizableAction >---
+    /**
+     * @see AuthorizableAction#onCreate(org.apache.jackrabbit.api.security.user.Authorizable, javax.jcr.Session)
+     */
+    public void onCreate(Authorizable authorizable, Session session) throws RepositoryException {
+        // nothing to do
+    }
+
+    /**
+     * @see AuthorizableAction#onRemove(org.apache.jackrabbit.api.security.user.Authorizable, javax.jcr.Session)
+     */
+    public void onRemove(Authorizable authorizable, Session session) throws RepositoryException {
+        Iterator<Group> membership = authorizable.declaredMemberOf();
+        while (membership.hasNext()) {
+            membership.next().removeMember(authorizable);
+        }
+    }
+}
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/config/SecurityConfigTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/config/SecurityConfigTest.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/config/SecurityConfigTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/config/SecurityConfigTest.java Fri Oct 21 09:38:37 2011
@@ -29,6 +29,7 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.authentication.DefaultLoginModule;
 import org.apache.jackrabbit.core.security.simple.SimpleAccessManager;
 import org.apache.jackrabbit.core.security.simple.SimpleSecurityManager;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.UserManager;
@@ -207,6 +208,30 @@ public class SecurityConfigTest extends 
         assertTrue(um.isAutoSave());
         // changing autosave behavior must succeed.
         um.autoSave(false);
+
+        // test authorizable-action configuration
+        xml = parseXML(new InputSource(new StringReader(USER_MANAGER_CONFIG_WITH_ACTIONS)), true);
+        umc = parser.parseSecurityConfig(xml).getSecurityManagerConfig().getUserManagerConfig();
+        AuthorizableAction[] actions = umc.getAuthorizableActions();
+        assertEquals(2, actions.length);
+
+        xml = parseXML(new InputSource(new StringReader(USER_MANAGER_CONFIG_WITH_INVALID_ACTIONS)), true);
+        umc = parser.parseSecurityConfig(xml).getSecurityManagerConfig().getUserManagerConfig();
+        try {
+            actions = umc.getAuthorizableActions();
+            fail("Invalid configuration - must fail");
+        } catch (ConfigurationException e) {
+            // success
+        }
+
+        xml = parseXML(new InputSource(new StringReader(USER_MANAGER_CONFIG_WITH_INVALID_ACTIONS_2)), true);
+        umc = parser.parseSecurityConfig(xml).getSecurityManagerConfig().getUserManagerConfig();
+        try {
+            actions = umc.getAuthorizableActions();
+            fail("Invalid configuration - must fail");
+        } catch (ConfigurationException e) {
+            // success
+        }
     }
 
     /**
@@ -356,6 +381,42 @@ public class SecurityConfigTest extends 
                     "        </SecurityManager>" +
                     "    </Security>";
 
+    private static final String USER_MANAGER_CONFIG_WITH_ACTIONS =
+            "    <Security appName=\"Jackrabbit\">" +
+            "        <SecurityManager class=\"org.apache.jackrabbit.core.DefaultSecurityManager\" workspaceName=\"security\">" +
+            "           <UserManager class=\"org.apache.jackrabbit.core.security.user.UserManagerImpl\">" +
+            "              <AuthorizableAction class=\"org.apache.jackrabbit.core.security.user.action.AccessControlAction\">" +
+            "                 <param name=\"groupPrivilegeNames\" value=\"jcr:read, jcr:write\"/>" +
+            "                 <param name=\"userPrivilegeNames\" value=\" jcr:read    ,  jcr:readAccessControl  \"/>" +
+            "              </AuthorizableAction>" +
+            "              <AuthorizableAction class=\"org.apache.jackrabbit.core.security.user.action.ClearMembershipAction\"/>" +
+            "           </UserManager>" +
+            "           <UserIdClass class=\"org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal\"/>" +
+            "        </SecurityManager>" +
+            "    </Security>";
+
+    private static final String USER_MANAGER_CONFIG_WITH_INVALID_ACTIONS =
+            "    <Security appName=\"Jackrabbit\">" +
+            "        <SecurityManager class=\"org.apache.jackrabbit.core.DefaultSecurityManager\" workspaceName=\"security\">" +
+            "           <UserManager class=\"org.apache.jackrabbit.core.security.user.UserManagerImpl\">" +
+            "              <AuthorizableAction class=\"org.apache.jackrabbit.core.security.user.action.NonExistingAction\"/>" +
+            "           </UserManager>" +
+            "           <UserIdClass class=\"org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal\"/>" +
+            "        </SecurityManager>" +
+            "    </Security>";
+
+    private static final String USER_MANAGER_CONFIG_WITH_INVALID_ACTIONS_2 =
+            "    <Security appName=\"Jackrabbit\">" +
+            "        <SecurityManager class=\"org.apache.jackrabbit.core.DefaultSecurityManager\" workspaceName=\"security\">" +
+            "           <UserManager class=\"org.apache.jackrabbit.core.security.user.UserManagerImpl\">" +
+            "              <AuthorizableAction class=\"org.apache.jackrabbit.core.security.user.action.AccessControlAction\">" +
+            "                 <param name=\"invalidParam\" value=\"any value\"/>" +
+            "              </AuthorizableAction>" +
+            "           </UserManager>" +
+            "           <UserIdClass class=\"org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal\"/>" +
+            "        </SecurityManager>" +
+            "    </Security>";
+
     private static final String PRINCIPAL_PROVIDER_CONFIG =
             "    <Security appName=\"Jackrabbit\">" +
             "        <SecurityManager class=\"org.apache.jackrabbit.core.DefaultSecurityManager\" workspaceName=\"security\">" +

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java?rev=1187257&r1=1187256&r2=1187257&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java Fri Oct 21 09:38:37 2011
@@ -25,6 +25,9 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.TestPrincipal;
 import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.core.security.user.action.AccessControlAction;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
+import org.apache.jackrabbit.core.security.user.action.ClearMembershipAction;
 import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
 
@@ -32,8 +35,13 @@ import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
 import javax.jcr.Node;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
 import java.security.Principal;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -854,4 +862,105 @@ public class UserManagerImplTest extends
             }
         }
     }
+
+    public void testAccessControlAction() throws Exception {
+        UserManagerImpl impl = (UserManagerImpl) userMgr;
+
+        AccessControlAction action = new AccessControlAction();
+        action.setUserPrivilegeNames("jcr:all");
+        action.setGroupPrivilegeNames("jcr:read");
+        AuthorizableAction[] testActions = new AuthorizableAction[] {action};
+
+        User u = null;
+        Group gr = null;
+        try {
+            impl.setAuthorizableActions(testActions);
+            String uid = getTestPrincipal().getName();
+            u = impl.createUser(uid, buildPassword(uid));
+            save(superuser);
+            assertAcAction(u, impl);
+
+            String grId = getTestPrincipal().getName();
+            gr = impl.createGroup(grId);
+            save(superuser);
+            assertAcAction(gr, impl);
+        } catch (UnsupportedRepositoryOperationException e) {
+            throw new NotExecutableException(e.getMessage());
+        } finally {
+            impl.setAuthorizableActions(new AuthorizableAction[0]);
+            if (u != null) {
+                u.remove();
+            }
+            if (gr != null) {
+                gr.remove();
+            }
+            save(superuser);
+        }
+    }
+
+    private static void assertAcAction(Authorizable a, UserManagerImpl umgr) throws RepositoryException, NotExecutableException {
+        Session s = umgr.getSession();
+        AccessControlManager acMgr = s.getAccessControlManager();
+        boolean hasACL = false;
+        AccessControlPolicyIterator it = acMgr.getApplicablePolicies("/");
+        while (it.hasNext()) {
+            if (it.nextAccessControlPolicy() instanceof AccessControlList) {
+                hasACL = true;
+                break;
+            }
+        }
+
+        if (!hasACL) {
+            for (AccessControlPolicy p : acMgr.getPolicies("/")) {
+                if (p instanceof AccessControlList) {
+                    hasACL = true;
+                    break;
+                }
+            }
+        }
+
+        if (!hasACL) {
+            throw new NotExecutableException("No ACLs in workspace containing users.");
+        }
+
+        String path = a.getPath();
+        assertEquals(1, acMgr.getPolicies(path).length);
+        assertTrue(acMgr.getPolicies(path)[0] instanceof AccessControlList);
+    }
+
+    public void testClearMembershipAction() throws Exception {
+        UserManagerImpl impl = (UserManagerImpl) userMgr;
+
+        AuthorizableAction[] testActions = new AuthorizableAction[] {new ClearMembershipAction()};
+
+        User u = null;
+        Group gr = null;
+        try {
+            impl.setAuthorizableActions(testActions);
+            String uid = getTestPrincipal().getName();
+            u = impl.createUser(uid, buildPassword(uid));
+
+            String grId = getTestPrincipal().getName();
+            gr = impl.createGroup(grId);
+            gr.addMember(u);
+
+            save(superuser);
+
+            assertTrue(gr.isMember(u));
+
+            u.remove();
+            u = null;
+
+            assertFalse(gr.isMember(u));
+        } finally {
+            impl.setAuthorizableActions(new AuthorizableAction[0]);
+            if (u != null) {
+                u.remove();
+            }
+            if (gr != null) {
+                gr.remove();
+            }
+            save(superuser);
+        }
+    }
 }