/*
* 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;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.commons.iterator.AccessControlPolicyIteratorAdapter;
import org.apache.jackrabbit.core.HierarchyManager;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
import org.apache.jackrabbit.core.security.authorization.AccessControlProvider;
import org.apache.jackrabbit.core.security.authorization.CompiledPermissions;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.AccessDeniedException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import javax.jcr.security.Privilege;
import javax.security.auth.Subject;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* The <code>DefaultAccessManager</code> controls access by evaluating access
* control policies for the <code>Subject</code> attached to the
* <code>Session</code> this manager has been built for.<p>
* Please note the following exceptional situations:<br>
* This manager allows all privileges for a particular item if
* <ul>
* <li>the Session's represents a system session or a session associated with
* the repository's administrator</li>
* </ul>
* <p>
* It allows to access all available workspaces if
* <ul>
* <li>no <code>WorkspaceAccessManager</code> is defined.</li>
* </ul>
* <p>
* How access control policies are matched to a particular item is defined by
* the <code>AccessControlProvider</code> set to this AccessManager.
*
* @see AccessManager
* @see javax.jcr.security.AccessControlManager
*/
public class DefaultAccessManager extends AbstractAccessControlManager implements AccessManager {
private static final Logger log = LoggerFactory.getLogger(DefaultAccessManager.class);
private boolean initialized;
private NamePathResolver resolver;
private Set<Principal> principals;
private AccessControlProvider acProvider;
private AccessControlEditor editor;
/**
* the workspace access
*/
private WorkspaceAccess wspAccess;
/**
* the hierarchy manager used to resolve path from itemId
*/
private HierarchyManager hierMgr;
/**
* The privilege manager
*/
private PrivilegeManager privilegeManager;
/**
* The permissions that apply for the principals, that are present with
* the session subject this manager has been created for.
* TODO: if the users group-membership gets modified the compiledPermissions
* TODO should ev. be recalculated. currently those modifications are only
* TODO reflected upon re-login to the repository.
*/
private CompiledPermissions compiledPermissions;
//------------------------------------------------------< AccessManager >---
/**
* @see AccessManager#init(AMContext)
*/
public void init(AMContext amContext) throws AccessDeniedException, Exception {
init(amContext, null, null);
}
/**
* @see AccessManager#init(AMContext, AccessControlProvider, WorkspaceAccessManager)
*/
public void init(AMContext amContext, AccessControlProvider acProvider,
WorkspaceAccessManager wspAccessManager) throws AccessDeniedException, Exception {
if (initialized) {
throw new IllegalStateException("Already initialized.");
}
this.acProvider = acProvider;
resolver = amContext.getNamePathResolver();
hierMgr = amContext.getHierarchyManager();
Subject subject = amContext.getSubject();
if (subject == null) {
principals = Collections.emptySet();
} else {
principals = subject.getPrincipals();
}
wspAccess = new WorkspaceAccess(wspAccessManager, isSystemOrAdmin(amContext.getSession()));
privilegeManager = amContext.getPrivilegeManager();
if (acProvider != null) {
editor = acProvider.getEditor(amContext.getSession());
compiledPermissions = acProvider.compilePermissions(principals);
} else {
log.warn("No AccessControlProvider defined -> no access is granted.");
editor = null;
compiledPermissions = CompiledPermissions.NO_PERMISSION;
}
initialized = true;
if (!canAccess(amContext.getWorkspaceName())) {
throw new AccessDeniedException("Not allowed to access Workspace " + amContext.getWorkspaceName());
}
}
/**
* @see AccessManager#close()
*/
public void close() throws Exception {
if (!initialized) {
throw new IllegalStateException("Manager is not initialized.");
}
initialized = false;
compiledPermissions.close();
hierMgr = null;
acProvider = null;
editor = null;
wspAccess = null;
}
/**
* @see AccessManager#checkPermission(ItemId, int)
*/
public void checkPermission(ItemId id, int permissions) throws AccessDeniedException, ItemNotFoundException, RepositoryException {
if (!isGranted(id, permissions)) {
throw new AccessDeniedException("Access denied.");
}
}
/**
* @see AccessManager#checkPermission(Path, int)
*/
public void checkPermission(Path absPath, int permissions) throws AccessDeniedException, RepositoryException {
if (!isGranted(absPath, permissions)) {
throw new AccessDeniedException("Access denied.");
}
}
/**
* @see AccessManager#checkRepositoryPermission(int)
*/
public void checkRepositoryPermission(int permissions) throws AccessDeniedException, RepositoryException {
checkInitialized();
if (!compiledPermissions.grants(null, permissions)) {
throw new AccessDeniedException("Access denied.");
}
}
/**
* @see AccessManager#isGranted(ItemId, int)
*/
public boolean isGranted(ItemId id, int actions)
throws ItemNotFoundException, RepositoryException {
checkInitialized();
if (actions == READ && compiledPermissions.canReadAll()) {
return true;
} else {
int perm = 0;
if ((actions & READ) == READ) {
perm |= Permission.READ;
}
if ((actions & WRITE) == WRITE) {
if (id.denotesNode()) {
// TODO: check again if correct
perm |= Permission.SET_PROPERTY;
perm |= Permission.ADD_NODE;
} else {
perm |= Permission.SET_PROPERTY;
}
}
if ((actions & REMOVE) == REMOVE) {
perm |= (id.denotesNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY;
}
Path path = hierMgr.getPath(id);
return isGranted(path, perm);
}
}
/**
* @see AccessManager#isGranted(Path, int)
*/
public boolean isGranted(Path absPath, int permissions) throws RepositoryException {
checkInitialized();
if (!absPath.isAbsolute()) {
throw new RepositoryException("Absolute path expected");
}
return compiledPermissions.grants(absPath, permissions);
}
/**
* @see AccessManager#isGranted(Path, Name, int)
*/
public boolean isGranted(Path parentPath, Name childName, int permissions) throws RepositoryException {
Path p = PathFactoryImpl.getInstance().create(parentPath, childName, true);
return isGranted(p, permissions);
}
/**
* @see AccessManager#canRead(org.apache.jackrabbit.spi.Path,org.apache.jackrabbit.core.id.ItemId)
*/
public boolean canRead(Path itemPath, ItemId itemId) throws RepositoryException {
checkInitialized();
if (compiledPermissions.canReadAll()) {
return true;
} else {
return compiledPermissions.canRead(itemPath, itemId);
}
}
/**
* @see AccessManager#canAccess(String)
*/
public boolean canAccess(String workspaceName) throws RepositoryException {
checkInitialized();
return wspAccess.canAccess(workspaceName);
}
//-----------------------------------------------< AccessControlManager >---
/**
* @see javax.jcr.security.AccessControlManager#hasPrivileges(String, Privilege[])
*/
public boolean hasPrivileges(String absPath, Privilege[] privileges) throws PathNotFoundException, RepositoryException {
checkInitialized();
checkValidNodePath(absPath);
if (privileges == null || privileges.length == 0) {
// null or empty privilege array -> return true
log.debug("No privileges passed -> allowed.");
return true;
} else {
Path p = getPath(absPath);
return compiledPermissions.hasPrivileges(p, privileges);
}
}
/**
* @see javax.jcr.security.AccessControlManager#getPrivileges(String)
*/
public Privilege[] getPrivileges(String absPath) throws PathNotFoundException, RepositoryException {
checkInitialized();
checkValidNodePath(absPath);
Set<Privilege> privs = compiledPermissions.getPrivilegeSet(getPath(absPath));
return privs.toArray(new Privilege[privs.size()]);
}
/**
* @see javax.jcr.security.AccessControlManager#getPolicies(String)
*/
@Override
public AccessControlPolicy[] getPolicies(String absPath) throws PathNotFoundException, AccessDeniedException, RepositoryException {
checkInitialized();
checkPermission(absPath, Permission.READ_AC);
AccessControlPolicy[] policies;
if (editor != null) {
policies = editor.getPolicies(absPath);
} else {
policies = new AccessControlPolicy[0];
}
return policies;
}
/**
* @see javax.jcr.security.AccessControlManager#getEffectivePolicies(String)
*/
public AccessControlPolicy[] getEffectivePolicies(String absPath) throws PathNotFoundException, AccessDeniedException, RepositoryException {
checkInitialized();
checkPermission(absPath, Permission.READ_AC);
return acProvider.getEffectivePolicies(getPath(absPath), compiledPermissions);
}
/**
* @see javax.jcr.security.AccessControlManager#getApplicablePolicies(String)
*/
@Override
public AccessControlPolicyIterator getApplicablePolicies(String absPath) throws PathNotFoundException, AccessDeniedException, RepositoryException {
checkInitialized();
checkPermission(absPath, Permission.READ_AC);
if (editor != null) {
try {
AccessControlPolicy[] applicable = editor.editAccessControlPolicies(absPath);
return new AccessControlPolicyIteratorAdapter(Arrays.asList(applicable));
} catch (AccessControlException e) {
log.debug("No applicable policy at " + absPath);
}
}
// no applicable policies -> return empty iterator.
return AccessControlPolicyIteratorAdapter.EMPTY;
}
/**
* @see javax.jcr.security.AccessControlManager#setPolicy(String, AccessControlPolicy)
*/
@Override
public void setPolicy(String absPath, AccessControlPolicy policy) throws PathNotFoundException, AccessControlException, AccessDeniedException, RepositoryException {
checkInitialized();
checkPermission(absPath, Permission.MODIFY_AC);
if (editor == null) {
throw new UnsupportedRepositoryOperationException("Modification of AccessControlPolicies is not supported. ");
}
editor.setPolicy(absPath, policy);
}
/**
* @see javax.jcr.security.AccessControlManager#removePolicy(String, AccessControlPolicy)
*/
@Override
public void removePolicy(String absPath, AccessControlPolicy policy) throws PathNotFoundException, AccessControlException, AccessDeniedException, RepositoryException {
checkInitialized();
checkPermission(absPath, Permission.MODIFY_AC);
if (editor == null) {
throw new UnsupportedRepositoryOperationException("Removal of AccessControlPolicies is not supported.");
}
editor.removePolicy(absPath, policy);
}
//-------------------------------------< JackrabbitAccessControlManager >---
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getApplicablePolicies(Principal)
*/
@Override
public JackrabbitAccessControlPolicy[] getApplicablePolicies(Principal principal) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
checkInitialized();
if (editor == null) {
throw new UnsupportedRepositoryOperationException("Editing of access control policies is not supported.");
}
return editor.editAccessControlPolicies(principal);
}
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getPolicies(Principal)
*/
@Override
public JackrabbitAccessControlPolicy[] getPolicies(Principal principal) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
checkInitialized();
if (editor == null) {
throw new UnsupportedRepositoryOperationException("Editing of access control policies is not supported.");
}
return editor.getPolicies(principal);
}
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getEffectivePolicies(Set)
*/
public AccessControlPolicy[] getEffectivePolicies(Set<Principal> principals) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
checkInitialized();
return acProvider.getEffectivePolicies(principals, compiledPermissions);
}
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#hasPrivileges(String, Set, Privilege[])
*/
public boolean hasPrivileges(String absPath, Set<Principal> principals, Privilege[] privileges) throws PathNotFoundException, RepositoryException {
checkInitialized();
checkValidNodePath(absPath);
checkPermission(absPath, Permission.READ_AC);
if (privileges == null || privileges.length == 0) {
// null or empty privilege array -> return true
log.debug("No privileges passed -> allowed.");
return true;
} else {
Path p = getPath(absPath);
CompiledPermissions perms = acProvider.compilePermissions(principals);
try {
return perms.hasPrivileges(p, privileges);
} finally {
perms.close();
}
}
}
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getPrivileges(String, Set)
*/
public Privilege[] getPrivileges(String absPath, Set<Principal> principals) throws PathNotFoundException, RepositoryException {
checkInitialized();
checkValidNodePath(absPath);
checkPermission(absPath, Permission.READ_AC);
CompiledPermissions perms = acProvider.compilePermissions(principals);
try {
Set<Privilege> privs = perms.getPrivilegeSet(getPath(absPath));
return privs.toArray(new Privilege[privs.size()]);
} finally {
perms.close();
}
}
//---------------------------------------< AbstractAccessControlManager >---
/**
* @see AbstractAccessControlManager#checkInitialized()
*/
@Override
protected void checkInitialized() {
if (!initialized) {
throw new IllegalStateException("not initialized");
}
}
/**
* @see AbstractAccessControlManager#checkValidNodePath(String)
*/
@Override
protected void checkValidNodePath(String absPath) throws PathNotFoundException, RepositoryException {
Path p = getPath(absPath);
if (p != null) {
if (!p.isAbsolute()) {
throw new RepositoryException("Absolute path expected.");
}
if (hierMgr.resolveNodePath(p) == null) {
throw new PathNotFoundException("No such node " + absPath);
}
}
}
/**
* @see AbstractAccessControlManager#checkPermission(String,int)
*/
@Override
protected void checkPermission(String absPath, int permission) throws AccessDeniedException, RepositoryException {
checkValidNodePath(absPath);
Path p = getPath(absPath);
if (!compiledPermissions.grants(p, permission)) {
throw new AccessDeniedException("Access denied at " + absPath);
}
}
/**
* @see AbstractAccessControlManager#getPrivilegeManager()
*/
@Override
protected PrivilegeManager getPrivilegeManager() throws RepositoryException {
checkInitialized();
return privilegeManager;
}
//------------------------------------------------------------< private >---
private Path getPath(String absPath) throws RepositoryException {
return (absPath == null) ? null : resolver.getQPath(absPath);
}
/**
* @param s the session
* @return if created with system-privileges
*/
private static boolean isSystemOrAdmin(Session s) {
if (s == null || !(s instanceof SessionImpl)) {
return false;
} else {
SessionImpl sImpl = (SessionImpl) s;
return sImpl.isSystem() || sImpl.isAdmin();
}
}
//--------------------------------------------------------------------------
/**
* Simple wrapper around the repository's <code>WorkspaceAccessManager</code>
* that remembers for which workspaces the access has already been
* evaluated.
*/
private class WorkspaceAccess {
private final WorkspaceAccessManager wspAccessManager;
private final boolean alwaysAllowed;
// TODO: entries must be cleared if access permission to wsp changes.
private final List <String>allowed;
private final List<String> denied;
private WorkspaceAccess(WorkspaceAccessManager wspAccessManager,
boolean alwaysAllowed) {
this.wspAccessManager = wspAccessManager;
this.alwaysAllowed = alwaysAllowed;
if (!alwaysAllowed) {
allowed = new ArrayList<String>(5);
denied = new ArrayList<String>(5);
} else {
allowed = denied = null;
}
}
private boolean canAccess(String workspaceName) throws RepositoryException {
if (alwaysAllowed || wspAccessManager == null || allowed.contains(workspaceName)) {
return true;
} else if (denied.contains(workspaceName)) {
return false;
}
// not yet tested -> ask the workspace-accessmanager.
boolean canAccess = wspAccessManager.grants(principals, workspaceName);
if (canAccess) {
allowed.add(workspaceName);
} else {
denied.add(workspaceName);
}
return canAccess;
}
}
}