001    package org.trails.hibernate;
002    
003    import java.io.Serializable;
004    
005    import ognl.Ognl;
006    import ognl.OgnlException;
007    
008    import org.acegisecurity.AuthenticationCredentialsNotFoundException;
009    import org.acegisecurity.GrantedAuthority;
010    import org.acegisecurity.context.SecurityContext;
011    import org.acegisecurity.context.SecurityContextHolder;
012    import org.acegisecurity.userdetails.UserDetails;
013    import org.apache.commons.logging.Log;
014    import org.apache.commons.logging.LogFactory;
015    import org.hibernate.CallbackException;
016    import org.hibernate.Transaction;
017    import org.hibernate.type.Type;
018    import org.trails.security.EntityModificationInterception;
019    import org.trails.security.RestrictionType;
020    import org.trails.security.TrailsSecurityException;
021    import org.trails.security.annotation.RemoveRequiresAssociation;
022    import org.trails.security.annotation.RemoveRequiresRole;
023    import org.trails.security.annotation.UpdateRequiresAssociation;
024    import org.trails.security.annotation.UpdateRequiresRole;
025    
026    public class TrailsSecurityInterceptor extends TrailsInterceptor {
027            private static final Log log = LogFactory.getLog(TrailsSecurityInterceptor.class);
028            
029            private void checkRestriction(final Object entity, final RestrictionType restrictionType) {
030                    log.info("Check restriction for entity : " + entity);
031                    // Role-base restrictions override association based
032                    if (entity == null || restrictionType == null) return;
033                    SecurityContext context = SecurityContextHolder.getContext();
034                    // Assume that context should have been established for each request, and if it's not,
035                    // it's an internal service call 
036                    if (context == null || context.getAuthentication() == null) return;
037                    
038                    boolean roleRestriction = false;
039    
040                    String[] requiredRole = null;
041                    switch (restrictionType) {
042                            case UPDATE : 
043                                    UpdateRequiresRole updateRestriction = entity.getClass().getAnnotation(UpdateRequiresRole.class );
044                                    if (updateRestriction != null) requiredRole = updateRestriction.value();
045                            break;
046                            case REMOVE : 
047                                    RemoveRequiresRole removeRestriction = entity.getClass().getAnnotation(RemoveRequiresRole.class );
048                                    if (removeRestriction != null) requiredRole = removeRestriction.value();
049                            break;
050                    }
051                    if (requiredRole != null) {
052                            GrantedAuthority[] authorities = context.getAuthentication().getAuthorities();
053                            for (GrantedAuthority authority : authorities)
054                                    for (String role : requiredRole) if (role.equals(authority.getAuthority()) ) return;
055                            roleRestriction = true;
056                    }
057                    
058                    String ownerPropertyAssociation = null;
059                    switch (restrictionType) {
060                            case UPDATE : 
061                                    UpdateRequiresAssociation updateRestriction = entity.getClass().getAnnotation(UpdateRequiresAssociation.class );
062                                    if (updateRestriction != null) ownerPropertyAssociation = updateRestriction.value();
063                            break;
064                            case REMOVE : 
065                                    RemoveRequiresAssociation removeRestriction = entity.getClass().getAnnotation(RemoveRequiresAssociation.class );
066                                    if (removeRestriction != null) ownerPropertyAssociation = removeRestriction.value();
067                            break;
068                    }
069                    // If check returns true, exit immediately, ignoring possible role restriction
070                    if (ownerPropertyAssociation != null) if (checkOwnershipRestriction(entity, ownerPropertyAssociation) ) return;
071                    
072                    // Note that because we place no restriction if no annotations are specified, but check the ownership restriction
073                    // only after role restriction, we need to delay throwing an exception if the role isn't available
074                    if (roleRestriction) throw new EntityModificationInterception(entity, "Authenticated user does not have a required role or ownership");
075                    
076            }
077            
078      private boolean checkOwnershipRestriction(final Object entity, final String associatedProperty) {
079            if (entity == null || associatedProperty == null) return false;
080    
081            try {
082                            SecurityContext context = SecurityContextHolder.getContext();
083                            if (context.getAuthentication() == null) throw new AuthenticationCredentialsNotFoundException("Entity requires an authenticated user as owner");
084                            String currentUserName = context.getAuthentication().getName();
085                            if (currentUserName == null) currentUserName = "";
086                            
087                            // Empty string as associated property denotes entity itself
088                            if ("".equals(associatedProperty) ) {
089                                    if (!(entity instanceof UserDetails) ) throw new TrailsSecurityException("Entity is not of type UserDetails"); 
090                                    UserDetails userDetails = (UserDetails)entity;
091                                    if (currentUserName.equals(userDetails.getUsername()) ) return true;
092                                    else throw new EntityModificationInterception(entity, "Entity does not represent the authenticated user");
093                            }
094                            
095                            Object value = Ognl.getValue(associatedProperty, entity);
096                            if (value == null) throw new EntityModificationInterception(entity, "Associated owner property is null"); 
097                                    
098                            if (value instanceof Iterable) {
099                                    try {
100                                            Iterable<UserDetails> iterable = (Iterable<UserDetails>)value;
101                                            for (UserDetails userDetails : iterable) {
102                                                    if (currentUserName.equals(userDetails.getUsername()) ) {
103                                                            value = null;
104                                                            break;
105                                                    }
106                                            }
107                                    }
108                                    catch (ClassCastException e) {
109                                            throw new TrailsSecurityException("Associated collection doesn't contain UserDetails objects");
110                                    }
111                                    
112                                    if (value != null) throw new EntityModificationInterception(entity, "Authenticated user is not in the owners collection");
113                            }
114                            else {
115                                    if (!(value instanceof UserDetails) ) throw new TrailsSecurityException("Associate property is not of type UserDetails");
116                                    UserDetails userDetails = (UserDetails)value;
117                                    if (!currentUserName.equals(userDetails.getUsername()) ) throw new EntityModificationInterception(entity, "Authenticated user is not the owner");
118                            }
119                    }
120                    catch(OgnlException e) {
121                            throw new TrailsSecurityException("Could not evaluate the owner association", e);
122                    }
123            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                    checkRestriction(entity, RestrictionType.UPDATE);
133            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                    checkRestriction(entity, RestrictionType.UPDATE);
143            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                    checkRestriction(entity, RestrictionType.REMOVE);
153            super.onDelete(entity, arg1, arg2, arg3, arg4);
154      }
155    }