From 18c8ea2376fe85245d50a98ea7c0a95a51b17370 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 18 Feb 2012 18:41:47 +0000 Subject: [PATCH] Improve JCR authorizations git-svn-id: https://svn.argeo.org/commons/trunk@5098 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../jackrabbit/JackrabbitAuthorizations.java | 132 +++------------- .../argeo/jackrabbit/repository-memory.xml | 2 +- .../src/main/java/org/argeo/jcr/JcrUtils.java | 100 ++++++++++++ .../argeo/jcr/security/JcrAuthorizations.java | 145 ++++++++++++++++++ .../AbstractInternalJackrabbitTestCase.java | 43 ++++++ .../test/java/org/argeo/jcr/MapperTest.java | 22 +-- .../org/argeo/jcr/tabular/JcrTabularTest.java | 28 +--- .../server/jcr/JcrResourceAdapterTest.java | 21 +-- .../argeo/server/jcr/repository-memory.xml | 55 +++++++ 9 files changed, 372 insertions(+), 176 deletions(-) create mode 100644 server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java create mode 100644 server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java create mode 100644 server/runtime/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitAuthorizations.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitAuthorizations.java index 3a731df58..2347e8c7b 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitAuthorizations.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitAuthorizations.java @@ -1,134 +1,48 @@ package org.argeo.jackrabbit; +import java.security.Principal; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.Executor; -import javax.jcr.Repository; import javax.jcr.RepositoryException; -import javax.jcr.security.AccessControlList; -import javax.jcr.security.AccessControlPolicy; -import javax.jcr.security.AccessControlPolicyIterator; -import javax.jcr.security.Privilege; +import javax.jcr.Session; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.api.JackrabbitSession; -import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager; import org.apache.jackrabbit.api.security.user.Authorizable; -import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.argeo.ArgeoException; -import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.security.JcrAuthorizations; /** Apply authorizations to a Jackrabbit repository. */ -public class JackrabbitAuthorizations { +public class JackrabbitAuthorizations extends JcrAuthorizations { private final static Log log = LogFactory .getLog(JackrabbitAuthorizations.class); - private Repository repository; - private Executor systemExecutor; - - /** - * key := privilege1,privilege2/path/to/node
- * value := group1,group2 - */ - private Map groupPrivileges = new HashMap(); - - public void init() { - Runnable action = new Runnable() { - public void run() { - JackrabbitSession session = null; - try { - session = (JackrabbitSession) repository.login(); - initAuthorizations(session); - } catch (Exception e) { - JcrUtils.discardQuietly(session); - } finally { - JcrUtils.logoutQuietly(session); + private List groupPrefixes = new ArrayList(); + + @Override + protected Principal getOrCreatePrincipal(Session session, + String principalName) throws RepositoryException { + UserManager um = ((JackrabbitSession) session).getUserManager(); + Authorizable authorizable = um.getAuthorizable(principalName); + if (authorizable == null) { + groupPrefixes: for (String groupPrefix : groupPrefixes) { + if (principalName.startsWith(groupPrefix)) { + authorizable = um.createGroup(principalName); + log.info("Created group " + principalName); + break groupPrefixes; } } - }; - - if (systemExecutor != null) - systemExecutor.execute(action); - else - action.run(); - } - - protected void initAuthorizations(JackrabbitSession session) - throws RepositoryException { - JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session - .getAccessControlManager(); - UserManager um = session.getUserManager(); - - for (String privileges : groupPrivileges.keySet()) { - String path = null; - int slashIndex = privileges.indexOf('/'); - if (slashIndex == 0) { - throw new ArgeoException("Privilege " + privileges - + " badly formatted it starts with /"); - } else if (slashIndex > 0) { - path = privileges.substring(slashIndex); - privileges = privileges.substring(0, slashIndex); - } - - if (path == null) - path = "/"; - - List privs = new ArrayList(); - for (String priv : privileges.split(",")) { - privs.add(acm.privilegeFromName(priv)); - } - - String groupNames = groupPrivileges.get(privileges); - for (String groupName : groupNames.split(",")) { - Group group = (Group) um.getAuthorizable(groupName); - if (group == null) - group = um.createGroup(groupName); - addPrivileges(session, group, path, privs); - } - } - session.save(); - } - - public static void addPrivileges(JackrabbitSession session, - Authorizable authorizable, String path, List privs) - throws RepositoryException { - JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session - .getAccessControlManager(); - AccessControlPolicy policy = null; - AccessControlPolicyIterator policyIterator = acm - .getApplicablePolicies(path); - if (policyIterator.hasNext()) { - policy = policyIterator.nextAccessControlPolicy(); - } else { - AccessControlPolicy[] existingPolicies = acm.getPolicies(path); - policy = existingPolicies[0]; - } - if (policy instanceof AccessControlList) { - ((AccessControlList) policy).addAccessControlEntry( - authorizable.getPrincipal(), - privs.toArray(new Privilege[privs.size()])); - acm.setPolicy(path, policy); + if (authorizable == null) + throw new ArgeoException("Authorizable " + principalName + + " not found"); } - if (log.isDebugEnabled()) - log.debug("Added privileges " + privs + " to " + authorizable - + " on " + path); + return authorizable.getPrincipal(); } - public void setGroupPrivileges(Map groupPrivileges) { - this.groupPrivileges = groupPrivileges; + public void setGroupPrefixes(List groupsToCreate) { + this.groupPrefixes = groupsToCreate; } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setSystemExecutor(Executor systemExecutor) { - this.systemExecutor = systemExecutor; - } - } diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/resources/org/argeo/jackrabbit/repository-memory.xml b/server/runtime/org.argeo.server.jackrabbit/src/main/resources/org/argeo/jackrabbit/repository-memory.xml index 9f49e551b..70cf19011 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/resources/org/argeo/jackrabbit/repository-memory.xml +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/resources/org/argeo/jackrabbit/repository-memory.xml @@ -7,7 +7,7 @@ + defaultWorkspace="main" configRootPath="/workspaces" /> 0) { + for (AccessControlPolicy policy : effectivePolicies) { + if (policy instanceof AccessControlList) { + AccessControlList acl = (AccessControlList) policy; + log.debug("Access control list for " + path + "\n" + + accessControlListSummary(acl)); + } + } + } else { + log.debug("No effective access control policy for " + path); + } + } catch (RepositoryException e) { + log.error("Cannot log effective access policies of " + path, e); + } + } + + /** Returns a human-readable summary of this access control list. */ + public static String accessControlListSummary(AccessControlList acl) { + StringBuffer buf = new StringBuffer(""); + try { + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + buf.append('\t').append(ace.getPrincipal().getName()) + .append('\n'); + for (Privilege priv : ace.getPrivileges()) + buf.append("\t\t").append(priv.getName()).append('\n'); + } + return buf.toString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot write summary of " + acl, e); + } + } + /** * Copies recursively the content of a node to another one. Do NOT copy the * property values of {@link NodeType#MIX_CREATED} and @@ -1307,4 +1363,48 @@ public class JcrUtils implements ArgeoJcrConstants { re); } } + + /* + * SECURITY + */ + /** + * Add privileges on a path to a {@link Principal}. The path must already + * exist. + */ + public static void addPrivileges(Session session, String path, + Principal principal, List privs) + throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + // search for an access control list + AccessControlList acl = null; + AccessControlPolicyIterator policyIterator = acm + .getApplicablePolicies(path); + if (policyIterator.hasNext()) { + while (policyIterator.hasNext()) { + AccessControlPolicy acp = policyIterator + .nextAccessControlPolicy(); + if (acp instanceof AccessControlList) + acl = ((AccessControlList) acp); + } + } else { + AccessControlPolicy[] existingPolicies = acm.getPolicies(path); + for (AccessControlPolicy acp : existingPolicies) { + if (acp instanceof AccessControlList) + acl = ((AccessControlList) acp); + } + } + + if (acl != null) { + acl.addAccessControlEntry(principal, + privs.toArray(new Privilege[privs.size()])); + acm.setPolicy(path, acl); + if (log.isDebugEnabled()) + log.debug("Added privileges " + privs + " to " + principal + + " on " + path); + } else { + throw new ArgeoException("Don't know how to apply privileges " + + privs + " to " + principal + " on " + path); + } + } + } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java new file mode 100644 index 000000000..f6741624c --- /dev/null +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java @@ -0,0 +1,145 @@ +package org.argeo.jcr.security; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.security.AccessControlList; +import javax.jcr.security.AccessControlManager; +import javax.jcr.security.AccessControlPolicy; +import javax.jcr.security.AccessControlPolicyIterator; +import javax.jcr.security.Privilege; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.JcrUtils; +import org.argeo.util.security.SimplePrincipal; + +/** Apply authorizations to a JCR repository. */ +public class JcrAuthorizations implements Runnable { + private final static Log log = LogFactory.getLog(JcrAuthorizations.class); + + private Repository repository; + + /** + * key := privilege1,privilege2/path/to/node
+ * value := group1,group2,user1 + */ + private Map principalPrivileges = new HashMap(); + + public void run() { + Session session = null; + try { + session = repository.login(); + initAuthorizations(session); + } catch (Exception e) { + JcrUtils.discardQuietly(session); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + /** @deprecated call {@link #run()} instead. */ + @Deprecated + public void init() { + run(); + } + + protected void initAuthorizations(Session session) + throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + + for (String privileges : principalPrivileges.keySet()) { + String path = null; + int slashIndex = privileges.indexOf('/'); + if (slashIndex == 0) { + throw new ArgeoException("Privilege " + privileges + + " badly formatted it starts with /"); + } else if (slashIndex > 0) { + path = privileges.substring(slashIndex); + privileges = privileges.substring(0, slashIndex); + } + + if (path == null) + path = "/"; + + List privs = new ArrayList(); + for (String priv : privileges.split(",")) { + privs.add(acm.privilegeFromName(priv)); + } + + String principalNames = principalPrivileges.get(privileges); + for (String principalName : principalNames.split(",")) { + Principal principal = getOrCreatePrincipal(session, + principalName); + addPrivileges(session, principal, path, privs); + } + } + session.save(); + } + + /** + * Returns a {@link SimplePrincipal}, does not check whether it exists since + * such capabilities is not provided by the standard JCR API. Can be + * overridden to provide smarter handling + */ + protected Principal getOrCreatePrincipal(Session session, + String principalName) throws RepositoryException { + return new SimplePrincipal(principalName); + } + + public static void addPrivileges(Session session, Principal principal, + String path, List privs) throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + // search for an access control list + AccessControlList acl = null; + AccessControlPolicyIterator policyIterator = acm + .getApplicablePolicies(path); + if (policyIterator.hasNext()) { + while (policyIterator.hasNext()) { + AccessControlPolicy acp = policyIterator + .nextAccessControlPolicy(); + if (acp instanceof AccessControlList) + acl = ((AccessControlList) acp); + } + } else { + AccessControlPolicy[] existingPolicies = acm.getPolicies(path); + for (AccessControlPolicy acp : existingPolicies) { + if (acp instanceof AccessControlList) + acl = ((AccessControlList) acp); + } + } + + if (acl != null) { + acl.addAccessControlEntry(principal, + privs.toArray(new Privilege[privs.size()])); + acm.setPolicy(path, acl); + if (log.isDebugEnabled()) + log.debug("Added privileges " + privs + " to " + principal + + " on " + path); + } else { + throw new ArgeoException("Don't know how to apply privileges " + + privs + " to " + principal + " on " + path); + } + } + + @Deprecated + public void setGroupPrivileges(Map groupPrivileges) { + this.principalPrivileges = groupPrivileges; + } + + public void setPrincipalPrivileges(Map principalPrivileges) { + this.principalPrivileges = principalPrivileges; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + +} diff --git a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java new file mode 100644 index 000000000..078763f8b --- /dev/null +++ b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010 Mathieu Baudier + * + * Licensed 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.argeo.jcr; + +import java.io.File; + +import javax.jcr.Repository; + +import org.apache.jackrabbit.core.TransientRepository; +import org.argeo.jcr.unit.AbstractJcrTestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +/** Factorizes configuration of an in memory transient repository */ +public abstract class AbstractInternalJackrabbitTestCase extends + AbstractJcrTestCase { + protected File getRepositoryFile() throws Exception { + Resource res = new ClassPathResource( + "org/argeo/server/jcr/repository-memory.xml"); + return res.getFile(); + } + + protected Repository createRepository() throws Exception { + Repository repository = new TransientRepository(getRepositoryFile(), + getHomeDir()); + return repository; + } + +} diff --git a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java index 8f276991d..03aab05fd 100644 --- a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java +++ b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java @@ -16,18 +16,11 @@ package org.argeo.jcr; -import java.io.File; - import javax.jcr.Node; -import javax.jcr.Repository; -import org.apache.jackrabbit.core.TransientRepository; import org.argeo.jcr.spring.BeanNodeMapper; -import org.argeo.jcr.unit.AbstractJcrTestCase; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -public class MapperTest extends AbstractJcrTestCase { +public class MapperTest extends AbstractInternalJackrabbitTestCase { public void testSimpleObject() throws Exception { SimpleObject mySo = new SimpleObject(); mySo.setInteger(100); @@ -49,17 +42,4 @@ public class MapperTest extends AbstractJcrTestCase { session().save(); JcrUtils.debug(node); } - - protected File getRepositoryFile() throws Exception { - Resource res = new ClassPathResource( - "org/argeo/server/jcr/repository-h2.xml"); - return res.getFile(); - } - - protected Repository createRepository() throws Exception{ - Repository repository = new TransientRepository(getRepositoryFile(), getHomeDir()); - return repository; - } - - } diff --git a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java index 7bb8e90bc..672c87420 100644 --- a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java +++ b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java @@ -16,30 +16,25 @@ package org.argeo.jcr.tabular; -import java.io.File; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import javax.jcr.Node; import javax.jcr.PropertyType; -import javax.jcr.Repository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.commons.cnd.CndImporter; -import org.apache.jackrabbit.core.TransientRepository; +import org.argeo.jcr.AbstractInternalJackrabbitTestCase; import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.ArgeoTypes; -import org.argeo.jcr.unit.AbstractJcrTestCase; import org.argeo.util.tabular.TabularColumn; import org.argeo.util.tabular.TabularRow; import org.argeo.util.tabular.TabularRowIterator; import org.argeo.util.tabular.TabularWriter; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -public class JcrTabularTest extends AbstractJcrTestCase { +public class JcrTabularTest extends AbstractInternalJackrabbitTestCase { private final static Log log = LogFactory.getLog(JcrTabularTest.class); public void testWriteReadCsv() throws Exception { @@ -88,23 +83,4 @@ public class JcrTabularTest extends AbstractJcrTestCase { log.debug("Read tabular content " + rowCount + " rows, " + columnCount + " columns"); } - - protected File getRepositoryFile() throws Exception { - Resource res = new ClassPathResource( - "org/argeo/server/jcr/repository-h2.xml"); - return res.getFile(); - } - - protected Repository createRepository() throws Exception { - // JackrabbitContainer repo = new JackrabbitContainer(); - // repo.setHomeDirectory(getHomeDir()); - // repo.setConfiguration(new FileSystemResource( - // getRepositoryFile())); - // repo.setInMemory(true); - // repo.set - Repository repository = new TransientRepository(getRepositoryFile(), - getHomeDir()); - return repository; - } - } diff --git a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java index 32bfa9fc2..400811341 100644 --- a/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java +++ b/server/runtime/org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java @@ -16,24 +16,20 @@ package org.argeo.server.jcr; -import java.io.File; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.List; -import javax.jcr.Repository; - import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.jackrabbit.core.TransientRepository; +import org.argeo.jcr.AbstractInternalJackrabbitTestCase; import org.argeo.jcr.JcrResourceAdapter; -import org.argeo.jcr.unit.AbstractJcrTestCase; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -public class JcrResourceAdapterTest extends AbstractJcrTestCase { +public class JcrResourceAdapterTest extends AbstractInternalJackrabbitTestCase { private static SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd:hhmmss.SSS"); @@ -103,17 +99,4 @@ public class JcrResourceAdapterTest extends AbstractJcrTestCase { log.debug("TEAR DOWN"); super.tearDown(); } - - protected File getRepositoryFile() throws Exception { - Resource res = new ClassPathResource( - "org/argeo/server/jcr/repository-h2.xml"); - return res.getFile(); - } - - protected Repository createRepository() throws Exception { - Repository repository = new TransientRepository(getRepositoryFile(), - getHomeDir()); - return repository; - } - } diff --git a/server/runtime/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml b/server/runtime/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml new file mode 100644 index 000000000..d1790af60 --- /dev/null +++ b/server/runtime/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.30.2