You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:48:48 UTC

[sling-org-apache-sling-jcr-repoinit] 05/43: SLING-5355 - AclOperationVisitor partially implemented, and webconsole plugin added

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

rombert pushed a commit to annotated tag org.apache.sling.jcr.repoinit-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-repoinit.git

commit d7a5f83fda7e92c420a387cffc938059a3726fda
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Wed Dec 23 11:00:28 2015 +0000

    SLING-5355 - AclOperationVisitor partially implemented, and webconsole plugin added
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/acldef/oak-jcr@1721521 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  24 ++-
 .../sling/acldef/jcr/AclOperationVisitor.java      |  39 +++-
 .../java/org/apache/sling/acldef/jcr/AclUtil.java  |  65 +++++++
 .../apache/sling/acldef/jcr/ServiceUserUtil.java   |   4 +
 .../jcr/webconsole/OakAclDefConsolePlugin.java     | 203 +++++++++++++++++++++
 src/main/resources/ui/acldef.css                   |  20 ++
 .../sling/acldef/jcr/NonExistentPathTest.java      |  70 +++++++
 .../sling/acldef/jcr/RootAddChildNodeTest.java     | 126 +++++++++++++
 .../java/org/apache/sling/acldef/jcr/TestUtil.java |  12 +-
 9 files changed, 554 insertions(+), 9 deletions(-)

diff --git a/pom.xml b/pom.xml
index 31eda7e..e0a4b01 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,10 @@
 	  Applies ACLs to an Oak JCR repository based on the output
 	  of the ACL Definition Language parser.
   </description>
+  
+  <properties>
+    <jackrabbit.version>2.10.0</jackrabbit.version>
+  </properties>
 
   <scm>
     <connection>scm:svn: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/acldef/oak-jcr</connection>
@@ -75,13 +79,29 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-api</artifactId>
-      <version>2.11.3</version>
+      <version>${jackrabbit.version}</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.2.9</version>
+      <version>${jackrabbit.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+    </dependency>
+     <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.api</artifactId>
+      <version>2.4.0</version>
+      <scope>provided</scope>
+    </dependency>
+     <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.jcr.api</artifactId>
+      <version>2.0.4</version>
       <scope>provided</scope>
     </dependency>
      <dependency>
diff --git a/src/main/java/org/apache/sling/acldef/jcr/AclOperationVisitor.java b/src/main/java/org/apache/sling/acldef/jcr/AclOperationVisitor.java
index 48ff05e..e4748ec 100644
--- a/src/main/java/org/apache/sling/acldef/jcr/AclOperationVisitor.java
+++ b/src/main/java/org/apache/sling/acldef/jcr/AclOperationVisitor.java
@@ -16,8 +16,15 @@
  */
 package org.apache.sling.acldef.jcr;
 
+import static org.apache.sling.acldef.parser.ACLDefinitions.PROP_PATHS;
+import static org.apache.sling.acldef.parser.ACLDefinitions.PROP_PRINCIPALS;
+import static org.apache.sling.acldef.parser.ACLDefinitions.PROP_PRIVILEGES;
+
+import java.util.List;
+
 import javax.jcr.Session;
 
+import org.apache.sling.acldef.parser.operations.AclLine;
 import org.apache.sling.acldef.parser.operations.CreateServiceUser;
 import org.apache.sling.acldef.parser.operations.DeleteServiceUser;
 import org.apache.sling.acldef.parser.operations.OperationVisitor;
@@ -70,13 +77,37 @@ public class AclOperationVisitor implements OperationVisitor {
         }
     }
 
+    private List<String> require(AclLine line, String propertyName) {
+        final List<String> result = line.getProperty(propertyName);
+        if(result == null) {
+            throw new IllegalStateException("Missing property " + propertyName + " on " + line);
+        }
+        return result;
+    }
+    
+    private void setAcl(AclLine line, Session s, List<String> principals, List<String> paths, List<String> privileges, boolean isAllow) {
+        try {
+            AclUtil.setAcl(s, principals, paths, privileges, isAllow);
+        } catch(Exception e) {
+            throw new RuntimeException("Failed to set ACL (" + e.getClass().getSimpleName() + ") " + line, e);
+        }
+    }
+    
     @Override
     public void visitSetAclPrincipal(SetAclPrincipals s) {
-        log.warn("TODO - set ACL for Principals");
-    }
+        final List<String> principals = s.getPrincipals();
+        for(AclLine line : s.getLines()) {
+            final boolean isAllow = line.getAction().equals(AclLine.Action.ALLOW);
+            setAcl(line, session, principals, require(line, PROP_PATHS), require(line, PROP_PRIVILEGES), isAllow);
+        }
+     }
 
     @Override
     public void visitSetAclPaths(SetAclPaths s) {
-        log.warn("TODO - set ACL for Paths");
+        final List<String> paths = s.getPaths();
+        for(AclLine line : s.getLines()) {
+            final boolean isAllow = line.getAction().equals(AclLine.Action.ALLOW);
+            setAcl(line, session, require(line, PROP_PRINCIPALS), paths, require(line, PROP_PRIVILEGES), isAllow); 
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/acldef/jcr/AclUtil.java b/src/main/java/org/apache/sling/acldef/jcr/AclUtil.java
new file mode 100644
index 0000000..1a70be4
--- /dev/null
+++ b/src/main/java/org/apache/sling/acldef/jcr/AclUtil.java
@@ -0,0 +1,65 @@
+/*
+ * 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.acldef.jcr;
+
+import java.security.Principal;
+import java.util.List;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+
+/** Utilities for ACL management */
+public class AclUtil {
+
+    public static JackrabbitAccessControlManager getJACM(Session s) throws UnsupportedRepositoryOperationException, RepositoryException {
+        final AccessControlManager acm = s.getAccessControlManager();
+        if(!(acm instanceof JackrabbitAccessControlManager)) {
+            throw new IllegalStateException(
+                "AccessControlManager is not a JackrabbitAccessControlManager:" 
+                + acm.getClass().getName());
+        }
+        return (JackrabbitAccessControlManager) acm;
+    }
+    
+    public static void setAcl(Session s, List<String> principals, List<String> paths, List<String> privileges, boolean isAllow) 
+            throws UnsupportedRepositoryOperationException, RepositoryException {
+        
+        final String [] privArray = privileges.toArray(new String[privileges.size()]);
+        final Privilege[] jcrPriv = AccessControlUtils.privilegesFromNames(s, privArray);
+
+        
+        for(String path : paths) {
+            if(!s.nodeExists(path)) {
+                throw new PathNotFoundException("Cannot set ACL on non-existent path " + path);
+            }
+            JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(s, path);
+            for(String principal : principals) {
+                final Principal p = ServiceUserUtil.getAuthorizable(s, principal).getPrincipal(); 
+                acl.addEntry(p, jcrPriv, isAllow);
+            }
+            getJACM(s).setPolicy(path, acl);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/acldef/jcr/ServiceUserUtil.java b/src/main/java/org/apache/sling/acldef/jcr/ServiceUserUtil.java
index 265c7df..09b6426 100644
--- a/src/main/java/org/apache/sling/acldef/jcr/ServiceUserUtil.java
+++ b/src/main/java/org/apache/sling/acldef/jcr/ServiceUserUtil.java
@@ -33,6 +33,10 @@ public class ServiceUserUtil {
         return ((JackrabbitSession)session).getUserManager();
     }
     
+    public static Authorizable getAuthorizable(Session session, String username) throws RepositoryException {
+        return getUserManager(session).getAuthorizable(username);
+    }
+    
     public static void createServiceUser(Session s, String username) throws RepositoryException {
         getUserManager(s).createSystemUser(username, null);
     }
diff --git a/src/main/java/org/apache/sling/acldef/jcr/webconsole/OakAclDefConsolePlugin.java b/src/main/java/org/apache/sling/acldef/jcr/webconsole/OakAclDefConsolePlugin.java
new file mode 100644
index 0000000..16d3ff3
--- /dev/null
+++ b/src/main/java/org/apache/sling/acldef/jcr/webconsole/OakAclDefConsolePlugin.java
@@ -0,0 +1,203 @@
+/*
+ * 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.acldef.jcr.webconsole;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.acldef.jcr.AclOperationVisitor;
+import org.apache.sling.acldef.parser.ACLDefinitions;
+import org.apache.sling.acldef.parser.ParseException;
+import org.apache.sling.acldef.parser.operations.Operation;
+import org.apache.sling.acldef.parser.operations.OperationVisitor;
+import org.apache.sling.api.request.ResponseUtil;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.osgi.framework.Constants;
+
+@Component
+@Service(value=Servlet.class)
+@Properties({
+    @Property(name=Constants.SERVICE_VENDOR, value="The Apache Software Foundation"),
+    @Property(name=Constants.SERVICE_DESCRIPTION, value="Apache Sling ACL Definitions Console Plugin"),
+    @Property(name="felix.webconsole.label", value=OakAclDefConsolePlugin.LABEL),
+    @Property(name="felix.webconsole.title", value="Set ACLs"),
+    @Property(name="felix.webconsole.css", value="/" + OakAclDefConsolePlugin.LABEL + "/res/ui/acldef.css"),
+    @Property(name="felix.webconsole.category", value="Sling"),
+})
+public class OakAclDefConsolePlugin extends HttpServlet {
+
+    private static final long serialVersionUID = 1234;
+    private static final String PAR_ACLDEF = "acldef";
+    private static final String PAR_MSG = "msg";
+    public static final String LABEL = "setACL";
+    private static final String ATTR_SUBMIT = "plugin.submit";
+    private AtomicInteger counter = new AtomicInteger();
+
+    private static final String EXAMPLE =
+            "# Example ACL definition\n"
+            + "# (with service user creation commented out)\n"
+            + "# create service user test_42\n"
+            + "set ACL for test_42\n"
+            + "  allow jcr:read,jcr:modifyProperties on /tmp\n"
+            + "end\n"
+            ;
+    
+    @Reference
+    private SlingRepository repository;
+
+    private String thisPath(HttpServletRequest request) {
+        return request.getContextPath() + request.getServletPath() + request.getPathInfo();
+    }
+    
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) 
+            throws ServletException, IOException {
+        String aclDef = request.getParameter(PAR_ACLDEF);
+        if(aclDef == null || aclDef.trim().length() == 0) {
+            aclDef = EXAMPLE;
+        }
+        
+        String msg = request.getParameter(PAR_MSG);
+        if(msg == null) {
+            msg = "";
+        }
+
+        final PrintWriter pw = response.getWriter();
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+        titleHtml(
+                pw,
+                "ACL definitions",
+                "To create service users or set ACLs, enter a valid statement below."
+                );
+
+        pw.println("<tr class='content'>");
+        pw.print("<td class='content' colspan='3'>");
+        pw.print("<form method='post' action='" + thisPath(request) + "'>");
+        pw.print("<input type='submit' name='" + ATTR_SUBMIT + "' value='Execute' class='submit'>");
+        pw.print("<div class='msg'>");
+        pw.print(msg);
+        pw.println("</div>");
+        pw.print("<textarea type='text' name='" + PAR_ACLDEF + "' class='input' cols='80' rows='25'>");
+        pw.print(ResponseUtil.escapeXml(aclDef));
+        pw.println("</textarea>");
+        pw.print("</form>");
+        pw.print("</td>");
+        pw.println("</tr>");
+        pw.println("</table>");
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+
+        String aclDef = request.getParameter(PAR_ACLDEF);
+        String msg = "No ACL definitions executed";
+        if(aclDef == null || aclDef.trim().length() == 0) {
+            aclDef = "";
+        } else {
+            try {
+                setAcl(aclDef);
+                msg = "ACL definitions successfully executed";
+            } catch(Exception e) {
+                throw new ServletException("Error setting ACLs:\n" + e.getMessage(), e);
+            }
+        }
+        msg += " (" + counter.incrementAndGet() + ")";
+        
+        // Redirect to GET on the same page
+        final StringBuilder target = new StringBuilder();
+        target
+            .append(thisPath(request))
+            .append("?").append(PAR_ACLDEF).append("=").append(encodeParam(aclDef))
+            .append("&").append(PAR_MSG).append("=").append(encodeParam(msg))
+        ;
+        response.sendRedirect(target.toString());
+    }
+    
+    private static String encodeParam(final String value) {
+        try {
+            return URLEncoder.encode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Unexpected UnsupportedEncodingException", e);
+        }
+    }
+    
+    private void setAcl(String aclDef) throws RepositoryException, IOException, ParseException {
+        final Reader r = new StringReader(aclDef);
+        Session s = null;
+        try {
+            s = repository.loginAdministrative(null);
+            final OperationVisitor v = new AclOperationVisitor(s);
+            for(Operation op : new ACLDefinitions(r).parse()) {
+                op.accept(v);
+            }
+            s.save();
+        } finally {
+            r.close();
+            if(s != null) {
+                s.logout();
+            }
+        }
+    }
+
+    private void titleHtml(PrintWriter pw, String title, String description) {
+        pw.print("<tr class='content'>");
+        pw.print("<th colspan='3'class='content container'>");
+        pw.print(ResponseUtil.escapeXml(title));
+        pw.println("</th></tr>");
+
+        if (description != null) {
+            pw.print("<tr class='content'>");
+            pw.print("<td colspan='3'class='content'>");
+            pw.print(ResponseUtil.escapeXml(description));
+            pw.println("</th></tr>");
+        }
+    }
+
+    /**
+     * Method to retrieve static resources from this bundle.
+     */
+    @SuppressWarnings("unused")
+    private URL getResource(final String path) {
+        final String prefix = "/" + LABEL + "/res";
+        if(path.startsWith(prefix + "/ui")) {
+            return this.getClass().getResource(path.substring(prefix.length()));
+        }
+        return null;
+    }
+}
diff --git a/src/main/resources/ui/acldef.css b/src/main/resources/ui/acldef.css
new file mode 100644
index 0000000..9181889
--- /dev/null
+++ b/src/main/resources/ui/acldef.css
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+ 
+ .msg { color:blue; }
+td { vertical-align: top; }
+textarea { width:100%; }
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/acldef/jcr/NonExistentPathTest.java b/src/test/java/org/apache/sling/acldef/jcr/NonExistentPathTest.java
new file mode 100644
index 0000000..184853d
--- /dev/null
+++ b/src/test/java/org/apache/sling/acldef/jcr/NonExistentPathTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.acldef.jcr;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.acldef.parser.ParseException;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Test setting ACLS on non-existent paths */
+public class NonExistentPathTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK);
+    
+    private TestUtil U;
+    private Session s;
+    
+    @Before
+    public void setup() throws ParseException, RepositoryException {
+        U = new TestUtil(context);
+        U.parseAndExecute("create service user " + U.username);
+        s = U.loginService(U.username);
+    }
+
+    @After
+    public void cleanup() throws ParseException, RepositoryException {
+        U.parseAndExecute("delete service user " + U.username);
+        s.logout();
+    }
+    
+    @Test
+    public void setAclOnFoo() throws Exception {
+        final String aclDef =
+            "set ACL on /foo_" + U.id + "\n"
+            + "  allow jcr:all for " + U.username + "\n"
+            + "end"
+        ;
+        try {
+            U.parseAndExecute(aclDef);
+            fail("Expecting a wrapped PathNotFoundException");
+        } catch(RuntimeException rux) {
+            assertEquals(PathNotFoundException.class, rux.getCause().getClass());
+        }
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/acldef/jcr/RootAddChildNodeTest.java b/src/test/java/org/apache/sling/acldef/jcr/RootAddChildNodeTest.java
new file mode 100644
index 0000000..63d5b3e
--- /dev/null
+++ b/src/test/java/org/apache/sling/acldef/jcr/RootAddChildNodeTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.acldef.jcr;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.acldef.parser.ParseException;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Test root node and node creation privileges */
+public class RootAddChildNodeTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK);
+    
+    private TestUtil U;
+    private Session s;
+    
+    @Before
+    public void setup() throws ParseException, RepositoryException {
+        U = new TestUtil(context);
+        U.parseAndExecute("create service user " + U.username);
+        s = U.loginService(U.username);
+    }
+
+    @After
+    public void cleanup() throws ParseException, RepositoryException {
+        U.parseAndExecute("delete service user " + U.username);
+        s.logout();
+    }
+    
+    @Test(expected=AccessDeniedException.class)
+    public void getRootNodeFails() throws Exception {
+        s.getRootNode();
+    }
+    
+    @Test
+    public void readOnlyThenWrite() throws Exception {
+        final Node tmp = U.adminSession.getRootNode().addNode("tmp_" + U.id);
+        U.adminSession.save();
+        final String path = tmp.getPath();
+        
+        try {
+            s.getNode(path);
+            fail("Expected read access to be initially denied:" + path);
+        } catch(PathNotFoundException ignore) {
+        }
+        
+        final String allowRead =  
+                "set ACL for " + U.username + "\n"
+                + "allow jcr:read on " + path + "\n"
+                + "end"
+                ;
+        U.parseAndExecute(allowRead);
+        final Node n = s.getNode(path);
+        
+        try {
+            n.setProperty("U.id", U.id);
+            s.save();
+            fail("Expected write access to be denied:" + path);
+        } catch(AccessDeniedException ignore) {
+        }
+        s.refresh(false);
+        
+        final String allowWrite = 
+                "set ACL for " + U.username + "\n"
+                + "allow jcr:write on " + path + "\n"
+                + "end"
+                ;
+        U.parseAndExecute(allowWrite);
+        n.setProperty("U.id", U.id);
+        s.save();
+    }
+    
+    @Test
+    public void addChildAtRoot() throws Exception {
+        final String nodename = "test_" + U.id;
+        final String path = "/" + nodename;
+        
+        final String aclSetup = 
+            "set ACL for " + U.username + "\n"
+            + "allow jcr:all on /\n"
+            + "end"
+            ;
+        
+        U.parseAndExecute(aclSetup);
+        try {
+            assertFalse(s.itemExists(path));
+            s.getRootNode().addNode(nodename);
+            s.save();
+            assertTrue(s.nodeExists(path));
+            s.getNode(path).remove();
+            s.save();
+            assertFalse(s.itemExists(path));
+        } finally {
+            s.logout();
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/acldef/jcr/TestUtil.java b/src/test/java/org/apache/sling/acldef/jcr/TestUtil.java
index c07dd21..70d5533 100644
--- a/src/test/java/org/apache/sling/acldef/jcr/TestUtil.java
+++ b/src/test/java/org/apache/sling/acldef/jcr/TestUtil.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNull;
 import java.io.Reader;
 import java.io.StringReader;
 import java.util.List;
+import java.util.UUID;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -39,9 +40,13 @@ import org.apache.sling.testing.mock.sling.junit.SlingContext;
 class TestUtil {
     
     final Session adminSession;
+    final String id;
+    final String username;
     
     TestUtil(SlingContext ctx) {
-        adminSession = ctx.resourceResolver().adaptTo(Session.class); 
+        adminSession = ctx.resourceResolver().adaptTo(Session.class);
+        id = UUID.randomUUID().toString();
+        username = "user_" + id;
     }
     
     List<Operation> parse(String input) throws ParseException {
@@ -64,14 +69,15 @@ class TestUtil {
         }
     }
     
-    void parseAndExecute(String input) throws ParseException {
+    void parseAndExecute(String input) throws ParseException, RepositoryException {
         final AclOperationVisitor v = new AclOperationVisitor(adminSession);
         for(Operation o : parse(input)) {
             o.accept(v);
         }
+        adminSession.save();
     }
     
-    Session getServiceSession(String serviceUsername) throws RepositoryException {
+    Session loginService(String serviceUsername) throws RepositoryException {
         final SimpleCredentials cred = new SimpleCredentials(serviceUsername, new char[0]);
         return adminSession.impersonate(cred);
     }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.