001    package org.trails.security;
002    
003    import java.util.ArrayList;
004    import java.util.HashMap;
005    import java.util.List;
006    import java.util.Map;
007    
008    import org.acegisecurity.context.SecurityContext;
009    import org.acegisecurity.context.SecurityContextHolder;
010    import org.acegisecurity.ui.session.HttpSessionApplicationEvent;
011    import org.acegisecurity.ui.session.HttpSessionCreatedEvent;
012    import org.acegisecurity.ui.session.HttpSessionDestroyedEvent;
013    import org.apache.commons.logging.Log;
014    import org.apache.commons.logging.LogFactory;
015    import org.aspectj.lang.ProceedingJoinPoint;
016    import org.aspectj.lang.annotation.Around;
017    import org.aspectj.lang.annotation.Aspect;
018    import org.springframework.context.ApplicationEvent;
019    import org.springframework.context.ApplicationListener;
020    import org.trails.descriptor.IClassDescriptor;
021    import org.trails.descriptor.TrailsClassDescriptor;
022    
023    @Aspect
024    public class DescriptorSecurity implements ApplicationListener
025    {
026            private static final Log log = LogFactory.getLog(DescriptorSecurity.class);
027            // Only used to log a warning each time an element is added to cache when no session events have been published
028            private static boolean sessionCreationDetected = false;
029    
030            private SecurityService securityService;
031            Map<String, Map<String,IClassDescriptor>> perUserClassDescriptorCache = new HashMap<String, Map<String,IClassDescriptor>>();
032    
033            @Around(
034                    "execution(public org.trails.descriptor.IClassDescriptor org.trails.descriptor.DescriptorService+.getClassDescriptor(Class))")
035            public Object classDescriptorSecurity(ProceedingJoinPoint pjp) throws Throwable
036            {
037                    IClassDescriptor desc = (IClassDescriptor) pjp.proceed();
038                    if (desc != null) {
039                            SecurityContext context = SecurityContextHolder.getContext();
040                            if (context == null || context.getAuthentication() == null) return desc;
041                            return applyRestrictions(desc, context);
042                    } 
043                    else return null;
044            }
045    
046            @Around("execution(public java.util.List org.trails.descriptor.DescriptorService+.getAllDescriptors())")
047            public Object getAllClassDescriptorSecurity(ProceedingJoinPoint pjp) throws Throwable
048            {
049                    List<IClassDescriptor> descriptors = (List<IClassDescriptor>) pjp.proceed();
050                    if (descriptors == null) return null;
051    
052                    SecurityContext context = SecurityContextHolder.getContext();
053                    if (context == null || context.getAuthentication() == null) return descriptors;
054    
055                    List<IClassDescriptor> cloneDescriptors = new ArrayList<IClassDescriptor>(descriptors.size());
056                    for (IClassDescriptor descriptor : descriptors) cloneDescriptors.add(applyRestrictions(descriptor, context) );
057                    return cloneDescriptors;
058            }
059    
060            protected IClassDescriptor applyRestrictions(IClassDescriptor descriptor, SecurityContext context)
061            {
062                    // Return a clone specific to this user rather than modify the shared original
063                    // See if the class descriptor for this user is already cached, otherwise cache
064                    Map<String,IClassDescriptor> cachedDescriptors = perUserClassDescriptorCache.get(context.getAuthentication().getName());
065                    IClassDescriptor cloneDescriptor = null;
066                    if (cachedDescriptors != null) cloneDescriptor = cachedDescriptors.get(descriptor.getType().getSimpleName());
067                    else {
068                            cachedDescriptors = new HashMap<String,IClassDescriptor>();
069                            perUserClassDescriptorCache.put(context.getAuthentication().getName(), cachedDescriptors);
070                            if (!sessionCreationDetected) log.warn("This implementation caches security-enhanced class descriptors for each user\n "
071                                            + "but no session events are detected. Descriptors for expired sessions cannot be removed from the cache\n"
072                                            + "Check that you have configured <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>?\n");
073                    }
074                    
075                    if (cloneDescriptor != null) return cloneDescriptor;
076    
077                    cloneDescriptor = new TrailsClassDescriptor(descriptor);
078                    cachedDescriptors.put(descriptor.getType().getSimpleName(), cloneDescriptor);
079                    
080                    List<SecurityRestriction> restrictions = securityService.findRestrictions(descriptor);
081                    if (restrictions != null)
082                    {
083                            for (SecurityRestriction restriction : restrictions)
084                            {
085                                    restriction.restrict(context.getAuthentication().getAuthorities(), cloneDescriptor);
086                            }
087                    }
088                    return cloneDescriptor;
089            }
090    
091            public void setSecurityService(SecurityService securityService)
092            {
093                    this.securityService = securityService;
094            }
095    
096            public void onApplicationEvent(ApplicationEvent event) {
097                    if (!(event instanceof HttpSessionApplicationEvent)) return;
098                    
099                    if (event instanceof HttpSessionDestroyedEvent) {
100                            SecurityContext context = SecurityContextHolder.getContext();
101                            // Do nothing if security context wasn't available
102                            if (context == null || context.getAuthentication() == null) return;
103                            Object object = perUserClassDescriptorCache.remove(context.getAuthentication().getName());
104                            if (object != null && log.isDebugEnabled()) log.debug("Removing cached descriptors for user " + context.getAuthentication().getName());
105                    }
106                    else if (event instanceof HttpSessionCreatedEvent) sessionCreationDetected = true;
107            }
108    }