Coverage Report - org.trails.hibernate.TrailsSecurityInterceptor
 
Classes in this File Line Coverage Branch Coverage Complexity
TrailsSecurityInterceptor
72% 
77% 
0
 
 1  
 package org.trails.hibernate;
 2  
 
 3  
 import java.io.Serializable;
 4  
 
 5  
 import ognl.Ognl;
 6  
 import ognl.OgnlException;
 7  
 
 8  
 import org.acegisecurity.AuthenticationCredentialsNotFoundException;
 9  
 import org.acegisecurity.GrantedAuthority;
 10  
 import org.acegisecurity.context.SecurityContext;
 11  
 import org.acegisecurity.context.SecurityContextHolder;
 12  
 import org.acegisecurity.userdetails.UserDetails;
 13  
 import org.apache.commons.logging.Log;
 14  
 import org.apache.commons.logging.LogFactory;
 15  
 import org.hibernate.CallbackException;
 16  
 import org.hibernate.Transaction;
 17  
 import org.hibernate.type.Type;
 18  
 import org.trails.security.EntityModificationInterception;
 19  
 import org.trails.security.RestrictionType;
 20  
 import org.trails.security.TrailsSecurityException;
 21  
 import org.trails.security.annotation.RemoveRequiresAssociation;
 22  
 import org.trails.security.annotation.RemoveRequiresRole;
 23  
 import org.trails.security.annotation.UpdateRequiresAssociation;
 24  
 import org.trails.security.annotation.UpdateRequiresRole;
 25  
 
 26  9
 public class TrailsSecurityInterceptor extends TrailsInterceptor {
 27  3
         private static final Log log = LogFactory.getLog(TrailsSecurityInterceptor.class);
 28  
         
 29  
         private void checkRestriction(final Object entity, final RestrictionType restrictionType) {
 30  15
                 log.info("Check restriction for entity : " + entity);
 31  
                 // Role-base restrictions override association based
 32  15
                 if (entity == null || restrictionType == null) return;
 33  15
                 SecurityContext context = SecurityContextHolder.getContext();
 34  
                 // Assume that context should have been established for each request, and if it's not,
 35  
                 // it's an internal service call 
 36  15
                 if (context == null || context.getAuthentication() == null) return;
 37  
                 
 38  15
                 boolean roleRestriction = false;
 39  
 
 40  15
                 String[] requiredRole = null;
 41  15
                 switch (restrictionType) {
 42  
                         case UPDATE : 
 43  15
                                 UpdateRequiresRole updateRestriction = entity.getClass().getAnnotation(UpdateRequiresRole.class );
 44  15
                                 if (updateRestriction != null) requiredRole = updateRestriction.value();
 45  
                         break;
 46  
                         case REMOVE : 
 47  0
                                 RemoveRequiresRole removeRestriction = entity.getClass().getAnnotation(RemoveRequiresRole.class );
 48  0
                                 if (removeRestriction != null) requiredRole = removeRestriction.value();
 49  9
                         break;
 50  
                 }
 51  15
                 if (requiredRole != null) {
 52  6
                         GrantedAuthority[] authorities = context.getAuthentication().getAuthorities();
 53  12
                         for (GrantedAuthority authority : authorities)
 54  6
                                 for (String role : requiredRole) if (role.equals(authority.getAuthority()) ) return;
 55  6
                         roleRestriction = true;
 56  
                 }
 57  
                 
 58  15
                 String ownerPropertyAssociation = null;
 59  15
                 switch (restrictionType) {
 60  
                         case UPDATE : 
 61  15
                                 UpdateRequiresAssociation updateRestriction = entity.getClass().getAnnotation(UpdateRequiresAssociation.class );
 62  15
                                 if (updateRestriction != null) ownerPropertyAssociation = updateRestriction.value();
 63  
                         break;
 64  
                         case REMOVE : 
 65  0
                                 RemoveRequiresAssociation removeRestriction = entity.getClass().getAnnotation(RemoveRequiresAssociation.class );
 66  0
                                 if (removeRestriction != null) ownerPropertyAssociation = removeRestriction.value();
 67  
                         break;
 68  
                 }
 69  
                 // If check returns true, exit immediately, ignoring possible role restriction
 70  15
                 if (ownerPropertyAssociation != null) if (checkOwnershipRestriction(entity, ownerPropertyAssociation) ) return;
 71  
                 
 72  
                 // Note that because we place no restriction if no annotations are specified, but check the ownership restriction
 73  
                 // only after role restriction, we need to delay throwing an exception if the role isn't available
 74  0
                 if (roleRestriction) throw new EntityModificationInterception(entity, "Authenticated user does not have a required role or ownership");
 75  
                 
 76  0
         }
 77  
         
 78  
   private boolean checkOwnershipRestriction(final Object entity, final String associatedProperty) {
 79  15
           if (entity == null || associatedProperty == null) return false;
 80  
 
 81  
           try {
 82  15
                         SecurityContext context = SecurityContextHolder.getContext();
 83  15
                         if (context.getAuthentication() == null) throw new AuthenticationCredentialsNotFoundException("Entity requires an authenticated user as owner");
 84  15
                         String currentUserName = context.getAuthentication().getName();
 85  15
                         if (currentUserName == null) currentUserName = "";
 86  
                         
 87  
                         // Empty string as associated property denotes entity itself
 88  15
                         if ("".equals(associatedProperty) ) {
 89  6
                                 if (!(entity instanceof UserDetails) ) throw new TrailsSecurityException("Entity is not of type UserDetails"); 
 90  6
                                 UserDetails userDetails = (UserDetails)entity;
 91  6
                                 if (currentUserName.equals(userDetails.getUsername()) ) return true;
 92  3
                                 else throw new EntityModificationInterception(entity, "Entity does not represent the authenticated user");
 93  
                         }
 94  
                         
 95  9
                         Object value = Ognl.getValue(associatedProperty, entity);
 96  6
                         if (value == null) throw new EntityModificationInterception(entity, "Associated owner property is null"); 
 97  
                                 
 98  6
                         if (value instanceof Iterable) {
 99  
                                 try {
 100  0
                                         Iterable<UserDetails> iterable = (Iterable<UserDetails>)value;
 101  0
                                         for (UserDetails userDetails : iterable) {
 102  0
                                                 if (currentUserName.equals(userDetails.getUsername()) ) {
 103  0
                                                         value = null;
 104  0
                                                         break;
 105  
                                                 }
 106  
                                         }
 107  
                                 }
 108  0
                                 catch (ClassCastException e) {
 109  0
                                         throw new TrailsSecurityException("Associated collection doesn't contain UserDetails objects");
 110  0
                                 }
 111  
                                 
 112  0
                                 if (value != null) throw new EntityModificationInterception(entity, "Authenticated user is not in the owners collection");
 113  
                         }
 114  
                         else {
 115  6
                                 if (!(value instanceof UserDetails) ) throw new TrailsSecurityException("Associate property is not of type UserDetails");
 116  6
                                 UserDetails userDetails = (UserDetails)value;
 117  6
                                 if (!currentUserName.equals(userDetails.getUsername()) ) throw new EntityModificationInterception(entity, "Authenticated user is not the owner");
 118  
                         }
 119  
                 }
 120  3
                 catch(OgnlException e) {
 121  3
                         throw new TrailsSecurityException("Could not evaluate the owner association", e);
 122  3
                 }
 123  3
           return true;
 124  
   }
 125  
 
 126  
   /* (non-Javadoc)
 127  
    * @see org.hibernate.Interceptor#onFlushDirty(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
 128  
    */
 129  
   public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
 130  
       Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException
 131  
   {
 132  6
                 checkRestriction(entity, RestrictionType.UPDATE);
 133  3
           return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
 134  
   }
 135  
 
 136  
   /* (non-Javadoc)
 137  
    * @see org.hibernate.Interceptor#onSave(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
 138  
    */
 139  
   public boolean onSave(Object entity, Serializable id, Object[] state,
 140  
       String[] propertyNames, Type[] types) throws CallbackException
 141  
   {
 142  9
                 checkRestriction(entity, RestrictionType.UPDATE);
 143  3
           return super.onSave(entity, id, state, propertyNames, types);
 144  
   }
 145  
 
 146  
   /* (non-Javadoc)
 147  
    * @see org.hibernate.Interceptor#onDelete(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
 148  
    */
 149  
   public void onDelete(Object entity, Serializable arg1, Object[] arg2,
 150  
       String[] arg3, Type[] arg4) throws CallbackException
 151  
   {
 152  0
                 checkRestriction(entity, RestrictionType.REMOVE);
 153  0
           super.onDelete(entity, arg1, arg2, arg3, arg4);
 154  0
   }
 155  
 }