/*
* 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.core.security.authorization.acl;
import org.apache.jackrabbit.api.JackrabbitWorkspace;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.ProtectedItemModifier;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.apache.jackrabbit.spi.commons.conversion.NameParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.NodeIterator;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.Privilege;
import java.security.Principal;
import java.util.Set;
/**
* <code>ACLEditor</code>...
*/
public class ACLEditor extends ProtectedItemModifier implements AccessControlEditor, AccessControlConstants {
/**
* the default logger
*/
private static final Logger log = LoggerFactory.getLogger(ACLEditor.class);
/**
* Default name for ace nodes
*/
private static final String DEFAULT_ACE_NAME = "ace";
/**
* the editing session
*/
private final SessionImpl session;
private final AccessControlUtils utils;
private final boolean allowUnknownPrincipals;
ACLEditor(Session editingSession, AccessControlUtils utils, boolean allowUnknownPrincipals) {
super(Permission.MODIFY_AC);
if (editingSession instanceof SessionImpl) {
session = ((SessionImpl) editingSession);
} else {
throw new IllegalArgumentException("org.apache.jackrabbit.core.SessionImpl expected. Found " + editingSession.getClass());
}
this.utils = utils;
this.allowUnknownPrincipals = allowUnknownPrincipals;
}
/**
*
* @param aclNode the node
* @param path
* @return the control list
* @throws RepositoryException if an error occurs
*/
ACLTemplate getACL(NodeImpl aclNode, String path) throws RepositoryException {
return new ACLTemplate(aclNode, path, allowUnknownPrincipals);
}
//------------------------------------------------< AccessControlEditor >---
/**
* @see AccessControlEditor#getPolicies(String)
*/
public AccessControlPolicy[] getPolicies(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
checkProtectsNode(nodePath);
NodeImpl aclNode = getAclNode(nodePath);
if (aclNode == null) {
return new AccessControlPolicy[0];
} else {
return new AccessControlPolicy[] {getACL(aclNode, nodePath)};
}
}
/**
* Always returns an empty array as no applicable policies are exposed.
*
* @see AccessControlEditor#getPolicies(Principal)
*/
public JackrabbitAccessControlPolicy[] getPolicies(Principal principal) throws AccessControlException, RepositoryException {
if (!session.getPrincipalManager().hasPrincipal(principal.getName())) {
throw new AccessControlException("Unknown principal.");
}
// TODO: impl. missing
return new JackrabbitAccessControlPolicy[0];
}
/**
* @see AccessControlEditor#editAccessControlPolicies(String)
*/
public AccessControlPolicy[] editAccessControlPolicies(String nodePath) throws AccessControlException, PathNotFoundException, RepositoryException {
checkProtectsNode(nodePath);
String mixin;
Name aclName;
NodeImpl controlledNode;
if (nodePath == null) {
controlledNode = (NodeImpl) session.getRootNode();
mixin = session.getJCRName(NT_REP_REPO_ACCESS_CONTROLLABLE);
aclName = N_REPO_POLICY;
} else {
controlledNode = getNode(nodePath);
mixin = session.getJCRName(NT_REP_ACCESS_CONTROLLABLE);
aclName = N_POLICY;
}
AccessControlPolicy acl = null;
NodeImpl aclNode = getAclNode(controlledNode, nodePath);
if (aclNode == null) {
// create an empty acl unless the node is protected or cannot have
// mixin set (e.g. due to a lock) or
// has colliding rep:policy or rep:repoPolicy child node set.
if (controlledNode.hasNode(aclName)) {
// policy child node without node being access controlled
log.warn("Colliding policy child without node being access controllable ({}).", nodePath);
} else {
PrivilegeManager privMgr = ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
if (controlledNode.isNodeType(mixin) || controlledNode.canAddMixin(mixin)) {
acl = new ACLTemplate(nodePath, session.getPrincipalManager(),
privMgr, session.getValueFactory(), session, allowUnknownPrincipals);
} else {
log.warn("Node {} cannot be made access controllable.", nodePath);
}
}
} // else: acl already present -> getPolicies must be used.
return (acl != null) ? new AccessControlPolicy[] {acl} : new AccessControlPolicy[0];
}
/**
* @see AccessControlEditor#editAccessControlPolicies(Principal)
*/
public JackrabbitAccessControlPolicy[] editAccessControlPolicies(Principal principal) throws AccessDeniedException, AccessControlException, RepositoryException {
if (!session.getPrincipalManager().hasPrincipal(principal.getName())) {
throw new AccessControlException("Unknown principal.");
}
// TODO: impl. missing
return new JackrabbitAccessControlPolicy[0];
}
/**
* @see AccessControlEditor#setPolicy(String,AccessControlPolicy)
*/
public void setPolicy(String nodePath, AccessControlPolicy policy) throws RepositoryException {
checkProtectsNode(nodePath);
checkValidPolicy(nodePath, policy);
NodeImpl aclNode = getAclNode(nodePath);
if (aclNode != null) {
// remove all existing aces
for (NodeIterator aceNodes = aclNode.getNodes(); aceNodes.hasNext();) {
NodeImpl aceNode = (NodeImpl) aceNodes.nextNode();
removeItem(aceNode);
}
} else {
// create the acl node
aclNode = (nodePath == null) ? createRepoAclNode() : createAclNode(nodePath);
}
AccessControlEntry[] entries = ((ACLTemplate) policy).getAccessControlEntries();
for (AccessControlEntry entry : entries) {
AccessControlEntryImpl ace = (AccessControlEntryImpl) entry;
Name nodeName = getUniqueNodeName(aclNode, ace.isAllow() ? "allow" : "deny");
Name ntName = (ace.isAllow()) ? NT_REP_GRANT_ACE : NT_REP_DENY_ACE;
ValueFactory vf = session.getValueFactory();
// create the ACE node
NodeImpl aceNode = addNode(aclNode, nodeName, ntName);
// write the rep:principalName property
String principalName = ace.getPrincipal().getName();
setProperty(aceNode, P_PRINCIPAL_NAME, vf.createValue(principalName));
// ... and the rep:privileges property
Privilege[] pvlgs = ace.getPrivileges();
Value[] names = getPrivilegeNames(pvlgs, vf);
setProperty(aceNode, P_PRIVILEGES, names);
// store the restrictions:
Set<Name> restrNames = ace.getRestrictions().keySet();
for (Name restrName : restrNames) {
Value value = ace.getRestriction(restrName);
setProperty(aceNode, restrName, value);
}
}
// mark the parent modified.
markModified(((NodeImpl)aclNode.getParent()));
}
/**
* @see AccessControlEditor#removePolicy(String,AccessControlPolicy)
*/
public synchronized void removePolicy(String nodePath, AccessControlPolicy policy) throws AccessControlException, RepositoryException {
checkProtectsNode(nodePath);
checkValidPolicy(nodePath, policy);
NodeImpl aclNode = getAclNode(nodePath);
if (aclNode != null) {
removeItem(aclNode);
} else {
throw new AccessControlException("No policy to remove at " + nodePath);
}
}
//--------------------------------------------------------------------------
/**
* Check if the Node identified by <code>nodePath</code> is itself part of ACL
* defining content. It this case setting or modifying an AC-policy is
* obviously not possible.
*
* @param nodePath the node path
* @throws AccessControlException If the given nodePath identifies a Node that
* represents a ACL or ACE item.
* @throws RepositoryException
*/
private void checkProtectsNode(String nodePath) throws RepositoryException {
if (nodePath != null) {
NodeImpl node = getNode(nodePath);
if (utils.isAcItem(node)) {
throw new AccessControlException("Node " + nodePath + " defines ACL or ACE itself.");
}
}
}
/**
* Check if the specified policy can be set/removed from this editor.
*
* @param nodePath the node path
* @param policy the policy
* @throws AccessControlException if not allowed
*/
private static void checkValidPolicy(String nodePath, AccessControlPolicy policy) throws AccessControlException {
if (policy == null || !(policy instanceof ACLTemplate)) {
throw new AccessControlException("Attempt to set/remove invalid policy " + policy);
}
ACLTemplate acl = (ACLTemplate) policy;
boolean matchingPath = (nodePath == null) ? acl.getPath() == null : nodePath.equals(acl.getPath());
if (!matchingPath) {
throw new AccessControlException("Policy " + policy + " cannot be applied/removed from the node at " + nodePath);
}
}
/**
*
* @param path the path
* @return the node
* @throws PathNotFoundException if not found
* @throws RepositoryException if an error occurs
*/
private NodeImpl getNode(String path) throws PathNotFoundException, RepositoryException {
return (NodeImpl) session.getNode(path);
}
/**
* Returns the rep:Policy node below the Node identified at the given
* path or <code>null</code> if the node is not mix:AccessControllable
* or if no policy node exists.
*
* @param nodePath the node path
* @return node or <code>null</code>
* @throws PathNotFoundException if not found
* @throws RepositoryException if an error occurs
*/
private NodeImpl getAclNode(String nodePath) throws PathNotFoundException, RepositoryException {
NodeImpl controlledNode;
if (nodePath == null) {
controlledNode = (NodeImpl) session.getRootNode();
} else {
controlledNode = getNode(nodePath);
}
return getAclNode(controlledNode, nodePath);
}
/**
* Returns the rep:Policy node below the given Node or <code>null</code>
* if the node is not mix:AccessControllable or if no policy node exists.
*
* @param controlledNode the controlled node
* @param nodePath
* @return node or <code>null</code>
* @throws RepositoryException if an error occurs
*/
private NodeImpl getAclNode(NodeImpl controlledNode, String nodePath) throws RepositoryException {
NodeImpl aclNode = null;
if (nodePath == null) {
if (ACLProvider.isRepoAccessControlled(controlledNode)) {
aclNode = controlledNode.getNode(N_REPO_POLICY);
}
} else {
if (ACLProvider.isAccessControlled(controlledNode)) {
aclNode = controlledNode.getNode(N_POLICY);
}
}
return aclNode;
}
/**
*
* @param nodePath the node path
* @return the new node
* @throws RepositoryException if an error occurs
*/
private NodeImpl createAclNode(String nodePath) throws RepositoryException {
NodeImpl protectedNode = getNode(nodePath);
if (!protectedNode.isNodeType(NT_REP_ACCESS_CONTROLLABLE)) {
protectedNode.addMixin(NT_REP_ACCESS_CONTROLLABLE);
}
return addNode(protectedNode, N_POLICY, NT_REP_ACL);
}
/**
*
* @return the new acl node used to store repository level privileges.
* @throws RepositoryException if an error occurs
*/
private NodeImpl createRepoAclNode() throws RepositoryException {
NodeImpl root = (NodeImpl) session.getRootNode();
if (!root.isNodeType(NT_REP_REPO_ACCESS_CONTROLLABLE)) {
root.addMixin(NT_REP_REPO_ACCESS_CONTROLLABLE);
}
return addNode(root, N_REPO_POLICY, NT_REP_ACL);
}
/**
* 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
*/
protected static Name getUniqueNodeName(Node node, String name) throws RepositoryException {
if (name == null) {
name = DEFAULT_ACE_NAME;
} else {
try {
NameParser.checkFormat(name);
} catch (NameException e) {
name = DEFAULT_ACE_NAME;
log.debug("Invalid path name for Permission: " + name + ".");
}
}
int i = 0;
String check = name;
while (node.hasNode(check)) {
check = name + i;
i++;
}
return ((SessionImpl) node.getSession()).getQName(check);
}
/**
* Build an array of Value from the specified <code>privileges</code> using
* the given <code>valueFactory</code>.
*
* @param privileges the privileges
* @param valueFactory the value factory
* @return an array of Value.
* @throws ValueFormatException if an error occurs
*/
private static Value[] getPrivilegeNames(Privilege[] privileges, ValueFactory valueFactory)
throws ValueFormatException {
Value[] names = new Value[privileges.length];
for (int i = 0; i < privileges.length; i++) {
names[i] = valueFactory.createValue(privileges[i].getName(), PropertyType.NAME);
}
return names;
}
}