You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by si...@apache.org on 2019/04/30 21:36:11 UTC

[sling-org-apache-sling-feature-cpconverter] 15/29: SLING-8371 - [cp2fm] map rep:ACL JCR nodes to repoinit Feature extension

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

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

commit 8e6b9097328d9fe5d9c62beae0460e02a8d4269c
Author: stripodi <st...@192.168.1.111>
AuthorDate: Wed Apr 24 15:51:45 2019 +0200

    SLING-8371 - [cp2fm] map rep:ACL JCR nodes to repoinit Feature extension
    
    initial implementation
    old System User refactored in order to use the new ACL Manager
---
 .../ContentPackage2FeatureModelConverter.java      |  31 ++--
 .../apache/sling/feature/cpconverter/acl/Acl.java  |  59 ++++++++
 .../sling/feature/cpconverter/acl/AclManager.java  |  84 +++++++++++
 .../feature/cpconverter/acl/package-info.java      |  21 +++
 .../handlers/AbstractJcrNodeParser.java            |   6 +-
 .../handlers/RepPolicyEntryHandler.java            | 143 ++++++++++++++++++
 .../handlers/SystemUsersEntryHandler.java          |   9 +-
 .../handlers/XmlConfigurationEntryHandler.java     |  13 +-
 ...ache.sling.feature.cpconverter.spi.EntryHandler |   1 +
 .../handlers/RepPolicyEntryHandlerTest.java        | 161 +++++++++++++++++++++
 .../handlers/SystemUsersEntryHandlerTest.java      |   5 +-
 .../vltpkg/VaultPackageAssemblerTest.java          |   1 -
 .../handlers/jcr_root/asd/public/_rep_policy.xml   |  38 ++++-
 13 files changed, 532 insertions(+), 40 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 9cf9b1e..378ac20 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -41,6 +41,7 @@ import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Extensions;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.cpconverter.acl.AclManager;
 import org.apache.sling.feature.cpconverter.spi.BundlesDeployer;
 import org.apache.sling.feature.cpconverter.spi.EntryHandler;
 import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
@@ -66,8 +67,6 @@ public class ContentPackage2FeatureModelConverter {
 
     private static final String DEFEAULT_VERSION = "0.0.0";
 
-    private static final String REPOINIT = "repoinit";
-
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final PackageManager packageManager = new PackageManagerImpl();
@@ -76,6 +75,8 @@ public class ContentPackage2FeatureModelConverter {
 
     private final Map<String, Feature> runModes = new HashMap<>();
 
+    private final AclManager aclManager = new AclManager();
+
     private final RegexBasedResourceFilter filter = new RegexBasedResourceFilter();
 
     private BundlesDeployer artifactDeployer;
@@ -95,7 +96,7 @@ public class ContentPackage2FeatureModelConverter {
     private VaultPackageAssembler mainPackageAssembler = null;
 
     private String id;
-    
+
     private String idOverride;
 
     public ContentPackage2FeatureModelConverter setStrictValidation(boolean strictValidation) {
@@ -152,12 +153,15 @@ public class ContentPackage2FeatureModelConverter {
         this.id = id;
         return this;
     }
-    
+
     public ContentPackage2FeatureModelConverter setIdOverride(String id) {
         this.idOverride = id;
         return this;
     }
 
+    public AclManager getAclManager() {
+        return aclManager;
+    }
 
     public Feature getRunMode(String runMode) {
         if (getTargetFeature() == null) {
@@ -305,7 +309,9 @@ public class ContentPackage2FeatureModelConverter {
 
             // finally serialize the Feature Model(s) file(s)
 
-            seralize(targetFeature, null);
+            aclManager.addRepoinitExtension(getTargetFeature());
+
+            seralize(getTargetFeature(), null);
 
             if (!runModes.isEmpty()) {
                 for (java.util.Map.Entry<String, Feature> runmodeEntry : runModes.entrySet()) {
@@ -315,21 +321,6 @@ public class ContentPackage2FeatureModelConverter {
         }
     }
 
-    public void addRepoinitStatement(String format, Object...args) {
-        Extension repoInitExtension = getTargetFeature().getExtensions().getByName(REPOINIT);
-        if (repoInitExtension == null) {
-            repoInitExtension = new Extension(ExtensionType.TEXT, REPOINIT, true);
-            getTargetFeature().getExtensions().add(repoInitExtension);
-        }
-
-        String statement = String.format(format, args);
-        if (repoInitExtension.getText() == null) {
-            repoInitExtension.setText(statement);
-        } else {
-            repoInitExtension.setText(repoInitExtension.getText() + '\n' + statement);
-        }
-    }
-
     public void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties) {
         if (!mergeConfigurations) {
             checkConfigurationExist(getTargetFeature(), pid);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
new file mode 100644
index 0000000..0f483ff
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
@@ -0,0 +1,59 @@
+/*
+ * 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.acl;
+
+import java.util.Formatter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Simple single ACL statement representation.
+ */
+public final class Acl {
+
+    private final String operation;
+
+    private final String privileges;
+
+    private final String path;
+
+    private final List<String> restrictions = new LinkedList<>();
+
+    protected Acl(String operation, String privileges, String path) {
+        this.operation = operation;
+        this.privileges = privileges;
+        this.path = path;
+    }
+
+    public void addRestriction(String restriction) {
+        if (restriction != null && !restriction.isEmpty()) {
+            restrictions.add(restriction);
+        }
+    }
+
+    protected void addAclStatement(Formatter formatter) {
+        formatter.format("%s %s on %s", operation, privileges, path);
+
+        if (!restrictions.isEmpty()) {
+            formatter.format(" restriction(%s)", restrictions.stream().collect(Collectors.joining(",")));
+        }
+
+        formatter.format("%n");
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
new file mode 100644
index 0000000..54564c9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
@@ -0,0 +1,84 @@
+/*
+ * 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.acl;
+
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+
+/**
+ * The Manager able to collect and build System Users and related ACL policies.
+ */
+public final class AclManager {
+
+    private final Set<String> systemUsers = new LinkedHashSet<>();
+
+    private final Map<String, List<Acl>> acls = new HashMap<>();
+
+    public boolean addSystemUser(String systemUser) {
+        if (systemUser != null && !systemUser.isEmpty()) {
+            return systemUsers.add(systemUser);
+        }
+        return false;
+    }
+
+    public Acl addAcl(String systemUser, String operation, String privileges, String path) {
+        Acl acl = new Acl(operation, privileges, path);
+        acls.computeIfAbsent(systemUser, k -> new LinkedList<>()).add(acl);
+        return acl;
+    }
+
+    public void addRepoinitExtension(Feature feature) {
+        if (systemUsers.isEmpty()) {
+            return;
+        }
+
+        Extension repoInitExtension = new Extension(ExtensionType.TEXT, Extension.EXTENSION_NAME_REPOINIT, true);
+
+        Formatter formatter = new Formatter();
+
+        for (String systemUser : systemUsers) {
+            formatter.format("create service user %s%n", systemUser);
+
+            List<Acl> authorizations = acls.get(systemUser);
+            if (authorizations != null && !authorizations.isEmpty()) {
+                formatter.format("set ACL for %s%n", systemUser);
+
+                for (Acl authorization : authorizations) {
+                    authorization.addAclStatement(formatter);
+                }
+
+                formatter.format("end%n");
+            }
+        }
+
+        String text = formatter.toString();
+        formatter.close();
+        repoInitExtension.setText(text);
+
+        feature.getExtensions().add(repoInitExtension);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/package-info.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/package-info.java
new file mode 100644
index 0000000..0ef4e34
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Small utility set to handle System Users and related ACL policies.
+ */
+package org.apache.sling.feature.cpconverter.acl;
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
index 12e3089..e346ff3 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractJcrNodeParser.java
@@ -27,7 +27,7 @@ import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
-abstract class AbstractJcrNodeParser extends DefaultHandler {
+abstract class AbstractJcrNodeParser<O> extends DefaultHandler {
 
     private static final String JCR_ROOT = "jcr:root";
 
@@ -45,7 +45,7 @@ abstract class AbstractJcrNodeParser extends DefaultHandler {
     }
 
     @Override
-    public final void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
         String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
 
         if (JCR_ROOT.equals(qName) && this.primaryType.equals(primaryType)) {
@@ -55,4 +55,6 @@ abstract class AbstractJcrNodeParser extends DefaultHandler {
 
     protected abstract void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) throws SAXException;
 
+    protected abstract O getParsingResult();
+
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
new file mode 100644
index 0000000..6dad716
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
@@ -0,0 +1,143 @@
+/*
+ * 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 static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+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.acl.Acl;
+import org.apache.sling.feature.cpconverter.acl.AclManager;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+public final class RepPolicyEntryHandler extends AbstractRegexEntryHandler {
+
+    public RepPolicyEntryHandler() {
+        super("(jcr_root)?(/.+)/_rep_policy.xml");
+    }
+
+    @Override
+    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+        Matcher matcher = getPattern().matcher(path);
+        // we are pretty sure it matches, here
+        if (matcher.matches()) {
+            path = matcher.group(2);
+        } else {
+            throw new IllegalStateException("Something went terribly wrong: pattern '"
+                                            + getPattern().pattern()
+                                            + "' should have matched already with path '"
+                                            + path
+                                            + "' but it does not, currently");
+        }
+
+        RepPolicyParser systemUserParser = new RepPolicyParser(path, converter.getAclManager());
+        try (InputStream input = archive.openInputStream(entry)) {
+            systemUserParser.parse(input);
+        }
+    }
+
+    private static final class RepPolicyParser extends AbstractJcrNodeParser<Void> {
+
+        private static final String REP_ACL = "rep:ACL";
+
+        private static final String REP_GRANT_ACE = "rep:GrantACE";
+
+        private static final String REP_DENY_ACE = "rep:DenyACE";
+
+        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_GLOB = "rep:glob";
+
+        private static final Map<String, String> operations = new HashMap<>();
+
+        static {
+            operations.put(REP_GRANT_ACE, "allow");
+            operations.put(REP_DENY_ACE, "deny");
+        }
+
+        private final Stack<Acl> acls = new Stack<>();
+
+        private final String path;
+
+        private final AclManager aclManager;
+
+        private boolean onRepAclNode = false;
+
+        public RepPolicyParser(String path, AclManager aclManager) {
+            super(REP_ACL);
+            this.path = path;
+            this.aclManager = aclManager;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            if (onRepAclNode) {
+                String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
+                if (REP_GRANT_ACE.equals(primaryType) || REP_DENY_ACE.equals(primaryType)) {
+                    String principalName = attributes.getValue(REP_PRINCIPAL_NAME);
+
+                    String operation = operations.get(primaryType);
+
+                    String privileges = attributes.getValue(REP_PRIVILEGES);
+                    int beginIndex = privileges.indexOf('[') + 1;
+                    int endIndex = privileges.indexOf(']');
+                    privileges = privileges.substring(beginIndex, endIndex);
+
+                    acls.add(aclManager.addAcl(principalName, operation, privileges, path));
+                } else if (REP_RESTRICTIONS.equals(primaryType) && !acls.isEmpty()) {
+                    String restriction = attributes.getValue(REP_GLOB);
+                    acls.peek().addRestriction(restriction);
+                }
+            } else {
+                super.startElement(uri, localName, qName, attributes);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (onRepAclNode && !acls.isEmpty()) {
+                acls.pop();
+            }
+        }
+
+        @Override
+        protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
+            onRepAclNode = true;
+        }
+
+        @Override
+        protected Void getParsingResult() {
+            return null;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
index b451e8b..7fe64aa 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
@@ -38,7 +38,7 @@ public final class SystemUsersEntryHandler extends AbstractRegexEntryHandler {
         }
     }
 
-    private static final class SystemUserParser extends AbstractJcrNodeParser {
+    private static final class SystemUserParser extends AbstractJcrNodeParser<Void> {
 
         private final static String REP_SYSTEM_USER = "rep:SystemUser";
 
@@ -55,10 +55,15 @@ public final class SystemUsersEntryHandler extends AbstractRegexEntryHandler {
         protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
             String authorizableId = attributes.getValue(REP_AUTHORIZABLE_ID);
             if (authorizableId != null && !authorizableId.isEmpty()) {
-                converter.addRepoinitStatement("create service user %s", authorizableId);
+                converter.getAclManager().addSystemUser(authorizableId);
             }
         }
 
+        @Override
+        protected Void getParsingResult() {
+            return null;
+        }
+
     }
 
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
index 5188b24..d1b1919 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/XmlConfigurationEntryHandler.java
@@ -34,19 +34,15 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
     protected Dictionary<String, Object> parseConfiguration(String name, InputStream input) throws Exception {
         JcrConfigurationHandler configurationHandler = new JcrConfigurationHandler();
         configurationHandler.parse(input);
-        return configurationHandler.getConfiguration();
+        return configurationHandler.getParsingResult();
     }
 
-    private static final class JcrConfigurationHandler extends AbstractJcrNodeParser {
+    private static final class JcrConfigurationHandler extends AbstractJcrNodeParser<Dictionary<String, Object>> {
 
         private static final String SLING_OSGICONFIG = "sling:OsgiConfig";
 
         private final Dictionary<String, Object> configuration = new Hashtable<>();
 
-        public Dictionary<String, Object> getConfiguration() {
-            return configuration;
-        }
-
         public JcrConfigurationHandler() {
             super(SLING_OSGICONFIG);
         }
@@ -75,6 +71,11 @@ public final class XmlConfigurationEntryHandler extends AbstractConfigurationEnt
             }
         }
 
+        @Override
+        protected Dictionary<String, Object> getParsingResult() {
+            return configuration;
+        }
+
     }
 
 }
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
index b4b9ec0..53e84b4 100644
--- a/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.spi.EntryHandler
@@ -3,5 +3,6 @@ org.apache.sling.feature.cpconverter.handlers.ConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.ContentPackageEntryHandler
 org.apache.sling.feature.cpconverter.handlers.JsonConfigurationEntryHandler
 org.apache.sling.feature.cpconverter.handlers.PropertiesConfigurationEntryHandler
+org.apache.sling.feature.cpconverter.handlers.RepPolicyEntryHandler
 org.apache.sling.feature.cpconverter.handlers.SystemUsersEntryHandler
 org.apache.sling.feature.cpconverter.handlers.XmlConfigurationEntryHandler
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
new file mode 100644
index 0000000..ecf5419
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+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.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+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.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class RepPolicyEntryHandlerTest {
+
+    private RepPolicyEntryHandler handler;
+
+    @Before
+    public void setUp() {
+        handler = new RepPolicyEntryHandler();
+    }
+
+    @After
+    public void tearDown() {
+        handler = null;
+    }
+
+    @Test
+    public void doesNotMatch() {
+        assertFalse(handler.matches("/this/is/a/path/not/pointing/to/a/valid/policy.xml"));
+    }
+
+    @Test
+    public void matches() {
+        assertTrue(handler.matches("/home/users/system/asd-share-commons/asd-index-definition-reader/_rep_policy.xml"));
+        assertTrue(handler.matches("jcr_root/home/users/system/asd-share-commons/asd-index-definition-reader/_rep_policy.xml"));
+    }
+
+    @Test
+    public void parseAcl() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit("acs-commons-ensure-oak-index-service",
+                                                          "acs-commons-dispatcher-flush-service",
+                                                          "acs-commons-package-replication-status-event-service",
+                                                          "acs-commons-ensure-service-user-service",
+                                                          "acs-commons-automatic-package-replicator-service",
+                                                          "acs-commons-on-deploy-scripts-service");
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+
+        String expected = "create service user acs-commons-ensure-oak-index-service\n" + 
+                "set ACL for acs-commons-ensure-oak-index-service\n" + 
+                "allow jcr:read,rep:write,rep:indexDefinitionManagement on /asd/public restriction(*/oak:index/*)\n" + 
+                "end\n" + 
+                "create service user acs-commons-dispatcher-flush-service\n" + 
+                "set ACL for acs-commons-dispatcher-flush-service\n" + 
+                "allow jcr:read,crx:replicate,jcr:removeNode on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-package-replication-status-event-service\n" + 
+                "set ACL for acs-commons-package-replication-status-event-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-ensure-service-user-service\n" + 
+                "set ACL for acs-commons-ensure-service-user-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-automatic-package-replicator-service\n" + 
+                "set ACL for acs-commons-automatic-package-replicator-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-on-deploy-scripts-service\n" + 
+                "set ACL for acs-commons-on-deploy-scripts-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n";
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void notDeclaredSystemUsersWontHaveAclSettings() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit("acs-commons-package-replication-status-event-service",
+                                                          "acs-commons-ensure-service-user-service",
+                                                          "acs-commons-automatic-package-replicator-service",
+                                                          "acs-commons-on-deploy-scripts-service");
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+
+        String expected = "create service user acs-commons-package-replication-status-event-service\n" + 
+                "set ACL for acs-commons-package-replication-status-event-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-ensure-service-user-service\n" + 
+                "set ACL for acs-commons-ensure-service-user-service\n" + 
+                "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-automatic-package-replicator-service\n" + 
+                "set ACL for acs-commons-automatic-package-replicator-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n" + 
+                "create service user acs-commons-on-deploy-scripts-service\n" + 
+                "set ACL for acs-commons-on-deploy-scripts-service\n" + 
+                "allow jcr:read on /asd/public\n" + 
+                "end\n";
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void parseEmptyAcl() throws Exception {
+        Extension repoinitExtension = parseAndSetRepoinit();
+        assertNull(repoinitExtension);
+    }
+
+    private Extension parseAndSetRepoinit(String...systemUsers) throws Exception {
+        String path = "jcr_root/asd/public/_rep_policy.xml";
+        Archive archive = mock(Archive.class);
+        Entry entry = mock(Entry.class);
+
+        when(archive.openInputStream(entry)).thenReturn(getClass().getResourceAsStream(path));
+
+        Feature feature = new Feature(new ArtifactId("org.apache.sling", "org.apache.sling.cp2fm", "0.0.1", null, null));
+        ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
+        when(converter.getTargetFeature()).thenReturn(feature);
+
+        handler.handle(path, archive, entry, converter);
+
+        if (systemUsers != null) {
+            for (String systemUser : systemUsers) {
+                converter.getAclManager().addSystemUser(systemUser);
+            }
+        }
+
+        converter.getAclManager().addRepoinitExtension(feature);
+        return feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
index 5126e0f..70270d0 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
@@ -67,7 +67,7 @@ public class SystemUsersEntryHandlerTest {
         assertNotNull(repoinitExtension);
         assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
         assertTrue(repoinitExtension.isRequired());
-        assertEquals("create service user asd-share-commons-asd-index-definition-reader-service", repoinitExtension.getText());
+        assertEquals("create service user asd-share-commons-asd-index-definition-reader-service\n", repoinitExtension.getText());
     }
 
     @Test
@@ -89,7 +89,8 @@ public class SystemUsersEntryHandlerTest {
 
         systemUsersEntryHandler.handle(path, archive, entry, converter);
 
-        return feature.getExtensions().getByName("repoinit");
+        converter.getAclManager().addRepoinitExtension(feature);
+        return feature.getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
     }
 
 }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
index d2aa3cc..011a516 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
@@ -81,7 +81,6 @@ public class VaultPackageAssemblerTest {
         return Arrays.asList(new Object[][] {
             { "jcr_root/.content.xml", assembler },
             { "jcr_root/asd/.content.xml", assembler },
-            { "jcr_root/asd/public/_rep_policy.xml", assembler },
             { "jcr_root/asd/public/license.txt", assembler }
         });
     }
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
index 2b1119a..8b639bb 100644
--- a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/asd/public/_rep_policy.xml
@@ -15,10 +15,34 @@
  License for the specific language governing permissions and limitations under
  the License.
 -->
-<jcr:root xmlns:crx="http://www.day.com/crx/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
-    jcr:primaryType="rep:ACL">
-    <allow
-        jcr:primaryType="rep:GrantACE"
-        rep:principalName="everyone"
-        rep:privileges="{Name}[jcr:read]"/>
-</jcr:root>
\ No newline at end of file
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+          jcr:primaryType="rep:ACL">
+    <allow0
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-ensure-oak-index-service"
+            rep:privileges="{Name}[jcr:read,rep:write,rep:indexDefinitionManagement]">
+        <rep:restrictions
+                jcr:primaryType="rep:Restrictions"
+                rep:glob="*/oak:index/*"/>
+    </allow0>
+    <allow1
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-dispatcher-flush-service"
+            rep:privileges="{Name}[jcr:read,crx:replicate,jcr:removeNode]"/>
+    <allow2
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-package-replication-status-event-service"
+            rep:privileges="{Name}[jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl]"/>
+    <allow3
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-ensure-service-user-service"
+            rep:privileges="{Name}[jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl]"/>
+    <allow4
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-automatic-package-replicator-service"
+            rep:privileges="{Name}[jcr:read]"/>
+    <allow5
+            jcr:primaryType="rep:GrantACE"
+            rep:principalName="acs-commons-on-deploy-scripts-service"
+            rep:privileges="{Name}[jcr:read]"/>
+</jcr:root>