@ -14,6 +14,7 @@
@@ -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;
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 + + ) {
sqlStringBuffe r . append ( repeatingSql ) ;
sqlStringBld r . append ( repeatingSql ) ;
if ( i ! = requiredRepetitions ) {
sqlStringBuffe r . append ( " or " ) ;
sqlStringBld r . append ( " or " ) ;
}
}
sqlStringBuffe r . append ( endSql ) ;
sqlStringBld r . append ( endSql ) ;
return sqlStringBuffe r . toString ( ) ;
return sqlStringBld r . toString ( ) ;
}
/ * *
@ -170,115 +179,52 @@ public final class BasicLookupStrategy implements LookupStrategy {
@@ -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 = ( 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 > ( ) ;
// Obtain the "aces" from the input ACL
List < AccessControlEntryImpl > 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 < AccessControlEntryImpl > acesNew = new ArrayList < AccessControlEntryImpl > ( ) ;
// 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 ) ;
@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 ) ;
}
}
// 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 ( ) + "]" ) ;
}
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 < 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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -516,9 +460,82 @@ public final class BasicLookupStrategy implements LookupStrategy {
}
}
// Return the parents left to lookup to the calll er
// 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 {