All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.security.util.CqActions Maven / Gradle / Ivy

There is a newer version: 6.5.21
Show newest version
/*
 * Copyright 1997-2009 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.cq.security.util;

import com.day.cq.replication.Replicator;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Collection;
import java.util.Arrays;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import javax.jcr.security.Privilege;

/**
 * This class defines the main CQ Actions and provides the mapping from and to
 * JCR Privileges.
 *
 * 
 * "read"           Privilege.JCR_READ
 *
 * "modify"         Privilege.JCR_MODIFY_PROPERTIES,
 *                  Privilege.JCR_LOCK_MANAGEMENT,
 *                  Privilege.JCR_VERSION_MANAGEMENT
 *
 * "create"         Privilege.JCR_ADD_CHILD_NODES,
 *                  Privilege.JCR_NODE_TYPE_MANAGEMENT
 *
 * "delete"         Privilege.JCR_REMOVE_CHILD_NODES,
 *                  Privilege.JCR_REMOVE_NODE
 *
 * "acl_read"       Privilege.JCR_READ_ACCESS_CONTROL
 *
 * "acl_write"      Privilege.JCR_MODIFY_ACCESS_CONTROL
 *
 * "replicate"      "crx:replicate" (custom privilege)
 *
 * 
*/ public class CqActions { private static Logger log = LoggerFactory.getLogger(CqActions.class); /** * CQ actions constants */ public static final String[] ACTIONS = new String[] { "read", "modify", "create", "delete", "acl_read", "acl_edit", "replicate" }; private static final String CONTENT_RESTRICTION = "*/jcr:content*"; private final Session session; private final Map> map = new HashMap>(); public CqActions(Session session) throws RepositoryException { this.session = session; AccessControlManager acMgr = session.getAccessControlManager(); map.put("read", getPrivilegeSet(Privilege.JCR_READ, acMgr)); map.put("modify", getPrivilegeSet(new String[] { /* privilege to add/modify/remove properties, lock the target node and execute version operations on it (ci, co, restore...) */ Privilege.JCR_MODIFY_PROPERTIES, Privilege.JCR_LOCK_MANAGEMENT, Privilege.JCR_VERSION_MANAGEMENT}, acMgr)); map.put("create", getPrivilegeSet(new String[] { /* can add any kind of nodes below the target node. nt_mgt is required for calls like Node.addNode(name, ntName) or adding mixins */ Privilege.JCR_ADD_CHILD_NODES, Privilege.JCR_NODE_TYPE_MANAGEMENT}, acMgr)); map.put("delete", getPrivilegeSet(new String[] { /* privileges required to remove any kind of nodes below the target node. NOTE: this does NOT include the permission to remove the target node itself as JCR_REMOVE_CHILD_NODES would be required on the parent node. This is - AFAIK - different compared to the behaviour of cq 5.2 After discussion with David, we decided that removing nodes below target is sufficient and most probably what is the desired effect. Otherwise granting delete would require privilege modifications on 2 different nodes that may also have different edit_acl permissions... :( */ Privilege.JCR_REMOVE_CHILD_NODES, Privilege.JCR_REMOVE_NODE}, acMgr)); map.put("acl_read", getPrivilegeSet(Privilege.JCR_READ_ACCESS_CONTROL, acMgr)); map.put("acl_edit", getPrivilegeSet(Privilege.JCR_MODIFY_ACCESS_CONTROL, acMgr)); try { map.put("replicate", getPrivilegeSet(Replicator.REPLICATE_PRIVILEGE, acMgr)); } catch (AccessControlException e) { log.warn("Replicate privilege not registered"); } // NOTE: JCR_LIFECYCLE_MANAGEMENT and JCR_RETENTION_MANAGEMENT have been // intentionally omitted. the first isn't used within the scope of // CQ and the latter is expected to be present with defined // retention management tools only. } /** * Returns the privileges that correspond to the given action string. * * @param action The action to be mapped. * @return A set of privileges. * @deprecated As of CQ 5.4 the mapping of CQ action to resulting ACEs/privileges * depends on the nature of the target node. */ public Set getPrivileges(String action) { return map.get(action); } /** * Tests if the given action is granted by the given set of * privileges. * * @param action the action to tested * @param privs the privileges (on the node) * @return true if the action is allowed. * @deprecated As of CQ 5.4 the mapping of CQ action to privileges depends on * the nature of the target node. */ public boolean isGranted(Set privs, String action) { Set mappedPrivs = getPrivileges(action); return privs.containsAll(mappedPrivs); } /** * Returns the names of the actions that are granted on the given path. * * @param path the path to check * @param session the session * @return the set of actions that are granted. * @throws RepositoryException if an error occurs * @deprecated Since 5.4 */ public Collection getActions(Session session, String path) throws RepositoryException { // first get all Set privileges = new HashSet(); for (Privilege priv : session.getAccessControlManager().getPrivileges(path)) { if (priv.isAggregate()) { privileges.addAll(Arrays.asList(priv.getAggregatePrivileges())); } else { privileges.add(priv); } } Collection granted = new HashSet(); for (Map.Entry> e : map.entrySet()) { if (privileges.containsAll(e.getValue())) { granted.add(e.getKey()); } } return granted; } /** * * @param nodePath * @param principals The set of principals for which the action set needs * to be evaluated. In case of a null value, this method will use the sessions's principals for evaluation * @return the set of actions granted for the specified principals at the * specified path. * @throws RepositoryException If an error occurs. * @since 5.4 */ public Collection getAllowedActions(String nodePath, Set principals) throws RepositoryException { AccessControlManager acMgr = session.getAccessControlManager(); Collection granted = new HashSet(); Set privileges = getPrivileges(nodePath, principals, acMgr); // evaluate the default mapping for (Map.Entry> e : map.entrySet()) { if (privileges.containsAll(e.getValue())) { granted.add(e.getKey()); } } // adjust the default mapping for nodes being defined to have a jcr:content // child node that implies additional logic for the final permissions. if (definesContent(session.getNode(nodePath))) { String contentPath = nodePath + "/" + JcrConstants.JCR_CONTENT; // additional requirements must be met for modify action if (granted.contains("modify")) { // jcr:nodeTypeManagement on target node AND complete write // permission on the jcr:content node is required as well. if (!session.nodeExists(contentPath) || !getPrivileges(contentPath, principals, acMgr).containsAll(getPrivilegeSet("rep:write", acMgr))) { granted.remove("modify"); } } // no extra check required for 'create' and 'delete' as this is covered // by the defaultMapping... while upon applying create/delete it revokes // the corresponding write permissions for the jcr:content node, there // is no need to check it here. } return granted; } /** * Installs the specified actions for the given principal at the specified * targetNode by converting it the corresponding JCR access control content. * * @param nodePath * @param actionMap A map of CQ Action name to a Boolean indicating whether * the action should be granted or denied. * @param inheritedAllows * @throws RepositoryException If an error occurs. */ public void installActions(String nodePath, Principal principal, Map actionMap, Collection inheritedAllows) throws RepositoryException { if (actionMap.isEmpty()) { // nothing to do return; } AccessControlManager acMgr = session.getAccessControlManager(); JackrabbitAccessControlList acl = getModifiableAcl(acMgr, nodePath); for (String action : actionMap.keySet()) { boolean isAllow = actionMap.get(action); // add the default ACE(s) common for all type of nodes Set privileges = map.get(action); if (privileges != null) { acl.addEntry(principal, privileges.toArray(new Privilege[privileges.size()]), isAllow); } // else: unsupported privilege name. ignore. } if (definesContent(session.getNode(nodePath))) { // add special ACE including restriction for any nodes having // a jcr:content child node. Map restrictions = null; for (String rName : acl.getRestrictionNames()) { if ("rep:glob".equals(rName)) { Value v = session.getValueFactory().createValue(CONTENT_RESTRICTION, acl.getRestrictionType(rName)); restrictions = Collections.singletonMap(rName, v); break; } } if (restrictions == null) { log.warn("Cannot install special permissions node with jcr:content primary item. rep:glob restriction not supported by AC model."); } else { Set allowPrivs = new HashSet(); Set denyPrivs = new HashSet(); boolean modify; if (actionMap.containsKey("modify")) { // additional allowed/denied write permissions for jcr:content nodes. Collection contentModify = Arrays.asList( acMgr.privilegeFromName(Privilege.JCR_NODE_TYPE_MANAGEMENT), acMgr.privilegeFromName(Privilege.JCR_ADD_CHILD_NODES), acMgr.privilegeFromName(Privilege.JCR_REMOVE_CHILD_NODES), acMgr.privilegeFromName(Privilege.JCR_REMOVE_NODE)); if (actionMap.get("modify")) { allowPrivs.addAll(contentModify); } else { denyPrivs.addAll(contentModify); } modify = actionMap.get("modify"); } else { modify = inheritedAllows.contains("modify"); } if (!modify) { /* if MODIFY is not allowed at the given path, granting CREATE and/or DELETE need to be installed with an additional ACE restricting the permissions at jcr:content */ if (actionMap.containsKey("create") && actionMap.get("create")) { denyPrivs.add(acMgr.privilegeFromName(Privilege.JCR_ADD_CHILD_NODES)); denyPrivs.add(acMgr.privilegeFromName(Privilege.JCR_NODE_TYPE_MANAGEMENT)); } if (actionMap.containsKey("delete") && actionMap.get("delete")) { denyPrivs.add(acMgr.privilegeFromName(Privilege.JCR_REMOVE_CHILD_NODES)); denyPrivs.add(acMgr.privilegeFromName(Privilege.JCR_REMOVE_NODE)); } } else { /* MODIFY is allow -> test if create/delete is explicitly denied in which case the additional ACE restriction at jcr:content need to be preserved. */ if (actionMap.containsKey("create") && !actionMap.get("create")) { allowPrivs.add(acMgr.privilegeFromName(Privilege.JCR_ADD_CHILD_NODES)); allowPrivs.add(acMgr.privilegeFromName(Privilege.JCR_NODE_TYPE_MANAGEMENT)); } if (actionMap.containsKey("delete") && !actionMap.get("delete")) { allowPrivs.add(acMgr.privilegeFromName(Privilege.JCR_REMOVE_CHILD_NODES)); allowPrivs.add(acMgr.privilegeFromName(Privilege.JCR_REMOVE_NODE)); } } if (!allowPrivs.isEmpty()) { acl.addEntry(principal, allowPrivs.toArray(new Privilege[allowPrivs.size()]), true, restrictions); } if (!denyPrivs.isEmpty()) { acl.addEntry(principal, denyPrivs.toArray(new Privilege[denyPrivs.size()]), false, restrictions); } } } acMgr.setPolicy(nodePath, acl); } /** * Returns true if the node is defined to have a jcr:content * child node (that may or may not be present yet). Note that the test * by intention does not rely on the existence of a jcr:content node * that may as well be present with an unstructured or folder node. * * @param node * @return true if the specified node is defined to possibly have a * jcr:content child (such as e.g. nt:file, cq:Page and similar). * @throws RepositoryException */ public static boolean definesContent(Node node) throws RepositoryException { NodeType nt = node.getPrimaryNodeType(); for (NodeDefinition cnd : nt.getChildNodeDefinitions()) { if (JcrConstants.JCR_CONTENT.equals(cnd.getName())) { return true; } } return false; } /** * * @param ace * @return * @throws RepositoryException */ public static boolean hasContentRestriction(AccessControlEntry ace) throws RepositoryException { if (ace instanceof JackrabbitAccessControlEntry) { JackrabbitAccessControlEntry jace = (JackrabbitAccessControlEntry) ace; for (String rName : jace.getRestrictionNames()) { if ("rep:glob".equals(rName) && CONTENT_RESTRICTION.equals(jace.getRestriction(rName).getString())) { return true; } } } return false; } //------------------------------------------------------------< private >--- private static Set getPrivileges(String path, Set principals, AccessControlManager acMgr) throws RepositoryException, AccessDeniedException { Set privileges = new HashSet(); Privilege[] privs; if (principals == null) { // the privileges of the editing session privs = acMgr.getPrivileges(path); } else { // the privileges of another set of principals privs = ((JackrabbitAccessControlManager) acMgr).getPrivileges(path, principals); } // for simplicity extract aggregate privileges for (Privilege priv : privs) { if (priv.isAggregate()) { privileges.addAll(Arrays.asList(priv.getAggregatePrivileges())); } else { privileges.add(priv); } } return privileges; } private static Set getPrivilegeSet(String privName, AccessControlManager acMgr) throws RepositoryException { Set privileges; Privilege p = acMgr.privilegeFromName(privName); if (p.isAggregate()) { privileges = new HashSet(Arrays.asList(p.getAggregatePrivileges())); } else { privileges = Collections.singleton(p); } return privileges; } private static Set getPrivilegeSet(String[] privNames, AccessControlManager acMgr) throws RepositoryException { Set privileges = new HashSet(privNames.length); for (String name : privNames) { Privilege p = acMgr.privilegeFromName(name); if (p.isAggregate()) { privileges.addAll(Arrays.asList(p.getAggregatePrivileges())); } else { privileges.add(p); } } return privileges; } private static JackrabbitAccessControlList getModifiableAcl(AccessControlManager acMgr, String path) throws RepositoryException, AccessDeniedException { // try to find an existing acl first AccessControlPolicy[] existing = acMgr.getPolicies(path); for (AccessControlPolicy p : existing) { if (p instanceof JackrabbitAccessControlList) { return (JackrabbitAccessControlList) p; } } // no yet set -> try to find an applicable, non-existing acl AccessControlPolicyIterator it = acMgr.getApplicablePolicies(path); while (it.hasNext()) { AccessControlPolicy p = it.nextAccessControlPolicy(); if (p instanceof JackrabbitAccessControlList) { return (JackrabbitAccessControlList) p; } } throw new AccessControlException("No modifiable ACL at " + path); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy