/* * 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; } } }