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

pauls pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-cpconverter.git


The following commit(s) were added to refs/heads/master by this push:
     new 8a04987  SLING-9692: Add support for principal-based access control 
entries (#52)
8a04987 is described below

commit 8a04987a6f539525728a1927232352696d3c7c58
Author: Karl Pauls <[email protected]>
AuthorDate: Fri Jan 15 16:17:28 2021 +0100

    SLING-9692: Add support for principal-based access control entries (#52)
    
    * SLING-9692 : Add support for principal-based access control entries
    
    Co-authored-by: angela <[email protected]>
---
 .../accesscontrol/AccessControlEntry.java          |  13 ++
 .../accesscontrol/DefaultAclManager.java           | 155 +++++++++-----
 ...ntentPackage2FeatureModelConverterLauncher.java |   8 +-
 .../cpconverter/handlers/AbstractPolicyParser.java |   2 +-
 .../handlers/RepPrincipalPolicyEntryHandler.java   | 119 +++++++++++
 ...sling.feature.cpconverter.handlers.EntryHandler |   1 +
 .../accesscontrol/EnforcePrincipalBasedTest.java   | 234 +++++++++++++++++++++
 .../handlers/GroupEntryHandlerTest.java            |  73 +++++++
 .../feature/cpconverter/handlers/ParseResult.java  |  48 +++++
 .../handlers/RepPolicyEntryHandlerTest.java        |  54 +----
 .../RepPrincipalPolicyEntryHandlerTest.java        | 177 ++++++++++++++++
 .../handlers/RepRepoPolicyEntryHandlerTest.java    |   9 +-
 .../handlers/UsersEntryHandlerTest.java            |  18 ++
 .../services/random1/_rep_principalPolicy.xml      |  26 +++
 .../services/random2/_rep_principalPolicy.xml      |  29 +++
 .../services/random3/_rep_principalPolicy.xml      |  26 +++
 .../services/random4/_rep_principalPolicy.xml      |  26 +++
 17 files changed, 905 insertions(+), 113 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AccessControlEntry.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AccessControlEntry.java
index 95151c4..984e7f3 100644
--- 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AccessControlEntry.java
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AccessControlEntry.java
@@ -36,10 +36,17 @@ public final class AccessControlEntry {
 
     private final List<String> restrictions = new LinkedList<>();
 
+    private final boolean isPrincipalBased;
+
     public AccessControlEntry(boolean isAllow, @NotNull String privileges, 
@NotNull RepoPath repositoryPath) {
+        this(isAllow, privileges, repositoryPath, false);
+    }
+
+    public AccessControlEntry(boolean isAllow, @NotNull String privileges, 
@NotNull RepoPath repositoryPath, boolean isPrincipalBased) {
         this.isAllow = isAllow;
         this.privileges = privileges;
         this.repositoryPath = repositoryPath;
+        this.isPrincipalBased = isPrincipalBased;
     }
 
     public void addRestriction(@Nullable String restriction) {
@@ -64,6 +71,10 @@ public final class AccessControlEntry {
         return restrictions;
     }
 
+    public boolean isPrincipalBased() {
+        return isPrincipalBased;
+    }
+
     @Override
     public String toString() {
         return "Acl [isAllow="
@@ -74,6 +85,8 @@ public final class AccessControlEntry {
                + repositoryPath
                + ", restrictions="
                + restrictions
+               + ", isPrincipalBased="
+               + isPrincipalBased
                + "]";
     }
 
diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
index 90c2277..728343b 100644
--- 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
@@ -47,10 +47,13 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public final class DefaultAclManager implements AclManager {
+public class DefaultAclManager implements AclManager {
 
     private static final String CONTENT_XML_FILE_NAME = ".content.xml";
 
+    private final boolean enforcePrincipalBased;
+    private final RepoPath supportedPrincipalBasedPath;
+
     private final Set<SystemUser> systemUsers = new LinkedHashSet<>();
     private final Set<Group> groups = new LinkedHashSet<>();
     private final Set<User> users = new LinkedHashSet<>();
@@ -61,6 +64,15 @@ public final class DefaultAclManager implements AclManager {
 
     private volatile PrivilegeDefinitions privilegeDefinitions;
 
+    public DefaultAclManager() {
+        this(false, null);
+    }
+    public DefaultAclManager(boolean enforcePrincipalBased, @Nullable String 
supportedPrincipalBasedPath) {
+        this.enforcePrincipalBased = enforcePrincipalBased;
+        this.supportedPrincipalBasedPath = (supportedPrincipalBasedPath == 
null) ? null : new RepoPath(supportedPrincipalBasedPath);
+    }
+
+
     @Override
     public boolean addUser(@NotNull User user) {
         return users.add(user);
@@ -97,7 +109,7 @@ public final class DefaultAclManager implements AclManager {
 
             for (SystemUser systemUser : systemUsers) {
                 // make sure all users are created first
-                formatter.format("create service user %s with path %s%n", 
systemUser.getId(), systemUser.getIntermediatePath());
+                formatter.format("create service user %s with path %s%n", 
systemUser.getId(), 
calculateIntermediatePath(systemUser.getIntermediatePath()));
                 if (aclIsBelow(systemUser.getPath())) {
                     throw new IllegalStateException("Detected policy on 
subpath of system-user: " + systemUser);
                 }
@@ -118,29 +130,33 @@ public final class DefaultAclManager implements 
AclManager {
                 }
             }
 
-            Set<RepoPath> paths = acls.entrySet().stream()
-                    .filter(entry -> getSystemUser(entry.getKey()).isPresent())
-                    .map(Entry::getValue)
-                    .flatMap(Collection::stream)
-                    .map(AccessControlEntry::getRepositoryPath)
-                    .collect(Collectors.toSet());
-
-            paths.stream()
-                    .filter(path -> !paths.stream().anyMatch(other -> 
!other.equals(path) && other.startsWith(path)))
-                    
.filter(((Predicate<RepoPath>)RepoPath::isRepositoryPath).negate())
-                    .filter(path -> Stream.of(systemUsers, users, 
groups).flatMap(Collection::stream)
-                            .noneMatch(user -> 
user.getPath().startsWith(path)))
-                    .map(path -> computePathWithTypes(path, packageAssemblers))
-                    .filter(Objects::nonNull)
-                    .forEach(
-                            path -> formatter.format("create path %s%n", path)
-                    );
+            if (!enforcePrincipalBased) {
+                Set<RepoPath> paths = acls.entrySet().stream()
+                        .filter(entry -> 
getSystemUser(entry.getKey()).isPresent())
+                        .map(Entry::getValue)
+                        .flatMap(Collection::stream)
+                        // paths only should/need to be create with 
resource-based access control
+                        .filter(((Predicate<AccessControlEntry>) 
AccessControlEntry::isPrincipalBased).negate())
+                        .map(AccessControlEntry::getRepositoryPath)
+                        .collect(Collectors.toSet());
+
+                paths.stream()
+                        .filter(path -> paths.stream().noneMatch(other -> 
!other.equals(path) && other.startsWith(path)))
+                        
.filter(((Predicate<RepoPath>)RepoPath::isRepositoryPath).negate())
+                        .filter(path -> Stream.of(systemUsers, users, 
groups).flatMap(Collection::stream)
+                                .noneMatch(user -> 
user.getPath().startsWith(path)))
+                        .map(path -> computePathWithTypes(path, 
packageAssemblers))
+                        .filter(Objects::nonNull)
+                        .forEach(
+                                path -> formatter.format("create path %s%n", 
path)
+                        );
+            }
 
             // add the acls
             acls.forEach((systemUserID, authorizations) ->
-                getSystemUser(systemUserID).ifPresent(systemUser ->
-                    addStatements(systemUser, authorizations, 
packageAssemblers, formatter)
-                ));
+                    getSystemUser(systemUserID).ifPresent(systemUser ->
+                            addStatements(systemUser, authorizations, 
formatter)
+                    ));
 
             String text = formatter.toString();
 
@@ -150,48 +166,76 @@ public final class DefaultAclManager implements 
AclManager {
         }
     }
 
-    private boolean aclStartsWith(RepoPath path) {
+    @NotNull
+    private String calculateIntermediatePath(@NotNull RepoPath 
intermediatePath) {
+        if (enforcePrincipalBased && supportedPrincipalBasedPath != null && 
!intermediatePath.startsWith(supportedPrincipalBasedPath)) {
+            RepoPath parent = intermediatePath.getParent();
+            while (parent != null) {
+                if (supportedPrincipalBasedPath.startsWith(parent)) {
+                    String relpath = 
intermediatePath.toString().substring(parent.toString().length());
+                    return supportedPrincipalBasedPath.toString() + relpath;
+                }
+                parent = parent.getParent();
+            }
+            throw new IllegalStateException("Cannot calculate intermediate 
path for service user. Configured Supported path " 
+supportedPrincipalBasedPath+" has no common ancestor with "+intermediatePath);
+        } else {
+            return intermediatePath.toString();
+        }
+    }
+
+    private boolean aclStartsWith(@NotNull RepoPath path) {
         return acls.values().stream().flatMap(List::stream).anyMatch(acl -> 
acl.getRepositoryPath().startsWith(path));
     }
 
-    private boolean aclIsBelow(RepoPath path) {
+    private boolean aclIsBelow(@NotNull RepoPath path) {
         return acls.values().stream().flatMap(List::stream).anyMatch(acl -> 
acl.getRepositoryPath().startsWith(path) && 
!acl.getRepositoryPath().equals(path));
     }
 
     private void addStatements(@NotNull SystemUser systemUser,
                                @NotNull List<AccessControlEntry> 
authorizations,
-                               @NotNull List<VaultPackageAssembler> 
packageAssemblers,
                                @NotNull Formatter formatter) {
         if (authorizations.isEmpty()) {
             return;
         }
 
-        Map<AccessControlEntry, String> entries = new LinkedHashMap<>();
+        Map<AccessControlEntry, String> resourceEntries = new 
LinkedHashMap<>();
+        Map<AccessControlEntry, String> principalEntries = new 
LinkedHashMap<>();
+
         authorizations.forEach(entry -> {
             String path = getRepoInitPath(entry.getRepositoryPath(), 
systemUser);
-            if (path != null) {
-                entries.put(entry, path);
+            if (enforcePrincipalBased || entry.isPrincipalBased()) {
+                principalEntries.put(entry, path);
+            } else {
+                resourceEntries.put(entry, path);
             }
         });
-        if (!entries.isEmpty()) {
-            formatter.format("set ACL for %s%n", systemUser.getId());
-            entries.forEach((entry, path) -> {
-                formatter.format("%s %s on %s",
-                        entry.getOperation(),
-                        entry.getPrivileges(),
-                        path);
-
-                if (!entry.getRestrictions().isEmpty()) {
-                    formatter.format(" restriction(%s)",
-                            String.join(",", entry.getRestrictions()));
-                }
 
-                formatter.format("%n");
-            });
+        if (!principalEntries.isEmpty()) {
+            formatter.format("set principal ACL for %s%n", systemUser.getId());
+            principalEntries.forEach((entry, path) -> writeEntry(entry, path, 
formatter));
+            formatter.format("end%n");
+        }
+        if (!resourceEntries.isEmpty()) {
+            formatter.format("set ACL for %s%n", systemUser.getId());
+            resourceEntries.forEach((entry, path) -> writeEntry(entry, path, 
formatter));
             formatter.format("end%n");
         }
     }
 
+    private void writeEntry(@NotNull AccessControlEntry entry, @NotNull String 
path, @NotNull Formatter formatter) {
+        formatter.format("%s %s on %s",
+                entry.getOperation(),
+                entry.getPrivileges(),
+                path);
+
+        if (!entry.getRestrictions().isEmpty()) {
+            formatter.format(" restriction(%s)",
+                    String.join(",", entry.getRestrictions()));
+        }
+
+        formatter.format("%n");
+    }
+
     private @NotNull Optional<SystemUser> getSystemUser(@NotNull String id) {
         return systemUsers.stream().filter(systemUser ->  
systemUser.getId().equals(id)).findFirst();
     }
@@ -215,7 +259,7 @@ public final class DefaultAclManager implements AclManager {
         privilegeDefinitions = null;
     }
 
-    private static @Nullable String computePathWithTypes(@NotNull RepoPath 
path, @NotNull List<VaultPackageAssembler> packageAssemblers) {
+     protected @Nullable String computePathWithTypes(@NotNull RepoPath path, 
@NotNull List<VaultPackageAssembler> packageAssemblers) {
         path = new 
RepoPath(PlatformNameFormat.getPlatformPath(path.toString()));
 
         boolean type = false;
@@ -228,7 +272,7 @@ public final class DefaultAclManager implements AclManager {
                     String primary;
                     String mixin;
                     try (FileInputStream input = new 
FileInputStream(currentContent);
-                        FileInputStream input2 = new 
FileInputStream(currentContent)) {
+                         FileInputStream input2 = new 
FileInputStream(currentContent)) {
                         primary = new PrimaryTypeParser().parse(input);
                         mixin = new MixinParser().parse(input2);
                         current += "(" + primary;
@@ -254,24 +298,25 @@ public final class DefaultAclManager implements 
AclManager {
         return type ? new RepoPath(current).toString() : null;
     }
 
-    @Nullable
+    @NotNull
     private String getRepoInitPath(@NotNull RepoPath path, @NotNull SystemUser 
systemUser) {
         if (path.isRepositoryPath()) {
             return ":repository";
         } else if (isHomePath(path, systemUser.getPath())) {
-            return getHomePath(path, systemUser);
+            return getHomePath(systemUser);
         } else {
             AbstractUser other = getOtherUser(path, Stream.of(systemUsers, 
groups).flatMap(Collection::stream));
             if (other != null) {
-                return getHomePath(path, other);
+                return getHomePath(other);
             }
             // not a special path
             return path.toString();
         }
     }
 
-    private static boolean isHomePath(@NotNull RepoPath path, @NotNull 
RepoPath systemUserPath) {
-        return path.startsWith(systemUserPath);
+    private boolean isHomePath(@NotNull RepoPath path, @NotNull RepoPath 
systemUserPath) {
+        // ACE located in the subtree are not supported
+        return path.equals(systemUserPath);
     }
 
     @Nullable
@@ -280,14 +325,10 @@ public final class DefaultAclManager implements 
AclManager {
     }
 
     @NotNull
-    private static String getHomePath(@NotNull RepoPath path, @NotNull 
AbstractUser abstractUser) {
-        return getHomePath(path, abstractUser.getPath(), abstractUser.getId());
-    }
-
-    @NotNull
-    private static String getHomePath(@NotNull RepoPath path, @NotNull 
RepoPath userPath, @NotNull String id) {
-        String subpath = (path.equals(userPath) ? "" : 
path.toString().substring(userPath.toString().length()));
-        return "home("+id+")"+subpath;
+    private String getHomePath(@NotNull AbstractUser abstractUser) {
+        // since ACEs located in the subtree of a user are not supported by 
the converter,
+        // there is no need to calculate a potential sub-path to be appended.
+        return "home("+abstractUser.getId()+")";
     }
 
     private static void registerPrivileges(@NotNull PrivilegeDefinitions 
definitions, @NotNull Formatter formatter) {
diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index bc2d44b..8e778f6 100644
--- 
a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -96,6 +96,12 @@ public final class 
ContentPackage2FeatureModelConverterLauncher implements Runna
     @Option(names = { "-Z", "--fail-on-mixed-packages" }, description = "Fail 
the conversion if the resulting attached content-package is MIXED type", 
required = false)
     private boolean failOnMixedPackages = false;
 
+    @Option(names = { "--enforce-principal-based" }, description = "Converts 
all service user access control entries to principal-based setup", required = 
false)
+    private boolean enforcePrincipalBased = false;
+
+    @Option(names = { "--supported-principal-based-path" }, description = 
"Path supported for principal-based access control setup", required = false)
+    private String supportedPrincipalBasedPath = null;
+
     @Override
     public void run() {
         if (quiet) {
@@ -143,7 +149,7 @@ public final class 
ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              
.setFeaturesManager(featuresManager)
                                                              
.setBundlesDeployer(new DefaultArtifactsDeployer(artifactsOutputDirectory))
                                                              
.setEntryHandlersManager(new DefaultEntryHandlersManager())
-                                                             
.setAclManager(new DefaultAclManager())
+                                                             
.setAclManager(new DefaultAclManager(enforcePrincipalBased, 
supportedPrincipalBasedPath))
                                                              
.setEmitter(DefaultPackagesEventsEmitter.open(featureModelsOutputDirectory))
                                                              
.setFailOnMixedPackages(failOnMixedPackages)
                                                              
.setDropContent(true);
diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractPolicyParser.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractPolicyParser.java
index fd68b31..31cd85e 100644
--- 
a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractPolicyParser.java
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractPolicyParser.java
@@ -56,7 +56,7 @@ abstract class AbstractPolicyParser extends 
AbstractJcrNodeParser<Boolean> {
         this.aclManager = aclManager;
     }
 
-    private static @Nullable String extractValue(@Nullable String expression) {
+    static @Nullable String extractValue(@Nullable String expression) {
         if (expression == null || expression.isEmpty()) {
             return expression;
         }
diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPrincipalPolicyEntryHandler.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPrincipalPolicyEntryHandler.java
new file mode 100644
index 0000000..1872c56
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPrincipalPolicyEntryHandler.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ */
+package org.apache.sling.feature.cpconverter.handlers;
+
+import org.apache.sling.feature.cpconverter.accesscontrol.AccessControlEntry;
+import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
+import org.apache.sling.feature.cpconverter.shared.RepoPath;
+import org.jetbrains.annotations.NotNull;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import javax.xml.transform.sax.TransformerHandler;
+import java.util.Stack;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+
+public final class RepPrincipalPolicyEntryHandler extends 
AbstractPolicyEntryHandler {
+
+    public RepPrincipalPolicyEntryHandler() {
+        super("/jcr_root(.*/)_rep_principalPolicy.xml");
+    }
+
+    @Override
+    @NotNull AbstractPolicyParser createPolicyParser(@NotNull RepoPath 
repositoryPath, @NotNull AclManager aclManager, @NotNull TransformerHandler 
handler) {
+        return new RepPrincipalPolicyParser(repositoryPath,
+                aclManager,
+                handler);
+    }
+
+    private static final class RepPrincipalPolicyParser extends 
AbstractPolicyParser {
+
+        private static final String REP_RESTRICTIONS = "rep:Restrictions";
+
+        private static final String REP_PRINCIPAL_NAME = "rep:principalName";
+
+        private static final String REP_PRIVILEGES = "rep:privileges";
+
+        private static final String REP_PRINCIPAL_POLICY = 
"rep:PrincipalPolicy";
+
+        private static final String REP_PRINCIPAL_ENTRY = "rep:PrincipalEntry";
+
+        private static final String REP_EFFECTIVE_PATH = "rep:effectivePath";
+
+        private final Stack<AccessControlEntry> aces = new Stack<>();
+
+        private boolean processCurrentAcl = false;
+
+        private String principalName = null;
+
+        public RepPrincipalPolicyParser(RepoPath repositoryPath, AclManager 
aclManager, TransformerHandler handler) {
+            super(REP_PRINCIPAL_POLICY, repositoryPath, aclManager, handler);
+        }
+
+        @Override
+        protected void onJcrRootElement(String uri, String localName, String 
qName, Attributes attributes) {
+            super.onJcrRootElement(uri, localName, qName, attributes);
+            principalName = attributes.getValue(REP_PRINCIPAL_NAME);
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, 
Attributes attributes)
+                throws SAXException {
+            if (onRepAclNode) {
+                String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
+                if (REP_PRINCIPAL_ENTRY.equals(primaryType)) {
+                    if (principalName == null) {
+                        throw new IllegalStateException("isolated 
principal-based access control entry. no principal found.");
+                    }
+                    String privileges = 
extractValue(attributes.getValue(REP_PRIVILEGES));
+                    RepoPath effectivePath = new 
RepoPath(attributes.getValue(REP_EFFECTIVE_PATH));
+
+                    AccessControlEntry ace = new AccessControlEntry(true, 
privileges, effectivePath, true);
+
+                    processCurrentAcl = aclManager.addAcl(principalName, ace);
+                    if (processCurrentAcl) {
+                        aces.add(ace);
+                    } else {
+                        hasRejectedNodes = true;
+                    }
+                } else if (REP_RESTRICTIONS.equals(primaryType) && 
!aces.isEmpty() && processCurrentAcl) {
+                    AccessControlEntry ace = aces.peek();
+                    aces.add(ace);
+                    addRestrictions(ace, attributes);
+                }
+            } else {
+                super.startElement(uri, localName, qName, attributes);
+            }
+
+            if (!onRepAclNode || !processCurrentAcl) {
+                handler.startElement(uri, localName, qName, attributes);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) 
throws SAXException {
+            if (onRepAclNode && processCurrentAcl && !aces.isEmpty()) {
+                aces.pop();
+            } else {
+                processCurrentAcl = false;
+                principalName = null;
+                handler.endElement(uri, localName, qName);
+            }
+        }
+    }
+}
diff --git 
a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.handlers.EntryHandler
 
b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.handlers.EntryHandler
index 7a82e4e..46fb089 100644
--- 
a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.handlers.EntryHandler
+++ 
b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.handlers.EntryHandler
@@ -7,6 +7,7 @@ 
org.apache.sling.feature.cpconverter.handlers.NodeTypesEntryHandler
 org.apache.sling.feature.cpconverter.handlers.PrivilegesHandler
 
org.apache.sling.feature.cpconverter.handlers.PropertiesConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.RepPolicyEntryHandler
+org.apache.sling.feature.cpconverter.handlers.RepPrincipalPolicyEntryHandler
 org.apache.sling.feature.cpconverter.handlers.RepRepoPolicyEntryHandler
 org.apache.sling.feature.cpconverter.handlers.UsersEntryHandler
 org.apache.sling.feature.cpconverter.handlers.XmlConfigurationEntryHandler
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/EnforcePrincipalBasedTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/EnforcePrincipalBasedTest.java
new file mode 100644
index 0000000..51d9884
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/EnforcePrincipalBasedTest.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.cpconverter.accesscontrol;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
+import org.apache.sling.feature.cpconverter.features.FeaturesManager;
+import org.apache.sling.feature.cpconverter.shared.RepoPath;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.repoinit.parser.RepoInitParser;
+import org.apache.sling.repoinit.parser.RepoInitParsingException;
+import org.apache.sling.repoinit.parser.impl.RepoInitParserService;
+import org.apache.sling.repoinit.parser.operations.Operation;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.StringReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class EnforcePrincipalBasedTest {
+
+    private final SystemUser systemUser = new SystemUser("user1", new 
RepoPath("/home/users/system/intermediate/usernode"), new 
RepoPath("/home/users/system/intermediate"));
+
+    private AclManager aclManager;
+    private Path tempDir;
+
+    private VaultPackageAssembler assembler;
+    private FeaturesManager fm;
+    private Feature feature;
+
+    @Before
+    public void setUp() throws Exception {
+        aclManager = new DefaultAclManager(true, 
"/home/users/system/some/subtree");
+        tempDir = Files.createTempDirectory(getClass().getSimpleName());
+
+        assembler = mock(VaultPackageAssembler.class);
+        when(assembler.getEntry(anyString())).thenReturn(new 
File(System.getProperty("java.io.tmpdir")));
+        feature = new Feature(new ArtifactId("org.apache.sling", 
"org.apache.sling.cp2fm", "0.0.1", null, null));
+
+        fm = Mockito.spy(new DefaultFeaturesManager(tempDir.toFile()));
+        when(fm.getTargetFeature()).thenReturn(feature);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        aclManager = null;
+
+        // Delete the temp dir again
+        Files.walk(tempDir)
+                .sorted(Comparator.reverseOrder())
+                .map(Path::toFile)
+                .forEach(File::delete);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInvalidSupportedPath() {
+        AclManager aclManager = new DefaultAclManager(true, 
"/an/invalid/supported/path");
+        aclManager.addSystemUser(systemUser);
+
+        RepoPath accessControlledPath = new RepoPath("/content/feature");
+        aclManager.addAcl(systemUser.getId(), new AccessControlEntry(true, 
"jcr:read", accessControlledPath , false));
+
+        aclManager.addRepoinitExtension(Arrays.asList(assembler), fm);
+
+    }
+
+    @Test
+    public void testResourceBasedConversionWithoutForce() throws 
RepoInitParsingException {
+        AclManager aclManager = new DefaultAclManager(false, 
"/home/users/system/some/subtree"){
+            @Override
+            protected @Nullable String computePathWithTypes(@NotNull RepoPath 
path, @NotNull List<VaultPackageAssembler> packageAssemblers) {
+                return "/content/feature(sling:Folder)";
+            }
+        };
+        aclManager.addSystemUser(systemUser);
+
+        RepoPath accessControlledPath = new RepoPath("/content/feature");
+        aclManager.addAcl(systemUser.getId(), new AccessControlEntry(true, 
"jcr:read", accessControlledPath , false));
+
+        aclManager.addRepoinitExtension(Arrays.asList(assembler), fm);
+
+        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+        assertNotNull(repoinitExtension);
+
+        String expected =
+                "create service user user1 with path 
/home/users/system/intermediate" + System.lineSeparator() +
+                "create path /content/feature(sling:Folder)" + 
System.lineSeparator() +
+                "set ACL for user1" + System.lineSeparator() +
+                "allow jcr:read on /content/feature" + System.lineSeparator() +
+                "end" + System.lineSeparator();
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+
+        aclManager = 
EnforcePrincipalBasedTest.this.aclManager;aclManager.addSystemUser(systemUser);
+        feature.getExtensions().clear();
+
+        accessControlledPath = new RepoPath("/content/feature");
+        aclManager.addAcl(systemUser.getId(), new AccessControlEntry(true, 
"jcr:read", accessControlledPath , false));
+
+        aclManager.addRepoinitExtension(Arrays.asList(assembler), fm);
+
+        repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+        assertNotNull(repoinitExtension);
+
+        expected =
+                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                        "set principal ACL for user1" + System.lineSeparator() 
+
+                        "allow jcr:read on /content/feature" + 
System.lineSeparator() +
+                        "end" + System.lineSeparator();
+
+        actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        repoInitParser = new RepoInitParserService();
+        operations = repoInitParser.parse(new StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test
+    public void testResourceBasedConversion() throws RepoInitParsingException {
+        aclManager.addSystemUser(systemUser);
+
+        RepoPath accessControlledPath = new RepoPath("/content/feature");
+        aclManager.addAcl(systemUser.getId(), new AccessControlEntry(true, 
"jcr:read", accessControlledPath , false));
+
+        aclManager.addRepoinitExtension(Arrays.asList(assembler), fm);
+
+        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+        assertNotNull(repoinitExtension);
+
+        String expected =
+                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                "set principal ACL for user1" + System.lineSeparator() +
+                "allow jcr:read on /content/feature" + System.lineSeparator() +
+                "end" + System.lineSeparator();
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test
+    public void testPrincipalBased() throws RepoInitParsingException {
+        aclManager.addSystemUser(systemUser);
+
+        RepoPath accessControlledPath = new RepoPath("/content/feature");
+        aclManager.addAcl("user1", new AccessControlEntry(true, "jcr:read", 
accessControlledPath, true));
+
+        aclManager.addRepoinitExtension(Arrays.asList(assembler), fm);
+
+        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+        assertNotNull(repoinitExtension);
+
+        String expected =
+                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                        "set principal ACL for user1" + System.lineSeparator() 
+
+                        "allow jcr:read on /content/feature" + 
System.lineSeparator() +
+                        "end" + System.lineSeparator();
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test
+    public void testPrincipalBasedForUserHome() throws 
RepoInitParsingException {
+        aclManager.addSystemUser(systemUser);
+
+        RepoPath accessControlledPath = systemUser.getPath();
+        AccessControlEntry acl = new AccessControlEntry(true, "jcr:read", 
accessControlledPath, true);
+        aclManager.addAcl("user1", acl);
+
+        aclManager.addRepoinitExtension(Arrays.asList(assembler), fm);
+
+        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+        assertNotNull(repoinitExtension);
+
+        String expected =
+                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                "set principal ACL for user1" + System.lineSeparator() +
+                "allow jcr:read on home(user1)" + System.lineSeparator() +
+                "end" + System.lineSeparator();
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandlerTest.java
new file mode 100644
index 0000000..97f2650
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandlerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ */
+package org.apache.sling.feature.cpconverter.handlers;
+
+import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
+import org.apache.sling.feature.cpconverter.accesscontrol.Group;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class GroupEntryHandlerTest {
+
+    private GroupEntryHandler handler;
+
+    @Before
+    public void setUp() {
+        handler = new GroupEntryHandler();
+    }
+
+    @After
+    public void tearDown() {
+        handler = null;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        
assertFalse(handler.matches("/this/is/a/path/not/pointing/to/a/valid/grouop.asd"));
+        assertFalse(handler.matches("/home/groups/g/groupnode/.content.xml"));
+    }
+
+    @Test
+    public void matches() {
+        
assertTrue(handler.matches("/jcr_root/home/groups/g/groupnode/.content.xml"));
+    }
+
+    @Test
+    public void parseGroup() throws Exception {
+        String path = 
"/jcr_root/home/groups/g/V084LLw1ypl2l9G0e28c/.content.xml";
+        AclManager aclManager = mock(AclManager.class);
+        TestUtils.createRepoInitExtension(handler, aclManager, path, 
getClass().getResourceAsStream(path.substring(1)));
+        verify(aclManager, times(1)).addGroup(any(Group.class));
+    }
+
+    @Test
+    public void parseUser() throws Exception {
+        String path = "/jcr_root/home/users/a/author/.content.xml";
+        AclManager aclManager = mock(AclManager.class);
+        TestUtils.createRepoInitExtension(handler, aclManager, path, 
getClass().getResourceAsStream(path.substring(1)));
+        verify(aclManager, never()).addGroup(any(Group.class));
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/ParseResult.java 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ParseResult.java
new file mode 100644
index 0000000..7f82909
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ParseResult.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ */
+package org.apache.sling.feature.cpconverter.handlers;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+final class ParseResult {
+
+    private final Extension repoinitExtension;
+    private final String excludedAcls;
+
+    ParseResult(@Nullable Extension repoinitExtension, @NotNull String 
excludedAcls) {
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+        this.repoinitExtension = repoinitExtension;
+        this.excludedAcls = excludedAcls;
+    }
+
+    @NotNull
+    Extension getRepoinitExtension() {
+        return repoinitExtension;
+    }
+
+    @NotNull
+    String getExcludedAcls() {
+        return excludedAcls;
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
index e2d9889..e1c4b48 100644
--- 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
@@ -16,22 +16,14 @@
  */
 package org.apache.sling.feature.cpconverter.handlers;
 
-import org.apache.jackrabbit.vault.fs.io.Archive;
-import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
-import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.Feature;
-import 
org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
 import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
 import org.apache.sling.feature.cpconverter.accesscontrol.DefaultAclManager;
 import org.apache.sling.feature.cpconverter.accesscontrol.Group;
 import org.apache.sling.feature.cpconverter.accesscontrol.SystemUser;
 import org.apache.sling.feature.cpconverter.accesscontrol.User;
-import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
-import org.apache.sling.feature.cpconverter.features.FeaturesManager;
 import org.apache.sling.feature.cpconverter.shared.RepoPath;
-import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
 import org.apache.sling.repoinit.parser.RepoInitParser;
 import org.apache.sling.repoinit.parser.impl.RepoInitParserService;
 import org.apache.sling.repoinit.parser.operations.Operation;
@@ -41,9 +33,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.StringReader;
-import java.util.Arrays;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
@@ -51,10 +41,8 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
 
 public final class RepPolicyEntryHandlerTest {
 
@@ -94,8 +82,6 @@ public final class RepPolicyEntryHandlerTest {
                                                           
"acs-commons-ensure-service-user-service",
                                                           
"acs-commons-automatic-package-replicator-service",
                                                           
"acs-commons-on-deploy-scripts-service").getRepoinitExtension();
-        assertNotNull(repoinitExtension);
-        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
 
         // commented ACLs are due SLING-8561
         String expected =
@@ -142,9 +128,6 @@ public final class RepPolicyEntryHandlerTest {
                                                  
"acs-commons-on-deploy-scripts-service");
         Extension repoinitExtension = result.getRepoinitExtension();
 
-        assertNotNull(repoinitExtension);
-        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
-
         String expected =
                 "create service user 
acs-commons-package-replication-status-event-service with path 
/home/users/system" + System.lineSeparator() +
                 "create service user acs-commons-ensure-service-user-service 
with path /home/users/system" + System.lineSeparator() +
@@ -188,9 +171,6 @@ public final class RepPolicyEntryHandlerTest {
         ParseResult result = parseAndSetRepoinit(new 
SystemUser("acs-commons-package-replication-status-event-service",
                 new RepoPath("/this/is/a/completely/different/path/foo"), new 
RepoPath("/this/is/a/completely/different/path")));
         Extension repoinitExtension = result.getRepoinitExtension();
-        assertNotNull(repoinitExtension);
-        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
-
         String expected =
                 "create service user 
acs-commons-package-replication-status-event-service with path 
/this/is/a/completely/different/path" + System.lineSeparator() +
                 "set ACL for 
acs-commons-package-replication-status-event-service" + System.lineSeparator() +
@@ -222,8 +202,8 @@ public final class RepPolicyEntryHandlerTest {
 
     @Test
     public void parseEmptyAcl() throws Exception {
-        Extension repoinitExtension = parseAndSetRepoinit(new String[] 
{}).getRepoinitExtension();
-        assertNull(repoinitExtension);
+        Extension extension = TestUtils.createRepoInitExtension(handler, new 
DefaultAclManager(), "/jcr_root/home/users/system/asd/_rep_policy.xml", 
getClass().getResourceAsStream("/jcr_root/home/users/system/asd/_rep_policy.xml".substring(1)),
 new ByteArrayOutputStream());
+        assertNull(extension);
     }
 
     @Test
@@ -235,8 +215,6 @@ public final class RepPolicyEntryHandlerTest {
 
         ParseResult result = 
parseAndSetRepoInit("/jcr_root/home/groups/g/_rep_policy.xml", aclManager);
         Extension repoinitExtension = result.getRepoinitExtension();
-        assertNotNull(repoinitExtension);
-        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
 
         String expected =
                 "create service user service1 with path 
/home/users/system/services" + System.lineSeparator() +
@@ -244,7 +222,7 @@ public final class RepPolicyEntryHandlerTest {
                 "allow jcr:read,rep:userManagement on /home/groups/g" + 
System.lineSeparator() +
                 "end" + System.lineSeparator();
         assertEquals(expected, repoinitExtension.getText());
-        assertTrue(result.excludedAcls.isEmpty());
+        assertTrue(result.getExcludedAcls().isEmpty());
     }
 
     @Test
@@ -258,8 +236,6 @@ public final class RepPolicyEntryHandlerTest {
 
         ParseResult result = 
parseAndSetRepoInit("/jcr_root/home/groups/g/HjDnfdMCjekaF4jhhUvO/_rep_policy.xml",
 aclManager);
         Extension repoinitExtension = result.getRepoinitExtension();
-        assertNotNull(repoinitExtension);
-        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
 
         String expected =
                 "create service user service1 with path 
/home/users/system/services" + System.lineSeparator() +
@@ -272,7 +248,7 @@ public final class RepPolicyEntryHandlerTest {
         String expectedExclusions = "<?xml version=\"1.0\" 
encoding=\"UTF-8\"?><jcr:root xmlns:jcr=\"http://www.jcp.org/jcr/1.0\"; 
xmlns:rep=\"internal\" jcr:primaryType=\"rep:ACL\">\n" +
                 "    <allow1 jcr:primaryType=\"rep:GrantACE\" 
rep:principalName=\"testgroup\" rep:privileges=\"{Name}[jcr:read]\"/>\n" +
                 "</jcr:root>\n";
-        assertEquals(expectedExclusions, result.excludedAcls);
+        assertEquals(expectedExclusions, result.getExcludedAcls());
     }
 
     @Test(expected = IllegalStateException.class)
@@ -317,8 +293,7 @@ public final class RepPolicyEntryHandlerTest {
         for (SystemUser systemUser : systemUsers) {
             aclManager.addSystemUser(systemUser);
         }
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        return new ParseResult(TestUtils.createRepoInitExtension(handler, 
aclManager, path, getClass().getResourceAsStream(path.substring(1)), baos), new 
String(baos.toByteArray()));
+        return parseAndSetRepoInit(path, aclManager);
     }
 
     @NotNull
@@ -326,23 +301,4 @@ public final class RepPolicyEntryHandlerTest {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         return new ParseResult(TestUtils.createRepoInitExtension(handler, 
aclManager, path, getClass().getResourceAsStream(path.substring(1)), baos), new 
String(baos.toByteArray()));
     }
-
-    private static final class ParseResult {
-
-        private final Extension repoinitExtension;
-        private final String excludedAcls;
-
-        public ParseResult(Extension repoinitExtension, String excludedAcls) {
-            this.repoinitExtension = repoinitExtension;
-            this.excludedAcls = excludedAcls;
-        }
-
-        public Extension getRepoinitExtension() {
-            return repoinitExtension;
-        }
-
-        public String getExcludedAcls() {
-            return excludedAcls;
-        }
-    }
 }
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPrincipalPolicyEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPrincipalPolicyEntryHandlerTest.java
new file mode 100644
index 0000000..9849b4f
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPrincipalPolicyEntryHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ */
+package org.apache.sling.feature.cpconverter.handlers;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
+import org.apache.sling.feature.cpconverter.accesscontrol.DefaultAclManager;
+import org.apache.sling.feature.cpconverter.accesscontrol.SystemUser;
+import org.apache.sling.feature.cpconverter.shared.RepoPath;
+import org.apache.sling.repoinit.parser.RepoInitParser;
+import org.apache.sling.repoinit.parser.impl.RepoInitParserService;
+import org.apache.sling.repoinit.parser.operations.Operation;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public final class RepPrincipalPolicyEntryHandlerTest {
+
+    private RepPrincipalPolicyEntryHandler handler;
+
+    @Before
+    public void setUp() {
+        handler = new RepPrincipalPolicyEntryHandler();
+    }
+
+    @After
+    public void tearDown() {
+        handler = null;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        
assertFalse(handler.matches("/this/is/a/path/not/pointing/to/a/valid/_rep_principalPolicy.xml"));
+        
assertFalse(handler.matches("/home/users/system/asd-share-commons/asd-index-definition-reader/_rep_principalPolicy.xml"));
+        
assertFalse(handler.matches("/jcr_root/home/users/system/services/feature/random1/_rep_policy.xml"));
+    }
+
+    @Test
+    public void matches() {
+        
assertTrue(handler.matches("/jcr_root/home/users/system/services/random1/_rep_principalPolicy.xml"));
+    }
+
+    @Test
+    public void parseSimplePolicy() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit("service1", 
"random1").getRepoinitExtension();
+        String expected =
+                "create service user service1 with path 
/home/users/system/services" + System.lineSeparator() +
+                "set principal ACL for service1\n" +
+                "allow jcr:read,jcr:readAccessControl on /asd/public\n" +
+                "end\n";
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test
+    public void parseMvRestrictions() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit("service2", 
"random2").getRepoinitExtension();
+        String expected =
+                "create service user service2 with path 
/home/users/system/services" + System.lineSeparator() +
+                        "set principal ACL for service2\n" +
+                        "allow jcr:read on /asd/public 
restriction(rep:ntNames,nt:folder,sling:Folder)\n" +
+                        "end\n";
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void parsePolicyInSubtree() throws Exception {
+        parseAndSetRepoinit("service3", "random3").getRepoinitExtension();
+    }
+
+    @Test
+    public void parseOtherUserHomeMissing() throws Exception {
+        SystemUser systemUser4 = createSystemUser("service4", "random4");
+
+        Extension repoinitExtension = 
parseAndSetRepoinit(getPolicyPath(systemUser4), 
systemUser4).getRepoinitExtension();
+        String expected =
+                "create service user service4 with path 
/home/users/system/services" + System.lineSeparator() +
+                "set principal ACL for service4" + System.lineSeparator() +
+                // since service3 is not known to the AclManager it treats the 
effective path as a regular node.
+                "allow jcr:read,rep:userManagement on 
/home/users/system/services/random3" + System.lineSeparator() +
+                "end"+ System.lineSeparator();
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test
+    public void parseOtherUserHome() throws Exception {
+        SystemUser systemUser3 = createSystemUser("service3", "random3");
+        SystemUser systemUser4 = createSystemUser("service4", "random4");
+
+        Extension repoinitExtension = 
parseAndSetRepoinit(getPolicyPath(systemUser4), systemUser4, 
systemUser3).getRepoinitExtension();
+        String expected =
+                "create service user service4 with path 
/home/users/system/services" + System.lineSeparator() +
+                "create service user service3 with path 
/home/users/system/services" + System.lineSeparator() +
+                "set principal ACL for service4" + System.lineSeparator() +
+                "allow jcr:read,rep:userManagement on home(service3)" + 
System.lineSeparator() +
+                "end"+ System.lineSeparator();
+
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @NotNull
+    private static SystemUser createSystemUser(@NotNull String 
systemUsersName, @NotNull String nodeName) {
+        RepoPath repoPath = new 
RepoPath("/home/users/system/services/"+nodeName);
+        return new SystemUser(systemUsersName, repoPath, new 
RepoPath("/home/users/system/services"));
+    }
+
+    @NotNull
+    private static String getPolicyPath(@NotNull SystemUser systemUser) {
+        return "/jcr_root"+systemUser.getPath().toString() + 
"/_rep_principalPolicy.xml";
+    }
+
+    private ParseResult parseAndSetRepoinit(@NotNull String systemUsersName, 
@NotNull String nodeName) throws Exception {
+        SystemUser systemUser = createSystemUser(systemUsersName, nodeName);
+
+        return parseAndSetRepoinit(getPolicyPath(systemUser), systemUser);
+    }
+
+    private ParseResult parseAndSetRepoinit(@NotNull String path, @NotNull 
SystemUser... systemUsers) throws Exception {
+        AclManager aclManager = new DefaultAclManager();
+        for (SystemUser systemUser : systemUsers) {
+            aclManager.addSystemUser(systemUser);
+        }
+
+        InputStream is = getClass().getResourceAsStream(path.substring(1));
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        return new ParseResult(TestUtils.createRepoInitExtension(handler, 
aclManager, path, is, baos), new String(baos.toByteArray()));
+    }
+
+}
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepRepoPolicyEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepRepoPolicyEntryHandlerTest.java
index f70c6f4..cf65878 100644
--- 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepRepoPolicyEntryHandlerTest.java
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepRepoPolicyEntryHandlerTest.java
@@ -29,6 +29,8 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
 import java.io.StringReader;
 import java.util.List;
 
@@ -67,11 +69,8 @@ public class RepRepoPolicyEntryHandlerTest {
         String path = "/jcr_root/_rep_repoPolicy.xml";
         AclManager aclManager = new DefaultAclManager();
         aclManager.addSystemUser(new SystemUser("repolevel-service", new 
RepoPath("/home/users/system/test"), new RepoPath("/home/users/system")));
-        Extension repoinitExtension = 
TestUtils.createRepoInitExtension(handler, aclManager, path, 
getClass().getResourceAsStream(path.substring(1)));
-
-        assertNotNull(repoinitExtension);
-        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
-        assertTrue(repoinitExtension.isRequired());
+        OutputStream out = new ByteArrayOutputStream();
+        Extension repoinitExtension = new 
ParseResult(TestUtils.createRepoInitExtension(handler, aclManager, path, 
getClass().getResourceAsStream(path.substring(1)), out), 
out.toString()).getRepoinitExtension();
 
         String expectedEnd =
                 "set ACL for repolevel-service" + System.lineSeparator() +
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
index b232330..5ba09b5 100644
--- 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
@@ -21,13 +21,22 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import java.io.StringReader;
 import java.util.List;
 
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
 import org.apache.sling.feature.cpconverter.accesscontrol.DefaultAclManager;
+import org.apache.sling.feature.cpconverter.accesscontrol.SystemUser;
+import org.apache.sling.feature.cpconverter.accesscontrol.User;
 import org.apache.sling.repoinit.parser.RepoInitParser;
 import org.apache.sling.repoinit.parser.impl.RepoInitParserService;
 import org.apache.sling.repoinit.parser.operations.Operation;
@@ -101,6 +110,15 @@ public class UsersEntryHandlerTest {
         assertTrue(actual.contains("/home/users/system/my:feature"));
     }
 
+    @Test
+    public void testUser() throws Exception {
+        String path = "/jcr_root/home/users/a/author/.content.xml";
+        AclManager aclManager = mock(AclManager.class);
+        TestUtils.createRepoInitExtension(usersEntryHandler, aclManager, path, 
getClass().getResourceAsStream(path.substring(1)));
+        verify(aclManager, times(1)).addUser(any(User.class));
+        verify(aclManager, never()).addSystemUser(any(SystemUser.class));
+    }
+
     private Extension parseAndSetRepoinit(String path) throws Exception {
         return TestUtils.createRepoInitExtension(usersEntryHandler, new 
DefaultAclManager(), path, getClass().getResourceAsStream(path.substring(1)));
     }
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/_rep_principalPolicy.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/_rep_principalPolicy.xml
new file mode 100644
index 0000000..e55c57f
--- /dev/null
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/_rep_principalPolicy.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"; xmlns:rep="internal"
+          jcr:primaryType="rep:PrincipalPolicy"
+          rep:principalName="service1">
+    <entry0
+            jcr:primaryType="rep:PrincipalEntry"
+            rep:privileges="{Name}[jcr:read,jcr:readAccessControl]"
+            rep:effectivePath="/asd/public">
+    </entry0>
+</jcr:root>
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random2/_rep_principalPolicy.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random2/_rep_principalPolicy.xml
new file mode 100644
index 0000000..38f5335
--- /dev/null
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random2/_rep_principalPolicy.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"; xmlns:rep="internal"
+          jcr:primaryType="rep:PrincipalPolicy"
+          rep:principalName="service2">
+    <entry0
+            jcr:primaryType="rep:PrincipalEntry"
+            rep:privileges="{Name}[jcr:read]"
+            rep:effectivePath="/asd/public">
+        <rep:restrictions
+                jcr:primaryType="rep:Restrictions"
+                rep:ntNames="{Name}[nt:folder,sling:Folder]"/>
+    </entry0>
+</jcr:root>
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random3/_rep_principalPolicy.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random3/_rep_principalPolicy.xml
new file mode 100644
index 0000000..f5048c0
--- /dev/null
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random3/_rep_principalPolicy.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"; xmlns:rep="internal"
+          jcr:primaryType="rep:PrincipalPolicy"
+          rep:principalName="service3">
+    <entry0
+            jcr:primaryType="rep:PrincipalEntry"
+            rep:privileges="{Name}[jcr:all]"
+            rep:effectivePath="/home/users/system/services/random3/subtree">
+    </entry0>
+</jcr:root>
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random4/_rep_principalPolicy.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random4/_rep_principalPolicy.xml
new file mode 100644
index 0000000..eff8224
--- /dev/null
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random4/_rep_principalPolicy.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"; xmlns:rep="internal"
+          jcr:primaryType="rep:PrincipalPolicy"
+          rep:principalName="service4">
+    <entry0
+            jcr:primaryType="rep:PrincipalEntry"
+            rep:privileges="{Name}[jcr:read,rep:userManagement]"
+            rep:effectivePath="/home/users/system/services/random3">
+    </entry0>
+</jcr:root>

Reply via email to