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 c338e2c  SLING-10070 : Option to enforce principal-based authorization 
(wip) (#56)
c338e2c is described below

commit c338e2c2d1713174eaf7acaa1560680a6117417f
Author: anchela <[email protected]>
AuthorDate: Wed Jan 20 13:25:26 2021 +0100

    SLING-10070 : Option to enforce principal-based authorization (wip) (#56)
    
    Co-authored-by: angela <[email protected]>
    Co-authored-by: Karl Pauls <[email protected]>
---
 .../cpconverter/accesscontrol/AclManager.java      |   2 +
 .../accesscontrol/DefaultAclManager.java           | 114 +++++++++++-----
 .../feature/cpconverter/accesscontrol/Mapping.java | 130 ++++++++++++++++++
 ...ntentPackage2FeatureModelConverterLauncher.java |   9 +-
 .../AbstractConfigurationEntryHandler.java         |  22 ++-
 .../accesscontrol/EnforcePrincipalBasedTest.java   | 149 +++++++++------------
 .../cpconverter/accesscontrol/MappingTest.java     | 134 ++++++++++++++++++
 .../handlers/ConfigurationEntryHandlerTest.java    |  55 +++++---
 .../JsonConfigurationEntryHandlerTest.java         |  34 +++++
 .../feature/cpconverter/handlers/TestUtils.java    |   3 +
 ...ceusermapping.impl.ServiceUserMapperImpl.config |   2 +-
 ...ceusermapping.impl.ServiceUserMapperImpl.config |   2 +-
 ...rviceusermapping.impl.ServiceUserMapperImpl.cfg |   2 +-
 ...usermapping.impl.ServiceUserMapperImpl.cfg.json |   5 +-
 ...ceusermapping.impl.ServiceUserMapperImpl.config |   2 +-
 ...ermapping.impl.ServiceUserMapperImpl.config.xml |   2 +-
 ...sermapping.impl.ServiceUserMapperImpl.typed.xml |   2 +-
 ...rviceusermapping.impl.ServiceUserMapperImpl.xml |   2 +-
 ...eusermapping.impl.ServiceUserMapperImpl.xml.cfg |   2 +-
 19 files changed, 510 insertions(+), 163 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AclManager.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AclManager.java
index 8ca506d..95f2f75 100644
--- 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AclManager.java
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AclManager.java
@@ -34,6 +34,8 @@ public interface AclManager {
 
     boolean addSystemUser(@NotNull SystemUser systemUser);
 
+    void addMapping(@NotNull Mapping mapping);
+
     boolean addAcl(@NotNull String systemUser, @NotNull AccessControlEntry 
acl);
 
     void addRepoinitExtension(@NotNull List<VaultPackageAssembler> 
packageAssemblers, @NotNull FeaturesManager featureManager);
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 aff08fc..e898d13 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
@@ -27,11 +27,15 @@ import org.apache.sling.feature.cpconverter.shared.RepoPath;
 import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.jcr.NamespaceException;
 import java.io.File;
 import java.io.FileInputStream;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Optional;
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -41,7 +45,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -49,14 +52,17 @@ import java.util.stream.Stream;
 
 public class DefaultAclManager implements AclManager {
 
+    private static final Logger log = 
LoggerFactory.getLogger(DefaultAclManager.class);
+
     private static final String CONTENT_XML_FILE_NAME = ".content.xml";
 
-    private final boolean enforcePrincipalBased;
-    private final RepoPath supportedPrincipalBasedPath;
+    private final RepoPath enforcePrincipalBasedSupportedPath;
 
     private final Set<SystemUser> systemUsers = new LinkedHashSet<>();
     private final Set<Group> groups = new LinkedHashSet<>();
     private final Set<User> users = new LinkedHashSet<>();
+    private final Set<Mapping> mappings = new HashSet<>();
+    private final Set<String> mappedById = new HashSet<>();
 
     private final Map<String, List<AccessControlEntry>> acls = new HashMap<>();
 
@@ -65,11 +71,10 @@ public class DefaultAclManager implements AclManager {
     private volatile PrivilegeDefinitions privilegeDefinitions;
 
     public DefaultAclManager() {
-        this(false, null);
+        this(null);
     }
-    public DefaultAclManager(boolean enforcePrincipalBased, @Nullable String 
supportedPrincipalBasedPath) {
-        this.enforcePrincipalBased = enforcePrincipalBased;
-        this.supportedPrincipalBasedPath = (supportedPrincipalBasedPath == 
null) ? null : new RepoPath(supportedPrincipalBasedPath);
+    public DefaultAclManager(@Nullable String 
enforcePrincipalBasedSupportedPath) {
+        this.enforcePrincipalBasedSupportedPath = 
(enforcePrincipalBasedSupportedPath == null) ? null : new 
RepoPath(enforcePrincipalBasedSupportedPath);
     }
 
     @Override
@@ -84,7 +89,26 @@ public class DefaultAclManager implements AclManager {
 
     @Override
     public boolean addSystemUser(@NotNull SystemUser systemUser) {
-        return systemUsers.add(systemUser);
+        if (systemUsers.add(systemUser)) {
+            if (mappings.stream().anyMatch(mapping -> 
mapping.mapsUser(systemUser.getId()))) {
+                mappedById.add(systemUser.getId());
+            }
+            return true;
+        } else {
+            return false;
+        }
+
+    }
+
+    @Override
+    public void addMapping(@NotNull Mapping mapping) {
+        if (mappings.add(mapping)) {
+            for (SystemUser user : systemUsers) {
+                if (mapping.mapsUser(user.getId())) {
+                    mappedById.add(user.getId());
+                }
+            }
+        }
     }
 
     @Override
@@ -128,7 +152,7 @@ public class DefaultAclManager implements AclManager {
     private void addUsersAndGroups(@NotNull Formatter formatter) {
         for (SystemUser systemUser : systemUsers) {
             // make sure all system users are created first
-            formatter.format("create service user %s with path %s%n", 
systemUser.getId(), 
calculateIntermediatePath(systemUser.getIntermediatePath()));
+            formatter.format("create service user %s with path %s%n", 
systemUser.getId(), calculateIntermediatePath(systemUser));
             if (aclIsBelow(systemUser.getPath())) {
                 throw new IllegalStateException("Detected policy on subpath of 
system-user: " + systemUser);
             }
@@ -138,50 +162,53 @@ public class DefaultAclManager implements AclManager {
         // created by repo-init statements generated here.
         Stream.concat(groups.stream(), users.stream()).forEach(abstractUser -> 
{
             if (aclStartsWith(abstractUser.getPath())) {
-                throw new IllegalStateException("Detected policy on user: " + 
abstractUser);
+                throw new IllegalStateException("Detected policy on 
user/group: " + abstractUser);
             }
         });
     }
 
     @NotNull
-    private String calculateIntermediatePath(@NotNull RepoPath 
intermediatePath) {
-        if (enforcePrincipalBased && supportedPrincipalBasedPath != null && 
!intermediatePath.startsWith(supportedPrincipalBasedPath)) {
+    private String calculateIntermediatePath(@NotNull SystemUser systemUser) {
+        RepoPath intermediatePath = systemUser.getIntermediatePath();
+        if (enforcePrincipalBased(systemUser) && 
!intermediatePath.startsWith(enforcePrincipalBasedSupportedPath)) {
             RepoPath parent = intermediatePath.getParent();
             while (parent != null) {
-                if (supportedPrincipalBasedPath.startsWith(parent)) {
+                if (enforcePrincipalBasedSupportedPath.startsWith(parent)) {
                     String relpath = 
intermediatePath.toString().substring(parent.toString().length());
-                    return supportedPrincipalBasedPath.toString() + relpath;
+                    return enforcePrincipalBasedSupportedPath.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);
+            throw new IllegalStateException("Cannot calculate intermediate 
path for service user. Configured Supported path " 
+enforcePrincipalBasedSupportedPath+" has no common ancestor with 
"+intermediatePath);
         } else {
             return intermediatePath.toString();
         }
     }
 
     private void addPaths(@NotNull Formatter formatter, @NotNull 
List<VaultPackageAssembler> packageAssemblers) {
-        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)
-                    );
-        }
+        Set<RepoPath> paths = acls.entrySet().stream()
+                // filter paths if service user does not exist or will have 
principal-based ac setup enforced
+                .filter(entry -> {
+                    Optional<SystemUser> su = getSystemUser(entry.getKey());
+                    return su.isPresent() && !enforcePrincipalBased(su.get());
+                })
+                .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)
+                );
     }
 
     private boolean aclStartsWith(@NotNull RepoPath path) {
@@ -200,7 +227,7 @@ public class DefaultAclManager implements AclManager {
 
         authorizations.forEach(entry -> {
             String path = getRepoInitPath(entry.getRepositoryPath(), 
systemUser);
-            if (enforcePrincipalBased || entry.isPrincipalBased()) {
+            if (entry.isPrincipalBased() || enforcePrincipalBased(systemUser)) 
{
                 principalEntries.put(entry, path);
             } else {
                 resourceEntries.put(entry, path);
@@ -219,6 +246,19 @@ public class DefaultAclManager implements AclManager {
         }
     }
 
+    private boolean enforcePrincipalBased(@NotNull SystemUser systemUser) {
+        if (enforcePrincipalBasedSupportedPath == null) {
+            return false;
+        } else {
+            if (mappedById.contains(systemUser.getId())) {
+                log.warn("Skip enforcing principalbased access control setup 
for system user {} due to existing mapping", systemUser.getId());
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+
     private void writeEntry(@NotNull AccessControlEntry entry, @NotNull String 
path, @NotNull Formatter formatter) {
         formatter.format("%s %s on %s",
                 entry.getOperation(),
diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/Mapping.java 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/Mapping.java
new file mode 100644
index 0000000..9b91372
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/Mapping.java
@@ -0,0 +1,130 @@
+/*
+ * 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.jetbrains.annotations.NotNull;
+
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+
+public class Mapping {
+
+    private final String serviceName;
+
+    private final String subServiceName;
+
+    private final String userName;
+
+    private final Set<String> principalNames;
+
+    /**
+     * Copied from 
https://github.com/apache/sling-org-apache-sling-serviceusermapper/blob/master/src/main/java/org/apache/sling/serviceusermapping/Mapping.java
+     */
+    public Mapping(@NotNull final String spec) {
+
+        final int colon = spec.indexOf(':');
+        final int equals = spec.indexOf('=');
+
+        if (colon == 0 || equals <= 0) {
+            throw new IllegalArgumentException("serviceName is required");
+        } else if (equals == spec.length() - 1) {
+            throw new IllegalArgumentException("userName or principalNames is 
required");
+        } else if (colon + 1 == equals) {
+            throw new IllegalArgumentException("serviceInfo must not be 
empty");
+        }
+
+        if (colon < 0 || colon > equals) {
+            this.serviceName = spec.substring(0, equals);
+            this.subServiceName = null;
+        } else {
+            this.serviceName = spec.substring(0, colon);
+            this.subServiceName = spec.substring(colon + 1, equals);
+        }
+
+        String s = spec.substring(equals + 1);
+        if (s.charAt(0) == '[' && s.charAt(s.length()-1) == ']') {
+            this.userName = null;
+            this.principalNames = extractPrincipalNames(s);
+        } else {
+            this.userName = s;
+            this.principalNames = null;
+        }
+    }
+
+    /**
+     * Copied from 
https://github.com/apache/sling-org-apache-sling-serviceusermapper/blob/master/src/main/java/org/apache/sling/serviceusermapping/Mapping.java
+     */
+    @NotNull
+    private static Set<String> extractPrincipalNames(@NotNull String s) {
+        String[] sArr = s.substring(1, s.length() - 1).split(",");
+        Set<String> set = new LinkedHashSet<>();
+        for (String name : sArr) {
+            String n = name.trim();
+            if (!n.isEmpty()) {
+                set.add(n);
+            }
+        }
+        return set;
+    }
+
+    public boolean mapsUser(@NotNull String userId) {
+        return userId.equals(this.userName);
+    }
+
+    public boolean mapsPrincipal(@NotNull String principalName) {
+        return this.principalNames != null && 
principalNames.contains(principalName);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Objects.hash(serviceName);
+        result = prime * result + Objects.hash(subServiceName);
+        result = prime * result + Objects.hash(userName);
+        result = prime * result + Objects.hash(principalNames);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        Mapping other = (Mapping) obj;
+        return Objects.equals(serviceName, other.serviceName)
+               && Objects.equals(subServiceName, other.subServiceName)
+               && Objects.equals(userName, other.userName)
+               && Objects.equals(principalNames, other.principalNames);
+    }
+
+    /**
+     * Copied from 
https://github.com/apache/sling-org-apache-sling-serviceusermapper/blob/master/src/main/java/org/apache/sling/serviceusermapping/Mapping.java
+     */
+    @Override
+    public String toString() {
+        String name = (userName != null) ? "userName=" + userName : 
"principleNames" + principalNames.toString();
+        return "Mapping [serviceName=" + serviceName + ", subServiceName="
+                + subServiceName + ", " + name;
+    }
+}
\ No newline at end of file
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 73dd0c2..94332af 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,11 +96,8 @@ 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;
+    @Option(names = { "--enforce-principal-based-supported-path" }, 
description = "Converts service user access control entries to principal-based 
setup using the given supported path.", required = false)
+    private String enforcePrincipalBasedSupportedPath = null;
 
     @Option(names = { "--entry-handler-config" }, description = "Config for 
entry handlers that support it (classname:<config-string>", required = false)
     private List<String> entryHandlerConfigs = null;
@@ -161,7 +158,7 @@ public final class 
ContentPackage2FeatureModelConverterLauncher implements Runna
                                                              
.setFeaturesManager(featuresManager)
                                                              
.setBundlesDeployer(new DefaultArtifactsDeployer(artifactsOutputDirectory))
                                                              
.setEntryHandlersManager(new 
DefaultEntryHandlersManager(entryHandlerConfigsMap))
-                                                             
.setAclManager(new DefaultAclManager(enforcePrincipalBased, 
supportedPrincipalBasedPath))
+                                                             
.setAclManager(new DefaultAclManager(enforcePrincipalBasedSupportedPath))
                                                              
.setEmitter(DefaultPackagesEventsEmitter.open(featureModelsOutputDirectory))
                                                              
.setFailOnMixedPackages(failOnMixedPackages)
                                                              
.setDropContent(true);
diff --git 
a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
 
b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
index 8be1241..4ade6d8 100644
--- 
a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
+++ 
b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractConfigurationEntryHandler.java
@@ -24,6 +24,9 @@ import java.util.regex.Matcher;
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
 import 
org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
+import org.apache.sling.feature.cpconverter.accesscontrol.Mapping;
+import org.apache.sling.feature.cpconverter.features.FeaturesManager;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.osgi.util.converter.Converters;
@@ -34,6 +37,8 @@ abstract class AbstractConfigurationEntryHandler extends 
AbstractRegexEntryHandl
 
     private static final String REPOINIT_PID = 
"org.apache.sling.jcr.repoinit.impl.RepositoryInitializer";
 
+    private static final String SERVICE_USER_MAPPING_PID = 
"org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl";
+
     public AbstractConfigurationEntryHandler(@NotNull String extension) {
         
super("/jcr_root/(?:apps|libs)/.+/config(\\.(?<runmode>[^/]+))?/(?<pid>.*)\\." 
+ extension);
     }
@@ -80,22 +85,31 @@ abstract class AbstractConfigurationEntryHandler extends 
AbstractRegexEntryHandl
             }
             // there is a specified RunMode
             runMode = matcher.group("runmode");
-            
+
+            FeaturesManager featuresManager = 
Objects.requireNonNull(converter.getFeaturesManager());
             if (REPOINIT_FACTORY_PID.equals(factoryPid)) {
                 final String[] scripts = 
Converters.standardConverter().convert(configurationProperties.get("scripts")).to(String[].class);
                 if (scripts != null && scripts.length > 0 ) {
                     for(final String text : scripts) {
                         if ( text != null && !text.trim().isEmpty() ) {
-                            
Objects.requireNonNull(converter.getFeaturesManager()).addOrAppendRepoInitExtension(text,
 runMode);
+                            featuresManager.addOrAppendRepoInitExtension(text, 
runMode);
                         }
                     }
                 }
                 checkReferences(configurationProperties, pid);
             } else if ( REPOINIT_PID.equals(pid) ) {
                 checkReferences(configurationProperties, pid);
-
+            } else if (pid.startsWith(SERVICE_USER_MAPPING_PID)) {
+                String[] mappings = 
Converters.standardConverter().convert(configurationProperties.get("user.mapping")).to(String[].class);
+                if (mappings != null) {
+                    AclManager aclManager = 
Objects.requireNonNull(converter.getAclManager());
+                    for (String usermapping : mappings) {
+                        aclManager.addMapping(new Mapping(usermapping));
+                    }
+                }
+                featuresManager.addConfiguration(runMode, id, 
configurationProperties);
             } else {
-                
Objects.requireNonNull(converter.getFeaturesManager()).addConfiguration(runMode,
 id, configurationProperties);
+                featuresManager.addConfiguration(runMode, id, 
configurationProperties);
             }
         } else {
             throw new IllegalStateException("Something went terribly wrong: 
pattern '"
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
index 33c390f..13955dc 100644
--- 
a/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/EnforcePrincipalBasedTest.java
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/EnforcePrincipalBasedTest.java
@@ -53,6 +53,7 @@ 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 final String remappedIntermediatePath = 
"/home/users/system/some/subtree/intermediate";
 
     private AclManager aclManager;
     private Path tempDir;
@@ -63,7 +64,7 @@ public class EnforcePrincipalBasedTest {
 
     @Before
     public void setUp() throws Exception {
-        aclManager = new DefaultAclManager(true, 
"/home/users/system/some/subtree");
+        aclManager = new DefaultAclManager("/home/users/system/some/subtree");
         tempDir = Files.createTempDirectory(getClass().getSimpleName());
 
         assembler = mock(VaultPackageAssembler.class);
@@ -87,53 +88,29 @@ public class EnforcePrincipalBasedTest {
 
     @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(Collections.singletonList(assembler), 
fm);
-    }
-
-    @Test
-    public void testMissingSupportedPath() {
-        AclManager aclManager = new DefaultAclManager(true, null);
-        aclManager.addSystemUser(systemUser);
-
+        AclManager acMgr = new DefaultAclManager("/an/invalid/supported/path");
         RepoPath accessControlledPath = new RepoPath("/content/feature");
-        aclManager.addAcl(systemUser.getId(), new AccessControlEntry(true, 
"jcr:read", accessControlledPath , false));
-
-        aclManager.addRepoinitExtension(Collections.singletonList(assembler), 
fm);
-        String txt = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT).getText();
-        assertFalse(txt.contains("create service user user1 with path 
/home/users/system/some/subtree/intermediate"));
-        assertTrue(txt.contains("create service user user1 with path " + 
systemUser.getIntermediatePath()));
+        getRepoInitExtension(acMgr, accessControlledPath, systemUser, false);
     }
 
     @Test
     public void testResourceBasedConversionWithoutForce() throws 
RepoInitParsingException {
-        AclManager aclManager = new DefaultAclManager(false, 
"/home/users/system/some/subtree"){
+        AclManager acMgr = new DefaultAclManager(null) {
             @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(Collections.singletonList(assembler), 
fm);
-
-        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
-        assertNotNull(repoinitExtension);
+        Extension repoinitExtension = getRepoInitExtension(acMgr, 
accessControlledPath, systemUser, false);
 
         String expected =
                 "create service user user1 with path " + 
systemUser.getIntermediatePath() + 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();
+                        "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);
@@ -141,46 +118,15 @@ public class EnforcePrincipalBasedTest {
         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(Collections.singletonList(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(Collections.singletonList(assembler), 
fm);
-
-        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
-        assertNotNull(repoinitExtension);
+        Extension repoinitExtension = getRepoInitExtension(aclManager, 
accessControlledPath, systemUser, false);
 
         String expected =
-                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                "create service user user1 with path " + 
remappedIntermediatePath + System.lineSeparator() +
                 "set principal ACL for user1" + System.lineSeparator() +
                 "allow jcr:read on /content/feature" + System.lineSeparator() +
                 "end" + System.lineSeparator();
@@ -195,18 +141,11 @@ public class EnforcePrincipalBasedTest {
 
     @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(Collections.singletonList(assembler), 
fm);
-
-        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
-        assertNotNull(repoinitExtension);
+        Extension repoinitExtension = getRepoInitExtension(aclManager, 
accessControlledPath, systemUser, true);
 
         String expected =
-                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                "create service user user1 with path " + 
remappedIntermediatePath + System.lineSeparator() +
                         "set principal ACL for user1" + System.lineSeparator() 
+
                         "allow jcr:read on /content/feature" + 
System.lineSeparator() +
                         "end" + System.lineSeparator();
@@ -221,19 +160,11 @@ public class EnforcePrincipalBasedTest {
 
     @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(Collections.singletonList(assembler), 
fm);
-
-        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
-        assertNotNull(repoinitExtension);
+        Extension repoinitExtension = getRepoInitExtension(aclManager, 
accessControlledPath, systemUser, true);
 
         String expected =
-                "create service user user1 with path 
/home/users/system/some/subtree/intermediate" + System.lineSeparator() +
+                "create service user user1 with path " + 
remappedIntermediatePath + System.lineSeparator() +
                 "set principal ACL for user1" + System.lineSeparator() +
                 "allow jcr:read on home(user1)" + System.lineSeparator() +
                 "end" + System.lineSeparator();
@@ -245,4 +176,52 @@ public class EnforcePrincipalBasedTest {
         List<Operation> operations = repoInitParser.parse(new 
StringReader(actual));
         assertFalse(operations.isEmpty());
     }
+
+    @Test
+    public void testSingleUserMapping() {
+        aclManager.addMapping(new 
Mapping("org.apache.sling.testbundle:subservice="+systemUser.getId()));
+
+        RepoPath accessControlledPath = new RepoPath("/content/feature");
+        Extension repoinitExtension = getRepoInitExtension(aclManager, 
accessControlledPath, systemUser, false);
+
+        String expected =
+                "create service user user1 with path " 
+systemUser.getIntermediatePath()+ 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);
+    }
+
+    @Test
+    public void testPrincipalMapping() {
+        aclManager.addMapping(new 
Mapping("org.apache.sling.testbundle:subservice=["+systemUser.getId()+"]"));
+
+        RepoPath accessControlledPath = new RepoPath("/content/feature");
+        Extension repoinitExtension = getRepoInitExtension(aclManager, 
accessControlledPath, systemUser, false);
+
+        String expected =
+                "create service user user1 with path " + 
remappedIntermediatePath + 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);
+    }
+
+    @NotNull
+    private Extension getRepoInitExtension(@NotNull AclManager aclManager, 
@NotNull RepoPath accessControlledPath, @NotNull SystemUser systemUser, boolean 
isPrincipalBased) {
+        aclManager.addSystemUser(systemUser);
+
+        AccessControlEntry acl = new AccessControlEntry(true, "jcr:read", 
accessControlledPath, isPrincipalBased);
+        aclManager.addAcl(systemUser.getId(), acl);
+
+        aclManager.addRepoinitExtension(Collections.singletonList(assembler), 
fm);
+
+        Extension repoinitExtension = 
feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+        assertNotNull(repoinitExtension);
+        return repoinitExtension;
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/MappingTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/MappingTest.java
new file mode 100644
index 0000000..96b5242
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/accesscontrol/MappingTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public class MappingTest {
+
+    @Test
+    public void testMapUserId() {
+        Mapping m = new 
Mapping("org.apache.sling.testbundle:sub-service-1=service1");
+        assertTrue(m.mapsUser("service1"));
+        assertFalse(m.mapsUser("another"));
+        assertFalse(m.mapsPrincipal("service1"));
+        assertFalse(m.mapsPrincipal("another"));
+    }
+
+    @Test
+    public void testMapPrincipalNames() {
+        Mapping m = new 
Mapping("org.apache.sling.testbundle:sub-service-1=[service1,service2]");
+        assertFalse(m.mapsUser("service1"));
+        assertFalse(m.mapsUser("another"));
+        assertTrue(m.mapsPrincipal("service1"));
+        assertTrue(m.mapsPrincipal("service2"));
+        assertFalse(m.mapsPrincipal("another"));
+    }
+
+    @Test
+    public void testMapSinglePrincipalName() {
+        Mapping m = new 
Mapping("org.apache.sling.testbundle:sub-service-1=[service1]");
+        assertFalse(m.mapsUser("service1"));
+        assertFalse(m.mapsUser("another"));
+        assertTrue(m.mapsPrincipal("service1"));
+        assertFalse(m.mapsPrincipal("service2"));
+        assertFalse(m.mapsPrincipal("another"));
+    }
+
+    @Test
+    public void testMapEmptyPrincipalNames() {
+        Mapping m = new 
Mapping("org.apache.sling.testbundle:sub-service-1=[]");
+        assertFalse(m.mapsUser("service1"));
+        assertFalse(m.mapsUser("another"));
+        assertFalse(m.mapsPrincipal("service1"));
+        assertFalse(m.mapsPrincipal("service2"));
+        assertFalse(m.mapsPrincipal("another"));
+    }
+
+    @Test
+    public void testMapMissingSubservice() {
+        Mapping m = new Mapping("org.apache.sling.testbundle=[service1]");
+        assertFalse(m.mapsUser("service1"));
+        assertFalse(m.mapsUser("another"));
+        assertTrue(m.mapsPrincipal("service1"));
+        assertFalse(m.mapsPrincipal("another"));
+    }
+
+    @Test
+    public void testMapIncompleteArray() {
+        Mapping m = new 
Mapping("org.apache.sling.testbundle:sub-service=[service1");
+        assertTrue(m.mapsUser("[service1"));
+        assertFalse(m.mapsPrincipal("service1"));
+
+        m = new Mapping("org.apache.sling.testbundle:sub-service=service1]");
+        assertTrue(m.mapsUser("service1]"));
+        assertFalse(m.mapsPrincipal("service1"));
+    }
+
+    @Test
+    public void testColonInUserName() {
+        Mapping m = new Mapping("org.apache.sling.testbundle=sling:service1");
+        assertTrue(m.mapsUser("sling:service1"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingUser() {
+        new Mapping("org.apache.sling.testbundle:subservice");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingUser2() {
+        new Mapping("org.apache.sling.testbundle:sub-service=");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingBundle() {
+        new Mapping(":sub-service=[service1]");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingSubservice() {
+        new Mapping("org.apache.sling.testbundle:=service1");
+    }
+
+    @Test
+    public void testEquals() {
+        Mapping m = new 
Mapping("org.apache.sling.testbundle:sub-service-1=service1");
+        Mapping m2 = new 
Mapping("org.apache.sling.testbundle:sub-service-1=[service1,service2]");
+        Mapping m3 = new 
Mapping("org.apache.sling.testbundle:sub-service-1=[service1]");
+
+        assertEquals(m, new 
Mapping("org.apache.sling.testbundle:sub-service-1=service1"));
+
+        assertNotEquals(m, new 
Mapping("org.apache.sling.testbundle=service1"));
+        assertNotEquals(m, new 
Mapping("org.apache.sling.testbundle:other-sub-service=service1"));
+        assertNotEquals(m, new 
Mapping("org.apache.sling.otherbundle:sub-service1=service1"));
+        assertNotEquals(m, m2);
+        assertNotEquals(m, m3);
+        assertNotEquals(m2, m3);
+        assertNotEquals(m2, new 
Mapping("org.apache.sling.testbundle:sub-service-1=[service3,service4]"));
+
+        assertTrue(m.equals(m));
+        assertTrue(m2.equals(m2));
+        assertFalse(m.equals(null));
+        assertFalse(m2.equals(m2.toString()));
+    }
+}
\ No newline at end of file
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java
index b5ddbd4..6107275 100644
--- 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/ConfigurationEntryHandlerTest.java
@@ -24,12 +24,15 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.StringWriter;
 import java.io.Writer;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Dictionary;
 
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
@@ -39,6 +42,8 @@ import org.apache.sling.feature.Configurations;
 import org.apache.sling.feature.Extension;
 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.Mapping;
 import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
 import org.apache.sling.feature.cpconverter.features.FeaturesManager;
 import org.apache.sling.feature.io.json.ConfigurationJSONWriter;
@@ -71,9 +76,10 @@ public class ConfigurationEntryHandlerTest {
         "    \"test.dateproperty\":1604743842669,\n" + 
         "    \"test.booleanproperty\":true,\n" + 
         "    \"user.mapping\":[\n" + 
-        "      \"com.adobe.acs.acs-aem-samples-bundle=admin\",\n" + 
-        "      
\"com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice\"\n" + 
-        "    ]\n" + 
+        "      \"org.apache.sling.testbundle:sub-service-1=service1\",\n" +
+        "      
\"org.apache.sling.testbundle:sub-service-2=[service1,service2]\",\n" +
+        "      
\"org.apache.sling.testbundle=[service1,external-service-user]\"\n" +
+        "    ]\n" +
         "  }\n" + 
         "}";
     
@@ -81,6 +87,7 @@ public class ConfigurationEntryHandlerTest {
 
     private final int expectedConfigurationsSize;
     private final int expectedConfigurationsEntrySize;
+    private final int expectedMappings;
 
     private final AbstractConfigurationEntryHandler configurationEntryHandler;
     private final String expectedRunMode;
@@ -88,11 +95,13 @@ public class ConfigurationEntryHandlerTest {
     public ConfigurationEntryHandlerTest(String resourceConfiguration,
                                          int expectedConfigurationsSize,
                                          int expectedConfigurationsEntrySize,
+                                         int expectedMappings,
                                          AbstractConfigurationEntryHandler 
configurationEntryHandler, 
                                          String expectedRunMode) {
         this.resourceConfiguration = resourceConfiguration;
         this.expectedConfigurationsSize = expectedConfigurationsSize;
         this.expectedConfigurationsEntrySize = expectedConfigurationsEntrySize;
+        this.expectedMappings = expectedMappings;
         this.configurationEntryHandler = configurationEntryHandler;
         this.expectedRunMode = expectedRunMode;
     }
@@ -122,6 +131,8 @@ public class ConfigurationEntryHandlerTest {
         when(featuresManager.getRunMode(anyString())).thenReturn(feature);
         ContentPackage2FeatureModelConverter converter = 
mock(ContentPackage2FeatureModelConverter.class);
         when(converter.getFeaturesManager()).thenReturn(featuresManager);
+        AclManager aclManager = mock(AclManager.class);
+        when(converter.getAclManager()).thenReturn(aclManager);
 
         configurationEntryHandler.handle(resourceConfiguration, archive, 
entry, converter);
 
@@ -138,10 +149,12 @@ public class ConfigurationEntryHandlerTest {
 
             assertTrue(configuration.getPid(), 
configuration.getPid().startsWith(EXPECTED_PID));
 
+            Dictionary<String,Object> props = configuration.getProperties();
             if (configuration.getPid().contains(".empty")) {
-                assertTrue(configuration.getProperties().isEmpty());
+                assertTrue(props.isEmpty());
             } else {
-                assertEquals("Unmatching size: " + 
configuration.getProperties().size(), expectedConfigurationsEntrySize, 
configuration.getProperties().size());
+                assertEquals("Unmatching size: " + props.size(), 
expectedConfigurationsEntrySize, configuration.getProperties().size());
+                verify(aclManager, 
times(expectedMappings)).addMapping(any(Mapping.class));
             }
             // type & value check for typed configuration
             if (this.resourceConfiguration.equals(TYPED_TESTCONFIG_PATH)) {
@@ -157,32 +170,32 @@ public class ConfigurationEntryHandlerTest {
         String path = "/jcr_root/apps/asd/config/";
 
         return Arrays.asList(new Object[][] {
-            { path + EXPECTED_PID + ".empty.cfg", 1, 2, new 
PropertiesConfigurationEntryHandler(), null },
-            { path + EXPECTED_PID + ".cfg", 1, 2, new 
PropertiesConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".empty.cfg", 1, 2, 0, new 
PropertiesConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".cfg", 1, 2, 1, new 
PropertiesConfigurationEntryHandler(), null },
 
-            { path + EXPECTED_PID + ".empty.cfg.json", 1, 2, new 
JsonConfigurationEntryHandler(), null },
-            { path + EXPECTED_PID + ".cfg.json", 1, 2, new 
JsonConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".empty.cfg.json", 1, 2, 0, new 
JsonConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".cfg.json", 1, 2, 3, new 
JsonConfigurationEntryHandler(), null },
 
-            { path + EXPECTED_PID + ".empty.config", 1, 2, new 
ConfigurationEntryHandler(), null },
-            { path + EXPECTED_PID + ".config", 1, 2, new 
ConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".empty.config", 1, 2, 0, new 
ConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".config", 1, 2, 3, new 
ConfigurationEntryHandler(), null },
 
-            { path + EXPECTED_PID + ".empty.xml", 1, 2, new 
XmlConfigurationEntryHandler(), null },
-            { path + EXPECTED_PID + ".xml", 1, 2, new 
XmlConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".empty.xml", 1, 2, 0, new 
XmlConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".xml", 1, 2, 3, new 
XmlConfigurationEntryHandler(), null },
 
-            { path + EXPECTED_PID + ".empty.config.xml", 1, 2, new 
XmlConfigurationEntryHandler(), null },
-            { path + EXPECTED_PID + ".config.xml", 1, 2, new 
XmlConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".empty.config.xml", 1, 2, 0, new 
XmlConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".config.xml", 1, 2, 3, new 
XmlConfigurationEntryHandler(), null },
 
             
-            { path + EXPECTED_PID + ".empty.xml.cfg", 1, 2, new 
PropertiesConfigurationEntryHandler(), null },
-            { path + EXPECTED_PID + ".xml.cfg", 1, 2, new 
PropertiesConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".empty.xml.cfg", 1, 2, 0,  new 
PropertiesConfigurationEntryHandler(), null },
+            { path + EXPECTED_PID + ".xml.cfg", 1, 2, 1, new 
PropertiesConfigurationEntryHandler(), null },
 
             // runmode aware folders
-            { "/jcr_root/apps/asd/config.author/" + EXPECTED_PID + ".config", 
1, 2, new ConfigurationEntryHandler(), "author" },
-            { REPOINIT_TESTCONFIG_PATH, 0, 2, new ConfigurationEntryHandler() 
, "author"},
-            { "/jcr_root/apps/asd/config.publish/" + EXPECTED_PID + ".config", 
1, 2, new ConfigurationEntryHandler(), "publish" },
+            { "/jcr_root/apps/asd/config.author/" + EXPECTED_PID + ".config", 
1, 2, 3, new ConfigurationEntryHandler(), "author" },
+            { REPOINIT_TESTCONFIG_PATH, 0, 2, 1, new 
ConfigurationEntryHandler() , "author"},
+            { "/jcr_root/apps/asd/config.publish/" + EXPECTED_PID + ".config", 
1, 2, 3, new ConfigurationEntryHandler(), "publish" },
 
             //test typed config
-            { TYPED_TESTCONFIG_PATH, 1, 6, new XmlConfigurationEntryHandler(), 
null }
+            { TYPED_TESTCONFIG_PATH, 1, 6, 3, new 
XmlConfigurationEntryHandler(), null }
         });
     }
 
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java
 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java
index 9711ba8..67d2ec9 100644
--- 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java
+++ 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/JsonConfigurationEntryHandlerTest.java
@@ -16,14 +16,24 @@
  */
 package org.apache.sling.feature.cpconverter.handlers;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 
 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.Feature;
 import 
org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.accesscontrol.AclManager;
+import org.apache.sling.feature.cpconverter.accesscontrol.Mapping;
+import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
+import org.apache.sling.feature.cpconverter.features.FeaturesManager;
 import org.junit.Test;
 
 public class JsonConfigurationEntryHandlerTest {
@@ -43,4 +53,28 @@ public class JsonConfigurationEntryHandlerTest {
         new JsonConfigurationEntryHandler().handle(resourceConfiguration, 
archive, entry, converter);
     }
 
+    @Test
+    public void validConfigurationThrowsException() throws Exception {
+        String resourceConfiguration = 
"/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json";
+
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        
when(entry.getName()).thenReturn(resourceConfiguration.substring(resourceConfiguration.lastIndexOf('/')
 + 1));
+        
when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(resourceConfiguration.substring(1)));
+
+        AclManager aclManager = mock(AclManager.class);
+        Feature feature = new Feature(new ArtifactId("org.apache.sling", 
"org.apache.sling.cp2fm", "0.0.1", null, null));
+        FeaturesManager featuresManager = spy(DefaultFeaturesManager.class);
+        when(featuresManager.getTargetFeature()).thenReturn(feature);
+
+        ContentPackage2FeatureModelConverter converter = 
mock(ContentPackage2FeatureModelConverter.class);
+        when(converter.getAclManager()).thenReturn(aclManager);
+        when(converter.getFeaturesManager()).thenReturn(featuresManager);
+
+        new JsonConfigurationEntryHandler().handle(resourceConfiguration, 
archive, entry, converter);
+
+        verify(aclManager, times(3)).addMapping(any(Mapping.class));
+    }
+
 }
diff --git 
a/src/test/java/org/apache/sling/feature/cpconverter/handlers/TestUtils.java 
b/src/test/java/org/apache/sling/feature/cpconverter/handlers/TestUtils.java
index a950260..737a777 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/TestUtils.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/TestUtils.java
@@ -31,8 +31,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.acl.Acl;
 import java.util.Collections;
 
 import static org.mockito.ArgumentMatchers.anyString;
@@ -49,6 +51,7 @@ class TestUtils {
     static Extension createRepoInitExtension(@NotNull EntryHandler handler, 
@NotNull AclManager aclManager, @NotNull String path, @NotNull InputStream is) 
throws Exception {
         return createRepoInitExtension(handler, aclManager, path, is, null);
     }
+
     static Extension createRepoInitExtension(@NotNull EntryHandler handler, 
@NotNull AclManager aclManager, @NotNull String path, @NotNull InputStream is, 
@Nullable OutputStream out) throws Exception {
         Archive archive = mock(Archive.class);
         Archive.Entry entry = mock(Archive.Entry.class);
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
index 4c50ea3..d8884c9 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
@@ -14,4 +14,4 @@
 # the License.
 
 user.default="admin"
-user.mapping=["com.adobe.acs.acs-aem-samples-bundle\=admin","com.adobe.acs.acs-aem-samples-bundle:sample-service\=oauthservice"]
+user.mapping=["org.apache.sling.testbundle:sub-service-1\=service1","org.apache.sling.testbundle:sub-service-2\=[service1,service2]","org.apache.sling.testbundle\=[service1,external-service-user]"]
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
index 4c50ea3..fc60138 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config.publish/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
@@ -14,4 +14,4 @@
 # the License.
 
 user.default="admin"
-user.mapping=["com.adobe.acs.acs-aem-samples-bundle\=admin","com.adobe.acs.acs-aem-samples-bundle:sample-service\=oauthservice"]
+user.mapping=["org.apache.sling.testbundle:sub-service-1\=service1","org.apache.sling.testbundle:sub-service-2\=[service1,service2]","org.apache.sling.testbundle\=org.apache.sling.testbundle\=[service1,external-service-user]"]
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg
index bf91183..712f4e7 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg
@@ -14,4 +14,4 @@
 # the License.
 
 user.default=admin
-user.mapping=[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]
+user.mapping=org.apache.sling.testbundle:sub-service-1=service-1
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json
index 98e506c..3b80428 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json
@@ -1,7 +1,8 @@
 {
     "user.default":"admin",
     "user.mapping": [
-        "com.adobe.acs.acs-aem-samples-bundle=admin",
-        "com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice"
+        "org.apache.sling.testbundle:sub-service-1=service1",
+        "org.apache.sling.testbundle:sub-service-2=[service1,service2]",
+        "org.apache.sling.testbundle=[service1,external-service-user]"
     ]
 }
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
index 4c50ea3..d8884c9 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config
@@ -14,4 +14,4 @@
 # the License.
 
 user.default="admin"
-user.mapping=["com.adobe.acs.acs-aem-samples-bundle\=admin","com.adobe.acs.acs-aem-samples-bundle:sample-service\=oauthservice"]
+user.mapping=["org.apache.sling.testbundle:sub-service-1\=service1","org.apache.sling.testbundle:sub-service-2\=[service1,service2]","org.apache.sling.testbundle\=[service1,external-service-user]"]
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config.xml
index f23afc9..7b8c6ca 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config.xml
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.config.xml
@@ -18,4 +18,4 @@
 <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"; 
xmlns:jcr="http://www.jcp.org/jcr/1.0";
     jcr:primaryType="sling:OsgiConfig"
     user.default="admin"
-    
user.mapping="[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]"/>
+    
user.mapping="[org.apache.sling.testbundle:sub-service-1=service1,org.apache.sling.testbundle:sub-service-2=\[service1\,service2\],org.apache.sling.testbundle=\[service1\,external-service-user\]]"/>
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.typed.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.typed.xml
index 91ed2cf..0f3e3c8 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.typed.xml
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.typed.xml
@@ -22,4 +22,4 @@
     test.doubleproperty="{Double}1.23"
     test.dateproperty="{Date}2020-11-07T10:10:42.669"
     test.booleanproperty="{Boolean}true"
-    
user.mapping="[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]"/>
+    
user.mapping="[org.apache.sling.testbundle:sub-service-1=service1,org.apache.sling.testbundle:sub-service-2=\[service1\,service2\],org.apache.sling.testbundle=\[service1\,external-service-user\]]"/>
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml
index f23afc9..7b8c6ca 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml
@@ -18,4 +18,4 @@
 <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"; 
xmlns:jcr="http://www.jcp.org/jcr/1.0";
     jcr:primaryType="sling:OsgiConfig"
     user.default="admin"
-    
user.mapping="[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]"/>
+    
user.mapping="[org.apache.sling.testbundle:sub-service-1=service1,org.apache.sling.testbundle:sub-service-2=\[service1\,service2\],org.apache.sling.testbundle=\[service1\,external-service-user\]]"/>
diff --git 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg
 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg
index 05ce515..7dd338f 100644
--- 
a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg
+++ 
b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/apps/asd/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml.cfg
@@ -18,5 +18,5 @@
 -->
 <properties>
   <entry key="user.default">admin</entry>
-  <entry 
key="user.mapping">[com.adobe.acs.acs-aem-samples-bundle=admin,com.adobe.acs.acs-aem-samples-bundle:sample-service=oauthservice]</entry>
+  <entry 
key="user.mapping">org.apache.sling.testbundle:sub-service-1=service1</entry>
 </properties>

Reply via email to