/*
* 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.principalbased;
import org.apache.jackrabbit.api.JackrabbitWorkspace;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
import org.apache.jackrabbit.core.security.authorization.AbstractACLTemplate;
import org.apache.jackrabbit.core.security.authorization.GlobPattern;
import org.apache.jackrabbit.core.security.authorization.PrivilegeBits;
import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Item;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.Privilege;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Implementation of the {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlList} interface that
* is detached from the effective access control content. Consequently, any
* modifications applied to this ACL only take effect, if the policy gets
* {@link javax.jcr.security.AccessControlManager#setPolicy(String, javax.jcr.security.AccessControlPolicy) reapplied}
* to the <code>AccessControlManager</code> and the changes are saved.
*/
class ACLTemplate extends AbstractACLTemplate {
private static Logger log = LoggerFactory.getLogger(ACLTemplate.class);
/**
* rep:nodePath property name (optional if the ACL is stored with the
* node itself).
*/
static final Name P_NODE_PATH = NameConstants.REP_NODE_PATH;
private final Principal principal;
private final List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>();
private final String jcrNodePathName;
private final String jcrGlobName;
private final NameResolver resolver;
private final PrivilegeManagerImpl privilegeMgr;
ACLTemplate(Principal principal, String path, NamePathResolver resolver, ValueFactory vf)
throws RepositoryException {
this(principal, path, null, resolver, vf);
}
ACLTemplate(Principal principal, NodeImpl acNode) throws RepositoryException {
this(principal, acNode.getPath(), acNode, (SessionImpl) acNode.getSession(),
acNode.getSession().getValueFactory());
}
private ACLTemplate(Principal principal, String path, NodeImpl acNode,
NamePathResolver resolver, ValueFactory vf)
throws RepositoryException {
super(path, vf);
this.principal = principal;
jcrNodePathName = resolver.getJCRName(P_NODE_PATH);
jcrGlobName = resolver.getJCRName(P_GLOB);
this.resolver = resolver;
Session session = (acNode != null) ? acNode.getSession() : (Session) resolver;
this.privilegeMgr = (PrivilegeManagerImpl) ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
if (acNode != null && acNode.hasNode(N_POLICY)) {
// build the list of policy entries;
NodeImpl aclNode = acNode.getNode(N_POLICY);
// loop over all entries in the aclNode for the princ-Principal
for (NodeIterator aceNodes = aclNode.getNodes(); aceNodes.hasNext();) {
NodeImpl aceNode = (NodeImpl) aceNodes.nextNode();
if (aceNode.isNodeType(NT_REP_ACE)) {
// the isAllow flag:
boolean isAllow = aceNode.isNodeType(NT_REP_GRANT_ACE);
// the privileges
InternalValue[] pValues = aceNode.getProperty(P_PRIVILEGES).internalGetValues();
Name[] privilegeNames = new Name[pValues.length];
for (int i = 0; i < pValues.length; i++) {
privilegeNames[i] = pValues[i].getName();
}
// the restrictions:
Map<String, Value> restrictions = new HashMap<String, Value>(2);
Property prop = aceNode.getProperty(P_NODE_PATH);
restrictions.put(prop.getName(), prop.getValue());
if (aceNode.hasProperty(P_GLOB)) {
prop = aceNode.getProperty(P_GLOB);
restrictions.put(prop.getName(), prop.getValue());
}
// finally add the entry
AccessControlEntry entry = new Entry(principal, privilegeMgr.getBits(privilegeNames), isAllow, restrictions);
entries.add(entry);
} else {
log.warn("ACE must be of nodetype rep:ACE -> ignored child-node " + aceNode.getPath());
}
}
} // else: no-node at all or no acl-node present.
}
AccessControlEntry createEntry(Principal princ, Privilege[] privileges,
boolean allow, Map<String, Value> restrictions) throws RepositoryException {
// adjust restrictions if necessary
Map<String, Value> rest = adjustRestrictions(restrictions);
checkValidEntry(princ, privileges, allow, rest);
return new Entry(princ, privileges, allow, rest);
}
private Map<String, Value> adjustRestrictions(Map<String, Value> restrictions) throws RepositoryException {
// make sure the nodePath restriction is of type PATH
Value v = restrictions.get(jcrNodePathName);
if (v == null) {
v = restrictions.get(P_NODE_PATH.toString());
}
if (v != null && v.getType() != PropertyType.PATH) {
v = valueFactory.createValue(v.getString(), PropertyType.PATH);
restrictions.put(jcrNodePathName, v);
}
// ... and glob is of type STRING.
v = restrictions.get(jcrGlobName);
if (v == null) {
v = restrictions.get(P_GLOB.toString());
}
if (v != null && v.getType() != PropertyType.STRING) {
v = valueFactory.createValue(v.getString(), PropertyType.STRING);
restrictions.put(jcrGlobName, v);
}
return restrictions;
}
//------------------------------------------------< AbstractACLTemplate >---
/**
* @see AbstractACLTemplate#checkValidEntry(java.security.Principal, javax.jcr.security.Privilege[], boolean, java.util.Map)
*/
@Override
protected void checkValidEntry(Principal principal, Privilege[] privileges,
boolean isAllow, Map<String, Value> restrictions)
throws AccessControlException {
if (!this.principal.equals(principal)) {
throw new AccessControlException("Invalid principal. Expected: " + principal);
}
Set<String> rNames = restrictions.keySet();
if (!rNames.contains(jcrNodePathName) && !rNames.contains(P_NODE_PATH.toString())) {
throw new AccessControlException("Missing mandatory restriction: " + jcrNodePathName);
}
}
/**
* @see org.apache.jackrabbit.core.security.authorization.AbstractACLTemplate#getEntries()
*/
@Override
protected List<AccessControlEntry> getEntries() {
return entries;
}
//----------------------------------------< JackrabbitAccessControlList >---
/**
* @see JackrabbitAccessControlList#getRestrictionNames()
*/
public String[] getRestrictionNames() {
return new String[] {jcrNodePathName, jcrGlobName};
}
/**
* @see JackrabbitAccessControlList#getRestrictionType(String)
*/
public int getRestrictionType(String restrictionName) {
if (jcrNodePathName.equals(restrictionName) || P_NODE_PATH.toString().equals(restrictionName)) {
return PropertyType.PATH;
} else if (jcrGlobName.equals(restrictionName) || P_GLOB.toString().equals(restrictionName)) {
return PropertyType.STRING;
} else {
return PropertyType.UNDEFINED;
}
}
/**
* Known restrictions are:
* <pre>
* rep:nodePath (mandatory) value-type: PATH
* rep:glob (optional) value-type: STRING
* </pre>
*
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean, Map)
*/
public boolean addEntry(Principal principal, Privilege[] privileges,
boolean isAllow, Map<String, Value> restrictions)
throws AccessControlException, RepositoryException {
if (restrictions == null || restrictions.isEmpty()) {
log.debug("Restrictions missing. Using default: rep:nodePath = " + getPath() + "; rep:glob = null.");
// default restrictions:
restrictions = Collections.singletonMap(jcrNodePathName,
valueFactory.createValue(getPath(), PropertyType.PATH));
}
AccessControlEntry entry = createEntry(principal, privileges, isAllow, restrictions);
if (entries.contains(entry)) {
log.debug("Entry is already contained in policy -> no modification.");
return false;
} else {
// TODO: to be improved. clean redundant entries
entries.add(0, entry);
return true;
}
}
//--------------------------------------------------< AccessControlList >---
/**
* @see javax.jcr.security.AccessControlList#removeAccessControlEntry(AccessControlEntry)
*/
public void removeAccessControlEntry(AccessControlEntry ace)
throws AccessControlException, RepositoryException {
if (!(ace instanceof Entry)) {
throw new AccessControlException("Invalid AccessControlEntry implementation " + ace.getClass().getName() + ".");
}
if (!entries.remove(ace)) {
throw new AccessControlException("Cannot remove AccessControlEntry " + ace);
}
}
//-------------------------------------------------------------< Object >---
/**
* Returns zero to satisfy the Object equals/hashCode contract.
* This class is mutable and not meant to be used as a hash key.
*
* @return always zero
* @see Object#hashCode()
*/
@Override
public int hashCode() {
return 0;
}
/**
* Returns true if the path and the entries are equal; false otherwise.
*
* @param obj Object to test.
* @return true if the path and the entries are equal; false otherwise.
* @see Object#equals(Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof ACLTemplate) {
ACLTemplate acl = (ACLTemplate) obj;
return principal.equals(acl.principal) &&
path.equals(acl.path) && entries.equals(acl.entries);
}
return false;
}
//--------------------------------------------------------------------------
/**
* The access control entry of a principalbased ACL.
*/
class Entry extends AccessControlEntryImpl {
/**
* The path of the Node this entry applies to.
*/
private final String nodePath;
/**
* Globing pattern
*/
private final GlobPattern pattern;
private Entry(Principal principal, Privilege[] privileges, boolean allow,
Map<String, Value> restrictions)
throws AccessControlException, RepositoryException {
super(principal, privileges, allow, restrictions);
Map<Name, Value> rstr = getRestrictions();
nodePath = rstr.get(P_NODE_PATH).getString();
Value glob = rstr.get(P_GLOB);
if (glob != null) {
pattern = GlobPattern.create(nodePath, glob.getString());
} else {
pattern = GlobPattern.create(nodePath);
}
}
private Entry(Principal principal, PrivilegeBits privilegeBits, boolean allow,
Map<String, Value> restrictions)
throws AccessControlException, RepositoryException {
super(principal, privilegeBits, allow, restrictions);
Map<Name, Value> rstr = getRestrictions();
nodePath = rstr.get(P_NODE_PATH).getString();
Value glob = rstr.get(P_GLOB);
if (glob != null) {
pattern = GlobPattern.create(nodePath, glob.getString());
} else {
pattern = GlobPattern.create(nodePath);
}
}
boolean matches(String jcrPath) throws RepositoryException {
return pattern.matches(jcrPath);
}
boolean matches(Item item) throws RepositoryException {
return pattern.matches(item);
}
@Override
protected NameResolver getResolver() {
return resolver;
}
@Override
protected ValueFactory getValueFactory() {
return valueFactory;
}
@Override
protected PrivilegeManagerImpl getPrivilegeManager() {
return privilegeMgr;
}
}
}