Merge pull request #73 from apache/SLING-10286

SLING-10286 : Converter ignores disabled status of system user
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AbstractUser.java b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AbstractUser.java
index 93c93c1..202dc44 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AbstractUser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/AbstractUser.java
@@ -18,6 +18,7 @@
 
 import org.apache.sling.feature.cpconverter.shared.RepoPath;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Objects;
 
@@ -27,6 +28,8 @@
 
     private final RepoPath path;
     private final RepoPath intermediatePath;
+    
+    private final String disabledReason;
 
     /**
      * @param id - the authorizableId to use.
@@ -34,9 +37,14 @@
      * @param intermediatePath - the intermediate path the user should have - most likely the (direct) parent of the path.
      */
     protected AbstractUser(@NotNull String id, @NotNull RepoPath path, @NotNull RepoPath intermediatePath) {
+        this(id, path, intermediatePath, null);
+    }
+
+    protected AbstractUser(@NotNull String id, @NotNull RepoPath path, @NotNull RepoPath intermediatePath, @Nullable String disabledReason) {
         this.id = id;
         this.path = path;
         this.intermediatePath = intermediatePath;
+        this.disabledReason = disabledReason;
     }
 
     public @NotNull String getId() {
@@ -50,6 +58,10 @@
     public @NotNull RepoPath getIntermediatePath() {
         return intermediatePath;
     }
+    
+    public @Nullable String getDisabledReason() {
+        return disabledReason;
+    }
 
     @Override
     public int hashCode() {
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 f681e6e..c20118e 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
@@ -31,6 +31,7 @@
 import org.apache.sling.repoinit.parser.RepoInitParsingException;
 import org.apache.sling.repoinit.parser.impl.RepoInitParserService;
 import org.apache.sling.repoinit.parser.operations.CreateServiceUser;
+import org.apache.sling.repoinit.parser.operations.DisableServiceUser;
 import org.apache.sling.repoinit.parser.operations.Operation;
 import org.apache.sling.repoinit.parser.impl.WithPathOptions;
 import org.apache.sling.repoinit.parser.operations.AclLine;
@@ -217,6 +218,10 @@
             // make sure all system users are created first
             CreateServiceUser operation = new CreateServiceUser(systemUser.getId(), new WithPathOptions(calculateIntermediatePath(systemUser), enforcePrincipalBased(systemUser)));
             formatter.format("%s", operation.asRepoInitString());
+            if (systemUser.getDisabledReason() != null) {
+                DisableServiceUser disable = new DisableServiceUser(systemUser.getId(), systemUser.getDisabledReason());
+                formatter.format("%s", disable.asRepoInitString());
+            }
 
             if (aclIsBelow(systemUser.getPath())) {
                 throw new IllegalStateException("Detected policy on subpath of system-user: " + systemUser);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/SystemUser.java b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/SystemUser.java
index e03eda1..5eec329 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/SystemUser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/SystemUser.java
@@ -18,6 +18,7 @@
 
 import org.apache.sling.feature.cpconverter.shared.RepoPath;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public class SystemUser extends AbstractUser {
 
@@ -29,4 +30,14 @@
     public SystemUser(@NotNull String id, @NotNull RepoPath path, @NotNull RepoPath intermediatePath) {
         super(id, path, intermediatePath);
     }
+
+    /**
+     * @param id - the authorizableId to use.
+     * @param path - the original repository path of the user in the content-package.
+     * @param intermediatePath - the intermediate path the user should have - most likely the (direct) parent of the path.
+     * @param disabledReason - the reason why this user has been disabled or {@code null}.
+     */
+    public SystemUser(@NotNull String id, @NotNull RepoPath path, @NotNull RepoPath intermediatePath, @Nullable String disabledReason) {
+        super(id, path, intermediatePath, disabledReason);
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/User.java b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/User.java
index cad22bb..c864d72 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/User.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/User.java
@@ -18,6 +18,7 @@
 
 import org.apache.sling.feature.cpconverter.shared.RepoPath;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public class User extends AbstractUser {
 
@@ -29,4 +30,14 @@
     public User(@NotNull String id, @NotNull RepoPath path, @NotNull RepoPath intermediatePath) {
         super(id, path, intermediatePath);
     }
+
+    /**
+     * @param id               - the authorizableId to use.
+     * @param path             - the original repository path of the user in the content-package.
+     * @param intermediatePath - the intermediate path the user should have - most likely the (direct) parent of the path.
+     * @param disabledReason   - the reason why this user has been disabled or {@code null}.
+     */
+    public User(@NotNull String id, @NotNull RepoPath path, @NotNull RepoPath intermediatePath, @Nullable String disabledReason) {
+        super(id, path, intermediatePath, disabledReason);
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractUserParser.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractUserParser.java
index 3d360c5..354e9fa 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractUserParser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractUserParser.java
@@ -49,7 +49,7 @@
     protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
         String authorizableId = attributes.getValue(REP_AUTHORIZABLE_ID);
         if (authorizableId != null && !authorizableId.isEmpty()) {
-            handleUser(authorizableId);
+            handleUser(authorizableId, attributes);
         }
     }
 
@@ -58,6 +58,6 @@
         return null;
     }
 
-    abstract void handleUser(@NotNull String id);
+    abstract void handleUser(@NotNull String id, @NotNull Attributes attributes);
 
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandler.java
index 7d1b2b2..68a2d38 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/GroupEntryHandler.java
@@ -20,6 +20,7 @@
 import org.apache.sling.feature.cpconverter.accesscontrol.Group;
 import org.apache.sling.feature.cpconverter.shared.RepoPath;
 import org.jetbrains.annotations.NotNull;
+import org.xml.sax.Attributes;
 
 public final class GroupEntryHandler extends AbstractUserEntryHandler {
 
@@ -55,7 +56,7 @@
         }
 
         @Override
-        void handleUser(@NotNull String id) {
+        void handleUser(@NotNull String id, @NotNull Attributes attributes) {
             converter.getAclManager().addGroup(new Group(id, path, intermediatePath));
         }
     }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandler.java
index 3291415..c55f9f7 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandler.java
@@ -21,6 +21,7 @@
 import org.apache.sling.feature.cpconverter.accesscontrol.User;
 import org.apache.sling.feature.cpconverter.shared.RepoPath;
 import org.jetbrains.annotations.NotNull;
+import org.xml.sax.Attributes;
 
 public final class UsersEntryHandler extends AbstractUserEntryHandler {
 
@@ -39,10 +40,10 @@
 
     @Override
     AbstractUserParser createParser(@NotNull ContentPackage2FeatureModelConverter converter, @NotNull RepoPath originalPath, @NotNull RepoPath intermediatePath) {
-        return new SystemUserParser(converter, originalPath, intermediatePath);
+        return new UserParser(converter, originalPath, intermediatePath);
     }
 
-    private static final class SystemUserParser extends AbstractUserParser {
+    private static final class UserParser extends AbstractUserParser {
 
         private static final String REP_SYSTEM_USER = "rep:SystemUser";
         private static final String REP_USER = "rep:User";
@@ -52,16 +53,17 @@
          * @param path - the original repository path of the user in the content-package.
          * @param intermediatePath - the intermediate path the user should have - most likely the (direct) parent of the path.
          */
-        public SystemUserParser(@NotNull ContentPackage2FeatureModelConverter converter, @NotNull RepoPath path, @NotNull RepoPath intermediatePath) {
+        public UserParser(@NotNull ContentPackage2FeatureModelConverter converter, @NotNull RepoPath path, @NotNull RepoPath intermediatePath) {
             super(converter, path, intermediatePath, REP_SYSTEM_USER, REP_USER);
         }
 
         @Override
-        void handleUser(@NotNull String id) {
+        void handleUser(@NotNull String id, @NotNull Attributes attributes) {
+            String disabledReason = attributes.getValue("rep:disabled");
             if (REP_USER.equals(detectedPrimaryType)) {
-                converter.getAclManager().addUser(new User(id, path, intermediatePath));
-            } else{
-                converter.getAclManager().addSystemUser(new SystemUser(id, path, intermediatePath));
+                converter.getAclManager().addUser(new User(id, path, intermediatePath, disabledReason));
+            } else {
+                converter.getAclManager().addSystemUser(new SystemUser(id, path, intermediatePath, disabledReason));
             }
         }
     }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
index 40c54dc..96b8fc9 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/UsersEntryHandlerTest.java
@@ -30,7 +30,9 @@
 import org.junit.Test;
 
 import java.io.StringReader;
+import java.lang.reflect.Field;
 import java.util.List;
+import java.util.Set;
 
 import static org.apache.sling.feature.cpconverter.Util.normalize;
 import static org.junit.Assert.assertEquals;
@@ -43,6 +45,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class UsersEntryHandlerTest {
 
@@ -88,6 +91,24 @@
     }
 
     @Test
+    public void parseDisabledSystemUser() throws Exception {
+        String path = "/jcr_root/home/users/system/services/random1/.content.xml";
+        Extension repoinitExtension = parseAndSetRepoinit(path);
+
+        assertNotNull(repoinitExtension);
+        assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
+        assertTrue(repoinitExtension.isRequired());
+
+        String expected = normalize("create service user service1 with path system/services\ndisable service user service1 : \"a reason\"\n");
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
+
+        RepoInitParser repoInitParser = new RepoInitParserService();
+        List<Operation> operations = repoInitParser.parse(new StringReader(actual));
+        assertFalse(operations.isEmpty());
+    }
+
+    @Test
     public void unrecognisedSystemUserJcrNode() throws Exception {
         String path = "/jcr_root/home/users/system/asd-share-commons/asd-index-definition-invalid/.content.xml";
         Extension repoinitExtension = parseAndSetRepoinit(path);
@@ -120,6 +141,22 @@
     }
 
     @Test
+    public void testDisabledUser() throws Exception {
+        String path = "/jcr_root/home/users/a/disableduser/.content.xml";
+        DefaultAclManager aclManager = new DefaultAclManager();
+        TestUtils.createRepoInitExtension(usersEntryHandler, aclManager, path, getClass().getResourceAsStream(path.substring(1)));
+        
+        Field f = DefaultAclManager.class.getDeclaredField("users");
+        f.setAccessible(true);
+        
+        Set<User> users = (Set<User>) f.get(aclManager);
+        assertNotNull(users);
+        assertEquals(1, users.size());
+        String disabledReason = users.iterator().next().getDisabledReason();
+        assertEquals("a reason", disabledReason);
+    }
+
+    @Test
     public void parseUserWithConfig() throws Exception {
         String path = "/jcr_root/rep:security/rep:authorizables/rep:users/a/author/.content.xml";
         String filePath = path.substring(1).replace("rep:", "_rep_");
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/a/disableduser/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/a/disableduser/.content.xml
new file mode 100644
index 0000000..c229ef3
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/a/disableduser/.content.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with this
+ work for additional information regarding copyright ownership. The ASF
+ licenses this file to You under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+          jcr:mixinTypes="[rep:AccessControllable]"
+          jcr:primaryType="rep:User"
+          jcr:uuid="098f6bcd-4621-3373-8ade-4e832627b4f6"
+          rep:authorizableId="test"
+          rep:principalName="test" 
+          rep:disabled="a reason"/>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/.content.xml
index 5b113ba..cf9b705 100644
--- a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/.content.xml
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/jcr_root/home/users/system/services/random1/.content.xml
@@ -19,4 +19,5 @@
           jcr:primaryType="rep:SystemUser"
           jcr:uuid="0a39b2bb-8594-3d80-a4fe-3464cfb7038b"
           rep:authorizableId="service1"
-          rep:principalName="service1"/>
+          rep:principalName="service1"
+          rep:disabled="a reason"/>