com.day.cq.security.util.CqActions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
/*
* 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)
*
*
*
* @deprecated Please use Apache Jackrabbit privilege management and JCR access control management API instead.
* @see org.apache.jackrabbit.api.security.authorization.PrivilegeManager#registerPrivilege(String, boolean, String[])
* @see AccessControlManager#getPrivileges(String)
*/
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);
}
}