SEC-1081: Minor Acl refactoring to remove generic warnings. Minor efficiency improvements.

This commit is contained in:
Luke Taylor 2009-04-21 02:54:17 +00:00
parent ff3a9c7030
commit 3adbbdf50d
3 changed files with 163 additions and 132 deletions

View File

@ -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.<p>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 <code>LookupStrategy</code>. This class does not support
* subclassing, as it is likely to change in future releases and therefore subclassing is unsupported.</p>
* Performs lookups in a manner that is compatible with ANSI SQL.
* <p>
* 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
* <code>LookupStrategy</code>. 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<AccessControlEntryImpl> aces = readAces(inputAcl);
// Obtain the "aces" from the input ACL
List<AccessControlEntryImpl> aces = (List<AccessControlEntryImpl>) fieldAces.get(inputAcl);
// Create a list in which to store the "aces" for the "result" AclImpl instance
List<AccessControlEntryImpl> acesNew = new ArrayList<AccessControlEntryImpl>();
// Create a list in which to store the "aces" for the "result" AclImpl instance
List<AccessControlEntryImpl> acesNew = new ArrayList<AccessControlEntryImpl>();
// 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);
}
// 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 <code>ResultSet</code> row, and converts it into an <code>AclImpl</code> that
* contains a <code>StubAclParent</code>
*
* @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<Long,AclImpl> 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);
}
// 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<AccessControlEntryImpl> aces;
try {
acesField.setAccessible(true);
aces = (List<AccessControlEntryImpl>) acesField.get(acl);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Could not obtain AclImpl.ace field: cause[" + ex.getMessage() + "]");
}
// Add the ACE if it doesn't already exist in the ACL.aces field
if (!aces.contains(ace)) {
aces.add(ace);
}
@SuppressWarnings("unchecked")
private List<AccessControlEntryImpl> readAces(AclImpl acl) {
try {
return (List<AccessControlEntryImpl>) fieldAces.get(acl);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not obtain AclImpl.aces field", e);
}
}
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);
}
}
private void setAces(AclImpl acl, List<AccessControlEntryImpl> 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<Sid> sids) {
private void lookupPrimaryKeys(final Map<Serializable, Acl> acls, final Set<Long> findNow, final List<Sid> 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<Long> 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<Set<Long>> {
private Map<Serializable, Acl> acls;
private List<Sid> sids;
public ProcessResultSet(Map acls, List<Sid> sids) {
public ProcessResultSet(Map<Serializable, Acl> acls, List<Sid> 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<Long> extractData(ResultSet rs) throws SQLException, DataAccessException {
Set<Long> parentIdsToLookup = new HashSet<Long>(); // 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 <code>ResultSet</code> row, and converts it into an <code>AclImpl</code> that
* contains a <code>StubAclParent</code>
*
* @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<Serializable, Acl> 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<AccessControlEntryImpl> 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 {

View File

@ -73,9 +73,9 @@ public class JdbcAclService implements AclService {
public List<ObjectIdentity> 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<ObjectIdentity> objects = jdbcTemplate.query(selectAclObjectWithParent, args,
new RowMapper<ObjectIdentity>() {
public ObjectIdentity mapRow(ResultSet rs, int rowNum)
throws SQLException {
String javaType = rs.getString("class");
Long identifier = new Long(rs.getLong("obj_id"));

View File

@ -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<Permission> checkPermission = Arrays.asList(BasePermission.READ);
List<Sid> sids = Arrays.asList((Sid)new PrincipalSid("ben"));
List<Sid> sids = Arrays.asList(BEN_SID);
List<ObjectIdentity> childOids = Arrays.asList(childOid);
((BasicLookupStrategy) this.strategy).setBatchSize(6);
strategy.setBatchSize(6);
Map<ObjectIdentity, Acl> 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));
}
}