org.modeshape.jcr.AccessControlManagerImpl Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* Unless otherwise indicated, all code in ModeShape is licensed
* to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* ModeShape is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.modeshape.jcr;
import java.security.Principal;
import javax.jcr.AccessDeniedException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
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 javax.jcr.version.VersionException;
import org.modeshape.jcr.security.SimplePrincipal;
import org.modeshape.jcr.security.acl.AccessControlPolicyIteratorImpl;
import org.modeshape.jcr.security.acl.JcrAccessControlList;
import org.modeshape.jcr.security.acl.Privileges;
import org.modeshape.jcr.value.Path;
/**
* AccessControlManager implementation. AccessControlManager has been implemented suppose that node is associated with access
* control list which defines deny/allow actions for the given principal. Principals are transparent and can represent users or
* groups. ACLs are stored per node in a special child node called mode:acl . This node has a list of
* mode:{$principal_name} child nodes which has multi-value property permissions where permissions are defined by JCR
* specifications. {node} {mode:AccessControllable} +mode:acl{mode:Acl} +user-name{mode:permission} -permissions {String} To make
* node access controllable ModeShape adds "mode:AccessControllable" type to mixin types of the node. Access list related nodes
* are defined as protected do disallow normal add/remove/save item methods. Access list defined for the node also has affect on
* all child nodes unless child node defines its own Access list. Empty ACL means all permissions. Initially the default access
* list is assigned to the root node with all permissions granted to everyone. Access Control Manager implementation does not
* break any existing mechanism of authentications. It acts as an abstract secondary resource control feature. On the first stage
* one of the existing security acts and if it grants permission the ACL is asked to check permissions.
*
* @author kulikov
*/
public class AccessControlManagerImpl implements AccessControlManager {
private static final String MODE_ACCESS_CONTROLLABLE = "mode:accessControllable";
private static final String ACCESS_LIST_NODE = "mode:acl";
private static final String MODE_ACCESS_LIST_NODE = "mode:Acl";
private static final String MODE_ACCESS_LIST_ENTRY_NODE = "mode:Permission";
private static final String PRINCIPAL_NAME = "name";
private static final String PRIVILEGES = "privileges";
// session under resource control
private final JcrSession session;
// list of objects implemented privileges
private final Privileges privileges;
// default access list granted all permissions to everyone.
private final JcrAccessControlList defaultACL;
protected AccessControlManagerImpl( JcrSession session ) {
this.session = session;
this.privileges = new Privileges(session);
this.defaultACL = JcrAccessControlList.defaultAcl(this);
}
/**
* Gets full list of known and supported privileges irrespective of the path.
*
* @return list of privileges.
*/
public Privilege[] privileges() {
return privileges.listOfSupported();
}
@Override
public Privilege[] getSupportedPrivileges( String path ) {
return privileges.listOfSupported();
}
@Override
public Privilege privilegeFromName( String name ) throws AccessControlException, RepositoryException {
Privilege p = privileges.forName(name);
if (p == null) {
throw new AccessControlException(name + " is not a valid name for privilege");
}
return p;
}
@Override
public boolean hasPrivileges( String path,
Privilege[] privileges ) throws PathNotFoundException, RepositoryException {
// recursively search for first available access list
JcrAccessControlList acl;
if (!found(acl = findAccessList(path))) {
// access list is not assigned, use default
return defaultACL.hasPrivileges(session.context().getSecurityContext(), privileges);
}
// perform checking of the privileges
return acl.isEmpty() || acl.hasPrivileges(session.context().getSecurityContext(), privileges);
}
@Override
public Privilege[] getPrivileges( String path ) throws PathNotFoundException, RepositoryException {
// recursively search for first available access list
JcrAccessControlList acl;
if (!found(acl = findAccessList(path))) {
// access list is not assigned, use default
return defaultACL.getPrivileges(session.context().getSecurityContext());
}
// access list found, ask it to get defined privileges
Privilege[] pp = acl.getPrivileges(session.context().getSecurityContext());
return pp;
}
@Override
public AccessControlPolicy[] getPolicies( String path )
throws PathNotFoundException, AccessDeniedException, RepositoryException {
if (session.isReadOnly()) {
throw new AccessDeniedException(JcrI18n.permissionDenied.text(path, "read access control content"));
}
if (!hasPrivileges(path, new Privilege[] {privileges.forName(Privilege.JCR_READ_ACCESS_CONTROL)})) {
throw new AccessDeniedException();
}
Node node = session.getNode(path, true);
if (node.hasNode(ACCESS_LIST_NODE)) {
JcrAccessControlList policy = new JcrAccessControlList(this, path);
// load entries
AbstractJcrNode aclNode = ((AbstractJcrNode)node).getNode(ACCESS_LIST_NODE, true);
NodeIterator it = aclNode.getNodes();
while (it.hasNext()) {
Node entryNode = it.nextNode();
String principalName = entryNode.getProperty(PRINCIPAL_NAME).getString();
Value[] values = entryNode.getProperty(PRIVILEGES).getValues();
Privilege[] privileges = new Privilege[values.length];
for (int i = 0; i < privileges.length; i++) {
privileges[i] = this.privilegeFromName(values[i].getString());
}
policy.addAccessControlEntry(principal(principalName), privileges);
}
return new AccessControlPolicy[] {policy};
}
return new AccessControlPolicy[] {};
}
@Override
public AccessControlPolicy[] getEffectivePolicies( String path )
throws PathNotFoundException, AccessDeniedException, RepositoryException {
AccessControlPolicy[] policies = getPolicies(path);
if (policies.length == 0) {
return new AccessControlPolicy[] {(AccessControlPolicy)this.getApplicablePolicies(path).next()};
}
return policies;
}
@Override
public AccessControlPolicyIterator getApplicablePolicies( String path )
throws PathNotFoundException, AccessDeniedException, RepositoryException {
if (session.isReadOnly()) {
throw new AccessDeniedException(JcrI18n.permissionDenied.text(path, "read access control content"));
}
// Current implementation supports only one policy - access list
// So we need to check the node specified by path for the policy bound to it
// if node has already policy bound then we need return empty list and
// access list otherwise
Node node = session.getNode(path, true);
if (node.hasNode(ACCESS_LIST_NODE)) {
// node already has policy, nothing is applicable except it
return AccessControlPolicyIteratorImpl.EMPTY;
}
// access list still applicable
return new AccessControlPolicyIteratorImpl(new JcrAccessControlList(this, path));
}
@Override
public void setPolicy( String path,
AccessControlPolicy policy )
throws PathNotFoundException, AccessControlException, AccessDeniedException, LockException, VersionException,
RepositoryException {
if (session.isReadOnly()) {
throw new AccessDeniedException(JcrI18n.permissionDenied.text(path, "read access control content"));
}
if (!hasPrivileges(path, new Privilege[] {privileges.forName(Privilege.JCR_MODIFY_ACCESS_CONTROL)})) {
throw new AccessDeniedException();
}
// we support only access list then cast policy to access list
if (!(policy instanceof AccessControlList)) {
throw new AccessControlException("");
}
JcrAccessControlList acl = (JcrAccessControlList)policy;
// binding given policy to the specified path as special child node
AbstractJcrNode node = session.getNode(path, true);
// make node access controllable and add specsial child node
// which belongs to the access list
node.addMixin(MODE_ACCESS_CONTROLLABLE, false);
AbstractJcrNode aclNode = node.hasNode(ACCESS_LIST_NODE) ? node.getNode(ACCESS_LIST_NODE, true) : node.addAclNode(ACCESS_LIST_NODE,
MODE_ACCESS_LIST_NODE);
// store entries as child nodes of acl
for (AccessControlEntry ace : acl.getAccessControlEntries()) {
assert (ace.getPrincipal() != null);
String name = ace.getPrincipal().getName();
AbstractJcrNode entryNode = aclNode.hasNode(name) ? aclNode.getNode(name, true) : aclNode.addAclNode(name,
MODE_ACCESS_LIST_ENTRY_NODE);
entryNode.setPropertyInAccessControlScope(PRINCIPAL_NAME, ace.getPrincipal().getName());
entryNode.setPropertyInAccessControlScope(PRIVILEGES, privileges(ace.getPrivileges()));
}
// delete removed entries
NodeIterator it = aclNode.getNodes();
while (it.hasNext()) {
Node entryNode = it.nextNode();
String name = entryNode.getProperty(PRINCIPAL_NAME).getString();
if (!acl.hasEntry(name)) {
entryNode.remove();
}
}
session.repository.repositoryCache().setAccessControlEnabled(true);
}
@Override
public void removePolicy( String path,
AccessControlPolicy policy )
throws PathNotFoundException, AccessControlException, AccessDeniedException, LockException, VersionException,
RepositoryException {
if (session.isReadOnly()) {
throw new AccessDeniedException(JcrI18n.permissionDenied.text(path, "read access control content"));
}
try {
if (!hasPrivileges(path, new Privilege[] {privileges.forName(Privilege.JCR_MODIFY_ACCESS_CONTROL)})) {
throw new AccessDeniedException();
}
Node node = session.getNode(path);
if (node.hasNode(ACCESS_LIST_NODE)) {
AbstractJcrNode aclNode = ((AbstractJcrNode)node).getNode(ACCESS_LIST_NODE, true);
aclNode.remove();
node.removeMixin(MODE_ACCESS_CONTROLLABLE);
}
} catch (PathNotFoundException e) {
}
}
/**
* Recursively searches for the available access list.
*
* @param absPath the absolute path of the node
* @return JCR defined access list object.
* @throws PathNotFoundException
* @throws RepositoryException
*/
public JcrAccessControlList findAccessList( String absPath ) throws PathNotFoundException, RepositoryException {
AbstractJcrNode node = session.getNode(absPath, true);
while (!node.hasNode(ACCESS_LIST_NODE)) {
try {
node = node.getParent();
} catch (ItemNotFoundException e) {
break;
}
}
if (node.hasNode(ACCESS_LIST_NODE)) {
return acl(node.getNode(ACCESS_LIST_NODE, true));
}
return null;
}
/**
* Constructs AccessControlList object from node.
*
* @param node the node which represents the access list.
* @return JCR defined AccessControlList object.
* @throws RepositoryException
*/
private JcrAccessControlList acl( Node node ) throws RepositoryException {
// create new access list object
JcrAccessControlList acl = new JcrAccessControlList(this, node.getPath());
// fill access list with entries
NodeIterator entryNodes = node.getNodes();
while (entryNodes.hasNext()) {
// pickup next entry
Node entry = entryNodes.nextNode();
String name = entry.getProperty(PRINCIPAL_NAME).getString();
Value[] privileges = entry.getProperty(PRIVILEGES).getValues();
acl.addAccessControlEntry(principal(name), privileges(privileges));
}
return acl;
}
/**
* Extracts names of the given privileges.
*
* @param privileges the list of privileges.
* @return names of the given privileges.
*/
private String[] privileges( Privilege[] privileges ) {
String[] names = new String[privileges.length];
for (int i = 0; i < privileges.length; i++) {
names[i] = privileges[i].getName();
}
return names;
}
/**
* Constructs list of Privilege objects using privilege's name.
*
* @param names names of privileges
* @return Privilege objects.
* @throws ValueFormatException
* @throws AccessControlException
* @throws RepositoryException
*/
private Privilege[] privileges( Value[] names ) throws ValueFormatException, AccessControlException, RepositoryException {
Privilege[] privileges = new Privilege[names.length];
for (int i = 0; i < names.length; i++) {
privileges[i] = privilegeFromName(names[i].getString());
}
return privileges;
}
/**
* Tests given object for null.
*
* @param o the givem object
* @return true if the given object not null and false otherwise.
*/
private boolean found( Object o ) {
return o != null;
}
public boolean hasPermission( Path absPath,
String... actions ) {
// convert actions to privileges
Privilege[] permissions = new Privilege[actions.length];
for (int i = 0; i < actions.length; i++) {
permissions[i] = privileges.forAction(actions[i]);
}
// check privileges for the given path
try {
return this.hasPrivileges(absPath.toString(), permissions);
} catch (Exception e) {
return true;
}
}
/**
* Gets principal instance for the given name. This method uses feature of the security context to discover known principals.
*
* @param name the name of the principal.
* @return principal instance.
*/
private Principal principal( String name ) {
return SimplePrincipal.newInstance(name);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy