X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jcr%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=e2f5b54279ad424ce3aec09248f695d2f1c7ab1c;hb=1d5afdce3e91054f07ddd3c98309c363b4cf1d46;hp=facd475cafd1e789c1689e7f88e317ff1ec1d749;hpb=8b8ee149b20e2578a55e17413fa5f7399ff7ba14;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index facd475ca..e2f5b5427 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Mathieu Baudier + * Copyright (C) 2007-2012 Mathieu Baudier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.argeo.jcr; import java.io.ByteArrayInputStream; @@ -21,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.Principal; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; @@ -51,19 +51,23 @@ import javax.jcr.nodetype.NodeType; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; -import javax.jcr.query.qom.Constraint; -import javax.jcr.query.qom.DynamicOperand; -import javax.jcr.query.qom.QueryObjectModelFactory; -import javax.jcr.query.qom.Selector; -import javax.jcr.query.qom.StaticOperand; +import javax.jcr.security.AccessControlEntry; +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 javax.jcr.version.VersionManager; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; +import org.argeo.util.security.SimplePrincipal; /** Utility methods to simplify common JCR operations. */ public class JcrUtils implements ArgeoJcrConstants { + private final static Log log = LogFactory.getLog(JcrUtils.class); /** @@ -346,8 +350,7 @@ public class JcrUtils implements ArgeoJcrConstants { if (session.itemExists(path)) { Node node = session.getNode(path); // check type - if (type != null - && !type.equals(node.getPrimaryNodeType().getName())) + if (type != null && !node.isNodeType(type)) throw new ArgeoException("Node " + node + " exists but is of type " + node.getPrimaryNodeType().getName() @@ -508,6 +511,55 @@ public class JcrUtils implements ArgeoJcrConstants { } + /** Logs the effective access control policies */ + public static void logEffectiveAccessPolicies(Node node) { + try { + logEffectiveAccessPolicies(node.getSession(), node.getPath()); + } catch (RepositoryException e) { + log.error("Cannot log effective access policies of " + node, e); + } + } + + /** Logs the effective access control policies */ + public static void logEffectiveAccessPolicies(Session session, String path) { + if (!log.isDebugEnabled()) + return; + + try { + AccessControlPolicy[] effectivePolicies = session + .getAccessControlManager().getEffectivePolicies(path); + if (effectivePolicies.length > 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 @@ -925,34 +977,47 @@ public class JcrUtils implements ArgeoJcrConstants { } } - /** Returns the home node of the session user or null if none was found. */ - public static Node getUserHome(Session session) { - String userID = session.getUserID(); - return getUserHome(session, userID); - } - /** - * Returns user home has path, embedding exceptions. Contrary to - * {@link #getUserHome(Session)}, it never returns null but throws and - * exception if not found. + * Convenient method to add a listener. uuids passed as null, deep=true, + * local=true, only one node type */ - public static String getUserHomePath(Session session) { - String userID = session.getUserID(); + public static void addListener(Session session, EventListener listener, + int eventTypes, String basePath, String nodeType) { try { - Node userHome = getUserHome(session, userID); - if (userHome != null) - return userHome.getPath(); - else - throw new ArgeoException("No home registered for " + userID); + session.getWorkspace() + .getObservationManager() + .addEventListener(listener, eventTypes, basePath, true, + null, new String[] { nodeType }, true); } catch (RepositoryException e) { - throw new ArgeoException("Cannot find user home path", e); + throw new ArgeoException("Cannot add JCR listener " + listener + + " to session " + session, e); } } - /** Get the profile of the user attached to this session. */ - public static Node getUserProfile(Session session) { + /** Removes a listener without throwing exception */ + public static void removeListenerQuietly(Session session, + EventListener listener) { + if (session == null || !session.isLive()) + return; + try { + session.getWorkspace().getObservationManager() + .removeEventListener(listener); + } catch (RepositoryException e) { + // silent + } + } + + /** Returns the home node of the session user or null if none was found. */ + public static Node getUserHome(Session session) { String userID = session.getUserID(); - return getUserProfile(session, userID); + return getUserHome(session, userID); + } + + /** User home path is NOT configurable */ + public static String getUserHomePath(String username) { + String homeBasePath = DEFAULT_HOME_BASE_PATH; + return homeBasePath + '/' + firstCharsToPath(username, 2) + '/' + + username; } /** @@ -967,102 +1032,210 @@ public class JcrUtils implements ArgeoJcrConstants { */ public static Node getUserHome(Session session, String username) { try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - - // query the user home for this user id - Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, - "userHome"); - DynamicOperand userIdDop = qomf.propertyValue("userHome", - ArgeoNames.ARGEO_USER_ID); - StaticOperand userIdSop = qomf.literal(session.getValueFactory() - .createValue(username)); - Constraint constraint = qomf.comparison(userIdDop, - QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - Query query = qomf.createQuery(userHomeSel, constraint, null, null); - Node userHome = JcrUtils.querySingleNode(query); - return userHome; + String homePath = getUserHomePath(username); + return session.itemExists(homePath) ? session.getNode(homePath) + : null; + // kept for example of QOM queries + // QueryObjectModelFactory qomf = session.getWorkspace() + // .getQueryManager().getQOMFactory(); + // Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, + // "userHome"); + // DynamicOperand userIdDop = qomf.propertyValue("userHome", + // ArgeoNames.ARGEO_USER_ID); + // StaticOperand userIdSop = qomf.literal(session.getValueFactory() + // .createValue(username)); + // Constraint constraint = qomf.comparison(userIdDop, + // QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); + // Query query = qomf.createQuery(userHomeSel, constraint, null, + // null); + // Node userHome = JcrUtils.querySingleNode(query); } catch (RepositoryException e) { throw new ArgeoException("Cannot find home for user " + username, e); } } - public static Node getUserProfile(Session session, String username) { + /** + * Creates an Argeo user home, does nothing if it already exists. Session is + * NOT saved. + */ + public static Node createUserHomeIfNeeded(Session session, String username) { try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - Selector sel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE, - "userProfile"); - DynamicOperand userIdDop = qomf.propertyValue("userProfile", - ArgeoNames.ARGEO_USER_ID); - StaticOperand userIdSop = qomf.literal(session.getValueFactory() - .createValue(username)); - Constraint constraint = qomf.comparison(userIdDop, - QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - Query query = qomf.createQuery(sel, constraint, null, null); - Node userProfile = JcrUtils.querySingleNode(query); + String homePath = getUserHomePath(username); + if (session.itemExists(homePath)) + return session.getNode(homePath); + else { + Node userHome = JcrUtils.mkdirs(session, homePath); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + return userHome; + } + } catch (RepositoryException e) { + discardQuietly(session); + throw new ArgeoException("Cannot create home for " + username + + " in workspace " + session.getWorkspace().getName(), e); + } + } + + /** + * Creates a user profile in the home of this user. Creates the home if + * needed, but throw an exception if a profile already exists. The session + * is not saved and the node is in a checkedOut state (that is, it requires + * a subsequent checkin after saving the session). + */ + public static Node createUserProfile(Session session, String username) { + try { + Node userHome = createUserHomeIfNeeded(session, username); + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + throw new ArgeoException( + "There is already a user profile under " + userHome); + Node userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); + userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true); + userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, + true); return userProfile; } catch (RepositoryException e) { - throw new ArgeoException( - "Cannot find profile for user " + username, e); + discardQuietly(session); + throw new ArgeoException("Cannot create user profile for " + + username + " in workspace " + + session.getWorkspace().getName(), e); } } - /** Creates an Argeo user home. */ - public static Node createUserHome(Session session, String homeBasePath, + /** + * Create user profile if needed, the session IS saved. + * + * @return the user profile + */ + public static Node createUserProfileIfNeeded(Session securitySession, String username) { try { - if (session == null) - throw new ArgeoException("Session is null"); - if (session.hasPendingChanges()) - throw new ArgeoException( - "Session has pending changes, save them first"); - - String homePath = homeBasePath + '/' - + firstCharsToPath(username, 2) + '/' + username; - - if (session.itemExists(homePath)) { - try { - throw new ArgeoException( - "Trying to create a user home that already exists"); - } catch (Exception e) { - // we use this workaround to be sure to get the stack trace - // to identify the sink of the bug. - log.warn("trying to create an already existing userHome at path:" - + homePath + ". Stack trace : "); - e.printStackTrace(); - } - } + Node userHome = JcrUtils.createUserHomeIfNeeded(securitySession, + username); + Node userProfile = userHome.hasNode(ArgeoNames.ARGEO_PROFILE) ? userHome + .getNode(ArgeoNames.ARGEO_PROFILE) : JcrUtils + .createUserProfile(securitySession, username); + if (securitySession.hasPendingChanges()) + securitySession.save(); + VersionManager versionManager = securitySession.getWorkspace() + .getVersionManager(); + if (versionManager.isCheckedOut(userProfile.getPath())) + versionManager.checkin(userProfile.getPath()); + return userProfile; + } catch (RepositoryException e) { + discardQuietly(securitySession); + throw new ArgeoException("Cannot create user profile for " + + username + " in workspace " + + securitySession.getWorkspace().getName(), e); + } + } - Node userHome = JcrUtils.mkdirs(session, homePath); - Node userProfile; - if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { - log.warn("userProfile node already exists for userHome path: " - + homePath + ". We do not add a new one"); - } else { - userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); - userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - // session.getWorkspace().getVersionManager() - // .checkout(userProfile.getPath()); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - session.getWorkspace().getVersionManager() - .checkin(userProfile.getPath()); - // we need to save the profile before adding the user home type - } - userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); - // see - // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html - userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - return userHome; + /** Creates an Argeo user home. */ + // public static Node createUserHome(Session session, String homeBasePath, + // String username) { + // try { + // if (session == null) + // throw new ArgeoException("Session is null"); + // if (session.hasPendingChanges()) + // throw new ArgeoException( + // "Session has pending changes, save them first"); + // + // String homePath = getUserHomePath(username); + // + // if (session.itemExists(homePath)) { + // try { + // throw new ArgeoException( + // "Trying to create a user home that already exists"); + // } catch (Exception e) { + // // we use this workaround to be sure to get the stack trace + // // to identify the sink of the bug. + // log.warn("trying to create an already existing userHome at path:" + // + homePath + ". Stack trace : "); + // e.printStackTrace(); + // } + // } + // + // Node userHome = JcrUtils.mkdirs(session, homePath); + // Node userProfile; + // if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { + // log.warn("userProfile node already exists for userHome path: " + // + homePath + ". We do not add a new one"); + // } else { + // userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); + // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + // // session.getWorkspace().getVersionManager() + // // .checkout(userProfile.getPath()); + // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + // session.save(); + // session.getWorkspace().getVersionManager() + // .checkin(userProfile.getPath()); + // // we need to save the profile before adding the user home type + // } + // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + // // see + // // + // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html + // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + // session.save(); + // return userHome; + // } catch (RepositoryException e) { + // discardQuietly(session); + // throw new ArgeoException("Cannot create home node for user " + // + username, e); + // } + // } + + /** + * Returns user home has path, embedding exceptions. Contrary to + * {@link #getUserHome(Session)}, it never returns null but throws and + * exception if not found. + * + * @deprecated use getUserHome() instead, throwing an exception if it + * returns null + */ + @Deprecated + public static String getUserHomePath(Session session) { + String userID = session.getUserID(); + try { + String homePath = getUserHomePath(userID); + if (session.itemExists(homePath)) + return homePath; + else + throw new ArgeoException("No home registered for " + userID); } catch (RepositoryException e) { - discardQuietly(session); - throw new ArgeoException("Cannot create home node for user " - + username, e); + throw new ArgeoException("Cannot find user home path", e); + } + } + + /** + * @return null if not found * + */ + public static Node getUserProfile(Session session, String username) { + try { + Node userHome = getUserHome(session, username); + if (userHome == null) + return null; + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + return userHome.getNode(ArgeoNames.ARGEO_PROFILE); + else + return null; + } catch (RepositoryException e) { + throw new ArgeoException( + "Cannot find profile for user " + username, e); } } + /** + * Get the profile of the user attached to this session. + */ + public static Node getUserProfile(Session session) { + String userID = session.getUserID(); + return getUserProfile(session, userID); + } + /** * Quietly unregisters an {@link EventListener} from the udnerlying * workspace of this node. @@ -1190,4 +1363,61 @@ public class JcrUtils implements ArgeoJcrConstants { re); } } + + /* + * SECURITY + */ + + /** + * Convenience method for adding a single privilege to a principal (user or + * role), typically jcr:all + */ + public static void addPrivilege(Session session, String path, + String principal, String privilege) throws RepositoryException { + List privileges = new ArrayList(); + privileges.add(session.getAccessControlManager().privilegeFromName( + privilege)); + addPrivileges(session, path, new SimplePrincipal(principal), privileges); + } + + /** + * 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); + } + } + }