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 2008/08/27 17:12:07 UTC

svn commit: r689499 [5/11] - in /jackrabbit/trunk: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/ jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/retention/ jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/sec...

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java?rev=689499&r1=689498&r2=689499&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java Wed Aug 27 08:12:04 2008
@@ -16,17 +16,18 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
-import org.apache.jackrabbit.api.jsr283.security.AccessControlEntry;
 import org.apache.jackrabbit.api.jsr283.security.AccessControlException;
 import org.apache.jackrabbit.api.jsr283.security.Privilege;
-import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlEntry;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlList;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SecurityItemModifier;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
 import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
-import org.apache.jackrabbit.core.security.authorization.PolicyEntry;
-import org.apache.jackrabbit.core.security.authorization.PolicyTemplate;
+import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
+import org.apache.jackrabbit.core.security.authorization.JackrabbitAccessControlEntry;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.conversion.NameException;
@@ -42,9 +43,6 @@
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
 import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * <code>ACLEditor</code>...
@@ -58,65 +56,86 @@
     /**
      * Default name for ace nodes
      */
-    private static final String DEFAULT_PERMISSION_NAME = "permission";
-
-    protected final SessionImpl session;
-    private final PrincipalManager principalManager;
+    private static final String DEFAULT_ACE_NAME = "ace";
+    /**
+     * the editing session
+     */
+    private final SessionImpl session;
+    private final PrivilegeRegistry privilegeRegistry;
+    private final AccessControlUtils utils;
 
-    protected ACLEditor(Session editingSession) throws RepositoryException {
+    ACLEditor(Session editingSession, AccessControlUtils utils) {
         if (editingSession instanceof SessionImpl) {
             session = ((SessionImpl) editingSession);
-            principalManager = ((SessionImpl) editingSession).getPrincipalManager();
+            // TODO: review and find better solution
+            privilegeRegistry = new PrivilegeRegistry(session);
         } else {
             throw new IllegalArgumentException("org.apache.jackrabbit.core.SessionImpl expected. Found " + editingSession.getClass());
         }
+        this.utils = utils;
+    }
+
+    /**
+     *
+     * @param aclNode
+     * @return
+     * @throws RepositoryException
+     */
+    AccessControlList getACL(NodeImpl aclNode) throws RepositoryException {
+        return new ACLTemplate(aclNode, privilegeRegistry);
     }
 
     //------------------------------------------------< AccessControlEditor >---
     /**
-     * @see AccessControlEditor#getPolicyTemplate(String)
+     * @see AccessControlEditor#getPolicies(String)
      * @param nodePath
      */
-    public PolicyTemplate getPolicyTemplate(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
+    public AccessControlPolicy[] getPolicies(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
         checkProtectsNode(nodePath);
 
-        PolicyTemplate tmpl = null;
         NodeImpl aclNode = getAclNode(nodePath);
-        if (aclNode != null) {
-            tmpl = new ACLTemplate(aclNode, Collections.EMPTY_SET);
+        if (aclNode == null) {
+            return new AccessControlPolicy[0];
+        } else {
+            return new AccessControlPolicy[] {getACL(aclNode)};
         }
-        return tmpl;
     }
 
     /**
-     * @see AccessControlEditor#editPolicyTemplate(String)
+     * @see AccessControlEditor#editAccessControlPolicies(String)
      * @param nodePath
      */
-    public PolicyTemplate editPolicyTemplate(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
+    public AccessControlPolicy[] editAccessControlPolicies(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
         checkProtectsNode(nodePath);
 
-        PolicyTemplate tmpl;
+        AccessControlPolicy acl;
         NodeImpl aclNode = getAclNode(nodePath);
         if (aclNode == null) {
-            tmpl = new ACLTemplate(nodePath);
+            // create an empty acl
+            acl = new ACLTemplate(nodePath, session.getPrincipalManager(), privilegeRegistry);
         } else {
-            tmpl = new ACLTemplate(aclNode, Collections.EMPTY_SET);
+            acl = getACL(aclNode);
         }
-        return tmpl;
+        return new AccessControlPolicy[] {acl};
     }
 
     /**
-     * @see AccessControlEditor#editPolicyTemplate(Principal)
+     * @see AccessControlEditor#editAccessControlPolicies(Principal)
      */
-    public PolicyTemplate editPolicyTemplate(Principal principal) throws AccessDeniedException, AccessControlException, RepositoryException {
-        throw new AccessControlException("Unable to edit policy for principal " + principal.getName());
+    public AccessControlPolicy[] editAccessControlPolicies(Principal principal) throws AccessDeniedException, AccessControlException, RepositoryException {
+        if (!session.getPrincipalManager().hasPrincipal(principal.getName())) {
+            throw new AccessControlException("Unknown principal.");
+        }
+        // TODO: impl. missing
+        return new AccessControlPolicy[0];
     }
 
     /**
-     * @see AccessControlEditor#setPolicyTemplate(String,PolicyTemplate)
+     * @see AccessControlEditor#setPolicy(String,AccessControlPolicy)
      */
-    public void setPolicyTemplate(String nodePath, PolicyTemplate template) throws RepositoryException {
+    public void setPolicy(String nodePath, AccessControlPolicy policy) throws RepositoryException {
         checkProtectsNode(nodePath);
+        checkValidPolicy(nodePath, policy);
 
         NodeImpl aclNode = getAclNode(nodePath);
         /* in order to assert that the parent (ac-controlled node) gets modified
@@ -130,18 +149,21 @@
         // now (re) create it
         aclNode = createAclNode(nodePath);
 
-        PolicyEntry[] entries = template.getEntries();
+        AccessControlEntry[] entries = ((ACLTemplate) policy).getAccessControlEntries();
         for (int i = 0; i < entries.length; i++) {
-            ACEImpl ace = (ACEImpl) entries[i];
-            // TODO: improve
+            JackrabbitAccessControlEntry ace = (JackrabbitAccessControlEntry) entries[i];
+
             Name nodeName = getUniqueNodeName(aclNode, ace.isAllow() ? "allow" : "deny");
             Name ntName = (ace.isAllow()) ? NT_REP_GRANT_ACE : NT_REP_DENY_ACE;
             ValueFactory vf = session.getValueFactory();
 
+            // create the ACE node
             NodeImpl aceNode = addSecurityNode(aclNode, nodeName, ntName);
+
             // write the rep:principalName property
             String principalName = ace.getPrincipal().getName();
             setSecurityProperty(aceNode, P_PRINCIPAL_NAME, vf.createValue(principalName));
+
             // ... and the rep:privileges property
             Privilege[] pvlgs = ace.getPrivileges();
             Value[] names = getPrivilegeNames(pvlgs, vf);
@@ -150,114 +172,56 @@
     }
 
     /**
-     * @see AccessControlEditor#removePolicyTemplate(String)
+     * @see AccessControlEditor#removePolicy(String,AccessControlPolicy)
      */
-    public PolicyTemplate removePolicyTemplate(String nodePath) throws AccessControlException, RepositoryException {
+    public synchronized void removePolicy(String nodePath, AccessControlPolicy policy) throws AccessControlException, RepositoryException {
         checkProtectsNode(nodePath);
+        checkValidPolicy(nodePath, policy);
 
-        PolicyTemplate tmpl = null;
         NodeImpl aclNode = getAclNode(nodePath);
         if (aclNode != null) {
-            // need to build the template in order to have a return value.
-            tmpl = new ACLTemplate(aclNode, Collections.EMPTY_SET);
             removeSecurityItem(aclNode);
-        }
-        return tmpl;
-    }
-
-    /**
-     * @see AccessControlEditor#getAccessControlEntries(String)
-     */
-    public AccessControlEntry[] getAccessControlEntries(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
-        PolicyTemplate pt = getPolicyTemplate(nodePath);
-        if (pt == null) {
-            return new AccessControlEntry[0];
         } else {
-            PolicyEntry[] entries = pt.getEntries();
-            List l = new ArrayList();
-            for (int i = 0; i < entries.length; i++) {
-                if (entries[i].isAllow()) {
-                    l.add(entries[i]);
-                }
-            }
-            return (AccessControlEntry[]) l.toArray(new AccessControlEntry[l.size()]);
+            throw new AccessControlException("No policy to remove at " + nodePath);
         }
     }
 
-    /**
-     * @see AccessControlEditor#addAccessControlEntry(String,Principal,Privilege[])
-     */
-    public AccessControlEntry addAccessControlEntry(String nodePath, Principal principal, Privilege[] privileges) throws AccessControlException, PathNotFoundException, RepositoryException {
-        // JSR 283 requires that the principal is known TODO: check again.
-        if (!principalManager.hasPrincipal(principal.getName())) {
-            throw new AccessControlException("Principal " + principal.getName() + " does not exist.");
-        }
-
-        ACLTemplate pt = (ACLTemplate) editPolicyTemplate(nodePath);
-        // TODO: check again. maybe these 'grant-ACE' should be stored/evaluated separated
-        int privs = PrivilegeRegistry.getBits(privileges);
-        /*
-        since added access control entry may never remove privileges that are
-        granted by the policy -> retrieve existing allow entries and add
-        the new privileges to be granted.
-        Reason: PolicyTemplate#setEntry does in fact overwrite (which is fine
-        when editing the policy itself, but wrong when adding ACEs over the JCR-API.
-        */
-        ACEImpl[] existing = pt.getEntries(principal);
-        for (int i = 0; i < existing.length; i++) {
-            if (existing[i].isAllow()) {
-                privs |= existing[i].getPrivilegeBits();
-            }
-        }
-
-        pt.setEntry(new ACEImpl(principal, privs, true));
-        setPolicyTemplate(nodePath, pt);
-        ACEImpl[] tmpls = pt.getEntries(principal);
-        for (int i = 0; i < tmpls.length; i++) {
-            if (tmpls[i].isAllow()) {
-                return tmpls[i];
-            }
-        }
-        // should never get here
-        throw new AccessControlException("Internal error: No access control entry added.");
-    }
-
-
-    /**
-     * @see AccessControlEditor#removeAccessControlEntry(String,AccessControlEntry)
-     */
-    public boolean removeAccessControlEntry(String nodePath, AccessControlEntry entry) throws AccessControlException, PathNotFoundException, RepositoryException {
-        if (!(entry instanceof ACEImpl)) {
-            throw new AccessControlException("Unknown AccessControlEntry implementation.");
-        }
-        // TODO: check again. maybe these 'grant-ACE' should be removed separated
-        PolicyTemplate pt = editPolicyTemplate(nodePath);
-        boolean removed = pt.removeEntry((ACEImpl) entry);
-        if (removed) {
-            setPolicyTemplate(nodePath, pt);
-        }
-        return removed;
-    }
-
     //--------------------------------------------------------------------------
     /**
-     * Test if the Node identified by <code>id</code> is itself part of ACL
+     * Check if the Node identified by <code>nodePath</code> is itself part of ACL
      * defining content. It this case setting or modifying an AC-policy is
      * obviously not possible.
      *
      * @param nodePath
-     * @throws AccessControlException If the given id identifies a Node that
+     * @throws AccessControlException If the given nodePath identifies a Node that
      * represents a ACL or ACE item.
      * @throws RepositoryException
      */
     private void checkProtectsNode(String nodePath) throws RepositoryException {
         NodeImpl node = getNode(nodePath);
-        if (ACLProvider.protectsNode(node)) {
+        if (utils.isAcItem(node)) {
             throw new AccessControlException("Node " + nodePath + " defines ACL or ACE itself.");
         }
     }
 
     /**
+     * Check if the specified policy can be set/removed from this editor.
+     *
+     * @param nodePath
+     * @param policy
+     * @throws AccessControlException
+     */
+    private static void checkValidPolicy(String nodePath, AccessControlPolicy policy) throws AccessControlException {
+        if (policy == null || !(policy instanceof ACLTemplate)) {
+            throw new AccessControlException("Attempt to set/remove invalid policy " + policy);
+        }
+        ACLTemplate acl = (ACLTemplate) policy;
+        if (!nodePath.equals(acl.getPath())) {
+            throw new AccessControlException("Policy " + policy + " cannot be applied/removed from the node at " + nodePath);
+        }
+    }
+
+    /**
      *
      * @param path
      * @return
@@ -305,18 +269,18 @@
      * Create a unique valid name for the Permission nodes to be save.
      *
      * @param node a name for the child is resolved
-     * @param name if missing the {@link #DEFAULT_PERMISSION_NAME} is taken
+     * @param name if missing the {@link #DEFAULT_ACE_NAME} is taken
      * @return
      * @throws RepositoryException
      */
     protected static Name getUniqueNodeName(Node node, String name) throws RepositoryException {
         if (name == null) {
-            name = DEFAULT_PERMISSION_NAME;
+            name = DEFAULT_ACE_NAME;
         } else {
             try {
                 NameParser.checkFormat(name);
             } catch (NameException e) {
-                name = DEFAULT_PERMISSION_NAME;
+                name = DEFAULT_ACE_NAME;
                 log.debug("Invalid path name for Permission: " + name + ".");
             }
         }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java?rev=689499&r1=689498&r2=689499&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java Wed Aug 27 08:12:04 2008
@@ -16,14 +16,16 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.jsr283.security.AccessControlEntry;
 import org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy;
 import org.apache.jackrabbit.api.jsr283.security.Privilege;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlList;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlManager;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.core.NodeId;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.PropertyImpl;
+import org.apache.jackrabbit.core.ItemImpl;
+import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.observation.SynchronousEventListener;
 import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.authorization.AbstractAccessControlProvider;
@@ -33,18 +35,19 @@
 import org.apache.jackrabbit.core.security.authorization.AccessControlProvider;
 import org.apache.jackrabbit.core.security.authorization.CompiledPermissions;
 import org.apache.jackrabbit.core.security.authorization.Permission;
-import org.apache.jackrabbit.core.security.authorization.PolicyEntry;
-import org.apache.jackrabbit.core.security.authorization.PolicyTemplate;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
+import org.apache.jackrabbit.core.security.authorization.UnmodifiableAccessControlList;
+import org.apache.jackrabbit.core.security.authorization.AccessControlEntryIterator;
+import org.apache.jackrabbit.core.security.authorization.JackrabbitAccessControlEntry;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
 import org.apache.jackrabbit.util.Text;
+import org.apache.commons.collections.map.ListOrderedMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -55,14 +58,14 @@
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryManager;
 import java.security.Principal;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Arrays;
 
 /**
  * The ACLProvider generates access control policies out of the items stored
@@ -92,18 +95,27 @@
      */
     private static final Logger log = LoggerFactory.getLogger(ACLProvider.class);
 
-    private AccessControlEditor systemEditor;
+    /**
+     * the system acl editor.
+     */
+    private ACLEditor systemEditor;
 
     /**
      * The node id of the root node
      */
     private NodeId rootNodeId;
 
-    //--------------------------------------< AbstractAccessControlProvider >---
+    /**
+     * Flag indicating whether or not this provider should be create the default
+     * ACLs upon initialization.
+     */
+    private boolean initializedWithDefaults;
+
+    //-------------------------------------------------< AccessControlUtils >---
     /**
      * @see AbstractAccessControlProvider#isAcItem(Path)
      */
-    protected boolean isAcItem(Path absPath) throws RepositoryException {
+    public boolean isAcItem(Path absPath) throws RepositoryException {
         Path.Element[] elems = absPath.getElements();
         for (int i = 0; i < elems.length; i++) {
             if (N_POLICY.equals(elems[i].getName())) {
@@ -113,80 +125,55 @@
         return false;
     }
 
+    /**
+     * Test if the given node is itself a rep:ACL or a rep:ACE node.
+     * @see AbstractAccessControlProvider#isAcItem(ItemImpl)
+     */
+    public boolean isAcItem(ItemImpl item) throws RepositoryException {
+        NodeImpl n = ((item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent());
+        return n.isNodeType(NT_REP_ACL) || n.isNodeType(NT_REP_ACE);
+    }
+
     //----------------------------------------------< AccessControlProvider >---
     /**
      * @see AccessControlProvider#init(Session, Map)
      */
-    public void init(Session systemSession, Map options) throws RepositoryException {
-        super.init(systemSession, options);
+    public void init(Session systemSession, Map configuration) throws RepositoryException {
+        super.init(systemSession, configuration);
 
         // make sure the workspace of the given systemSession has a
         // minimal protection on the root node.
         NodeImpl root = (NodeImpl) session.getRootNode();
         rootNodeId = root.getNodeId();
-        systemEditor = new ACLEditor(systemSession);
-
-        if (!isAccessControlled(root)) {
+        systemEditor = new ACLEditor(systemSession, this);
+        initializedWithDefaults = !configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS);
+        if (initializedWithDefaults && !isAccessControlled(root)) {
             initRootACL(session, systemEditor);
         }
     }
 
     /**
-     * @see AccessControlProvider#getPolicy(Path)
+     * @see AccessControlProvider#getEffectivePolicies(Path)
      * @param absPath
      */
-    public AccessControlPolicy getPolicy(Path absPath) throws ItemNotFoundException, RepositoryException {
+    public AccessControlPolicy[] getEffectivePolicies(Path absPath) throws ItemNotFoundException, RepositoryException {
         checkInitialized();
-        return getACL(absPath);
-    }
 
-    /**
-     * @see AccessControlProvider#getAccessControlEntries(Path)
-     * @param absPath
-     */
-    public AccessControlEntry[] getAccessControlEntries(Path absPath) throws RepositoryException {
-        checkInitialized();
-        ACLImpl acl = getACL(absPath);
-
-        // TODO: check again what the expected return value would be.
-        // TODO: check again if correct. call probably expensive.
-        Map allowed = new HashMap();
-        Map denied = new HashMap();
-        for (Iterator it = acl.getEntries(); it.hasNext();) {
-            ACEImpl ace = (ACEImpl) it.next();
-            Principal pc = ace.getPrincipal();
-
-            int pv = ace.getPrivilegeBits();
-
-            int allowPv = (allowed.containsKey(pc)) ? ((Integer) allowed.get(pc)).intValue() : 0;
-            int denyPv = (denied.containsKey(pc)) ? ((Integer) denied.get(pc)).intValue() : 0;
-
-            // shortcut:
-            if (allowPv == PrivilegeRegistry.ALL) {
-                continue;
-            }
-
-            // if the ace is a granting ACE -> make sure the permissions
-            // it grants are not denied by another ACE
-            if (ace.isAllow()) {
-                // determined those allow-priv from the current ace, that have
-                // not been denied by an ace ealier in the evaluation.
-                allowPv |= PrivilegeRegistry.diff(pv, denyPv);
-                allowed.put(pc, new Integer(allowPv));
-            } else {
-                // determined those deny-priv from the current ace, that have
-                // not been granted by an ace ealier in the evaluation.
-                denyPv |= PrivilegeRegistry.diff(pv, allowPv);
-                denied.put(pc, new Integer(denyPv));
-            }
-        }
-
-        Set s = new HashSet();
-        for (Iterator it = allowed.keySet().iterator(); it.hasNext();) {
-            Principal p = (Principal) it.next();
-            s.add(new ACEImpl(p, ((Integer) allowed.get(p)).intValue(), true));
+        NodeImpl targetNode = (NodeImpl) session.getNode(session.getJCRPath(absPath));
+        NodeImpl node = getNode(targetNode);
+        List acls = new ArrayList();
+
+        // collect all ACLs effective at node
+        collectAcls(node, acls);
+        // if no effective ACLs are present -> add a default, empty acl.
+        if (acls.isEmpty()) {
+            // no access control information can be retrieved for the specified
+            // node, since neither the node nor any of its parents is access
+            // controlled -> build a default policy.
+            log.warn("No access controlled node present in item hierarchy starting from " + targetNode.getPath());
+            acls.add(new UnmodifiableAccessControlList(Collections.EMPTY_LIST));
         }
-        return (AccessControlEntry[]) s.toArray(new AccessControlEntry[s.size()]);
+        return (AccessControlList[]) acls.toArray(new AccessControlList[acls.size()]);
     }
 
     /**
@@ -194,12 +181,7 @@
      */
     public AccessControlEditor getEditor(Session session) {
         checkInitialized();
-        try {
-            return new ACLEditor(session);
-        } catch (RepositoryException e) {
-            log.debug("Unable to create AccessControlEditor.", e.getMessage());
-            return null;
-        }
+        return new ACLEditor(session, this);
     }
 
     /**
@@ -224,108 +206,58 @@
         if (isAdminOrSystem(principals)) {
             return true;
         } else {
-            return new AclPermissions(principals, false).grants(PathFactoryImpl.getInstance().getRootPath(), Permission.READ);
+            CompiledPermissions cp = new AclPermissions(principals, false);
+            return cp.grants(PathFactoryImpl.getInstance().getRootPath(), Permission.READ);
         }
     }
 
     //------------------------------------------------------------< private >---
-    /**
-     * Build the ACL that is effective on the Node at
-     * <code>absPath</code>. In contrast to {@link #getACL(NodeImpl, Set)}
-     * the returned ACL contains all entries that apply to that node.
-     *
-     * @param absPath
-     * @return
-     * @throws ItemNotFoundException
-     * @throws RepositoryException
-     */
-    private ACLImpl getACL(Path absPath) throws ItemNotFoundException, RepositoryException {
-        return getACL((NodeImpl) session.getNode(session.getJCRPath(absPath)),
-                Collections.EMPTY_SET);
-    }
 
     /**
-     * Build the ACL that is effective on the Node at
-     * <code>absPath</code>, but only retrieve those entries that apply to
-     * any of the principals whose name is present in the given
-     * <code>principalNameFilter</code>.
+     * Returns the given <code>targetNode</code> unless the node itself stores
+     * access control information in which case it's nearest non-ac-parent is
+     * searched and returned.
      *
-     * @param node
-     * @param principalNameFilter
+     * @param targetNode The node for which AC information needs to be retrieved.
      * @return
-     * @throws ItemNotFoundException
      * @throws RepositoryException
      */
-    private ACLImpl getACL(NodeImpl node, Set principalNameFilter) throws ItemNotFoundException, RepositoryException {
-        // -> build the acl for the Node
-        ACLImpl acl;
-        // check for special ACL building item
-        if (protectsNode(node)) {
-            NodeImpl parentNode;
-            if (node.isNodeType(NT_REP_ACL)) {
-                parentNode = (NodeImpl) node.getParent();
+    private NodeImpl getNode(NodeImpl targetNode) throws RepositoryException {
+        NodeImpl node;
+        if (isAcItem(targetNode)) {
+            if (targetNode.isNodeType(NT_REP_ACL)) {
+                node = (NodeImpl) targetNode.getParent();
             } else {
-                parentNode = (NodeImpl) node.getParent().getParent();
+                node = (NodeImpl) targetNode.getParent().getParent();
             }
-            ACLImpl baseACL = buildAcl(parentNode, principalNameFilter);
-            acl = new ACLImpl(node.getNodeId(), baseACL, true);
         } else {
-            // build Acl for non-protection node.
-            acl = buildAcl(node, principalNameFilter);
+            node = targetNode;
         }
-        return acl;
+        return node;
     }
 
     /**
-     * Constructs the ACLImpl for a regular node, i.e. a node that does not
-     * store itself ACL-related information. The ACL to be returned combines both
-     * the base-ACL containing the inherited access control information
-     * and the access control information provided with the given node itself.
+     * Recursively collects all ACLs that are effective on the specified node.
      *
-     * @param node the Node to build the ACL for, which must NOT be part of the
+     * @param node the Node to collect the ACLs for, which must NOT be part of the
      * structure defined by mix:AccessControllable.
-     * @param principalNameFilter
-     * @return acl or <code>DefaultACL</code> if neither the node nor any of it's
-     * parents is access controlled.
+     * @param acls List used to collect the effective acls.
      * @throws RepositoryException
      */
-    private ACLImpl buildAcl(NodeImpl node, Set principalNameFilter) throws RepositoryException {
-        // preconditions:
-        // - node is not null
-        // - node is never an ACL building item
-        NodeId id = (NodeId) node.getId();
-        // retrieve the base-ACL (i.e. the ACL that belongs to parentNode)
-        // for this find nearest access controlled parent.
-        ACLImpl baseACL = null;
-        NodeImpl parentNode = id.equals(rootNodeId) ? null : (NodeImpl) node.getParent();
-        while (parentNode != null && baseACL == null) {
-            if (isAccessControlled(parentNode)) {
-                baseACL = buildAcl(parentNode, principalNameFilter);
-            } else {
-                parentNode = (rootNodeId.equals(parentNode.getId())) ? null
-                        : (NodeImpl) parentNode.getParent();
-            }
-        }
-        // the build the effective ACL from the specified Node and the base ACL
-        ACLImpl acl;
+    private void collectAcls(NodeImpl node, List acls) throws RepositoryException {
+        // if the given node is access-controlled, construct a new ACL and add
+        // it to the list
         if (isAccessControlled(node)) {
-            // build acl from access controlled node
+            // build acl for the access controlled node
             NodeImpl aclNode = node.getNode(N_POLICY);
-            PolicyTemplate tmpl = new ACLTemplate(aclNode, principalNameFilter);
-            List localEntries = Arrays.asList(tmpl.getEntries());
-
-            acl = new ACLImpl(aclNode.getNodeId(), localEntries, baseACL, false);
-        } else if (baseACL != null) {
-            // build acl for a non-access controlled item that has a base acl
-            acl = new ACLImpl(id, baseACL, false);
-        } else {
-            // no access control information can be retrieved for the specified
-            // node, since neither the node nor any of its parents is access
-            // controlled -> build a default policy.
-            log.warn("No access controlled node present in item hierarchy starting from " + id);
-            acl = new DefaultACL(id);
+            AccessControlList acl = systemEditor.getACL(aclNode);
+            acls.add(new UnmodifiableAccessControlList(acl));
+        }
+        // then, recursively look for access controlled parents up the hierarchy.
+        if (!rootNodeId.equals(node.getId())) {
+            NodeImpl parentNode = (NodeImpl) node.getParent();
+            collectAcls(parentNode, acls);
         }
-        return acl;
     }
 
     /**
@@ -339,12 +271,15 @@
      * @param session to the workspace to set-up inital ACL to
      * @throws RepositoryException
      */
-    private static void initRootACL(JackrabbitSession session, AccessControlEditor editor) throws RepositoryException {
+    private static void initRootACL(SessionImpl session, AccessControlEditor editor) throws RepositoryException {
         try {
             log.info("Install initial ACL:...");
             String rootPath = session.getRootNode().getPath();
-            PolicyTemplate tmpl = editor.editPolicyTemplate(rootPath);
+            AccessControlPolicy[] acls = editor.editAccessControlPolicies(rootPath);
+            ACLTemplate acl = (ACLTemplate) acls[0];
+
             PrincipalManager pMgr = session.getPrincipalManager();
+            AccessControlManager acMgr = session.getAccessControlManager();
 
             log.info("... Privilege.ALL for administrators.");
             Principal administrators;
@@ -355,16 +290,15 @@
                 log.warn("Administrators principal group is missing.");
                 administrators = new PrincipalImpl(pName);
             }
-            PolicyEntry entr = new ACEImpl(administrators, PrivilegeRegistry.ALL, true);
-            tmpl.setEntry(entr);
+            Privilege[] privs = new Privilege[]{acMgr.privilegeFromName(Privilege.JCR_ALL)};
+            acl.addAccessControlEntry(administrators, privs);
 
             Principal everyone = pMgr.getEveryone();
-            // TODO: to be improved. how to define where everyone has read-access
             log.info("... Privilege.READ for everyone.");
-            entr = new ACEImpl(everyone, PrivilegeRegistry.READ, true);
-            tmpl.setEntry(entr);
+            privs = new Privilege[]{acMgr.privilegeFromName(Privilege.JCR_READ)};
+            acl.addAccessControlEntry(everyone, privs);
 
-            editor.setPolicyTemplate(rootPath, tmpl);
+            editor.setPolicy(rootPath, acl);
             session.save();
             log.info("... done.");
 
@@ -391,44 +325,31 @@
         return node.isNodeType(NT_REP_ACCESS_CONTROLLABLE) && node.hasNode(N_POLICY);
     }
 
-    /**
-     * Test if the given node is itself a rep:ACL or a rep:ACE node.
-     *
-     * @param node
-     * @return
-     * @throws RepositoryException
-     */
-    static boolean protectsNode(NodeImpl node) throws RepositoryException {
-        return node.isNodeType(NT_REP_ACL) || node.isNodeType(NT_REP_ACE);
-    }
-
     //------------------------------------------------< CompiledPermissions >---
     /**
      *
      */
     private class AclPermissions extends AbstractCompiledPermissions implements SynchronousEventListener {
 
-        private final Set principalNames;
+        private final List principalNames;
+        private final String jcrReadPrivilegeName;
 
         /**
          * flag indicating that there is not 'deny READ'.
          * -> simplify {@link #grants(Path, int)} in case of permissions == READ
          */
         private boolean readAllowed = false;
-        /**
-         * flag indicating if only READ is granted
-         * -> simplify {@link #grants(Path, int)} in case of permissions != READ
-         */
-        private boolean readOnly = false;
 
         private AclPermissions(Set principals) throws RepositoryException {
             this(principals, true);
         }
+
         private AclPermissions(Set principals, boolean listenToEvents) throws RepositoryException {
-            principalNames = new HashSet(principals.size());
+            principalNames = new ArrayList(principals.size());
             for (Iterator it = principals.iterator(); it.hasNext();) {
                 principalNames.add(((Principal) it.next()).getName());
             }
+            jcrReadPrivilegeName = session.getAccessControlManager().privilegeFromName(Privilege.JCR_READ).getName();
 
             if (listenToEvents) {
                 /*
@@ -436,12 +357,7 @@
                  is that everyone can READ everywhere -> makes evaluation for
                  the most common check (can-read) easy.
                 */
-                searchReadDeny(principalNames);
-                /*
-                Determine if there is any ACE node that grants another permission
-                than READ.
-                */
-                searchNonReadAllow(principalNames);
+                readAllowed = isReadAllowed(principalNames);
 
                 /*
                  Make sure this AclPermission recalculates the permissions if
@@ -462,79 +378,49 @@
         }
 
         /**
-         * Search if there is any ACE that defines permissions for any of the
-         * principals AND denies-READ.
+         * If this provider defines read-permission for everyone (defined upon
+         * init with default values), search if there is any ACE that defines
+         * permissions for any of the principals AND denies-READ. Otherwise
+         * this shortcut is not possible.
          *
          * @param principalnames
          */
-        private void searchReadDeny(Set principalnames) {
-            try {
-                QueryManager qm = session.getWorkspace().getQueryManager();
-                StringBuffer stmt = new StringBuffer("/jcr:root");
-                stmt.append("//element(*,");
-                stmt.append(resolver.getJCRName(NT_REP_DENY_ACE));
-                stmt.append(")[(");
-
-                // where the rep:principalName property exactly matches any of
-                // the given principalsNames
-                int i = 0;
-                Iterator itr = principalnames.iterator();
-                while (itr.hasNext()) {
-                    stmt.append("@").append(resolver.getJCRName(P_PRINCIPAL_NAME)).append(" eq ");
-                    stmt.append("'").append(itr.next().toString()).append("'");
-                    if (++i < principalnames.size()) {
-                        stmt.append(" or ");
+        private boolean isReadAllowed(Collection principalnames) {
+            boolean isReadAllowed = false;
+            if (initializedWithDefaults) {
+                try {
+                    QueryManager qm = session.getWorkspace().getQueryManager();
+                    StringBuffer stmt = new StringBuffer("/jcr:root");
+                    stmt.append("//element(*,");
+                    stmt.append(resolver.getJCRName(NT_REP_DENY_ACE));
+                    stmt.append(")[(");
+
+                    // where the rep:principalName property exactly matches any of
+                    // the given principalsNames
+                    int i = 0;
+                    Iterator itr = principalnames.iterator();
+                    while (itr.hasNext()) {
+                        stmt.append("@").append(resolver.getJCRName(P_PRINCIPAL_NAME)).append(" eq ");
+                        stmt.append("'").append(itr.next().toString()).append("'");
+                        if (++i < principalnames.size()) {
+                            stmt.append(" or ");
+                        }
                     }
-                }
-                // AND rep:privileges contains the READ privilege
-                stmt.append(") and @ ");
-                stmt.append(resolver.getJCRName(P_PRIVILEGES));
-                stmt.append(" = '").append(Privilege.READ).append("']");
-
-                Query q = qm.createQuery(stmt.toString(), Query.XPATH);
+                    // AND rep:privileges contains the READ privilege
+                    stmt.append(") and @");
+                    stmt.append(resolver.getJCRName(P_PRIVILEGES));
+                    stmt.append(" = '").append(jcrReadPrivilegeName).append("']");
 
-                NodeIterator it = q.execute().getNodes();
-                readAllowed =  !it.hasNext();
-            } catch (RepositoryException e) {
-                log.error(e.toString());
-                // unable to determine... -> no shortcut upon grants
-                readAllowed = false;
-            }
-        }
+                    Query q = qm.createQuery(stmt.toString(), Query.XPATH);
 
-        private void searchNonReadAllow(Set principalnames) {
-            try {
-                QueryManager qm = session.getWorkspace().getQueryManager();
-                StringBuffer stmt = new StringBuffer("/jcr:root");
-                stmt.append("//element(*,");
-                stmt.append(resolver.getJCRName(NT_REP_GRANT_ACE));
-                stmt.append(")[(");
-                // where the rep:principalName property exactly matches any of
-                // the given principalsNames
-                int i = 0;
-                Iterator itr = principalnames.iterator();
-                while (itr.hasNext()) {
-                    stmt.append("@").append(resolver.getJCRName(P_PRINCIPAL_NAME)).append(" eq ");
-                    stmt.append("'").append(itr.next().toString()).append("'");
-                    if (++i < principalnames.size()) {
-                        stmt.append(" or ");
-                    }
+                    NodeIterator it = q.execute().getNodes();
+                    isReadAllowed =  !it.hasNext();
+                } catch (RepositoryException e) {
+                    log.error(e.toString());
+                    // unable to determine... -> no shortcut upon grants
                 }
-
-                // AND rep:privileges contains the READ privilege
-                stmt.append(") and @");
-                stmt.append(resolver.getJCRName(P_PRIVILEGES));
-                stmt.append(" ne \"").append(Privilege.READ).append("\"]");
-
-                Query q = qm.createQuery(stmt.toString(), Query.XPATH);
-
-                NodeIterator it = q.execute().getNodes();
-                readOnly =  !it.hasNext();
-            } catch (RepositoryException e) {
-                log.error(e.toString());
-                // unable to determine... -> no shortcut upon grants
-                readOnly = false;
             }
+            return isReadAllowed;
         }
 
         //------------------------------------< AbstractCompiledPermissions >---
@@ -543,11 +429,11 @@
          */
         protected Result buildResult(Path absPath) throws RepositoryException {
             boolean existingNode = false;
-            Node node = null;
+            NodeImpl node = null;
             String jcrPath = resolver.getJCRPath(absPath);
 
             if (session.nodeExists(jcrPath)) {
-                node = session.getNode(jcrPath);
+                node = (NodeImpl) session.getNode(jcrPath);
                 existingNode = true;
             } else {
                 // path points to existing prop or non-existing item (node or prop).
@@ -555,7 +441,7 @@
                 String parentPath = Text.getRelativeParent(jcrPath, 1);
                 while (parentPath.length() > 0) {
                     if (session.nodeExists(parentPath)) {
-                        node = session.getNode(parentPath);
+                        node = (NodeImpl) session.getNode(parentPath);
                         break;
                     }
                     parentPath = Text.getRelativeParent(parentPath, 1);
@@ -567,22 +453,59 @@
                 throw new ItemNotFoundException("Item out of hierarchy.");
             }
 
-            // build the ACL for the specified principals at path or at the
-            // direct ancestor of path (that must be definition exist).
-            ACLImpl acl = getACL((NodeImpl) node, principalNames);
-
-            // privileges to expose
-            int privileges = acl.getPrivileges();
-
-            // calculate the permissions
-            int permissions;
-            if (existingNode || session.propertyExists(jcrPath)) {
-                permissions = acl.getPermissions(session.getItem(jcrPath));
+            boolean isAcItem = isAcItem(absPath);
+
+            // retrieve all ACEs at path or at the direct ancestor of path that
+            // apply for the principal names.
+            AccessControlEntryIterator entries = new Entries(getNode(node), principalNames).iterator();
+            // build a list of ACEs that are defined locally at the node
+            List localACEs;
+            if (existingNode && isAccessControlled(node)) {
+                NodeImpl aclNode = node.getNode(N_POLICY);
+                localACEs = Arrays.asList(systemEditor.getACL(aclNode).getAccessControlEntries());
             } else {
-                String name = resolver.getJCRName(absPath.getNameElement().getName());
-                permissions = acl.getPermissions(name);
+                localACEs = Collections.EMPTY_LIST;
+            }
+            /*
+             Calculate privileges and permissions:
+             Since the ACEs only define privileges on a node and do not allow
+             to add additional restrictions, the permissions can be determined
+             without taking the given target name or target item into account.
+             */
+            int allows = Permission.NONE;
+            int denies = Permission.NONE;
+
+            int allowPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
+            int denyPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
+            int parentAllows = PrivilegeRegistry.NO_PRIVILEGE;
+            int parentDenies = PrivilegeRegistry.NO_PRIVILEGE;
+
+            while (entries.hasNext() && allows != PrivilegeRegistry.ALL) {
+                JackrabbitAccessControlEntry ace = (JackrabbitAccessControlEntry) entries.next();
+                // Determine if the ACE is defined on the node at absPath (locally):
+                // Except for READ-privileges the permissions must be determined
+                // from privileges defined for the parent. Consequently aces
+                // defined locally must be treated different than inherited entries.
+                int entryBits = ace.getPrivilegeBits();
+                boolean isLocal = localACEs.contains(ace);
+                if (!isLocal) {
+                    if (ace.isAllow()) {
+                        parentAllows |= Permission.diff(entryBits, parentDenies);
+                    } else {
+                        parentDenies |= Permission.diff(entryBits, parentAllows);
+                    }
+                }
+                if (ace.isAllow()) {
+                    allowPrivileges |= Permission.diff(entryBits, denyPrivileges);
+                    int permissions = Permission.calculatePermissions(allowPrivileges, parentAllows, true, isAcItem);
+                    allows |= Permission.diff(permissions, denies);
+                } else {
+                    denyPrivileges |= Permission.diff(entryBits, allowPrivileges);
+                    int permissions = Permission.calculatePermissions(denyPrivileges, parentDenies, false, isAcItem);
+                    denies |= Permission.diff(permissions, allows);
+                }
             }
-            return new Result(permissions, privileges);
+            return new Result(allows, denies, allowPrivileges, denyPrivileges);
         }
 
         //--------------------------------------------< CompiledPermissions >---
@@ -609,8 +532,6 @@
         public boolean grants(Path absPath, int permissions) throws RepositoryException {
             if (permissions == Permission.READ && readAllowed && !isAcItem(absPath)) {
                 return true;
-            } else if (permissions != Permission.READ && readOnly) {
-                return false;
             } else {
                 return super.grants(absPath, permissions);
             }
@@ -620,7 +541,7 @@
         /**
          * @see EventListener#onEvent(EventIterator)
          */
-        public void onEvent(EventIterator events) {
+        public synchronized void onEvent(EventIterator events) {
             // only invalidate cache if any of the events affects the
             // nodes defining permissions for principals compiled here.
             boolean clearCache = false;
@@ -628,55 +549,55 @@
                 try {
                     Event ev = events.nextEvent();
                     String path = ev.getPath();
-                    // TODO: check if valid. check required.
-
                     switch (ev.getType()) {
                         case Event.NODE_ADDED:
-                            // test if the new ACE-nodes affects the permission
-                            // of any of the 'principals'.
+                            // test if the new node is an ACE node that affects
+                            // the permission of any of the principals listed in
+                            // principalNames.
                             NodeImpl n = (NodeImpl) session.getNode(path);
-                            String pName = n.getProperty(P_PRINCIPAL_NAME).getString();
-                            if (principalNames.contains(pName)) {
-                                // new ACE entry for the principals -> clear cache
-                                clearCache = true;
-                                // if ace is a new DENY -> check if denies reading
+                            if (n.isNodeType(NT_REP_ACE) &&
+                                    principalNames.contains(n.getProperty(P_PRINCIPAL_NAME).getString())) {
+                                // and reset the readAllowed flag, if the new
+                                // ACE denies READ.
                                 if (readAllowed && n.isNodeType(NT_REP_DENY_ACE)) {
                                     Value[] vs = n.getProperty(P_PRIVILEGES).getValues();
                                     for (int i = 0; i < vs.length; i++) {
-                                        if (Privilege.READ.equals(vs[i].getString())) {
+                                        if (jcrReadPrivilegeName.equals(vs[i].getString())) {
                                             readAllowed = false;
                                         }
                                     }
                                 }
-                                // if ace is a new ALLOW -> check if obsoletes read-only
-                                if (readOnly && n.isNodeType(NT_REP_GRANT_ACE)) {
-                                    Value[] vs = n.getProperty(P_PRIVILEGES).getValues();
-                                    for (int i = 0; i < vs.length; i++) {
-                                        if (!Privilege.READ.equals(vs[i].getString())) {
-                                            readOnly = false;
-                                        }
-                                    }
-                                }
+                                clearCache = true;
                             }
                             break;
+                        case Event.PROPERTY_REMOVED:
                         case Event.NODE_REMOVED:
                             // can't find out if the removed ACL/ACE node was
                             // relevant for the principals
+                            readAllowed = isReadAllowed(principalNames);
                             clearCache = true;
                             break;
+                        case Event.PROPERTY_ADDED:
                         case Event.PROPERTY_CHANGED:
-                            // test if the changed ACE_prop affects the permission
-                            // of any of the 'principals' (most interesting are
-                            // changed privileges.
+                            // test if the added/changed prop belongs to an ACe
+                            // node and affects the permission of any of the
+                            // principals listed in principalNames.
                             PropertyImpl p = (PropertyImpl) session.getProperty(path);
-                            if (P_PRIVILEGES.equals(p.getQName())) {
-                                // test if principal-name sibling-prop matches
-                                pName = ((NodeImpl) p.getParent()).getProperty(P_PRINCIPAL_NAME).toString();
-                                clearCache = principalNames.contains(pName);
-                            } else if (P_PRINCIPAL_NAME.equals(p.getQName())) {
-                                // an ace change its principal-name. that should
-                                // not happen. -> clear cache to be on the safe side.
-                                clearCache = true;
+                            NodeImpl parent = (NodeImpl) p.getParent();
+                            if (parent.isNodeType(NT_REP_ACE)) {
+                                String principalName = null;
+                                if (P_PRIVILEGES.equals(p.getQName())) {
+                                    // test if principal-name sibling-prop matches
+                                    principalName = parent.getProperty(P_PRINCIPAL_NAME).getString();
+                                } else if (P_PRINCIPAL_NAME.equals(p.getQName())) {
+                                    // a new ace or an ace change its principal-name.
+                                    principalName = p.getString();
+                                }
+                                if (principalName != null &&
+                                        principalNames.contains(principalName)) {
+                                    readAllowed = isReadAllowed(principalNames);
+                                    clearCache = true;
+                                }
                             }
                             break;
                         default:
@@ -692,4 +613,43 @@
             }
         }
     }
+
+    private class Entries {
+
+        private final ListOrderedMap principalNamesToEntries;
+
+        private Entries(NodeImpl node, Collection principalNames) throws RepositoryException {
+            principalNamesToEntries = new ListOrderedMap();
+            for (Iterator it = principalNames.iterator(); it.hasNext();) {
+                principalNamesToEntries.put(it.next(), new ArrayList());
+            }
+            collectEntries(node);
+        }
+
+        private void collectEntries(NodeImpl node) throws RepositoryException {
+            // if the given node is access-controlled, construct a new ACL and add
+            // it to the list
+            if (isAccessControlled(node)) {
+                // build acl for the access controlled node
+                NodeImpl aclNode = node.getNode(N_POLICY);
+                ACLTemplate.collectEntries(aclNode, principalNamesToEntries);
+            }
+            // then, recursively look for access controlled parents up the hierarchy.
+            if (!rootNodeId.equals(node.getId())) {
+                NodeImpl parentNode = (NodeImpl) node.getParent();
+                collectEntries(parentNode);
+            }
+        }
+
+        private AccessControlEntryIterator iterator() {
+            List entries = new ArrayList();
+            for (Iterator it =
+                    principalNamesToEntries.asList().iterator(); it.hasNext();) {
+                Object key = it.next();
+                entries.addAll((List) principalNamesToEntries.get(key));
+            }
+            return new AccessControlEntryIterator(entries);
+        }
+    }
+
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java?rev=689499&r1=689498&r2=689499&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java Wed Aug 27 08:12:04 2008
@@ -17,16 +17,18 @@
 package org.apache.jackrabbit.core.security.authorization.acl;
 
 import org.apache.commons.collections.map.ListOrderedMap;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlEntry;
 import org.apache.jackrabbit.api.jsr283.security.AccessControlException;
+import org.apache.jackrabbit.api.jsr283.security.Privilege;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlManager;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
-import org.apache.jackrabbit.core.security.authorization.PolicyEntry;
-import org.apache.jackrabbit.core.security.authorization.PolicyTemplate;
+import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
+import org.apache.jackrabbit.core.security.authorization.JackrabbitAccessControlList;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.core.security.authorization.Permission;
 
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
@@ -35,91 +37,128 @@
 import java.security.acl.Group;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
- * {@link PolicyTemplate}-Implementation for the resource-based {@link ACLImpl}.
- *
- * @see PolicyTemplate
- * @see ACLImpl
+ * Implementation of the {@link JackrabbitAccessControlList} interface that
+ * is detached from the effective access control content. Consequently, any
+ * modifications applied to this ACL only take effect, if the policy gets
+ * {@link org.apache.jackrabbit.api.jsr283.security.AccessControlManager#setPolicy(String, org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy) reapplied}
+ * to the <code>AccessControlManager</code> and the changes are saved.
  */
-class ACLTemplate implements PolicyTemplate {
-
-    private static final Logger log = LoggerFactory.getLogger(ACLTemplate.class);
+class ACLTemplate implements JackrabbitAccessControlList {
 
+    /**
+     * Path of the node this ACL template has been created for.
+     */
     private final String path;
-    private final String name = ACLImpl.POLICY_NAME;
-    private final String description;
 
     /**
      * Map containing the entries of this ACL Template using the principal
      * name as key. The value represents a List containing maximal one grant
      * and one deny ACE per principal.
      */
-    private final Map entries = new HashMap();
+    private final Map entries = new ListOrderedMap();
 
     /**
-     * Construct a new empty {@link PolicyTemplate}.
+     * The principal manager used for validation checks
      */
-    ACLTemplate(String path) {
-        this.path = path;
-        description = null;
-    }
+    private final PrincipalManager principalMgr;
 
     /**
-     * Create a {@link PolicyTemplate} that is used to edit an existing ACL
-     * node.
+     * The privilege registry
      */
-    ACLTemplate(NodeImpl aclNode) throws RepositoryException {
-        this(aclNode, Collections.EMPTY_SET);
+    private final PrivilegeRegistry privilegeRegistry;
+
+    /**
+     * Construct a new empty {@link ACLTemplate}.
+     *
+     * @param path
+     * @param principalMgr
+     */
+    ACLTemplate(String path, PrincipalManager principalMgr, PrivilegeRegistry privilegeRegistry) {
+        this.path = path;
+        this.principalMgr = principalMgr;
+        this.privilegeRegistry = privilegeRegistry;
     }
 
     /**
-     * Create a {@link PolicyTemplate} that is used to edit an existing ACL
-     * policy but only lists those entries that match any of the principal
-     * names present in the given filter. If the set is empty all entry will
-     * be read as local entries. Otherwise only the entries matching any of
-     * the principals in the set will be retrieved.
+     * Create a {@link ACLTemplate} that is used to edit an existing ACL
+     * node.
      */
-    ACLTemplate(NodeImpl aclNode, Set principalNames) throws RepositoryException {
+    ACLTemplate(NodeImpl aclNode, PrivilegeRegistry privilegeRegistry) throws RepositoryException {
         if (aclNode == null || !aclNode.isNodeType(AccessControlConstants.NT_REP_ACL)) {
             throw new IllegalArgumentException("Node must be of type: " +
                     AccessControlConstants.NT_REP_ACL);
         }
+        SessionImpl sImpl = (SessionImpl) aclNode.getSession();
         path = aclNode.getParent().getPath();
-        description = null;
-        loadEntries(aclNode, principalNames);
+        principalMgr = sImpl.getPrincipalManager();
+        this.privilegeRegistry = privilegeRegistry;
+
+        // load the entries:
+        AccessControlManager acMgr = sImpl.getAccessControlManager();
+        NodeIterator itr = aclNode.getNodes();
+        while (itr.hasNext()) {
+            NodeImpl aceNode = (NodeImpl) itr.nextNode();
+
+            String principalName = aceNode.getProperty(AccessControlConstants.P_PRINCIPAL_NAME).getString();
+            Principal princ = principalMgr.getPrincipal(principalName);
+
+            Value[] privValues = aceNode.getProperty(AccessControlConstants.P_PRIVILEGES).getValues();
+            Privilege[] privs = new Privilege[privValues.length];
+            for (int i = 0; i < privValues.length; i++) {
+                privs[i] = acMgr.privilegeFromName(privValues[i].getString());
+            }
+            // create a new ACEImpl (omitting validation check)
+            Entry ace = new Entry(
+                    princ,
+                    privs,
+                    aceNode.isNodeType(AccessControlConstants.NT_REP_GRANT_ACE));
+            // add the entry
+            internalAdd(ace);
+        }
     }
 
     /**
-     * Returns those {@link PolicyEntry entries} of this
-     * <code>PolicyTemplate</code> that affect the permissions of the given
-     * <code>principal</code>.
+     * Separately collect the entries defined for the principals with the
+     * specified names and return a map consisting of principal name key
+     * and a list of ACEs as value.
      *
-     * @return the {@link PolicyEntry entries} present in this
-     * <code>PolicyTemplate</code> that affect the permissions of the given
-     * <code>principal</code>.
+     * @param aclNode
+     * @param princToEntries Map of key = principalName and value = ArrayList
+     * to be filled with ACEs matching the principal names.
+     * @throws RepositoryException
      */
-    ACEImpl[] getEntries(Principal principal) {
-        List l = internalGetEntries(principal);
-        return (ACEImpl[]) l.toArray(new ACEImpl[l.size()]);
-    }
+    static void collectEntries(NodeImpl aclNode, Map princToEntries)
+            throws RepositoryException {
+        SessionImpl sImpl = (SessionImpl) aclNode.getSession();
+        PrincipalManager principalMgr = sImpl.getPrincipalManager();
+        AccessControlManager acMgr = sImpl.getAccessControlManager();
 
-    private void checkValidEntry(PolicyEntry entry) throws AccessControlException {
-        if (!(entry instanceof ACEImpl)) {
-            throw new AccessControlException("Invalid PolicyEntry " + entry + ". Expected instanceof ACEImpl.");
-        }
-        ACEImpl ace = (ACEImpl) entry;
-        // TODO: ev. assert that the principal is known to the repository
-        // make sure valid privileges are provided.
-        PrivilegeRegistry.getBits(ace.getPrivileges());
+        NodeIterator itr = aclNode.getNodes();
+        while (itr.hasNext()) {
+            NodeImpl aceNode = (NodeImpl) itr.nextNode();
+            String principalName = aceNode.getProperty(AccessControlConstants.P_PRINCIPAL_NAME).getString();
+            // only process aceNode if 'principalName' is contained in the given set
+            if (princToEntries.containsKey(principalName)) {
+                Principal princ = principalMgr.getPrincipal(principalName);
 
-        if (!entry.isAllow() && entry.getPrincipal() instanceof Group) {
-            throw new AccessControlException("For group principals permissions can only be added but not denied.");
+                Value[] privValues = aceNode.getProperty(AccessControlConstants.P_PRIVILEGES).getValues();
+                Privilege[] privs = new Privilege[privValues.length];
+                for (int i = 0; i < privValues.length; i++) {
+                    privs[i] = acMgr.privilegeFromName(privValues[i].getString());
+                }
+                // create a new ACEImpl (omitting validation check)
+                Entry ace = new Entry(
+                        princ,
+                        privs,
+                        aceNode.isNodeType(AccessControlConstants.NT_REP_GRANT_ACE));
+                // add it to the proper list (e.g. separated by principals)
+                ((List) princToEntries.get(principalName)).add(ace);
+            }
         }
     }
 
@@ -140,187 +179,163 @@
         }
     }
 
-    private synchronized boolean internalAdd(ACEImpl entry) {
+    private synchronized boolean internalAdd(Entry entry) throws AccessControlException {
         Principal principal = entry.getPrincipal();
         List l = internalGetEntries(principal);
         if (l.isEmpty()) {
+            // simple case: just add the new entry
             l.add(entry);
             entries.put(principal.getName(), l);
             return true;
         } else {
-            return adjustEntries(entry, l);
-        }
-    }
-
-    private static boolean adjustEntries(ACEImpl entry, List l) {
-        if (l.contains(entry)) {
-            // the same entry is already contained -> no modification
-            return false;
-        }
+            if (l.contains(entry)) {
+                // the same entry is already contained -> no modification
+                return false;
+            }
+            // ev. need to adjust existing entries
+            Entry complementEntry = null;
+            Entry[] entries = (Entry[]) l.toArray(new Entry[l.size()]);
+            for (int i = 0; i < entries.length; i++) {
+                if (entry.isAllow() == entries[i].isAllow()) {
+                    int existingPrivs = entries[i].getPrivilegeBits();
+                    if ((existingPrivs | ~entry.getPrivilegeBits()) == -1) {
+                        // all privileges to be granted/denied are already present
+                        // in the existing entry -> not modified
+                        return false;
+                    }
+
+                    // remove the existing entry and create a new that includes
+                    // both the new privileges and the existing onces.
+                    l.remove(i);
+                    int mergedBits = entries[i].getPrivilegeBits() | entry.getPrivilegeBits();
+                    Privilege[] mergedPrivs = privilegeRegistry.getPrivileges(mergedBits);
+                    // omit validation check.
+                    entry = new Entry(entry.getPrincipal(), mergedPrivs, entry.isAllow());
+                } else {
+                    complementEntry = entries[i];
+                }
+            }
 
-        ACEImpl complementEntry = null;
-        ACEImpl[] entries = (ACEImpl[]) l.toArray(new ACEImpl[l.size()]);
-        for (int i = 0; i < entries.length; i++) {
-            ACEImpl t = entries[i];
-            if (entry.isAllow() == entries[i].isAllow()) {
-                // replace the existing entry with the new one at the end.
-                l.remove(i);
-            } else {
-                complementEntry = t;
+            // make sure, that the complement entry (if existing) does not
+            // grant/deny the same privileges -> remove privs that are now
+            // denied/granted.
+            if (complementEntry != null) {
+                int complPrivs = complementEntry.getPrivilegeBits();
+                int resultPrivs = Permission.diff(complPrivs, entry.getPrivilegeBits());
+                if (resultPrivs == PrivilegeRegistry.NO_PRIVILEGE) {
+                    l.remove(complementEntry);
+                } else if (resultPrivs != complPrivs) {
+                    l.remove(complementEntry);
+                    // omit validation check
+                    Entry tmpl = new Entry(entry.getPrincipal(),
+                            privilegeRegistry.getPrivileges(resultPrivs),
+                            !entry.isAllow());
+                    l.add(tmpl);
+                } /* else: does not need to be modified.*/
             }
-        }
 
-        // make sure, that the complement entry (if existing) does not
-        // grant/deny the same privileges -> remove privs that are now
-        // denied/granted.
-        if (complementEntry != null) {
-            int complPrivs = complementEntry.getPrivilegeBits();
-            int resultPrivs = PrivilegeRegistry.diff(complPrivs, entry.getPrivilegeBits());
-            if (resultPrivs == PrivilegeRegistry.NO_PRIVILEGE) {
-                l.remove(complementEntry);
-            } else if (resultPrivs != complPrivs) {
-                l.remove(complementEntry);
-                ACEImpl tmpl = new ACEImpl(entry.getPrincipal(), resultPrivs, !entry.isAllow());
-                l.add(tmpl);
-            } /* else: does not need to be modified.*/
-        }
-        // finally add the new entry at the end.
-        l.add(entry);
-        return true;
-    }
-
-    private synchronized boolean internalRemove(ACEImpl entry) {
-        List l = internalGetEntries(entry.getPrincipal());
-        boolean success = l.remove(entry);
-        if (l.isEmpty()) {
-            entries.remove(entry.getPrincipal().getName());
+            // finally add the new entry at the end.
+            l.add(entry);
+            return true;
         }
-        return success;
     }
 
     /**
-     * Read the child nodes of the given node and build {@link ACEImpl}
-     * objects. If the filter set is not empty, the entries are
-     * collected separately for each principal.
      *
-     * @param aclNode
-     * @param filter Set of principal names used to filter the entries present
-     * within this ACL.
-     */
-    private void loadEntries(NodeImpl aclNode, Set filter)
-            throws RepositoryException {
-        PrincipalManager pMgr = ((SessionImpl) aclNode.getSession()).getPrincipalManager();
-        // NOTE: don't simply add the individual matching entries, instead
-        // collect entries separated for the principals first and later add
-        // them in the order the need to be evaluated (order of principals).
-        // therefore use ListOrderedMap in order to preserve the order of the
-        // principalNames passed with the 'filter'.
-        String noFilter = "";
-        Map princToEntries = new ListOrderedMap();
-        if (filter == null || filter.isEmpty()) {
-            princToEntries.put(noFilter, new ArrayList());
-        } else {
-            for (Iterator it = filter.iterator(); it.hasNext();) {
-                princToEntries.put(it.next().toString(), new ArrayList());
-            }
+     * @param principal
+     * @param privileges
+     * @param isAllow
+     * @throws AccessControlException
+     */
+    private void checkValidEntry(Principal principal, Privilege[] privileges, boolean isAllow) throws AccessControlException {
+        // validate principal
+        if (!principalMgr.hasPrincipal(principal.getName())) {
+            throw new AccessControlException("Principal " + principal.getName() + " does not exist.");
         }
-
-        NodeIterator itr = aclNode.getNodes();
-        while (itr.hasNext()) {
-            NodeImpl aceNode = (NodeImpl) itr.nextNode();
-            String principalName = aceNode.getProperty(AccessControlConstants.P_PRINCIPAL_NAME).getString();
-            // only process aceNode if no filter is present of if the filter
-            // contains the principal-name defined with the ace-Node
-            String key = (filter == null || filter.isEmpty()) ? noFilter : principalName;
-            if (princToEntries.containsKey(key)) {
-                Principal princ = pMgr.getPrincipal(principalName);
-                Value[] privValues = aceNode.getProperty(AccessControlConstants.P_PRIVILEGES).getValues();
-                String[] privNames = new String[privValues.length];
-                for (int i = 0; i < privValues.length; i++) {
-                    privNames[i] = privValues[i].getString();
-                }
-                // create a new ACEImpl
-                ACEImpl ace = new ACEImpl(
-                        princ,
-                        PrivilegeRegistry.getBits(privNames),
-                        aceNode.isNodeType(AccessControlConstants.NT_REP_GRANT_ACE));
-                // add it to the proper list (e.g. separated by principals)
-                ((List) princToEntries.get(key)).add(ace);
-            }
+        // additional validation: a group may not have 'denied' permissions
+        if (!isAllow && principal instanceof Group) {
+            throw new AccessControlException("For group principals permissions can only be added but not denied.");
         }
+    }
 
-        // now retrieve the entries for each principal names and add them
-        // to the single (complete) list of all entries that need to
-        // be evaluated.
-        for (Iterator it = princToEntries.keySet().iterator(); it.hasNext();) {
-            String princName = it.next().toString();
-            for (Iterator entries = ((List) princToEntries.get(princName)).iterator();
-                 entries.hasNext();) {
-                ACEImpl ace = (ACEImpl) entries.next();
-                internalAdd(ace);
-            }
-        }
+    //--------------------------------------------------< AccessControlList >---
+    /**
+     * @see org.apache.jackrabbit.api.jsr283.security.AccessControlList#getAccessControlEntries()
+     */
+    public AccessControlEntry[] getAccessControlEntries() throws RepositoryException {
+        List l = internalGetEntries();
+        return (AccessControlEntry[]) l.toArray(new AccessControlEntry[l.size()]);
     }
 
-    //------------------------------------------------< AccessControlPolicy >---
     /**
-     * @see org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy#getName()
+     * @see org.apache.jackrabbit.api.jsr283.security.AccessControlList#addAccessControlEntry(Principal, Privilege[])
      */
-    public String getName() throws RepositoryException {
-        return name;
+    public boolean addAccessControlEntry(Principal principal, Privilege[] privileges)
+            throws AccessControlException, RepositoryException {
+        return addEntry(principal, privileges, true, Collections.EMPTY_MAP);
     }
 
     /**
-     * @see org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy#getDescription()
+     * @see org.apache.jackrabbit.api.jsr283.security.AccessControlList#removeAccessControlEntry(AccessControlEntry)
      */
-    public String getDescription() throws RepositoryException {
-        return description;
+    public synchronized void removeAccessControlEntry(AccessControlEntry ace)
+            throws AccessControlException, RepositoryException {
+        if (!(ace instanceof Entry)) {
+            throw new AccessControlException("Invalid AccessControlEntry implementation " + ace.getClass().getName() + ".");
+        }
+        List l = internalGetEntries(ace.getPrincipal());
+        if (l.remove(ace)) {
+            if (l.isEmpty()) {
+                entries.remove(ace.getPrincipal().getName());
+            }
+        } else {
+            throw new AccessControlException("AccessControlEntry " + ace + " cannot be removed from ACL defined at " + getPath());
+        }
     }
 
-    //-----------------------------------------------------< PolicyTemplate >---
+    //-----------------------------------------------------< JackrabbitAccessControlList >---
     /**
-     * @see PolicyTemplate#getPath()
+     * @see JackrabbitAccessControlList#getPath()
      */
     public String getPath() {
         return path;
     }
 
     /**
-     * @see PolicyTemplate#isEmpty()
+     * @see JackrabbitAccessControlList#isEmpty()
      */
     public boolean isEmpty() {
         return entries.isEmpty();
     }
 
     /**
-     * @see PolicyTemplate#size()
+     * @see JackrabbitAccessControlList#size()
      */
     public int size() {
         return internalGetEntries().size();
     }
 
     /**
-     * @see PolicyTemplate#getEntries()
+     * @see JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean)
      */
-    public PolicyEntry[] getEntries() {
-        List l = internalGetEntries();
-        return (PolicyEntry[]) l.toArray(new PolicyEntry[l.size()]);
+    public boolean addEntry(Principal principal, Privilege[] privileges, boolean isAllow)
+            throws AccessControlException, RepositoryException {
+        return addEntry(principal, privileges, isAllow, null);
     }
 
     /**
-     * @see PolicyTemplate#setEntry(PolicyEntry)
+     * @see JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean, Map)
      */
-    public boolean setEntry(PolicyEntry entry) throws AccessControlException, RepositoryException {
-        checkValidEntry(entry);
-        return internalAdd((ACEImpl) entry);
-    }
+    public boolean addEntry(Principal principal, Privilege[] privileges,
+                            boolean isAllow, Map restrictions)
+            throws AccessControlException, RepositoryException {
+        if (restrictions != null && !restrictions.isEmpty()) {
+            throw new AccessControlException("This AccessControlList does not allow for additional restrictions.");
+        }
 
-    /**
-     * @see PolicyTemplate#removeEntry(PolicyEntry)
-     */
-    public boolean removeEntry(PolicyEntry entry) throws AccessControlException, RepositoryException {
-        checkValidEntry(entry);
-        return internalRemove((ACEImpl) entry);
+        checkValidEntry(principal, privileges, isAllow);
+        Entry ace = new Entry(principal, privileges, isAllow);
+        return internalAdd(ace);
     }
 
     //-------------------------------------------------------------< Object >---
@@ -336,10 +351,10 @@
     }
 
     /**
-     * Returns true if the name and the entries are equal; false otherwise.
+     * Returns true if the path and the entries are equal; false otherwise.
      *
      * @param obj
-     * @return true if the name and the entries are equal; false otherwise.
+     * @return true if the path and the entries are equal; false otherwise.
      * @see Object#equals(Object)
      */
     public boolean equals(Object obj) {
@@ -348,10 +363,20 @@
         }
 
         if (obj instanceof ACLTemplate) {
-            ACLTemplate tmpl = (ACLTemplate) obj;
-            boolean equalName = (name == null || tmpl.name == null || name.equals(tmpl.name));
-            return equalName && entries.equals(tmpl.entries);
+            ACLTemplate acl = (ACLTemplate) obj;
+            return path.equals(acl.path) && entries.equals(acl.entries);
         }
         return false;
     }
+
+    //--------------------------------------------------------------------------
+    /**
+     *
+     */
+    static class Entry extends AccessControlEntryImpl {
+
+        Entry(Principal principal, Privilege[] privileges, boolean allow) throws AccessControlException {
+            super(principal, privileges, allow, Collections.EMPTY_MAP);
+        }
+    }
 }