You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2023/04/18 06:59:03 UTC

[jackrabbit-filevault] 02/03: Add method to persist the principal ACLs

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch bugfix/JCRVLT-683
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit 1abee0280b1902009bb6b11cfd154130470298ed
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Sun Apr 16 12:28:34 2023 +0200

    Add method to persist the principal ACLs
---
 .../vault/fs/impl/io/DocViewImporter.java          |   7 +-
 .../jackrabbit/vault/fs/spi/ACLManagement.java     |  16 +++
 .../spi/impl/jcr20/JackrabbitUserManagement.java   |  28 +++--
 .../vault/fs/spi/impl/jcr20/JcrACLManagement.java  | 140 +++++++++++++++++++++
 .../META-INF/vault/filter.xml                      |   2 +-
 .../spi/impl/AccessControlValidator.java           |   3 +-
 6 files changed, 185 insertions(+), 11 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
index 7ac3f3ad..dfb92644 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
@@ -54,6 +54,7 @@ import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.security.AccessControlManager;
 import javax.jcr.version.VersionException;
 
 import org.apache.jackrabbit.spi.Name;
@@ -481,6 +482,10 @@ public class DocViewImporter implements DocViewParserHandler {
                                     }
                                 }
                             } 
+                            
+                            // TODO: how to find authorizables in the quickest way?
+                            aclManagement.getPrincipalAcls(child);
+
                             if (shouldRemoveChild) {
                                 importInfo.onDeleted(path);
                                 child.remove();
@@ -736,7 +741,7 @@ public class DocViewImporter implements DocViewParserHandler {
             }
 
             // just import the authorizable node
-            log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
+            log.trace("Authorizable element detected. Starting sysview transformation {}", newPath);
             stack = stack.push();
             stack.adapter = new JcrSysViewTransformer(node, wspFilter.getImportMode(newPath));
             stack.adapter.startNode(docViewNode);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
index 90703db5..faa57c54 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
@@ -17,9 +17,14 @@
 
 package org.apache.jackrabbit.vault.fs.spi;
 
+import java.util.List;
+import java.util.Map;
+
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlPolicy;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 /**
@@ -70,4 +75,15 @@ public interface ACLManagement {
      * @throws RepositoryException if an error occurs
      */
     void clearACL(Node node) throws RepositoryException;
+
+    /**
+     * 
+     * @param node the start node from where to collect principal policies
+     * @return all collected principal access control policies per principal name inside the given node (even nested ones)
+     * @throws RepositoryException in case some error occurred while collecting the principal policies
+     * @see <a href="https://jackrabbit.apache.org/archive/wiki/JCR/AccessControl_115513330.html">Access Control</a>
+     * @since 3.6.10
+     */
+    @NotNull
+    Map<String, List<AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException;
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
index 2c1086be..04c313c9 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
@@ -55,21 +55,33 @@ public class JackrabbitUserManagement implements UserManagement {
      * {@inheritDoc}
      */
     public boolean isAuthorizableNodeType(String ntName) {
-        return ntName.equals("rep:Group") || ntName.equals("rep:User");
+        return ntName.equals("rep:Group") || ntName.equals("rep:User") || ntName.equals("rep:SystemUser");
     }
 
     /**
      * {@inheritDoc}
      */
-    public String getAuthorizablePath(Session session, String name) {
-        // currently we rely on the implementation detail to keep the API dependency to jackrabbit  < 2.3.
+    public String getAuthorizablePath(Session session, String id) {
+        UserManager uMgr;
         try {
-            UUID uuid = UUID.nameUUIDFromBytes(name.toLowerCase().getBytes("UTF-8"));
-            return session.getNodeByIdentifier(uuid.toString()).getPath();
-        } catch (Exception e) {
-            // ignore
+            uMgr = ((JackrabbitSession) session).getUserManager();
+        } catch (RepositoryException e) {
+            log.warn("Unable to get authorizable path of {}. Error while retrieving user manager.", id, e);
+            return null;
+        }
+        Authorizable authorizable;
+        try {
+            authorizable = uMgr.getAuthorizable(id);
+            if (authorizable == null) {
+                log.debug("No existing authorizable with id {} found", id);
+                return null;
+            }
+            return authorizable.getPath();
+        } catch (RepositoryException e) {
+            log.warn("Unable to get authorizable path of {}: {}", id, e.getMessage(), e);
+            return null;
         }
-        return null;
+       
     }
 
     @Override
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
index bd2ab782..0090cd0f 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
@@ -17,12 +17,32 @@
 
 package org.apache.jackrabbit.vault.fs.spi.impl.jcr20;
 
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.security.AccessControlManager;
 import javax.jcr.security.AccessControlPolicy;
 
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
+import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * {@code JcrACLManagement}...
@@ -30,6 +50,34 @@ import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
  */
 public class JcrACLManagement implements ACLManagement {
 
+    private String groupsRootPath;
+    private String usersRootPath;
+    private final UserManagement userManagement;
+
+    public JcrACLManagement() {
+        // figure out the root paths of users and groups
+        // how to get the securityProvider in Oak (https://issues.apache.org/jira/browse/OAK-9416) was never implemented
+        userManagement = new JackrabbitUserManagement();
+    }
+
+    /**
+     * Determines the authorizable root paths (as Jackrabbit/Oak stores authorizables inside the repo below a dedicated root path)
+     * @param session
+     * @throws RepositoryException
+     */
+    private synchronized void determineAuthorizableRootPaths(Session session) throws RepositoryException {
+        JackrabbitSession jrSession = (JackrabbitSession)session;
+        UserManager userMgr = jrSession.getUserManager();
+        // userMgr.autoSave(false) is not supported by Oak
+        String testAuthorizableId = UUID.randomUUID().toString();
+        Group group = userMgr.createGroup(new SimplePrincipal(testAuthorizableId), "intermediate");
+        groupsRootPath = Text.getRelativeParent(group.getPath(), 2);
+        group.remove();
+        User user = userMgr.createUser(testAuthorizableId, "test", new SimplePrincipal(testAuthorizableId), "intermediate");
+        usersRootPath = Text.getRelativeParent(user.getPath(), 2);
+        user.remove();
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -102,4 +150,96 @@ public class JcrACLManagement implements ACLManagement {
     private static boolean isRootNode(Node node) throws RepositoryException {
         return node.getDepth() == 0;
     }
+
+    private boolean areAuthorizablesAllowedBelowPath(Session session, String nodePath) throws RepositoryException {
+        if (usersRootPath == null || groupsRootPath == null) {
+            determineAuthorizableRootPaths(session);
+        }
+        return nodePath.startsWith(usersRootPath) || nodePath.startsWith(groupsRootPath);
+    }
+
+    @Override
+    public @NotNull Map<String, List<AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException {
+        // first do a quick check if path may contain principal ACLs at all before triggering expensive traversal
+        if (!areAuthorizablesAllowedBelowPath(node.getSession(), node.getPath())) {
+            // TODO: Oak does not allow principal based authorizables everywhere, so we may restrict further
+            return Collections.emptyMap();
+        }
+        JackrabbitSession jrSession = (JackrabbitSession)node.getSession();
+        AccessControlManager acMgr = jrSession.getAccessControlManager();
+        if (!(acMgr instanceof JackrabbitAccessControlManager)) {
+            throw new RepositoryException("The access control manager returned is no JackrabbitAccessControlManager, this is probably not a Jackrabbit/Oak repository");
+        }
+        JackrabbitAccessControlManager jrAcMgr = (JackrabbitAccessControlManager) acMgr;
+        PrincipalAccessControlPolicyCollector policiesCollector = new PrincipalAccessControlPolicyCollector(jrAcMgr);
+        findPrincipalsRecursively(jrSession.getUserManager(), node, policiesCollector);
+        return policiesCollector.getPoliciesPerPrincipal();
+    }
+
+    private static final class SimplePrincipal implements Principal {
+        private final String name;
+
+        SimplePrincipal(String name) {
+            if(name == null) {
+                throw new IllegalArgumentException("Name cannot be null");
+            }
+            this.name = name;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            return (
+                    other instanceof SimplePrincipal)
+                    && (this.name.equals(((SimplePrincipal)other).name));
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+    }
+
+    private static final class PrincipalAccessControlPolicyCollector implements Consumer<Principal> {
+
+        private final JackrabbitAccessControlManager jrAcMgr;
+        private final Map<String, List<AccessControlPolicy>> policiesPerPrincipal;
+
+        public PrincipalAccessControlPolicyCollector(JackrabbitAccessControlManager jrAcMgr) {
+            super();
+            this.jrAcMgr = jrAcMgr;
+            this.policiesPerPrincipal = new HashMap<>();
+        }
+
+        public Map<String, List<AccessControlPolicy>> getPoliciesPerPrincipal() {
+            return policiesPerPrincipal;
+        }
+
+        @Override
+        public void accept(Principal principal) {
+            try {
+                policiesPerPrincipal.put(principal.getName(), Arrays.asList(jrAcMgr.getPolicies(principal)));
+            } catch (RepositoryException e) {
+                //throw new UncheckedRepositoryException()
+            }
+        }
+    }
+
+    private void findPrincipalsRecursively(UserManager userMgr, Node node, Consumer<Principal> principalConsumer) throws RepositoryException {
+        if (userManagement.isAuthorizableNodeType(node.getPrimaryNodeType().getName())) {
+            // what is this authorizable's name?
+            Authorizable authorizable = userMgr.getAuthorizableByPath(node.getPath());
+            if (authorizable != null) {
+                principalConsumer.accept(authorizable.getPrincipal());
+            }
+        } else {
+            for (Node child : JcrUtils.in(node.getNodes())) {
+                findPrincipalsRecursively(userMgr, child, principalConsumer);
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/vault-core/src/test/resources/test-packages/principalbased_nopolicy.zip/META-INF/vault/filter.xml b/vault-core/src/test/resources/test-packages/principalbased_nopolicy.zip/META-INF/vault/filter.xml
index 44bdf212..87f5c26f 100644
--- a/vault-core/src/test/resources/test-packages/principalbased_nopolicy.zip/META-INF/vault/filter.xml
+++ b/vault-core/src/test/resources/test-packages/principalbased_nopolicy.zip/META-INF/vault/filter.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <workspaceFilter version="1.0">
-    <filter root="/home/users/system/intermediate" mode="update"/>
+    <filter root="/home/users/system/intermediate" mode="update_properties"/>
 </workspaceFilter>
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
index dcfda887..9b93d6db 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
@@ -20,6 +20,7 @@ import java.util.Collection;
 import java.util.Collections;
 
 import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
 import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JcrACLManagement;
 import org.apache.jackrabbit.vault.util.DocViewNode2;
 import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
@@ -34,7 +35,7 @@ import org.jetbrains.annotations.Nullable;
  */
 public class AccessControlValidator implements DocumentViewXmlValidator {
 
-    protected static final JcrACLManagement ACL_MANAGEMENT = new JcrACLManagement();
+    protected static final ACLManagement ACL_MANAGEMENT = new JcrACLManagement();
     protected static final String MESSAGE_IGNORED_ACCESS_CONTROL_LIST = "Found an access control list, but it is never considered during installation as the property 'acHandling' is set to '%s'!";
     protected static final String MESSAGE_INEFFECTIVE_ACCESS_CONTROL_LIST = "Found no access control list, but there is supposed to be one contained as the property 'acHandling' is set to '%s'!";
     private final ValidationMessageSeverity severity;