/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.ValueFormatException; import javax.jcr.security.AccessControlEntry; import javax.jcr.security.AccessControlException; 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.jackrabbit.commons.iterator.AccessControlPolicyIteratorAdapter; import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager; import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry; import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider; import org.apache.jackrabbit.jcr2spi.operation.AddNode; import org.apache.jackrabbit.jcr2spi.operation.Operation; import org.apache.jackrabbit.jcr2spi.operation.Remove; import org.apache.jackrabbit.jcr2spi.operation.SetMixin; import org.apache.jackrabbit.jcr2spi.operation.SetTree; import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider; import org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.AccessControlConstants; import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator; import org.apache.jackrabbit.jcr2spi.state.NodeState; import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QValue; import org.apache.jackrabbit.spi.QValueFactory; import org.apache.jackrabbit.spi.SessionInfo; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.conversion.NameParser; import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Jackrabbit-core specific implementation of the {@code AccessControlManager}. */ class AccessControlManagerImpl implements AccessControlManager, AccessControlConstants { private static final Logger log = LoggerFactory.getLogger(AccessControlManagerImpl.class); private static int REMOVE_POLICY_OPTIONS = ItemStateValidator.CHECK_ACCESS | ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_COLLISION | ItemStateValidator.CHECK_VERSIONING; private final SessionInfo sessionInfo; private final HierarchyManager hierarchyManager; private final NamePathResolver npResolver; private final QValueFactory qvf; private final AccessControlProvider acProvider; private final UpdatableItemStateManager itemStateMgr; private final ItemDefinitionProvider definitionProvider; AccessControlManagerImpl(SessionInfo sessionInfo, UpdatableItemStateManager itemStateMgr, ItemDefinitionProvider definitionProvider, HierarchyManager hierarchyManager, NamePathResolver npResolver, QValueFactory qvf, AccessControlProvider acProvider) { this.sessionInfo = sessionInfo; this.hierarchyManager = hierarchyManager; this.itemStateMgr = itemStateMgr; this.npResolver = npResolver; this.qvf = qvf; this.acProvider = acProvider; this.definitionProvider = definitionProvider; } public Privilege[] getSupportedPrivileges(String absPath) throws PathNotFoundException, RepositoryException { NodeState state = getNodeState(npResolver.getQPath(absPath)); Map<String, Privilege> privileges = acProvider.getSupportedPrivileges(sessionInfo, state.getNodeId(), npResolver); return privileges.values().toArray(new Privilege[privileges.size()]); } public Privilege privilegeFromName(String privilegeName) throws AccessControlException, RepositoryException { return acProvider.privilegeFromName(sessionInfo, npResolver, privilegeName); } public boolean hasPrivileges(String absPath, Privilege[] privileges) throws PathNotFoundException, RepositoryException { Set<Privilege> privs = acProvider.getPrivileges(sessionInfo, getNodeState(npResolver.getQPath(absPath)).getNodeId(), npResolver); List<Privilege> toTest = Arrays.asList(privileges); if (privs.containsAll(toTest)) { return true; } else { Set<Privilege> agg = new HashSet<Privilege>(privs); for (Privilege p : privs) { if (p.isAggregate()) { agg.addAll(Arrays.asList(p.getAggregatePrivileges())); } } return agg.containsAll(toTest); } } public Privilege[] getPrivileges(String absPath) throws PathNotFoundException, RepositoryException { Set<Privilege> privs = acProvider.getPrivileges(sessionInfo, getNodeState(npResolver.getQPath(absPath)).getNodeId(), npResolver); return privs.toArray(new Privilege[privs.size()]); } public AccessControlPolicy[] getEffectivePolicies(String absPath) throws RepositoryException { checkValidNodePath(absPath); checkAccessControlRead(absPath); // TODO : add proper implementation return new AccessControlPolicy[] {new AccessControlPolicy() {}}; } public AccessControlPolicyIterator getApplicablePolicies(String absPath) throws RepositoryException { checkValidNodePath(absPath); AccessControlPolicy[] applicable = getApplicable(absPath); if (applicable != null && applicable.length > 0) { return new AccessControlPolicyIteratorAdapter(Arrays.asList(applicable)); } else { return AccessControlPolicyIteratorAdapter.EMPTY; } } public AccessControlPolicy[] getPolicies(String absPath) throws RepositoryException { checkValidNodePath(absPath); List<AccessControlList> policies = new ArrayList<AccessControlList>(); NodeState aclNode = getAclNode(absPath); AccessControlList acl; if (aclNode != null) { acl = new AccessControlListImpl(aclNode, absPath, npResolver, qvf, this); policies.add(acl); } return policies.toArray(new AccessControlList[policies.size()]); } public void setPolicy(String absPath, AccessControlPolicy policy) throws RepositoryException { checkValidNodePath(absPath); checkValidPolicy(policy); checkAcccessControlItem(absPath); SetTree operation; NodeState aclNode = getAclNode(absPath); if (aclNode == null) { // policy node doesn't exist at absPath -> create one. Name name = (absPath == null) ? N_REPO_POLICY : N_POLICY; NodeState parent = null; Name mixinType = null; if (absPath == null) { parent = getRootNodeState(); mixinType = NT_REP_REPO_ACCESS_CONTROLLABLE; } else { parent = getNodeState(absPath); mixinType = NT_REP_ACCESS_CONTROLLABLE; } setMixin(parent, mixinType); operation = SetTree.create(itemStateMgr, parent, name, NT_REP_ACL, null); aclNode = operation.getTreeState(); } else { Iterator<NodeEntry> it = getNodeEntry(aclNode).getNodeEntries(); while(it.hasNext()) { it.next().transientRemove(); } operation = SetTree.create(aclNode); } // create the entry nodes for (AccessControlEntry entry : ((AccessControlListImpl) policy).getAccessControlEntries()) { createAceNode(operation, aclNode, entry); } itemStateMgr.execute(operation); } public void removePolicy(String absPath, AccessControlPolicy policy) throws RepositoryException { checkValidNodePath(absPath); checkValidPolicy(policy); NodeState aclNode = getAclNode(absPath); if (aclNode != null) { removeNode(aclNode); } else { throw new AccessControlException("No policy exist at "+absPath); } } //--------------------------------------------------< private >--- private AccessControlPolicy[] getApplicable(String absPath) throws RepositoryException { NodeState controlledState; if (absPath == null) { controlledState = getRootNodeState(); } else { controlledState = getNodeState(absPath); } AccessControlPolicy acl = null; NodeState aclNode = getAclNode(controlledState, absPath); if (aclNode == null) { acl = new AccessControlListImpl(absPath, npResolver, qvf); } return (acl == null) ? new AccessControlPolicy[0] : new AccessControlPolicy[] {acl}; } private NodeState getAclNode(String controlledNodePath) throws RepositoryException { NodeState controlledNode; if (controlledNodePath == null) { controlledNode = getRootNodeState(); } else { controlledNode = getNodeState(controlledNodePath); } return getAclNode(controlledNode, controlledNodePath); } private NodeState getAclNode(NodeState aclNode, String controlledNodePath) throws RepositoryException { NodeState acl = null; if (controlledNodePath == null) { if (isRepoAccessControlled(aclNode)) { acl = aclNode.getChildNodeState(N_REPO_POLICY, 1); } } else { if (isAccessControlled(aclNode)) { acl = aclNode.getChildNodeState(N_POLICY, 1); } } return acl; } /** * Test if the given node state is of node type * {@link AccessControlConstants#NT_REP_REPO_ACCESS_CONTROLLABLE} * and if it has a child node named * {@link AccessControlConstants#N_REPO_POLICY}. * * @param nodeState the node state to be tested * @return <code>true</code> if the node is access controlled and has a * rep:policy child; <code>false</code> otherwise. * @throws RepositoryException if an error occurs */ private boolean isRepoAccessControlled(NodeState nodeState) throws RepositoryException { return isNodeType(nodeState, NT_REP_REPO_ACCESS_CONTROLLABLE) && nodeState.hasChildNodeEntry(N_REPO_POLICY, 1); } private boolean isAccessControlled(NodeState nodeState) throws RepositoryException { return isNodeType(nodeState, NT_REP_ACCESS_CONTROLLABLE) && nodeState.hasChildNodeEntry(N_POLICY, 1); } /** * Checks if the given node state has the specified mixin. * NOTE: we take the transiently added mixins * into consideration e.g if added during * a setPolicies call and the changes are yet to be saved. * @param nodeState * @param mixinName */ private boolean isNodeType(NodeState nodeState, Name mixinName) throws RepositoryException { List<Name> lst = Arrays.asList(nodeState.getAllNodeTypeNames()); return (lst == null) ? false : lst.contains(mixinName); } /** * Checks whether if the given nodePath points to an access * control policy or entry node. * @param nodePath * @throws AccessControlException * @throws RepositoryException */ private void checkAcccessControlItem(String nodePath) throws AccessControlException, RepositoryException { NodeState controlledState = getNodeState(nodePath); Name ntName = controlledState.getNodeTypeName(); boolean isAcItem = ntName.equals(NT_REP_ACL) || ntName.equals(NT_REP_GRANT_ACE) || ntName.equals(NT_REP_DENY_ACE); if (isAcItem) { throw new AccessControlException("The path: "+nodePath+" points to an access control content node"); } } private void checkAccessControlRead(String absPath) throws RepositoryException { if (!hasPrivileges(absPath, new Privilege[] {privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL)})) { throw new AccessDeniedException(); } } private void createAceNode(SetTree operation, NodeState parentState, AccessControlEntry entry) throws RepositoryException { AccessControlEntryImpl ace = (AccessControlEntryImpl) entry; String uuid = null; boolean isAllow = ace.isAllow(); Name nodeName = getUniqueNodeName(parentState, (isAllow) ? "allow" : "deny"); Name nodeTypeName = (isAllow) ? NT_REP_GRANT_ACE : NT_REP_DENY_ACE; NodeState aceNode = addNode(operation, parentState, nodeName, uuid, nodeTypeName); // add rep:principalName property String valueStr = ace.getPrincipal().getName(); QValue value = qvf.create(valueStr, PropertyType.STRING); addProperty(operation, aceNode, N_REP_PRINCIPAL_NAME, PropertyType.STRING, new QValue[] {value}); // add rep:privileges MvProperty Privilege[] privs = ace.getPrivileges(); QValue[] vls = new QValue[privs.length]; Name privilegeName = null; try { for (int i = 0; i < privs.length; i++) { privilegeName = npResolver.getQName(privs[i].getName()); vls[i] = qvf.create(privilegeName.toString(), PropertyType.NAME); } } catch (ValueFormatException e) { throw new RepositoryException(e.getMessage()); } addProperty(operation, aceNode, N_REP_PRIVILEGES, PropertyType.NAME, vls); // TODO: add single and mv restrictions } private NodeState getNodeState(String nodePath) throws RepositoryException { return getNodeState(npResolver.getQPath(nodePath)); } private NodeState getRootNodeState() throws RepositoryException { return hierarchyManager.getRootEntry().getNodeState(); } private NodeState getNodeState(Path qPath) throws RepositoryException { return hierarchyManager.getNodeState(qPath); } private NodeEntry getNodeEntry(NodeState nodeState) throws RepositoryException { return hierarchyManager.getNodeEntry(nodeState.getPath()); } private void checkValidNodePath(String absPath) throws PathNotFoundException, RepositoryException { if (absPath != null) { Path qPath = npResolver.getQPath(absPath); if (!qPath.isAbsolute()) { throw new RepositoryException("Absolute path expected. Found: " + absPath); } if (hierarchyManager.getNodeEntry(qPath).getNodeState() == null) { throw new PathNotFoundException(absPath); } } } private void checkValidPolicy(AccessControlPolicy policy) throws AccessControlException { if (policy == null || !(policy instanceof AccessControlListImpl)) { throw new AccessControlException("Policy is not applicable "); } } private NodeState addNode(SetTree treeOperation, NodeState parent, Name nodeName, String uuid, Name nodeTypeName) throws RepositoryException { Operation sp = treeOperation.addChildNode(parent, nodeName, nodeTypeName, uuid); itemStateMgr.execute(sp); return (NodeState) ((AddNode) sp).getAddedStates().get(0); } private void addProperty(SetTree treeOperation, NodeState parent, Name propName, int propType, QValue[] values) throws RepositoryException { QPropertyDefinition definition = definitionProvider.getQPropertyDefinition(parent.getAllNodeTypeNames(), propName, propType); Operation ap = treeOperation.addChildProperty(parent, propName, propType, values, definition); itemStateMgr.execute(ap); } private void removeNode(NodeState aclNode) throws RepositoryException { Operation removePolicy = Remove.create(aclNode, REMOVE_POLICY_OPTIONS); itemStateMgr.execute(removePolicy); } private void setMixin(NodeState parent, Name mixinName) throws RepositoryException { if (!isNodeType(parent, mixinName)){ Operation sm = SetMixin.create(parent, new Name[]{mixinName}); itemStateMgr.execute(sm); } else { log.debug(mixinName.toString()+" is already present on the given node state "+parent.getName().toString()); } } // copied from jackrabbit-core ACLEditor /** * Create a unique valid name for the Permission nodes to be save. * * @param node a name for the child is resolved * @param name if missing the {@link #DEFAULT_ACE_NAME} is taken * @return the name * @throws RepositoryException if an error occurs */ private Name getUniqueNodeName(NodeState node, String name) throws RepositoryException { try { NameParser.checkFormat(name); } catch (NameException e) { log.debug("Invalid path name for Permission: " + name + "."); } int i = 0; String check = name; Name n = npResolver.getQName(check); while (node.hasChildNodeEntry(n, 1)) { check = name + i; n = npResolver.getQName(check); i++; } return n; } }