diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java index 35ceed5c8ce..84e40eaaf72 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java @@ -45,6 +45,7 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { private final GrantedAuthority gaModifyAuditing; private final GrantedAuthority gaTakeOwnership; private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); + private SidFactory sidFactory = new DefaultSidFactory(); //~ Constructors =================================================================================================== @@ -83,7 +84,7 @@ public void securityCheck(Acl acl, int changeType) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // Check if authorized by virtue of ACL ownership - Sid currentUser = new PrincipalSid(authentication); + Sid currentUser = sidFactory.createPrincipal(authentication); if (currentUser.equals(acl.getOwner()) && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) { @@ -123,4 +124,8 @@ public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); this.sidRetrievalStrategy = sidRetrievalStrategy; } + + public void setSidFactory(SidFactory sidFactory) { + this.sidFactory = sidFactory; + } } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/DefaultSidFactory.java b/acl/src/main/java/org/springframework/security/acls/domain/DefaultSidFactory.java new file mode 100644 index 00000000000..fb6f88a025f --- /dev/null +++ b/acl/src/main/java/org/springframework/security/acls/domain/DefaultSidFactory.java @@ -0,0 +1,49 @@ +package org.springframework.security.acls.domain; + +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Creates the same SIDs as the SpringSecurity before the {@link SidFactory} was introduced, which means it creates a + * {@link PrincipalSid} and {@link GrantedAuthoritySid}. + * + * @author stanislav bashkirtsev + */ +public class DefaultSidFactory implements SidFactory { + /** + * {@inheritDoc} + */ + @Override + public Sid create(String sidName, boolean principal) { + if (principal) { + return new PrincipalSid(sidName); + } else { + return new GrantedAuthoritySid(sidName); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Sid createPrincipal(Authentication authentication) { + return new PrincipalSid(authentication); + } + + /** + * {@inheritDoc} + */ + @Override + public List createGrantedAuthorities(Collection grantedAuthorities) { + List sids = new ArrayList(); + for (GrantedAuthority authority : grantedAuthorities) { + sids.add(new GrantedAuthoritySid(authority)); + } + return sids; + } +} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java b/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java index e31f265c930..cbd210dd92a 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java @@ -54,13 +54,17 @@ public boolean equals(Object object) { } // Delegate to getGrantedAuthority() to perform actual comparison (both should be identical) - return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority()); + return ((GrantedAuthoritySid) object).getSidId().equals(this.getSidId()); } public int hashCode() { - return this.getGrantedAuthority().hashCode(); + return this.getSidId().hashCode(); } + /** + * @deprecated use {@link #getSidId()} instead + */ + @Deprecated public String getGrantedAuthority() { return grantedAuthority; } @@ -68,4 +72,20 @@ public String getGrantedAuthority() { public String toString() { return "GrantedAuthoritySid[" + this.grantedAuthority + "]"; } + + /** + * {@inheritDoc} + */ + @Override + public String getSidId() { + return grantedAuthority; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrincipal() { + return false; + } } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java b/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java index d9253c7302e..65b4cab5115 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java @@ -60,13 +60,17 @@ public boolean equals(Object object) { } // Delegate to getPrincipal() to perform actual comparison (both should be identical) - return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal()); + return ((PrincipalSid) object).getSidId().equals(this.getSidId()); } public int hashCode() { - return this.getPrincipal().hashCode(); + return this.getSidId().hashCode(); } + /** + * @deprecated use {@link #getSidId()} instead. + */ + @Deprecated public String getPrincipal() { return principal; } @@ -74,4 +78,20 @@ public String getPrincipal() { public String toString() { return "PrincipalSid[" + this.principal + "]"; } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrincipal() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String getSidId() { + return principal; + } } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/SidFactory.java b/acl/src/main/java/org/springframework/security/acls/domain/SidFactory.java new file mode 100644 index 00000000000..15e8ad5651e --- /dev/null +++ b/acl/src/main/java/org/springframework/security/acls/domain/SidFactory.java @@ -0,0 +1,39 @@ +package org.springframework.security.acls.domain; + +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; +import java.util.List; + +/** + * Typical application that uses Spring ACL won't need anything but {@link PrincipalSid} or {@link GrantedAuthoritySid}, + * but sometimes we need to extend this list of implementations or replace it for more complicated scenarios when + * default capabilities of Spring ACL is not enough. You can implement this factory and inject it into different classes + * that work with {@link Sid}s in order them to create your SIDs. + * + * @author stanislav bashkirtsev + */ +public interface SidFactory { + /** + * The Factory Method that creates a particular implementation of {@link Sid} depending on the arguments. + * + * @param sidName the name of the sid representing its unique identifier. In typical ACL database schema it's + * located in table {@code acl_sid} table, {@code sid} column. + * @param principal whether it's a user or granted authority like role + * @return the instance of Sid with the {@code sidName} as an identifier + */ + Sid create(String sidName, boolean principal); + + /** + * Creates a principal-like sid from the authentication information. + * + * @param authentication the authentication information that can provide principal and thus the sid's id will be + * dependant on the value inside + * @return a sid with the ID taken from the authentication information + */ + Sid createPrincipal(Authentication authentication); + + List createGrantedAuthorities(Collection grantedAuthorities); +} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java index 4c0c0d6a816..f1bc4f69cef 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java @@ -37,7 +37,7 @@ * @author Ben Alex */ public class SidRetrievalStrategyImpl implements SidRetrievalStrategy { - + private SidFactory sidFactory = new DefaultSidFactory(); private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); public SidRetrievalStrategyImpl() { @@ -53,13 +53,12 @@ public SidRetrievalStrategyImpl(RoleHierarchy roleHierarchy) { public List getSids(Authentication authentication) { Collection authorities = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()); List sids = new ArrayList(authorities.size() + 1); - - sids.add(new PrincipalSid(authentication)); - - for (GrantedAuthority authority : authorities) { - sids.add(new GrantedAuthoritySid(authority)); - } - + sids.add(sidFactory.createPrincipal(authentication)); + sids.addAll(sidFactory.createGrantedAuthorities(authorities)); return sids; } + + public void setSidFactory(SidFactory sidFactory) { + this.sidFactory = sidFactory; + } } diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java b/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java index ab469d10d46..2537349099d 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java @@ -32,16 +32,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.security.acls.domain.AccessControlEntryImpl; -import org.springframework.security.acls.domain.AclAuthorizationStrategy; -import org.springframework.security.acls.domain.AclImpl; -import org.springframework.security.acls.domain.AuditLogger; -import org.springframework.security.acls.domain.DefaultPermissionFactory; -import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; -import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.domain.PermissionFactory; -import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.domain.*; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclCache; @@ -122,6 +113,7 @@ public final class BasicLookupStrategy implements LookupStrategy { private String lookupPrimaryKeysWhereClause = DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE; private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE; private String orderByClause = DEFAULT_ORDER_BY_CLAUSE; + private SidFactory sidFactory = new DefaultSidFactory(); //~ Constructors =================================================================================================== @@ -557,13 +549,7 @@ private void convertCurrentResultIntoObject(Map acls, ResultS boolean entriesInheriting = rs.getBoolean("entries_inheriting"); Sid owner; - - if (rs.getBoolean("acl_principal")) { - owner = new PrincipalSid(rs.getString("acl_sid")); - } else { - owner = new GrantedAuthoritySid(rs.getString("acl_sid")); - } - + owner = sidFactory.create(rs.getString("acl_sid"), rs.getBoolean("acl_principal")); acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy, grantingStrategy, parentAcl, null, entriesInheriting, owner); @@ -574,14 +560,7 @@ private void convertCurrentResultIntoObject(Map acls, ResultS // It is permissible to have no ACEs in an ACL (which is detected by a null ACE_SID) if (rs.getString("ace_sid") != null) { Long aceId = new Long(rs.getLong("ace_id")); - Sid recipient; - - if (rs.getBoolean("ace_principal")) { - recipient = new PrincipalSid(rs.getString("ace_sid")); - } else { - recipient = new GrantedAuthoritySid(rs.getString("ace_sid")); - } - + Sid recipient = sidFactory.create(rs.getString("ace_sid"), rs.getBoolean("ace_principal")); int mask = rs.getInt("mask"); Permission permission = permissionFactory.buildFromMask(mask); boolean granting = rs.getBoolean("granting"); @@ -602,6 +581,10 @@ private void convertCurrentResultIntoObject(Map acls, ResultS } } + public void setSidFactory(SidFactory sidFactory) { + this.sidFactory = sidFactory; + } + private class StubAclParent implements Acl { private final Long id; diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java index 22d86d4cb47..f2f24baf842 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java @@ -22,10 +22,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.security.acls.domain.AccessControlEntryImpl; -import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.domain.*; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclCache; @@ -80,6 +77,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS private String selectSidPrimaryKey = "select id from acl_sid where principal=? and sid=?"; private String updateObjectIdentity = "update acl_object_identity set " + "parent_object = ?, owner_sid = ?, entries_inheriting = ?" + " where id = ?"; + private SidFactory sidFactory = new DefaultSidFactory(); //~ Constructors =================================================================================================== @@ -101,7 +99,7 @@ public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsE // Need to retrieve the current principal, in order to know who "owns" this ACL (can be changed later on) Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - PrincipalSid sid = new PrincipalSid(auth); + Sid sid = sidFactory.createPrincipal(auth); // Create the acl_object_identity row createObjectIdentity(objectIdentity, sid); @@ -195,16 +193,8 @@ protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { Assert.notNull(sid, "Sid required"); String sidName; - boolean sidIsPrincipal = true; - - if (sid instanceof PrincipalSid) { - sidName = ((PrincipalSid) sid).getPrincipal(); - } else if (sid instanceof GrantedAuthoritySid) { - sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); - sidIsPrincipal = false; - } else { - throw new IllegalArgumentException("Unsupported implementation of Sid"); - } + sidName = sid.getSidId(); + boolean sidIsPrincipal = sid.isPrincipal(); List sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey, new Object[] {Boolean.valueOf(sidIsPrincipal), sidName}, Long.class); @@ -430,4 +420,8 @@ public void setUpdateObjectIdentity(String updateObjectIdentity) { public void setForeignKeysInDatabase(boolean foreignKeysInDatabase) { this.foreignKeysInDatabase = foreignKeysInDatabase; } + + public void setSidFactory(SidFactory sidFactory) { + this.sidFactory = sidFactory; + } } diff --git a/acl/src/main/java/org/springframework/security/acls/model/Sid.java b/acl/src/main/java/org/springframework/security/acls/model/Sid.java index 2fb06332358..31bfe2d0d85 100644 --- a/acl/src/main/java/org/springframework/security/acls/model/Sid.java +++ b/acl/src/main/java/org/springframework/security/acls/model/Sid.java @@ -31,7 +31,14 @@ * @author Ben Alex */ public interface Sid extends Serializable { - //~ Methods ======================================================================================================== + boolean isPrincipal(); + /** + * Gets the unique identifier of the SID (usually a database ID of the entity). It is string since the ACL tables + * require this. + * + * @return the unique identifier of the SID (usually a database ID of the entity) + */ + String getSidId(); /** * Refer to the java.lang.Object documentation for the interface contract. diff --git a/acl/src/test/java/org/springframework/security/acls/domain/DefaultSidFactoryTest.java b/acl/src/test/java/org/springframework/security/acls/domain/DefaultSidFactoryTest.java new file mode 100644 index 00000000000..5f64bbe7d4a --- /dev/null +++ b/acl/src/test/java/org/springframework/security/acls/domain/DefaultSidFactoryTest.java @@ -0,0 +1,67 @@ +package org.springframework.security.acls.domain; + +import org.junit.Test; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author stanislav bashkirtsev + */ +public class DefaultSidFactoryTest { + DefaultSidFactory sut = new DefaultSidFactory(); + + @Test + public void testCreate_principal() throws Exception { + Sid sid = sut.create("sid", true); + assertTrue(sid instanceof PrincipalSid); + assertEquals("sid", sid.getSidId()); + } + + @Test + public void testCreate_grantedAuthority() throws Exception { + Sid sid = sut.create("sid", false); + assertTrue((sid instanceof GrantedAuthoritySid)); + assertEquals("sid", sid.getSidId()); + } + + @Test + public void testCreatePrincipal_anonymous() throws Exception { + Authentication authentication = new AnonymousAuthenticationToken("key", "a", Arrays.asList(new SimpleGrantedAuthority("ROLE"))); + Sid principal = sut.createPrincipal(authentication); + assertTrue(principal instanceof PrincipalSid); + assertEquals("a", principal.getSidId()); + } + + @Test + public void testCreatePrincipal() throws Exception { + Authentication authentication = new UsernamePasswordAuthenticationToken("name", "credentials"); + Sid principal = sut.createPrincipal(authentication); + assertTrue(principal instanceof PrincipalSid); + assertEquals("name", principal.getSidId()); + } + + @Test + public void testCreateGrantedAuthorities() throws Exception { + List sids = sut.createGrantedAuthorities(Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); + assertEquals(1, sids.size()); + assertTrue(sids.get(0) instanceof GrantedAuthoritySid); + assertEquals("ROLE_USER", sids.get(0).getSidId()); + } + + @Test + public void testCreateGrantedAuthorities_withEmptyList() throws Exception { + List sids = sut.createGrantedAuthorities(new ArrayList()); + assertTrue(sids.isEmpty()); + } +}