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 ccb756b11a..57629eeefa 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 @@ -14,6 +14,7 @@ */ package org.springframework.security.acls.jdbc; +import java.io.Serializable; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -53,12 +54,14 @@ import org.springframework.util.Assert; /** - * Performs lookups in a manner that is compatible with ANSI SQL.

NB: This implementation does attempt to provide - * reasonably optimised lookups - within the constraints of a normalised database and standard ANSI SQL features. If - * you are willing to sacrifice either of these constraints (eg use a particular database feature such as hierarchical - * queries or materalized views, or reduce normalisation) you are likely to achieve better performance. In such - * situations you will need to provide your own custom LookupStrategy. This class does not support - * subclassing, as it is likely to change in future releases and therefore subclassing is unsupported.

+ * Performs lookups in a manner that is compatible with ANSI SQL. + *

+ * NB: This implementation does attempt to provide reasonably optimised lookups - within the constraints of a normalised + * database and standard ANSI SQL features. If you are willing to sacrifice either of these constraints + * (e.g. use a particular database feature such as hierarchical queries or materalized views, or reduce normalisation) + * you are likely to achieve better performance. In such situations you will need to provide your own custom + * LookupStrategy. This class does not support subclassing, as it is likely to change in future releases + * and therefore subclassing is unsupported. * * @author Ben Alex * @version $Id$ @@ -72,6 +75,9 @@ public final class BasicLookupStrategy implements LookupStrategy { private JdbcTemplate jdbcTemplate; private int batchSize = 50; + private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); + private final Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class, "acl"); + //~ Constructors =================================================================================================== /** @@ -87,18 +93,20 @@ public final class BasicLookupStrategy implements LookupStrategy { Assert.notNull(aclCache, "AclCache required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); Assert.notNull(auditLogger, "AuditLogger required"); - this.jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate = new JdbcTemplate(dataSource); this.aclCache = aclCache; this.aclAuthorizationStrategy = aclAuthorizationStrategy; this.auditLogger = auditLogger; + fieldAces.setAccessible(true); + fieldAcl.setAccessible(true); } //~ Methods ======================================================================================================== private static String computeRepeatingSql(String repeatingSql, int requiredRepetitions) { - Assert.isTrue(requiredRepetitions >= 1, "Must be => 1"); + assert requiredRepetitions > 0 : "requiredRepetitions must be > 0"; - String startSql = "select acl_object_identity.object_id_identity, " + final String startSql = "select acl_object_identity.object_id_identity, " + "acl_entry.ace_order, " + "acl_object_identity.id as acl_id, " + "acl_object_identity.parent_object, " @@ -114,29 +122,30 @@ public final class BasicLookupStrategy implements LookupStrategy { + "acli_sid.sid as acl_sid, " + "acl_class.class " + "from acl_object_identity " - + "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid " + + "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid " + "left join acl_class on acl_class.id = acl_object_identity.object_id_class " + "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity " + "left join acl_sid on acl_entry.sid = acl_sid.id " + "where ( "; - String endSql = ") order by acl_object_identity.object_id_identity" + final String endSql = ") order by acl_object_identity.object_id_identity" + " asc, acl_entry.ace_order asc"; - StringBuffer sqlStringBuffer = new StringBuffer(); - sqlStringBuffer.append(startSql); + StringBuilder sqlStringBldr = + new StringBuilder(startSql.length() + endSql.length() + requiredRepetitions * (repeatingSql.length() + 4)); + sqlStringBldr.append(startSql); for (int i = 1; i <= requiredRepetitions; i++) { - sqlStringBuffer.append(repeatingSql); + sqlStringBldr.append(repeatingSql); if (i != requiredRepetitions) { - sqlStringBuffer.append(" or "); + sqlStringBldr.append(" or "); } } - sqlStringBuffer.append(endSql); + sqlStringBldr.append(endSql); - return sqlStringBuffer.toString(); + return sqlStringBldr.toString(); } /** @@ -170,115 +179,52 @@ public final class BasicLookupStrategy implements LookupStrategy { auditLogger, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner()); // Copy the "aces" from the input to the destination - Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); - Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class, "acl"); - - try { - fieldAces.setAccessible(true); - fieldAcl.setAccessible(true); - - // Obtain the "aces" from the input ACL - List aces = (List) fieldAces.get(inputAcl); - // Create a list in which to store the "aces" for the "result" AclImpl instance - List acesNew = new ArrayList(); + // Obtain the "aces" from the input ACL + List aces = readAces(inputAcl); - // Iterate over the "aces" input and replace each nested AccessControlEntryImpl.getAcl() with the new "result" AclImpl instance - // This ensures StubAclParent instances are removed, as per SEC-951 - for (AccessControlEntryImpl ace : aces) { - fieldAcl.set(ace, result); - acesNew.add(ace); - } + // Create a list in which to store the "aces" for the "result" AclImpl instance + List acesNew = new ArrayList(); - // Finally, now that the "aces" have been converted to have the "result" AclImpl instance, modify the "result" AclImpl instance - fieldAces.set(result, acesNew); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("Could not obtain or set AclImpl or AccessControlEntryImpl fields"); + // Iterate over the "aces" input and replace each nested AccessControlEntryImpl.getAcl() with the new "result" AclImpl instance + // This ensures StubAclParent instances are removed, as per SEC-951 + for (AccessControlEntryImpl ace : aces) { + setAclOnAce(ace, result); + acesNew.add(ace); } + // Finally, now that the "aces" have been converted to have the "result" AclImpl instance, modify the "result" AclImpl instance + setAces(result, acesNew); return result; } - /** - * Accepts the current ResultSet row, and converts it into an AclImpl that - * contains a StubAclParent - * - * @param acls the Map we should add the converted Acl to - * @param rs the ResultSet focused on a current row - * - * @throws SQLException if something goes wrong converting values - */ - private void convertCurrentResultIntoObject(Map acls, ResultSet rs) throws SQLException { - Long id = new Long(rs.getLong("acl_id")); - - // If we already have an ACL for this ID, just create the ACE - AclImpl acl = (AclImpl) acls.get(id); - - if (acl == null) { - // Make an AclImpl and pop it into the Map - ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("class"), - new Long(rs.getLong("object_id_identity"))); - - Acl parentAcl = null; - long parentAclId = rs.getLong("parent_object"); - - if (parentAclId != 0) { - parentAcl = new StubAclParent(new Long(parentAclId)); - } - - 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")); - } - - acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy, auditLogger, parentAcl, null, - entriesInheriting, owner); - acls.put(id, acl); + @SuppressWarnings("unchecked") + private List readAces(AclImpl acl) { + try { + return (List) fieldAces.get(acl); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not obtain AclImpl.aces field", e); } + } - // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order) - // It is permissable 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")); - } - - int mask = rs.getInt("mask"); - Permission permission = convertMaskIntoPermission(mask); - boolean granting = rs.getBoolean("granting"); - boolean auditSuccess = rs.getBoolean("audit_success"); - boolean auditFailure = rs.getBoolean("audit_failure"); - - AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting, - auditSuccess, auditFailure); - - Field acesField = FieldUtils.getField(AclImpl.class, "aces"); - List aces; - - try { - acesField.setAccessible(true); - aces = (List) acesField.get(acl); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("Could not obtain AclImpl.ace field: cause[" + ex.getMessage() + "]"); - } + private void setAclOnAce(AccessControlEntryImpl ace, AclImpl acl) { + try { + fieldAcl.set(ace, acl); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not or set AclImpl on AccessControlEntryImpl fields", e); + } + } - // Add the ACE if it doesn't already exist in the ACL.aces field - if (!aces.contains(ace)) { - aces.add(ace); - } + private void setAces(AclImpl acl, List aces) { + try { + fieldAces.set(acl, aces); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not set AclImpl entries", e); } } + protected Permission convertMaskIntoPermission(int mask) { return BasePermission.buildFromMask(mask); } @@ -291,22 +237,20 @@ public final class BasicLookupStrategy implements LookupStrategy { * @param findNow Long-based primary keys to retrieve * @param sids */ - private void lookupPrimaryKeys(final Map acls, final Set findNow, final List sids) { + private void lookupPrimaryKeys(final Map acls, final Set findNow, final List sids) { Assert.notNull(acls, "ACLs are required"); Assert.notEmpty(findNow, "Items to find now required"); String sql = computeRepeatingSql("(acl_object_identity.id = ?)", findNow.size()); - Set parentsToLookup = (Set) jdbcTemplate.query(sql, + Set parentsToLookup = jdbcTemplate.query(sql, new PreparedStatementSetter() { - public void setValues(PreparedStatement ps) - throws SQLException { - Iterator iter = findNow.iterator(); + public void setValues(PreparedStatement ps) throws SQLException { int i = 0; - while (iter.hasNext()) { + for (Long toFind : findNow) { i++; - ps.setLong(i, ((Long) iter.next()).longValue()); + ps.setLong(i, toFind); } } }, new ProcessResultSet(acls, sids)); @@ -466,11 +410,11 @@ public final class BasicLookupStrategy implements LookupStrategy { //~ Inner Classes ================================================================================================== - private class ProcessResultSet implements ResultSetExtractor { - private Map acls; + private class ProcessResultSet implements ResultSetExtractor> { + private Map acls; private List sids; - public ProcessResultSet(Map acls, List sids) { + public ProcessResultSet(Map acls, List sids) { Assert.notNull(acls, "ACLs cannot be null"); this.acls = acls; this.sids = sids; // can be null @@ -487,7 +431,7 @@ public final class BasicLookupStrategy implements LookupStrategy { * @throws SQLException * @throws DataAccessException */ - public Object extractData(ResultSet rs) throws SQLException, DataAccessException { + public Set extractData(ResultSet rs) throws SQLException, DataAccessException { Set parentIdsToLookup = new HashSet(); // Set of parent_id Longs while (rs.next()) { @@ -516,9 +460,82 @@ public final class BasicLookupStrategy implements LookupStrategy { } } - // Return the parents left to lookup to the calller + // Return the parents left to lookup to the caller return parentIdsToLookup; } + + /** + * Accepts the current ResultSet row, and converts it into an AclImpl that + * contains a StubAclParent + * + * @param acls the Map we should add the converted Acl to + * @param rs the ResultSet focused on a current row + * + * @throws SQLException if something goes wrong converting values + */ + private void convertCurrentResultIntoObject(Map acls, ResultSet rs) throws SQLException { + Long id = new Long(rs.getLong("acl_id")); + + // If we already have an ACL for this ID, just create the ACE + Acl acl = acls.get(id); + + if (acl == null) { + // Make an AclImpl and pop it into the Map + ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("class"), + new Long(rs.getLong("object_id_identity"))); + + Acl parentAcl = null; + long parentAclId = rs.getLong("parent_object"); + + if (parentAclId != 0) { + parentAcl = new StubAclParent(new Long(parentAclId)); + } + + 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")); + } + + acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy, auditLogger, parentAcl, null, + entriesInheriting, owner); + + acls.put(id, acl); + } + + // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order) + // It is permissable 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")); + } + + int mask = rs.getInt("mask"); + Permission permission = convertMaskIntoPermission(mask); + boolean granting = rs.getBoolean("granting"); + boolean auditSuccess = rs.getBoolean("audit_success"); + boolean auditFailure = rs.getBoolean("audit_failure"); + + AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting, + auditSuccess, auditFailure); + + //Field acesField = FieldUtils.getField(AclImpl.class, "aces"); + List aces = readAces((AclImpl)acl); + + // Add the ACE if it doesn't already exist in the ACL.aces field + if (!aces.contains(ace)) { + aces.add(ace); + } + } + } } private class StubAclParent implements Acl { diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java index 70420b97ad..77e3bab9a3 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java @@ -73,9 +73,9 @@ public class JdbcAclService implements AclService { public List findChildren(ObjectIdentity parentIdentity) { Object[] args = {parentIdentity.getIdentifier(), parentIdentity.getJavaType().getName()}; - List objects = jdbcTemplate.query(selectAclObjectWithParent, args, - new RowMapper() { - public Object mapRow(ResultSet rs, int rowNum) + List objects = jdbcTemplate.query(selectAclObjectWithParent, args, + new RowMapper() { + public ObjectIdentity mapRow(ResultSet rs, int rowNum) throws SQLException { String javaType = rs.getString("class"); Long identifier = new Long(rs.getLong("obj_id")); diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java index 0226d91e5f..aa58c8b175 100644 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java +++ b/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java @@ -41,10 +41,13 @@ import org.springframework.util.FileCopyUtils; * @author Andrei Stefan */ public class BasicLookupStrategyTests { + + private static final Sid BEN_SID = new PrincipalSid("ben"); + //~ Instance fields ================================================================================================ private static JdbcTemplate jdbcTemplate; - private LookupStrategy strategy; + private BasicLookupStrategy strategy; private static TestDataSource dataSource; private static CacheManager cacheManager; @@ -265,10 +268,10 @@ public class BasicLookupStrategyTests { // First lookup only child, thus populating the cache with grandParent, parent1 and child List checkPermission = Arrays.asList(BasePermission.READ); - List sids = Arrays.asList((Sid)new PrincipalSid("ben")); + List sids = Arrays.asList(BEN_SID); List childOids = Arrays.asList(childOid); - ((BasicLookupStrategy) this.strategy).setBatchSize(6); + strategy.setBatchSize(6); Map foundAcls = strategy.readAclsById(childOids, sids); Acl foundChildAcl = (Acl) foundAcls.get(childOid); @@ -290,4 +293,15 @@ public class BasicLookupStrategyTests { Assert.assertTrue(foundParent2Acl.isGranted(checkPermission, sids, false)); } + @Test(expected=IllegalArgumentException.class) + public void nullOwnerIsNotSupported() { + String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,null,1);"; + + jdbcTemplate.execute(query); + + ObjectIdentity oid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(104)); + + strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID)); + } + }