/* * #%L * Alfresco Records Management Module * %% * 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.module.org_alfresco_module_rm.capability; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.module.org_alfresco_module_rm.capability.policy.ConfigAttributeDefinition; import org.alfresco.module.org_alfresco_module_rm.capability.policy.Policy; import org.alfresco.module.org_alfresco_module_rm.security.RMMethodSecurityInterceptor; import org.alfresco.module.org_alfresco_module_rm.util.AlfrescoTransactionSupport; import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; import org.alfresco.module.org_alfresco_module_rm.util.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.ConfigAttribute; import net.sf.acegisecurity.vote.AccessDecisionVoter; /** * Records managment entry voter. * * @author Roy Wetherall, Andy Hind */ public class RMEntryVoter extends RMSecurityCommon implements AccessDecisionVoter, InitializingBean, PolicyRegister { /** Logger */ private static Log logger = LogFactory.getLog(RMEntryVoter.class); /** Namespace resolver */ private NamespacePrefixResolver nspr; /** Capability Service */ private CapabilityService capabilityService; /** Transactional Resource Helper */ private TransactionalResourceHelper transactionalResourceHelper; /** Alfresco transaction support */ private AlfrescoTransactionSupport alfrescoTransactionSupport; /** authentication util */ private AuthenticationUtil authenticationUtil; /** Policy map */ private Map<String, Policy> policies = new HashMap<String, Policy>(); /** * @param capabilityService capability service */ public void setCapabilityService(CapabilityService capabilityService) { this.capabilityService = capabilityService; } /** * @param nspr namespace prefix resolver */ public void setNamespacePrefixResolver(NamespacePrefixResolver nspr) { this.nspr = nspr; } /** * @param transactionalResourceHelper transactional resource helper */ public void setTransactionalResourceHelper(TransactionalResourceHelper transactionalResourceHelper) { this.transactionalResourceHelper = transactionalResourceHelper; } /** * @param alfrescoTransactionSupport alfresco transaction support helper */ public void setAlfrescoTransactionSupport(AlfrescoTransactionSupport alfrescoTransactionSupport) { this.alfrescoTransactionSupport = alfrescoTransactionSupport; } /** * @param authenticationUtil authentication util */ public void setAuthenticationUtil(AuthenticationUtil authenticationUtil) { this.authenticationUtil = authenticationUtil; } /** * Register a policy the voter * * @param policy policy */ public void registerPolicy(Policy policy) { policies.put(policy.getName(), policy); } /** * @see net.sf.acegisecurity.vote.AccessDecisionVoter#supports(net.sf.acegisecurity.ConfigAttribute) */ @Override public boolean supports(ConfigAttribute configAttribute) { boolean supports = false; String attribute = configAttribute.getAttribute(); if (StringUtils.isNotBlank(attribute) && (attribute.equals(ConfigAttributeDefinition.RM_ABSTAIN) || attribute.equals(ConfigAttributeDefinition.RM_QUERY) || attribute.equals(ConfigAttributeDefinition.RM_ALLOW) || attribute.equals(ConfigAttributeDefinition.RM_DENY) || attribute.startsWith(ConfigAttributeDefinition.RM_CAP) || attribute.startsWith(ConfigAttributeDefinition.RM))) { supports = true; } return supports; } /** * @see net.sf.acegisecurity.vote.AccessDecisionVoter#supports(java.lang.Class) */ @SuppressWarnings("rawtypes") public boolean supports(Class clazz) { return (MethodInvocation.class.isAssignableFrom(clazz)); } /** * @see net.sf.acegisecurity.vote.AccessDecisionVoter#vote(net.sf.acegisecurity.Authentication, java.lang.Object, net.sf.acegisecurity.ConfigAttributeDefinition) */ @SuppressWarnings("rawtypes") public int vote(Authentication authentication, Object object, net.sf.acegisecurity.ConfigAttributeDefinition config) { // logging RMMethodSecurityInterceptor.isRMSecurityChecked(true); MethodInvocation mi = (MethodInvocation)object; if (transactionalResourceHelper.isResourcePresent("voting")) { if (logger.isDebugEnabled()) { logger.debug(" .. grant access already voting: " + mi.getMethod().getDeclaringClass().getName() + "." + mi.getMethod().getName()); } return AccessDecisionVoter.ACCESS_GRANTED; } if (logger.isDebugEnabled()) { logger.debug("Method: " + mi.getMethod().getDeclaringClass().getName() + "." + mi.getMethod().getName()); } alfrescoTransactionSupport.bindResource("voting", true); try { // The system user can do anything if (authenticationUtil.isRunAsUserTheSystemUser()) { if (logger.isDebugEnabled()) { logger.debug("Access granted for the system user"); } return AccessDecisionVoter.ACCESS_GRANTED; } List<ConfigAttributeDefinition> supportedDefinitions = extractSupportedDefinitions(config); // No RM definitions so we do not vote if (supportedDefinitions.size() == 0) { return AccessDecisionVoter.ACCESS_ABSTAIN; } // check we have an instance of a method invocation if (!(object instanceof MethodInvocation)) { // we expect a method invocation throw new AlfrescoRuntimeException("Passed object is not an instance of MethodInvocation as expected."); } // get information about the method MethodInvocation invocation = (MethodInvocation) object; Method method = invocation.getMethod(); Class[] params = method.getParameterTypes(); // If there are only capability (RM_CAP) and policy (RM) entries non must deny // If any abstain we deny // All present must vote to allow unless an explicit direction comes first (e.g. RM_ALLOW) for (ConfigAttributeDefinition cad : supportedDefinitions) { // Whatever is found first takes precedence if (cad.getTypeString().equals(ConfigAttributeDefinition.RM_DENY)) { // log message RMMethodSecurityInterceptor.addMessage("RM_DENY: check that a security policy has been set for this method"); return AccessDecisionVoter.ACCESS_DENIED; } else if (cad.getTypeString().equals(ConfigAttributeDefinition.RM_ABSTAIN)) { return AccessDecisionVoter.ACCESS_ABSTAIN; } else if (cad.getTypeString().equals(ConfigAttributeDefinition.RM_ALLOW)) { return AccessDecisionVoter.ACCESS_GRANTED; } // RM_QUERY is a special case - the entry is allowed and filtering sorts out the results // It is distinguished from RM_ALLOW so query may have additional behaviour in the future else if (cad.getTypeString().equals(ConfigAttributeDefinition.RM_QUERY)) { return AccessDecisionVoter.ACCESS_GRANTED; } // Ignore config that references method arguments that do not exist // Arguably we should deny here but that requires a full impact analysis // These entries effectively abstain else if (((cad.getParameters().get(0) != null) && (cad.getParameters().get(0) >= invocation.getArguments().length)) || ((cad.getParameters().get(1) != null) && (cad.getParameters().get(1) >= invocation.getArguments().length))) { continue; } else if (cad.getTypeString().equals(ConfigAttributeDefinition.RM_CAP)) { switch(checkCapability(invocation, params, cad)) { case AccessDecisionVoter.ACCESS_DENIED: { return AccessDecisionVoter.ACCESS_DENIED; } case AccessDecisionVoter.ACCESS_ABSTAIN: { if(logger.isDebugEnabled()) { if(logger.isTraceEnabled()) { logger.trace("Capability " + cad.getRequired() + " abstained for " + invocation.getMethod(), new IllegalStateException()); } else { logger.debug("Capability " + cad.getRequired() + " abstained for " + invocation.getMethod()); } } // abstain denies return AccessDecisionVoter.ACCESS_DENIED; } case AccessDecisionVoter.ACCESS_GRANTED: { break; } } } else if (cad.getTypeString().equals(ConfigAttributeDefinition.RM)) { switch(checkPolicy(invocation, params, cad)) { case AccessDecisionVoter.ACCESS_DENIED: { // log message RMMethodSecurityInterceptor.addMessage("Policy " + cad.getPolicyName() + " denied."); return AccessDecisionVoter.ACCESS_DENIED; } case AccessDecisionVoter.ACCESS_ABSTAIN: { if(logger.isDebugEnabled()) { if(logger.isTraceEnabled()) { logger.trace("Policy " + cad.getPolicyName() + " abstained for " + invocation.getMethod(), new IllegalStateException()); } else { logger.debug("Policy " + cad.getPolicyName() + " abstained for " + invocation.getMethod()); } } // abstain denies return AccessDecisionVoter.ACCESS_DENIED; } case AccessDecisionVoter.ACCESS_GRANTED: { break; } } } } } finally { alfrescoTransactionSupport.unbindResource("voting"); } // all voted to allow return AccessDecisionVoter.ACCESS_GRANTED; } /** * Check the capability * * @param invocation method invocation * @param params parameters * @param cad config definition * @return int evaluation result */ @SuppressWarnings("rawtypes") private int checkCapability(MethodInvocation invocation, Class[] params, ConfigAttributeDefinition cad) { NodeRef testNodeRef = getTestNode(invocation, params, cad.getParameters().get(0), cad.isParent()); if (testNodeRef == null) { return AccessDecisionVoter.ACCESS_ABSTAIN; } Capability capability = capabilityService.getCapability(cad.getRequired().getName()); if (capability == null) { throw new AlfrescoRuntimeException("The capability '" + cad.getRequired().getName() + "' set on method '" + invocation.getMethod().getName() + "' does not exist."); } return capability.hasPermissionRaw(testNodeRef); } /** * Evaluate policy to determine access * * @param invocation invocation information * @param params parameters * @param cad configuration attribute definition * @return int policy evaluation */ @SuppressWarnings("rawtypes") private int checkPolicy(MethodInvocation invocation, Class[] params, ConfigAttributeDefinition cad) { // try to get the policy Policy policy = policies.get(cad.getPolicyName()); if (policy == null) { // throw an exception if the policy is invalid throw new AlfrescoRuntimeException("The policy '" + cad.getPolicyName() + "' set on the method '" + invocation.getMethod().getName() + "' does not exist."); } else { // evaluate the policy return policy.evaluate(invocation, params, cad); } } /** * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() { } /** * * @param config * @return */ @SuppressWarnings("rawtypes") private List<ConfigAttributeDefinition> extractSupportedDefinitions(net.sf.acegisecurity.ConfigAttributeDefinition config) { List<ConfigAttributeDefinition> definitions = new ArrayList<ConfigAttributeDefinition>(2); Iterator iter = config.getConfigAttributes(); while (iter.hasNext()) { ConfigAttribute attr = (ConfigAttribute) iter.next(); if (this.supports(attr)) { definitions.add(new ConfigAttributeDefinition(attr, nspr)); } } return definitions; } }