diff --git a/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java b/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java new file mode 100644 index 0000000000..5fbc45d420 --- /dev/null +++ b/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java @@ -0,0 +1,115 @@ +package org.springframework.security.acls; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.Authentication; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.objectidentity.ObjectIdentity; +import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategy; +import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl; +import org.springframework.security.acls.sid.Sid; +import org.springframework.security.acls.sid.SidRetrievalStrategy; +import org.springframework.security.acls.sid.SidRetrievalStrategyImpl; +import org.springframework.security.expression.PermissionEvaluator; + +/** + * Used by Spring Security's expression-based access control implementation to evaluate permissions for a particular + * object using the ACL module. Similar in behaviour to + * {@link org.springframework.security.vote.AclEntryVoter AclEntryVoter}. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +public class AclPermissionEvaluator implements PermissionEvaluator { + + private final Log logger = LogFactory.getLog(getClass()); + + private AclService aclService; + private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); + private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); + + public AclPermissionEvaluator(AclService aclService) { + this.aclService = aclService; + } + + /** + * Determines whether the user has the given permission(s) on the domain object using the ACL + * configuration. If the domain object is null, returns false (this can always be overridden using a null + * check in the expression itself). + */ + public boolean hasPermission(Authentication authentication, Object domainObject, Object permission) { + if (domainObject == null) { + return false; + } + + ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); + + // Obtain the SIDs applicable to the principal + Sid[] sids = sidRetrievalStrategy.getSids(authentication); + Permission[] requiredPermission = resolvePermission(permission); + + try { + // Lookup only ACLs for SIDs we're interested in + Acl acl = aclService.readAclById(objectIdentity, sids); + + if (acl.isGranted(requiredPermission, sids, false)) { + if (logger.isDebugEnabled()) { + logger.debug("Access is granted"); + } + + return true; + } + + if (logger.isDebugEnabled()) { + logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal"); + } + + } catch (NotFoundException nfe) { + if (logger.isDebugEnabled()) { + logger.debug("Returning false - no ACLs apply for this principal"); + } + } + + return false; + } + + // TODO: Add permission resolver/PermissionFactory rewrite + Permission[] resolvePermission(Object permission) { + if (permission instanceof Integer) { + return new Permission[] {BasePermission.buildFromMask(((Integer)permission).intValue())}; + } + + if (permission instanceof Permission) { + return new Permission[] {(Permission)permission}; + } + + if (permission instanceof Permission[]) { + return (Permission[]) permission; + } + + if (permission instanceof String) { + String permString = (String)permission; + Permission p = BasePermission.buildFromName(permString); + + if (p == null) { + p = BasePermission.buildFromName(permString.toUpperCase()); + } + + if (p != null) { + return new Permission[] {p}; + } + + } + throw new IllegalArgumentException("unsupported permission: " + permission); + } + + public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { + this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; + } + + public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { + this.sidRetrievalStrategy = sidRetrievalStrategy; + } + +} diff --git a/acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java b/acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java new file mode 100644 index 0000000000..196779ec52 --- /dev/null +++ b/acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java @@ -0,0 +1,56 @@ +package org.springframework.security.acls; + +import static org.junit.Assert.*; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.Authentication; +import org.springframework.security.acls.objectidentity.ObjectIdentity; +import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategy; +import org.springframework.security.acls.sid.Sid; +import org.springframework.security.acls.sid.SidRetrievalStrategy; + +/** + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +public class AclPermissionEvaluatorTests { + Mockery jmock = new JUnit4Mockery(); + Authentication user; + private AclService service; + private ObjectIdentityRetrievalStrategy oidStrategy; + private SidRetrievalStrategy sidStrategy; + + @Before + public void setup() throws Exception { + user = jmock.mock(Authentication.class); + service = jmock.mock(AclService.class); + oidStrategy = jmock.mock(ObjectIdentityRetrievalStrategy.class); + sidStrategy = jmock.mock(SidRetrievalStrategy.class); + } + + @Test + public void hasPermissionReturnsTrueIfAclGrantsPermission() throws Exception { + AclPermissionEvaluator pe = new AclPermissionEvaluator(service); + final Acl acl = jmock.mock(Acl.class); + pe.setObjectIdentityRetrievalStrategy(oidStrategy); + pe.setSidRetrievalStrategy(sidStrategy); + + jmock.checking(new Expectations() {{ + ignoring(user); + ignoring(oidStrategy); + ignoring(sidStrategy); + oneOf(service).readAclById(with(any(ObjectIdentity.class)), with(any(Sid[].class))); + will(returnValue(acl)); + oneOf(acl).isGranted(with(any(Permission[].class)), with(any(Sid[].class)), with(equal(false))); + will(returnValue(true)); + }}); + + assertTrue(pe.hasPermission(user, new Object(), "read")); + } +}