Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.repo.security.permissions.impl.acegi; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.ConfigAttribute; import net.sf.acegisecurity.ConfigAttributeDefinition; import net.sf.acegisecurity.vote.AccessDecisionVoter; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; /** * @author andyh */ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { private static Log log = LogFactory.getLog(ACLEntryVoter.class); private static final String ACL_NODE = "ACL_NODE"; private static final String ACL_ITEM = "ACL_ITEM"; private static final String ACL_PRI_CHILD_ASSOC_ON_CHILD = "ACL_PRI_CHILD_ASSOC_ON_CHILD"; private static final String ACL_PARENT = "ACL_PARENT"; private static final String ACL_ALLOW = "ACL_ALLOW"; private static final String ACL_METHOD = "ACL_METHOD"; private static final String ACL_DENY = "ACL_DENY"; private PermissionService permissionService; private NamespacePrefixResolver nspr; private NodeService nodeService; private OwnableService ownableService; private AuthorityService authorityService; private Set<QName> abstainForClassQNames = new HashSet<QName>(); private Set<String> abstainFor = null; /** * Default constructor * */ public ACLEntryVoter() { super(); } // ~ Methods // ================================================================ /** * Set the permission service * @param permissionService PermissionService */ public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } /** * Get the permission service * @return the permission service */ public PermissionService getPermissionService() { return permissionService; } /** * Get the name space prefix resolver * @return the name space prefix resolver */ public NamespacePrefixResolver getNamespacePrefixResolver() { return nspr; } /** * Set the name space prefix resolver * @param nspr NamespacePrefixResolver */ public void setNamespacePrefixResolver(NamespacePrefixResolver nspr) { this.nspr = nspr; } /** * Get the node service * @return the node service */ public NodeService getNodeService() { return nodeService; } /** * Get the ownable service * @return the ownable service */ public OwnableService getOwnableService() { return ownableService; } /** * Set the node service * @param nodeService NodeService */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } /** * Set the ownable service * @param ownableService OwnableService */ public void setOwnableService(OwnableService ownableService) { this.ownableService = ownableService; } /** * Set the authentication service * @param authenticationService AuthenticationService */ public void setAuthenticationService(AuthenticationService authenticationService) { log.warn("Bean property 'authenticationService' no longer required on 'ACLEntryVoter'."); } /** * Set the authority service * @param authorityService AuthorityService */ public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; } /** * Types and aspects for which we will abstain on voting if they are present. */ public void setAbstainFor(Set<String> abstainFor) { this.abstainFor = abstainFor; } public void afterPropertiesSet() throws Exception { if (permissionService == null) { throw new IllegalArgumentException("There must be a permission service"); } if (nspr == null) { throw new IllegalArgumentException("There must be a namespace service"); } if (nodeService == null) { throw new IllegalArgumentException("There must be a node service"); } if (authorityService == null) { throw new IllegalArgumentException("There must be an authority service"); } if (abstainFor != null) { for (String qnameString : abstainFor) { QName qname = QName.resolveToQName(nspr, qnameString); abstainForClassQNames.add(qname); } } } public boolean supports(ConfigAttribute attribute) { if ((attribute.getAttribute() != null) && (attribute.getAttribute().startsWith(ACL_NODE) || attribute.getAttribute().startsWith(ACL_ITEM) || attribute.getAttribute().startsWith(ACL_PRI_CHILD_ASSOC_ON_CHILD) || attribute.getAttribute().startsWith(ACL_PARENT) || attribute.getAttribute().equals(ACL_ALLOW) || attribute.getAttribute().startsWith(ACL_METHOD) || attribute.getAttribute().equals(ACL_DENY))) { return true; } else { return false; } } /** * This implementation supports only <code>MethodSecurityInterceptor</code>, because it queries the presented <code>MethodInvocation</code>. * * @param clazz * the secure object * @return <code>true</code> if the secure object is <code>MethodInvocation</code>, <code>false</code> otherwise */ public boolean supports(Class clazz) { return (MethodInvocation.class.isAssignableFrom(clazz)); } public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { if (log.isDebugEnabled()) { MethodInvocation mi = (MethodInvocation) object; log.debug("Method: " + mi.getMethod().toString()); } if (AuthenticationUtil.isRunAsUserTheSystemUser()) { if (log.isDebugEnabled()) { log.debug("Access granted for the system user"); } return AccessDecisionVoter.ACCESS_GRANTED; } List<ConfigAttributeDefintion> supportedDefinitions = extractSupportedDefinitions(config); if (supportedDefinitions.size() == 0) { return AccessDecisionVoter.ACCESS_ABSTAIN; } MethodInvocation invocation = (MethodInvocation) object; Method method = invocation.getMethod(); Class<?>[] params = method.getParameterTypes(); Boolean hasMethodEntry = null; for (ConfigAttributeDefintion cad : supportedDefinitions) { NodeRef testNodeRef = null; if (cad.typeString.equals(ACL_DENY)) { return AccessDecisionVoter.ACCESS_DENIED; } else if (cad.typeString.equals(ACL_ALLOW)) { return AccessDecisionVoter.ACCESS_GRANTED; } else if (cad.typeString.equals(ACL_METHOD)) { if (hasMethodEntry == null) { hasMethodEntry = Boolean.FALSE; } if (cad.authority.equals(AuthenticationUtil.getRunAsUser())) { hasMethodEntry = Boolean.TRUE; } else if (authorityService.getAuthorities().contains(cad.authority)) { hasMethodEntry = Boolean.TRUE; } } else if (cad.typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD)) { if (cad.parameter.length == 2 && NodeRef.class.isAssignableFrom(params[cad.parameter[0]]) && NodeRef.class.isAssignableFrom(params[cad.parameter[1]])) { testNodeRef = getArgument(invocation, cad.parameter[1]); if (testNodeRef != null) { if (nodeService.exists(testNodeRef)) { if (log.isDebugEnabled()) { log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); } ChildAssociationRef primaryParent = nodeService.getPrimaryParent(testNodeRef); NodeRef testParentNodeRef = getArgument(invocation, cad.parameter[0]); if (primaryParent == null || testParentNodeRef == null || !testParentNodeRef.equals(primaryParent.getParentRef())) { if (log.isDebugEnabled()) { log.debug("\tPermission test ignoring secondary parent association to " + testParentNodeRef); } testNodeRef = null; } } else if (log.isDebugEnabled()) { log.debug("\tPermission test on non-existing node " + testNodeRef); } } } else if (cad.parameter.length == 1 && ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { ChildAssociationRef testParentRef = getArgument(invocation, cad.parameter[0]); if (testParentRef != null) { if (testParentRef.isPrimary()) { testNodeRef = testParentRef.getChildRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) { log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test on non-existing node " + testNodeRef); } } } else if (log.isDebugEnabled()) { log.debug("\tPermission test ignoring secondary parent association to " + testParentRef.getParentRef()); } } } else { throw new ACLEntryVoterException( "The specified parameter is not a NodeRef or ChildAssociationRef"); } } else if (cad.typeString.equals(ACL_NODE)) { if (cad.parameter.length != 1) { throw new ACLEntryVoterException( "The specified parameter is not a NodeRef or ChildAssociationRef"); } else if (StoreRef.class.isAssignableFrom(params[cad.parameter[0]])) { StoreRef storeRef = getArgument(invocation, cad.parameter[0]); if (storeRef != null) { if (log.isDebugEnabled()) { log.debug("\tPermission test against the store - using permissions on the root node"); } if (nodeService.exists(storeRef)) { testNodeRef = nodeService.getRootNode(storeRef); } } } else if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) { testNodeRef = getArgument(invocation, cad.parameter[0]); if (log.isDebugEnabled()) { if (testNodeRef != null) { if (nodeService.exists(testNodeRef)) { log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test on non-existing node " + testNodeRef); } } } } else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { ChildAssociationRef testChildRef = getArgument(invocation, cad.parameter[0]); if (testChildRef != null) { testNodeRef = testChildRef.getChildRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) { log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test on non-existing node " + testNodeRef); } } } } else { throw new ACLEntryVoterException( "The specified parameter is not a NodeRef or ChildAssociationRef"); } } else if (cad.typeString.equals(ACL_ITEM)) { if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) { if (Map.class.isAssignableFrom(params[1]) || Map.class.isAssignableFrom(params[2])) { Map<QName, Serializable> properties = (Map<QName, Serializable>) (Map.class .isAssignableFrom(params[1]) ? getArgument(invocation, 1) : getArgument(invocation, 2)); if (properties != null && properties.containsKey(ContentModel.PROP_OWNER)) { testNodeRef = getArgument(invocation, cad.parameter[0]); boolean isChanged = !properties.get(ContentModel.PROP_OWNER).toString() .equals(ownableService.getOwner(testNodeRef)); if (!isChanged) { testNodeRef = null; } if (log.isDebugEnabled()) { if (testNodeRef != null) { if (nodeService.exists(testNodeRef)) { log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test on non-existing node " + testNodeRef); } } } } } else if (QName.class.isAssignableFrom(params[1]) && params[2] != null) { testNodeRef = getArgument(invocation, cad.parameter[0]); QName arg1 = getArgument(invocation, 1); boolean isOwnerProperty = ContentModel.PROP_OWNER.equals(arg1); if (isOwnerProperty) { Object arg2 = getArgument(invocation, 2); boolean isChanged = (arg2 != null && !arg2.toString().equals(ownableService.getOwner(testNodeRef))); if (!isChanged) { testNodeRef = null; } } else { testNodeRef = null; } if (log.isDebugEnabled()) { if (testNodeRef != null) { if (nodeService.exists(testNodeRef)) { log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test on non-existing node " + testNodeRef); } } } } } else { throw new ACLEntryVoterException("The specified parameter is not a Item"); } } else if (cad.typeString.equals(ACL_PARENT)) { // There is no point having parent permissions for store // refs if (cad.parameter.length != 1) { throw new ACLEntryVoterException( "The specified parameter is not a NodeRef or ChildAssociationRef"); } else if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) { NodeRef child = getArgument(invocation, cad.parameter[0]); if (child != null) { testNodeRef = nodeService.getPrimaryParent(child).getParentRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) { log.debug( "\tPermission test for parent on node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test for parent on non-existing node " + testNodeRef); } log.debug("\tPermission test for parent on node " + nodeService.getPath(testNodeRef)); } } } else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { ChildAssociationRef testParentRef = getArgument(invocation, cad.parameter[0]); if (testParentRef != null) { testNodeRef = testParentRef.getParentRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) { log.debug("\tPermission test for parent on child assoc ref for node " + nodeService.getPath(testNodeRef)); } else { log.debug("\tPermission test for parent on child assoc ref for non existing node " + testNodeRef); } } } } else { throw new ACLEntryVoterException("The specified parameter is not a ChildAssociationRef"); } } if (testNodeRef != null) { // now we know the node - we can abstain for certain types and aspects (eg. RM) if (abstainForClassQNames.size() > 0) { // check node exists if (nodeService.exists(testNodeRef)) { QName typeQName = nodeService.getType(testNodeRef); if (abstainForClassQNames.contains(typeQName)) { return AccessDecisionVoter.ACCESS_ABSTAIN; } Set<QName> aspectQNames = nodeService.getAspects(testNodeRef); for (QName abstain : abstainForClassQNames) { if (aspectQNames.contains(abstain)) { return AccessDecisionVoter.ACCESS_ABSTAIN; } } } } if (log.isDebugEnabled()) { log.debug("\t\tNode ref is not null"); } if (permissionService.hasPermission(testNodeRef, cad.required.toString()) == AccessStatus.DENIED) { if (log.isDebugEnabled()) { log.debug("\t\tPermission is denied"); Thread.dumpStack(); } return AccessDecisionVoter.ACCESS_DENIED; } } } if ((hasMethodEntry == null) || (hasMethodEntry.booleanValue())) { return AccessDecisionVoter.ACCESS_GRANTED; } else { return AccessDecisionVoter.ACCESS_DENIED; } } @SuppressWarnings("unchecked") private <T> T getArgument(MethodInvocation invocation, int index) { Object[] args = invocation.getArguments(); return index > args.length ? null : (T) args[index]; } private List<ConfigAttributeDefintion> extractSupportedDefinitions(ConfigAttributeDefinition config) { List<ConfigAttributeDefintion> definitions = new ArrayList<ConfigAttributeDefintion>(2); Iterator iter = config.getConfigAttributes(); while (iter.hasNext()) { ConfigAttribute attr = (ConfigAttribute) iter.next(); if (this.supports(attr)) { definitions.add(new ConfigAttributeDefintion(attr)); } } return definitions; } private class ConfigAttributeDefintion { String typeString; SimplePermissionReference required; int[] parameter; String authority; ConfigAttributeDefintion(ConfigAttribute attr) { StringTokenizer st = new StringTokenizer(attr.getAttribute(), ".", false); if (st.countTokens() < 1) { throw new ACLEntryVoterException("There must be at least one token in a config attribute"); } typeString = st.nextToken(); if (!(typeString.equals(ACL_NODE) || typeString.equals(ACL_ITEM) || typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD) || typeString.equals(ACL_PARENT) || typeString.equals(ACL_ALLOW) || typeString.equals(ACL_METHOD) || typeString.equals(ACL_DENY))) { throw new ACLEntryVoterException( "Invalid type: must be ACL_NODE, ACL_ITEM, ACL_PARENT or ACL_ALLOW"); } if (typeString.equals(ACL_NODE) || typeString.equals(ACL_ITEM) || typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD) || typeString.equals(ACL_PARENT)) { int count = st.countTokens(); if (typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD)) { if (count != 3 && count != 4) { throw new ACLEntryVoterException( "There must be three or four . separated tokens in each config attribute"); } } else if (count != 3) { throw new ACLEntryVoterException( "There must be three . separated tokens in each config attribute"); } // Handle a variable number of parameters parameter = new int[count - 2]; for (int i = 0; i < parameter.length; i++) { parameter[i] = Integer.parseInt(st.nextToken()); } String qNameString = st.nextToken(); String permissionString = st.nextToken(); QName qName = QName.createQName(qNameString, nspr); required = SimplePermissionReference.getPermissionReference(qName, permissionString); } else if (typeString.equals(ACL_METHOD)) { if (st.countTokens() != 1) { throw new ACLEntryVoterException( "There must be two . separated tokens in each group or role config attribute"); } authority = st.nextToken(); } } } }