- * FIXME this might not be necessary.
+ * For cases where don't have a cascade but the other end is securable, so we must check the association.
+ * For example, when we persist an EE we also persist any new ADs in the same transaction. Thus the ADs need ACL
+ * attention at the same time (via the BioAssays).
*
- * @param acl may be modified by this call
- * @param parentAcl value may be changed by this call
- * @param sid value may be changed by this call
+ * @param object we are checking
+ * @param property of the object
+ * @return true if the association should be followed (even though it might not be based on cascade status)
*/
- protected void createOrUpdateAclSpecialCases( MutableAcl acl, @Nullable Acl parentAcl, Sid sid, Securable object ) {
- }
-
- protected boolean currentUserIsAdmin() {
- return SecurityUtil.isUserAdmin();
- }
-
- protected boolean currentUserIsAnonymous() {
- return SecurityUtil.isUserAnonymous();
- }
-
- protected boolean currentUserIsRunningAsAdmin() {
- return SecurityUtil.isRunningAsAdmin();
+ protected boolean canFollowAssociation( Object object, String property ) {
+ return false;
}
/**
- * For use by other overridden methods.
+ * For cases in which the object is not a SecuredChild, but we still want to erase ACEs on it when it has a parent.
+ * Implementers will check the class of the object, and the class of the parent (e.g. using Class.forName(
+ * parentAcl.getObjectIdentity().getType() )) and decide what to do.
+ *
+ * @return false if ACEs should be retained. True if ACEs should be removed (if possible).
*/
- protected final AclService getAclService() {
- return aclService;
+ protected boolean canRemoveAcesFromChild( Securable object, Acl parentAcl ) {
+ return false;
}
- protected abstract GrantedAuthority getUserGroupGrantedAuthority( Securable object );
-
- protected abstract String getUserName( Securable object );
-
/**
- * Called during create. Default implementation returns null unless the object is a SecuredChild, in which case it
- * returns the value of s.getSecurityOwner() (which will be null unless it is filled in)
+ * Indicate if the given object should not be made public immediately on creation by administrators.
*
- * For some cases, we want to find the parent so we don't have to rely on updates later to catch it and fill it in. - * Implementers must decide which cases can be handled this way. Care is required: the parent might not be created - * yet, in which case the cascade to s is surely going to fix it later. The best situation is when s has an accessor - * to reach the parent. + * The default implementation returns true if the object is a {@link SecuredChild}; otherwise false. * - * @param s which might have a parent already in the system + * @return true if it's a special case to be kept private on creation. */ - protected Acl locateParentAcl( SecuredChild s ) { - Securable parent = locateSecuredParent( s ); - - if ( parent != null ) return this.getAclService().readAclById( makeObjectIdentity( parent ) ); - - return null; + protected boolean isKeepPrivateOnCreation( Securable object ) { + return object instanceof SecuredChild; } - /** - * Forms the object identity to be inserted in acl_object_identity table. Note that this does not add an - * ObjectIdentity to the database; it just calls 'new'. - * - * @param object A persistent object - * @return object identity. - */ - protected final ObjectIdentity makeObjectIdentity( Securable object ) { - return objectIdentityRetrievalStrategy.getObjectIdentity( object ); - } + private void doAclAdvice( JoinPoint jp, @Nullable Object retValue, AclMode aclMode ) { + final Object[] args = jp.getArgs(); + Signature signature = jp.getSignature(); + final String methodName = signature.getName(); - protected abstract boolean objectIsUser( Securable object ); + assert args != null; + Object persistentObject; + if ( aclMode == AclMode.UPDATE || aclMode == AclMode.DELETE ) { + if ( args.length >= 1 ) { + persistentObject = args[0]; + if ( persistentObject == null ) { + log.warn( "First argument of " + jp + " is null, cannot update/delete ACLs." ); + return; + } + } else { + log.warn( jp + " has no argument; cannot update/delete ACLs." ); + return; + } + } else { + // SAVE and CREATE return a value + if ( retValue != null ) { + persistentObject = retValue; + } else { + log.warn( jp + " returned null, cannot create/update ACLs." ); + return; + } + } - protected abstract boolean objectIsUserGroup( Securable object ); + for ( Securable securable : extractSecurables( persistentObject ) ) { + process( securable, methodName, aclMode ); + } + } /** - * Called when objects are first created in the system and need their permissions initialized. Insert the access - * control entries that all objects should have (unless they inherit from another object). - *
- * Default implementation does the following: - *
Class.forName(
- * parentAcl.getObjectIdentity().getType() )) and decide what to do.
- *
- * @return false if ACEs should be retained. True if ACEs should be removed (if possible).
- */
- protected boolean specialCaseToAllowRemovingAcesFromChild( Securable object, Acl parentAcl ) {
- return false;
- }
+ if ( log.isDebugEnabled() ) {
+ log.debug( "Deleting ACL for " + object );
+ }
- /**
- * Indicate if the given object should not be made public immediately on creation by administrators.
- * - * The default implementation returns true if the object is a {@link SecuredChild}; otherwise false. - * - * @return true if it's a special case to be kept private on creation. - */ - protected boolean specialCaseToKeepPrivateOnCreation( Securable object ) { - return object instanceof SecuredChild; + /* + * This deletes children with the second parameter = true. + */ + aclService.deleteAcl( oi, true ); } /** @@ -324,14 +311,14 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab } if ( log.isTraceEnabled() ) log.trace( "Checking for ACLS on " + object ); - ObjectIdentity oi = makeObjectIdentity( object ); + ObjectIdentity oi = objectIdentityRetrievalStrategy.getObjectIdentity( object ); boolean create = false; if ( acl == null ) { // usually create, but could be update. try { // this is probably redundant. We shouldn't have ACLs already. - acl = ( MutableAcl ) getAclService().readAclById( oi ); // throws exception if not found + acl = ( MutableAcl ) aclService.readAclById( oi ); // throws exception if not found /* * If we get here, we're in update mode after all. Could be findOrCreate, or could be a second pass that * will let us fill in parent ACLs for associated objects missed earlier in a persist cycle. E.g. @@ -345,7 +332,7 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab } } catch ( NotFoundException nfe ) { // the current user will be the owner. - acl = getAclService().createAcl( oi ); + acl = aclService.createAcl( oi ); create = true; assert acl != null; assert acl.getOwner() != null; @@ -366,15 +353,11 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab AclPrincipalSid sid = new AclPrincipalSid( p.toString() ); - boolean isAdmin = currentUserIsAdmin(); - - boolean isRunningAsAdmin = currentUserIsRunningAsAdmin(); + boolean isAdmin = SecurityUtil.isUserAdmin(); - boolean objectIsAUser = objectIsUser( object ); + boolean isRunningAsAdmin = SecurityUtil.isRunningAsAdmin(); - boolean objectIsAGroup = objectIsUserGroup( object ); - - boolean keepPrivateEvenWhenAdmin = this.specialCaseToKeepPrivateOnCreation( object ); + boolean keepPrivateEvenWhenAdmin = this.isKeepPrivateOnCreation( object ); /* * The only case where we absolutely disallow inheritance is for SecuredNotChild. @@ -400,8 +383,8 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab /* * Make sure user groups can be read by future members of the group */ - if ( objectIsAGroup ) { - GrantedAuthority ga = getUserGroupGrantedAuthority( object ); + if ( object instanceof UserGroup ) { + GrantedAuthority ga = getUserGroupGrantedAuthority( ( UserGroup ) object ); if ( log.isDebugEnabled() ) log.debug( "Making group readable by " + ga + ": " + oi ); grant( acl, BasePermission.READ, new AclGrantedAuthoritySid( ga ) ); } @@ -417,8 +400,8 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab * fact, user creation runs with RUN_AS_ADMIN privileges. */ - if ( create && objectIsAUser ) { - String userName = getUserName( object ); + if ( create && object instanceof User ) { + String userName = ( ( User ) object ).getUserName(); if ( sid.getPrincipal().equals( userName ) ) { /* * This case should actually never happen. "we" are the user who is creating this user. We've already @@ -449,8 +432,6 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab } } - createOrUpdateAclSpecialCases( acl, parentAcl, sid, object ); - /* * Only the owner or an administrator can do these operations, and only in those cases would they be necessary * anyway (primarily in creating the objects in the first place, there's nearly no conceivable reason to change @@ -474,79 +455,91 @@ private void addOrUpdateAcl( @Nullable MutableAcl acl, Securable object, @Nullab } // finalize. - getAclService().updateAcl( acl ); + aclService.updateAcl( acl ); + } + private GrantedAuthority getUserGroupGrantedAuthority( UserGroup object ) { + Collection extends GroupAuthority> authorities = object.getAuthorities(); + assert authorities.size() == 1; + return new SimpleGrantedAuthority( authorities.iterator().next().getAuthority() ); } /** - * Determine which ACL is going to be the parent of the associations of the given object. + * Called when objects are first created in the system and need their permissions initialized. Insert the access + * control entries that all objects should have (unless they inherit from another object). *
- * If the object is a SecuredNotChild, then it will be treated as the parent. For example, ArrayDesigns associated - * with an Experiment has 'parent status' for securables associated with the AD, such as LocalFiles. + * Default implementation does the following: + *
+ * If the object is a SecuredNotChild, then it will be treated as the parent. For example, ArrayDesigns associated + * with an Experiment has 'parent status' for securables associated with the AD, such as LocalFiles. */ - private void deleteAcl( Securable object ) throws DataAccessException, IllegalArgumentException { - ObjectIdentity oi = makeObjectIdentity( object ); - - if ( oi == null ) { - log.warn( "Null object identity for : " + object ); - } - - if ( log.isDebugEnabled() ) { - log.debug( "Deleting ACL for " + object ); + @Nullable + private Acl chooseParentForAssociations( Object object, @Nullable Acl previousParent ) { + if ( object instanceof SecuredNotChild + || ( previousParent == null && object instanceof Securable && !( object instanceof SecuredChild ) ) ) { + return getAcl( ( Securable ) object ); + } else { + // Keep the previous parent. This means we 'pass through' and the parent is basically going to be the + // top-most object: there isn't a hierarchy of parenthood. This also means that the parent might be kept as + // null. + return previousParent; } - - /* - * This deletes children with the second parameter = true. - */ - this.getAclService().deleteAcl( oi, true ); } + @Nullable private MutableAcl getAcl( Securable s ) { ObjectIdentity oi = objectIdentityRetrievalStrategy.getObjectIdentity( s ); - try { - return ( MutableAcl ) getAclService().readAclById( oi ); + return ( MutableAcl ) aclService.readAclById( oi ); } catch ( NotFoundException e ) { return null; } } - private Object getPersistentObject( Object retValue, String methodName, Object[] args ) { - if ( methodIsDelete( methodName ) || methodIsUpdate( methodName ) ) { - - /* - * Only deal with single-argument update methods. MIGHT WANT TO RETURN THE FIRST ARGUMENT - */ - if ( args.length > 1 ) return null; - - assert args.length > 0; - return args[0]; - } - return retValue; - } - /** * Add ACE granting permission to sid to ACL (does not persist the change, you have to call update!) * @@ -558,27 +551,6 @@ private void grant( MutableAcl acl, Permission permission, Sid sid ) { acl.insertAce( acl.getEntries().size(), permission, sid, true ); } - private boolean isIneligibleForAcl( Object c ) { - return !( c instanceof Securable ); - } - - /** - * Recursively locate the actual secured parent. - */ - private Securable locateSecuredParent( SecuredChild s ) { - if ( s.getSecurityOwner() == null ) { - return null; - - } - Securable securityOwner = s.getSecurityOwner(); - - if ( securityOwner instanceof SecuredChild ) { - return locateSecuredParent( ( SecuredChild ) securityOwner ); - } - return securityOwner; - - } - /** * When setting the parent, we check to see if we can delete the ACEs on the 'child', if any. This is because we * want permissions to be managed by the parent, so ACEs on the child are redundant and possibly a source of later @@ -597,13 +569,13 @@ private boolean maybeClearACEsOnChild( Securable object, MutableAcl childAcl, @N int aceCount = childAcl.getEntries().size(); if ( aceCount == 0 ) { - if ( parentAcl.getEntries().size() == 0 ) { + if ( parentAcl.getEntries().isEmpty() ) { throw new IllegalStateException( "Either the child or the parent has to have ACEs" ); } return false; } - boolean force = specialCaseToAllowRemovingAcesFromChild( object, parentAcl ); + boolean force = canRemoveAcesFromChild( object, parentAcl ); if ( parentAcl.getEntries().size() == aceCount || force ) { @@ -635,7 +607,7 @@ private boolean maybeClearACEsOnChild( Securable object, MutableAcl childAcl, @N if ( log.isTraceEnabled() ) log.trace( "Erasing ACEs from child " + object ); - while ( childAcl.getEntries().size() > 0 ) { + while ( !childAcl.getEntries().isEmpty() ) { childAcl.deleteAce( 0 ); } @@ -670,7 +642,7 @@ private boolean maybeClearACEsOnChild( Securable object, MutableAcl childAcl, @N * @param parentAcl - the potential parent */ private void maybeSetParentACL( final Securable object, MutableAcl childAcl, @Nullable final Acl parentAcl ) { - if ( parentAcl != null && !SecuredNotChild.class.isAssignableFrom( object.getClass() ) ) { + if ( parentAcl != null && !( object instanceof SecuredNotChild ) ) { Acl currentParentAcl = childAcl.getParentAcl(); @@ -691,44 +663,18 @@ private void maybeSetParentACL( final Securable object, MutableAcl childAcl, @Nu boolean clearedACEs = maybeClearACEsOnChild( object, childAcl, parentAcl ); if ( changedParentAcl || clearedACEs ) { - getAclService().updateAcl( childAcl ); + aclService.updateAcl( childAcl ); } } childAcl.getParentAcl(); } - /** - * Do necessary ACL operations on the object. - */ - private void process( final Object o, final String methodName, final boolean isUpdate, final boolean isDelete ) { - - Securable s = ( Securable ) o; - - assert s != null; - - if ( isUpdate ) { - if ( log.isTraceEnabled() ) log.trace( "*********** Start update ACL on " + o + " *************" ); - startUpdate( methodName, s ); - } else if ( isDelete ) { - if ( log.isTraceEnabled() ) log.trace( "*********** Start delete ACL on " + o + " *************" ); - deleteAcl( s ); - } else { - if ( log.isTraceEnabled() ) log.trace( "*********** Start create ACL on " + o + " *************" ); - startCreate( methodName, s ); - } - - if ( log.isTraceEnabled() ) log.trace( "*========* End ACL on " + o + " *=========*" ); - } - /** * Walk the tree of associations and add (or update) acls. * - * @param methodName method name * @param previousParent The parent ACL of the given object (if it is a Securable) or of the last visited Securable. */ - @SuppressWarnings("unchecked") - private void processAssociations( String methodName, Object object, @Nullable Acl previousParent ) { - + private void processAssociations( Object object, @Nullable Acl previousParent ) { if ( canSkipAclCheck( object ) ) { return; } @@ -755,8 +701,8 @@ private void processAssociations( String methodName, Object object, @Nullable Ac * be a bit tricky because there are exceptions. This is kind of inelegant, but the alternative is to check * _every_ association, which will often not be reachable. */ - if ( !specialCaseForAssociationFollow( object, propertyName ) - && ( canSkipAssociationCheck( object, propertyName ) || !needCascade( methodName, cs ) ) ) { + if ( !canFollowAssociation( object, propertyName ) + && ( canSkipAssociationCheck( object, propertyName ) || !( cs.doCascade( CascadingAction.PERSIST ) || cs.doCascade( CascadingAction.SAVE_UPDATE ) || cs.doCascade( CascadingAction.MERGE ) ) ) ) { // if ( log.isTraceEnabled() ) // log.trace( "Skipping checking association: " + propertyName + " on " + object ); continue; @@ -767,7 +713,8 @@ private void processAssociations( String methodName, Object object, @Nullable Ac Object associatedObject; try { // FieldUtils DOES NOT WORK correctly with proxies - associatedObject = getProperty( object, descriptor ); + Method getter = descriptor.getReadMethod(); + associatedObject = getter.invoke( object ); } catch ( LazyInitializationException e ) { /* * This is not a problem. If this was reached via a create, the associated objects must not be new so @@ -788,17 +735,17 @@ private void processAssociations( String methodName, Object object, @Nullable Ac if ( associatedObject == null ) continue; if ( associatedObject instanceof Collection ) { - Collection