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:47:14 UTC

[sling-org-apache-sling-jcr-contentloader] 11/36: SLING-981 Patch from Eric Norman applied, thanks. Issues was first created by myself (ieb), but the patch represented a vast improvement on the orriginal contribution. This commit adds the ability to specify ACE's in the json file that would have previously only modified properties on the node. In addition it allows the creation of new principals to allow the ACE's to be added to the node.

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

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

commit c13c03294ae7437b45cfaf29cc00ad57087cfac3
Author: Ian Boston <ie...@apache.org>
AuthorDate: Thu Jul 16 11:33:43 2009 +0000

    SLING-981
    Patch from Eric Norman applied, thanks.
      Issues was first created by myself (ieb), but the patch represented a vast improvement on the orriginal contribution.
      This commit adds the ability to specify ACE's in the json file that would have previously only modified properties
      on the node. In addition it allows the creation of new principals to allow the ACE's to be added to the node.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/contentloader@794635 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  12 +
 .../jcr/contentloader/internal/ContentCreator.java |  34 +++
 .../internal/ContentLoaderService.java             |  44 ++++
 .../jcr/contentloader/internal/ContentReader.java  |   2 +-
 .../internal/DefaultContentCreator.java            | 257 ++++++++++++++++++++-
 .../contentloader/internal/readers/JsonReader.java | 166 ++++++++++++-
 .../jcr/contentloader/internal/JsonReaderTest.java |  31 +++
 .../internal/readers/XmlReaderTest.java            |  16 +-
 8 files changed, 558 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 40198fc..98260ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -144,5 +144,17 @@
             <groupId>org.jmock</groupId>
             <artifactId>jmock-junit4</artifactId>
         </dependency>
+        
+        <!-- for security content loader (users/groups/acls) -->
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-api</artifactId>
+            <version>1.5.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.base</artifactId>
+            <version>2.0.4-incubator</version>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index 3e93ac1..5f10d6a 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -19,6 +19,7 @@
 package org.apache.sling.jcr.contentloader.internal;
 
 import java.io.InputStream;
+import java.util.Map;
 
 import javax.jcr.RepositoryException;
 
@@ -132,4 +133,37 @@ public interface ContentCreator {
      */
     boolean switchCurrentNode(String subPath, String newNodeType)
     throws RepositoryException;
+    
+    
+    /**
+     * Create a User in the jackrabbit UserManager
+     * @param name the name of the user
+     * @param password the password of the user
+     * @param extraProperties extra properties to assign to the created user
+     * @throws RepositoryException
+     */
+    void createUser(String name, String password, Map<String, Object> extraProperties)
+    throws RepositoryException;
+    
+    /**
+     * Create a Group in the jackrabbit UserManager
+     * @param name the name of the group
+     * @param members the members of the group (principal names)
+     * @param extraProperties extra properties to assign to the created group
+     * @throws RepositoryException
+     */
+    void createGroup(String name, String[] members, Map<String, Object> extraProperties)
+    throws RepositoryException;
+    
+    /**
+     * Creates an Access Control Entry for the current node for the specified 
+     *  principal and privileges.
+     * 
+     * @param principal the user or group id for the ACE
+     * @param grantedPrivileges the set of privileges to grant the principal
+     * @param deniedPrivileges the set of privileges to deny the principal (for users only)
+     * @throws RepositoryException
+     */
+    void createAce(String principal, String [] grantedPrivileges, String [] deniedPrivileges )
+    throws RepositoryException;
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
index 1f94f63..cfa09e0 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
@@ -18,7 +18,10 @@
  */
 package org.apache.sling.jcr.contentloader.internal;
 
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
 import java.util.Calendar;
+import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -32,6 +35,7 @@ import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.lock.LockException;
 
+import org.apache.jackrabbit.util.Text;
 import org.apache.sling.commons.mime.MimeTypeService;
 import org.apache.sling.engine.SlingSettingsService;
 import org.apache.sling.jcr.api.SlingRepository;
@@ -66,6 +70,16 @@ public class ContentLoaderService implements SynchronousBundleListener {
 
     public static final String BUNDLE_CONTENT_NODE = "/var/sling/bundle-content";
 
+    /**
+     * To be used for the encryption. E.g. for passwords in
+     * {@link javax.jcr.SimpleCredentials#getPassword()} SimpleCredentials}
+     * 
+     * @scr.property valueRef="DEFAULT_PASSWORD_DIGEST_ALGORITHM"
+     */
+    private static final String PROP_PASSWORD_DIGEST_ALGORITHM = "password.digest.algorithm";
+    private static final String DEFAULT_PASSWORD_DIGEST_ALGORITHM = "sha1";
+    private String passwordDigestAlgoritm = null;
+    
     /** default log */
     final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -196,6 +210,27 @@ public class ContentLoaderService implements SynchronousBundleListener {
         }
     }
 
+    /**
+     * Digest the given password using the configured digest algorithm
+     * 
+     * @param pwd the value to digest
+     * @return the digested value
+     * @throws IllegalArgumentException
+     */
+    protected String digestPassword(String pwd) throws IllegalArgumentException {
+        try {
+            StringBuffer password = new StringBuffer();
+            password.append("{").append(passwordDigestAlgoritm).append("}");
+            password.append(Text.digest(passwordDigestAlgoritm,
+                pwd.getBytes("UTF-8")));
+            return password.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException(e.toString());
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalArgumentException(e.toString());
+        }
+    }
+    
     // ---------- SCR Integration ---------------------------------------------
 
     /** Activates this component, called by SCR before registering as a service */
@@ -205,6 +240,14 @@ public class ContentLoaderService implements SynchronousBundleListener {
 
         componentContext.getBundleContext().addBundleListener(this);
 
+        Dictionary<?, ?> props = componentContext.getProperties();
+        Object propValue = props.get(PROP_PASSWORD_DIGEST_ALGORITHM);
+        if (propValue instanceof String) {
+            passwordDigestAlgoritm = (String) propValue;
+        } else {
+            passwordDigestAlgoritm = DEFAULT_PASSWORD_DIGEST_ALGORITHM;
+        }
+        
         Session session = null;
         try {
             session = this.getSession();
@@ -258,6 +301,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
             this.initialContentLoader.dispose();
             this.initialContentLoader = null;
         }
+        passwordDigestAlgoritm = null;
     }
 
     // ---------- internal helper ----------------------------------------------
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
index fef05d9..14bdbc0 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
@@ -31,7 +31,7 @@ public interface ContentReader {
 
     /**
      * Read the content from the URL and create the
-     * content throught the provided content creator.
+     * content using the provided content creator.
      * @param url The input stream.
      * @throws IOException
      */
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
index c22b509..424a189 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
@@ -19,15 +19,22 @@
 package org.apache.sling.jcr.contentloader.internal;
 
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.Stack;
 import java.util.StringTokenizer;
+import java.util.Map.Entry;
 
 import javax.jcr.Item;
 import javax.jcr.Node;
@@ -37,13 +44,26 @@ import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
 
+import org.apache.jackrabbit.api.jsr283.security.AccessControlEntry;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlList;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlManager;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy;
+import org.apache.jackrabbit.api.jsr283.security.AccessControlPolicyIterator;
+import org.apache.jackrabbit.api.jsr283.security.Privilege;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.util.Text;
+import org.apache.sling.jcr.base.util.AccessControlUtil;
+
 /**
  * The <code>ContentLoader</code> creates the nodes and properties.
  * @since 2.0.4
  */
 public class DefaultContentCreator implements ContentCreator {
 
-    private PathEntry configuration;
+	private PathEntry configuration;
 
     private final Stack<Node> parentNodeStack = new Stack<Node>();
 
@@ -75,6 +95,16 @@ public class DefaultContentCreator implements ContentCreator {
     private List<String> createdNodes;
 
     /**
+     * A one time use seed to randomize the user location.
+     */
+    private static final long INSTANCE_SEED = System.currentTimeMillis();
+    
+    /**
+     * The number of levels folder used to store a user, could be a configuration option.
+     */
+    private static final int STORAGE_LEVELS = 3;
+    
+    /**
      * Constructor.
      * @param jcrContentHelper Helper class to get the mime type of a file
      */
@@ -603,4 +633,229 @@ public class DefaultContentCreator implements ContentCreator {
         return true;
     }
 
+
+	/* (non-Javadoc)
+	 * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createGroup(java.lang.String, java.lang.String[], java.util.Map)
+	 */
+	public void createGroup(final String name, String[] members,
+			Map<String, Object> extraProperties) throws RepositoryException {
+
+		final Node parentNode = this.parentNodeStack.peek();
+		Session session = parentNode.getSession();
+		
+        UserManager userManager = AccessControlUtil.getUserManager(session);
+        Authorizable authorizable = userManager.getAuthorizable(name);
+        if (authorizable == null) {
+            //principal does not exist yet, so create it
+        	Group group = userManager.createGroup(new Principal() {
+                    public String getName() {
+                        return name;
+                    }
+                },
+                hashPath(name));
+        	authorizable = group;
+        } else {
+        	//principal already exists, check to make sure it is the expected type
+        	if (!authorizable.isGroup()) {
+                throw new RepositoryException(
+                        "A user already exists with the requested name: "
+                            + name);
+        	} else {
+        		//group already exists so just update it below        		
+        	}
+        }
+        //update the group members
+        if (members != null) {
+        	Group group = (Group)authorizable;
+        	for (String member : members) {
+        		Authorizable memberAuthorizable = userManager.getAuthorizable(member);
+        		if (memberAuthorizable != null) {
+        			group.addMember(memberAuthorizable);
+        		}
+        	}
+        }
+        if (extraProperties != null) {
+        	ValueFactory valueFactory = session.getValueFactory();
+        	Set<Entry<String, Object>> entrySet = extraProperties.entrySet();
+        	for (Entry<String, Object> entry : entrySet) {
+        		Value value = createValue(valueFactory, entry.getValue());
+        		authorizable.setProperty(name, value);
+			}
+        }
+	}
+
+	/* (non-Javadoc)
+	 * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createUser(java.lang.String, java.lang.String, java.util.Map)
+	 */
+	public void createUser(final String name, String password,
+			Map<String, Object> extraProperties) throws RepositoryException {
+		final Node parentNode = this.parentNodeStack.peek();
+		Session session = parentNode.getSession();
+		
+        UserManager userManager = AccessControlUtil.getUserManager(session);
+        Authorizable authorizable = userManager.getAuthorizable(name);
+        if (authorizable == null) {
+            //principal does not exist yet, so create it
+        	String digestedPassword = jcrContentHelper.digestPassword(password);
+        	User user = userManager.createUser(name, 
+        			digestedPassword, 
+        			new Principal() {
+						public String getName() {
+							return name;
+						}
+		        	},
+		        	hashPath(name));
+        	authorizable = user;
+        } else {
+        	//principal already exists, check to make sure it is the expected type
+        	if (authorizable.isGroup()) {
+                throw new RepositoryException(
+                        "A group already exists with the requested name: "
+                            + name);
+        	} else {
+        		//user already exists so just update it below        		
+        	}
+        }
+        if (extraProperties != null) {
+        	ValueFactory valueFactory = session.getValueFactory();
+        	Set<Entry<String, Object>> entrySet = extraProperties.entrySet();
+        	for (Entry<String, Object> entry : entrySet) {
+        		Value value = createValue(valueFactory, entry.getValue());
+        		authorizable.setProperty(name, value);
+			}
+        }
+	}
+	
+	/**
+	 * @param item
+	 * @return a parent path fragment for the item.
+	 */
+	protected String hashPath(String item) throws RepositoryException {
+		try {
+			String hash = Text.digest("sha1", INSTANCE_SEED + item, "UTF-8");
+			StringBuilder sb = new StringBuilder();
+			for (int i = 0; i < STORAGE_LEVELS; i++) {
+				sb.append(hash, i * 2, (i * 2) + 2).append("/");
+			}
+			return sb.toString();
+		} catch (NoSuchAlgorithmException e) {
+			throw new RepositoryException("Unable to hash the path.", e);
+		} catch (UnsupportedEncodingException e) {
+			throw new RepositoryException("Unable to hash the path.", e);
+		}
+	}
+	
+
+    /* (non-Javadoc)
+	 * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createAce(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[])
+	 */
+	public void createAce(String principalId,
+			String[] grantedPrivilegeNames, String[] deniedPrivilegeNames)
+			throws RepositoryException {
+		final Node parentNode = this.parentNodeStack.peek();
+		Session session = parentNode.getSession();
+		
+		UserManager userManager = AccessControlUtil.getUserManager(session);
+		Authorizable authorizable = userManager.getAuthorizable(principalId);
+		if (authorizable == null) {
+			throw new RepositoryException("No principal found for id: " + principalId);
+		}
+
+		String resourcePath = parentNode.getPath();
+		
+		AccessControlManager accessControlManager = AccessControlUtil.getAccessControlManager(session);
+		AccessControlList updatedAcl = null;
+		AccessControlPolicyIterator applicablePolicies = accessControlManager.getApplicablePolicies(resourcePath);
+		while (applicablePolicies.hasNext()) {
+			AccessControlPolicy policy = applicablePolicies.nextAccessControlPolicy();
+			if (policy instanceof AccessControlList) {
+				updatedAcl = (AccessControlList)policy;
+				break;
+			}
+		}
+		if (updatedAcl == null) {
+			throw new RepositoryException("Unable to find an access conrol policy to update.");
+		}
+
+		Set<String> postedPrivilegeNames = new HashSet<String>();
+		if (grantedPrivilegeNames != null) {
+			postedPrivilegeNames.addAll(Arrays.asList(grantedPrivilegeNames));
+		}
+		if (deniedPrivilegeNames != null) {
+			postedPrivilegeNames.addAll(Arrays.asList(deniedPrivilegeNames));
+		}
+			
+		List<Privilege> preserveGrantedPrivileges = new ArrayList<Privilege>();
+		List<Privilege> preserveDeniedPrivileges = new ArrayList<Privilege>();
+			
+		//keep track of the existing Aces for the target principal
+		AccessControlEntry[] accessControlEntries = updatedAcl.getAccessControlEntries();
+		List<AccessControlEntry> oldAces = new ArrayList<AccessControlEntry>();
+		for (AccessControlEntry ace : accessControlEntries) {
+			if (principalId.equals(ace.getPrincipal().getName())) {
+				oldAces.add(ace);
+
+				boolean isAllow = AccessControlUtil.isAllow(ace);
+				Privilege[] privileges = ace.getPrivileges();
+				for (Privilege privilege : privileges) {
+					String privilegeName = privilege.getName();
+					if (!postedPrivilegeNames.contains(privilegeName)) {
+						//this privilege was not posted, so record the existing state to be 
+						// preserved when the ACE is re-created below
+						if (isAllow) {
+							preserveGrantedPrivileges.add(privilege);
+						} else {
+							preserveDeniedPrivileges.add(privilege);
+						}
+					}
+				}
+			}
+		}
+
+		//remove the old aces
+		if (!oldAces.isEmpty()) {
+			for (AccessControlEntry ace : oldAces) {
+				updatedAcl.removeAccessControlEntry(ace);
+			}
+		}
+			
+		//add a fresh ACE with the granted privileges
+		List<Privilege> grantedPrivilegeList = new ArrayList<Privilege>();
+		for (String name : grantedPrivilegeNames) {
+			if (name.length() == 0) {
+				continue; //empty, skip it.
+			}
+			Privilege privilege = accessControlManager.privilegeFromName(name);
+			grantedPrivilegeList.add(privilege);
+		}
+		//add the privileges that should be preserved
+		grantedPrivilegeList.addAll(preserveGrantedPrivileges);
+			
+		if (grantedPrivilegeList.size() > 0) {
+			Principal principal = authorizable.getPrincipal();
+			updatedAcl.addAccessControlEntry(principal, grantedPrivilegeList.toArray(new Privilege[grantedPrivilegeList.size()]));
+		}
+
+		//if the authorizable is a user (not a group) process any denied privileges
+		if (!authorizable.isGroup()) {
+			//add a fresh ACE with the denied privileges
+			List<Privilege> deniedPrivilegeList = new ArrayList<Privilege>();
+			for (String name : deniedPrivilegeNames) {
+				if (name.length() == 0) {
+					continue; //empty, skip it.
+				}
+				Privilege privilege = accessControlManager.privilegeFromName(name);
+				deniedPrivilegeList.add(privilege);
+
+			}
+			//add the privileges that should be preserved
+			deniedPrivilegeList.addAll(preserveDeniedPrivileges);
+			if (deniedPrivilegeList.size() > 0) {
+				Principal principal = authorizable.getPrincipal();
+				AccessControlUtil.addEntry(updatedAcl, principal, deniedPrivilegeList.toArray(new Privilege[deniedPrivilegeList.size()]), false);
+			}
+		}
+
+		accessControlManager.setPolicy(resourcePath, updatedAcl);
+	}	
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
index 8a4c441..3184b3a 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
@@ -23,6 +23,8 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Set;
 
 import javax.jcr.PropertyType;
@@ -55,6 +57,17 @@ public class JsonReader implements ContentReader {
         ignoredNames.add("jcr:checkedOut");
         ignoredNames.add("jcr:created");
     }
+    
+    private static final Set<String> ignoredPrincipalPropertyNames = new HashSet<String>();
+    static {
+    	ignoredPrincipalPropertyNames.add("name");
+    	ignoredPrincipalPropertyNames.add("isgroup");
+    	ignoredPrincipalPropertyNames.add("members");
+    	ignoredPrincipalPropertyNames.add("dynamic");
+    	ignoredPrincipalPropertyNames.add("password");
+    }
+    private static final String SECURITY_PRINCIPLES = "security:principals";
+    private static final String SECURITY_ACL = "security:acl";
 
     public static final ImportProvider PROVIDER = new ImportProvider() {
         private JsonReader jsonReader;
@@ -127,7 +140,11 @@ public class JsonReader implements ContentReader {
             // skip well known objects
             if (!ignoredNames.contains(n)) {
                 Object o = obj.get(n);
-                if (o instanceof JSONObject) {
+                if (SECURITY_PRINCIPLES.equals(n)) {
+                	this.createPrincipals(o, contentCreator);
+                } else if (SECURITY_ACL.equals(n)) {
+                	this.createAcl(o, contentCreator);
+                } else if (o instanceof JSONObject) {
                     this.createNode(n, (JSONObject) o, contentCreator);
                 } else {
                     this.createProperty(n, o, contentCreator);
@@ -213,4 +230,151 @@ public class JsonReader implements ContentReader {
 
         return new String(bos.toByteArray(), encoding);
     }
+    
+    
+    /**
+     * Create or update one or more user and/or groups
+     *	<code>
+     *  {
+     *     "security:principals" : [
+     *        {
+     *           "name":"owner",
+     *           "isgroup":"true",
+     *           "members":[],
+     *           "dynamic":"true"
+     *        }
+     *     ],
+     *  }
+     *  </code>
+     */
+    protected void createPrincipals(Object obj, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+    	if (obj instanceof JSONObject) {
+    		//single principal
+    		createPrincipal((JSONObject)obj, contentCreator);
+    	} else if (obj instanceof JSONArray) {
+    		//array of principals
+    		JSONArray jsonArray = (JSONArray)obj;
+    		for (int i=0; i < jsonArray.length(); i++) {
+    			Object object = jsonArray.get(i);
+    			if (object instanceof JSONObject) {
+    	    		createPrincipal((JSONObject)object, contentCreator);
+    			} else {
+    				throw new JSONException("Unexpected data type in principals array: " + object.getClass().getName());
+    			}
+    		}
+    	}
+    }
+    
+    /**
+     * Create or update a user or group
+     */
+    protected void createPrincipal(JSONObject json, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+    	//create a principal
+    	String name = json.getString("name");
+    	boolean isGroup = json.optBoolean("isgroup", false);
+
+    	//collect the extra property names to assign to the new principal
+    	Map<String, Object> extraProps = new LinkedHashMap<String, Object>();
+		JSONArray names = json.names();
+		for(int p=0; p < names.length(); p++) {
+			String propName = names.getString(p);
+			if (!ignoredPrincipalPropertyNames.contains(propName)) {
+    			Object value = json.get(propName);
+    			extraProps.put(propName, value);
+			}
+		}
+
+    	if (isGroup) {
+    		String [] members = null;
+    		JSONArray membersJSONArray = json.optJSONArray("members");
+    		if (membersJSONArray != null) {
+    			members = new String[membersJSONArray.length()];
+    			for (int i=0; i < membersJSONArray.length(); i++) {
+    				members[i] = membersJSONArray.getString(i);
+    			}
+    		}
+    		contentCreator.createGroup(name, members, extraProps);
+    	} else {
+    		String password = json.getString("password");
+    		contentCreator.createUser(name, password, extraProps);
+    	}
+    }
+    
+    /**
+     * Create or update one or more access control entries for the current
+     * node.
+     * 
+     *  <code>
+     *  {
+     *   "security:acl" : [
+     *     	{
+     *     		"principal" : "username1",
+     *     		"granted" : [
+     *      		"jcr:read",
+     *      		"jcr:write"
+     *     		],
+     *     		"denied" : [
+     *     		]
+     *     	},
+     *     	{
+     *     		"principal" : "groupname1",
+     *     		"granted" : [
+     *      		"jcr:read",
+     *      		"jcr:write"
+     *     		]
+     *     	}    
+     *   ]    
+     *  }
+     *  </code>
+     */
+    protected void createAcl(Object obj, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+    	if (obj instanceof JSONObject) {
+    		//single ace
+    		createAce((JSONObject)obj, contentCreator);
+    	} else if (obj instanceof JSONArray) {
+    		//array of aces
+    		JSONArray jsonArray = (JSONArray)obj;
+    		for (int i=0; i < jsonArray.length(); i++) {
+    			Object object = jsonArray.get(i);
+    			if (object instanceof JSONObject) {
+    	    		createAce((JSONObject)object, contentCreator);
+    			} else {
+    				throw new JSONException("Unexpected data type in acl array: " + object.getClass().getName());
+    			}
+    		}
+    	}
+    }
+    
+    /**
+     * Create or update an access control entry
+     */
+    protected void createAce(JSONObject ace, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+		String principalID = ace.getString("principal");
+		
+		String [] grantedPrivileges = null;
+		JSONArray granted = ace.optJSONArray("granted");
+		if (granted != null) {
+			grantedPrivileges = new String[granted.length()];
+			for (int a=0; a < granted.length(); a++) {
+				grantedPrivileges[a] = granted.getString(a);
+			}
+		}
+
+		String [] deniedPrivileges = null;
+		JSONArray denied = ace.optJSONArray("denied");
+		if (denied != null) {
+			deniedPrivileges = new String[denied.length()];
+			for (int a=0; a < denied.length(); a++) {
+				deniedPrivileges[a] = denied.getString(a);
+			}
+		}
+		
+		//do the work.
+		contentCreator.createAce(principalID, grantedPrivileges, deniedPrivileges);
+    }    
+    
 }
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
index d403145..c2d2a69 100644
--- a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
@@ -237,6 +237,37 @@ public class JsonReaderTest {
         }});
         this.parse(json);
     }
+    
+    
+    @org.junit.Test public void testCreateAcl() throws Exception {
+    	String json = " { " +
+    			"\"security:acl\" : [ " +
+    			"  { " +
+    			"    \"principal\" : \"username1\"," +
+    			"    \"granted\" : [\"jcr:read\",\"jcr:write\"]," +
+    			"    \"denied\" : []" +
+    			"  }," +
+    			"  {" +
+    			"    \"principal\" : \"groupname1\"," +
+    			"    \"granted\" : [\"jcr:read\",\"jcr:write\"]" +
+    			"  }," +
+    			"  {" +
+    			"    \"principal\" : \"groupname2\"," +
+    			"    \"granted\" : [\"jcr:read\"]," +
+    			"    \"denied\" : [\"jcr:write\"]" +
+    			"  }" +
+    			"]" +
+    			"}";	
+        this.mockery.checking(new Expectations() {{
+        	allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            
+            allowing(creator).createAce("username1",new String[]{"jcr:read","jcr:write"},new String[]{}); inSequence(mySequence);
+            allowing(creator).createAce("groupname1",new String[]{"jcr:read","jcr:write"},null); inSequence(mySequence);
+            allowing(creator).createAce("groupname2",new String[]{"jcr:read"},new String[]{"jcr:write"}); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
 
     //---------- internal helper ----------------------------------------------
 
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
index 554c5ea..46119dd 100644
--- a/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
@@ -26,6 +26,7 @@ import java.io.File;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Map;
 
 /**
  * 	Test the XmlReader with an XSLT transform
@@ -44,7 +45,7 @@ public class XmlReaderTest extends TestCase {
     @SuppressWarnings("serial")
 	private static class MockContentCreator extends ArrayList<String> implements ContentCreator {
 
-        public MockContentCreator() {
+		public MockContentCreator() {
         }
 
         public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
@@ -72,5 +73,18 @@ public class XmlReaderTest extends TestCase {
         public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
             return true;
         }
+        
+		public void createAce(String principal,
+				String[] grantedPrivileges, String[] deniedPrivileges)
+				throws RepositoryException {
+		}
+
+		public void createGroup(String name, String[] members,
+				Map<String, Object> extraProperties) throws RepositoryException {
+		}
+
+		public void createUser(String name, String password,
+				Map<String, Object> extraProperties) throws RepositoryException {
+		}
     }
 }

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