001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.messaging.service.security;
022    
023    import java.lang.reflect.InvocationTargetException;
024    import java.util.Arrays;
025    import java.util.List;
026    import java.util.Map;
027    
028    import javax.servlet.http.HttpServletRequest;
029    import javax.servlet.http.HttpSession;
030    
031    import org.granite.context.GraniteContext;
032    import org.granite.logging.Logger;
033    import org.granite.messaging.webapp.HttpGraniteContext;
034    import org.springframework.beans.factory.BeanFactoryUtils;
035    import org.springframework.context.ApplicationContext;
036    import org.springframework.security.AbstractAuthenticationManager;
037    import org.springframework.security.AccessDeniedException;
038    import org.springframework.security.Authentication;
039    import org.springframework.security.BadCredentialsException;
040    import org.springframework.security.GrantedAuthority;
041    import org.springframework.security.context.HttpSessionContextIntegrationFilter;
042    import org.springframework.security.context.SecurityContext;
043    import org.springframework.security.context.SecurityContextHolder;
044    import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
045    import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
046    import org.springframework.web.context.support.WebApplicationContextUtils;
047    
048    /**
049     * @author Bouiaw
050     * @author wdrai
051     */
052    public class SpringSecurityService extends AbstractSecurityService {
053            
054            private static final Logger log = Logger.getLogger(SpringSecurityService.class);
055            
056        private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
057        
058            private AbstractSpringSecurityInterceptor securityInterceptor = null;
059            
060            
061            public SpringSecurityService() {
062                    log.debug("Starting Spring Security Service!");
063        }
064    
065        public void configure(Map<String, String> params) {
066            log.debug("Configuring with parameters (NOOP) %s: ", params);
067        }
068            
069            public void setSecurityInterceptor(AbstractSpringSecurityInterceptor securityInterceptor) {
070                    this.securityInterceptor = securityInterceptor;
071            }
072        
073        public void login(Object credentials) {
074            List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials));
075    
076            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
077            HttpServletRequest httpRequest = context.getRequest();
078    
079            String user = decodedCredentials.get(0);
080            String password = decodedCredentials.get(1);
081            Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
082    
083            ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
084                httpRequest.getSession().getServletContext()
085            );
086            if (ctx != null) {
087                AbstractAuthenticationManager authenticationManager =
088                    (AbstractAuthenticationManager)BeanFactoryUtils.beanOfTypeIncludingAncestors(ctx, AbstractAuthenticationManager.class);
089                try {
090                    Authentication authentication = authenticationManager.authenticate(auth);
091                    SecurityContext securityContext = SecurityContextHolder.getContext();
092                    securityContext.setAuthentication(authentication);
093                    SecurityContextHolder.setContext(securityContext);
094                    saveSecurityContextInSession(securityContext, 0);
095                } catch (BadCredentialsException e) {
096                    throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
097                }
098            }
099    
100            log.debug("Logged In!");
101        }
102    
103        public Object authorize(AbstractSecurityContext context) throws Exception {
104            log.debug("Authorize: %s", context);
105            log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
106    
107            startAuthorization(context);
108    
109            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
110            
111            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
112            
113            SecurityContext securityContextBefore = null;
114            int securityContextHashBefore = 0;
115            if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
116                    securityContextBefore = loadSecurityContextFromSession();
117                    if (securityContextBefore == null)
118                            securityContextBefore = SecurityContextHolder.getContext();
119                    else
120                            securityContextHashBefore = securityContextBefore.hashCode();
121                    SecurityContextHolder.setContext(securityContextBefore);
122                    authentication = securityContextBefore.getAuthentication();
123            }
124            
125            if (context.getDestination().isSecured()) {
126                if (!isAuthenticated(authentication) || authentication instanceof AnonymousAuthenticationToken) {
127                    log.debug("Is not authenticated!");
128                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
129                }
130                if (!userCanAccessService(context, authentication)) { 
131                    log.debug("Access denied for: %s", authentication.getName());
132                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
133                }
134            }
135    
136            try {
137                    Object returnedObject = securityInterceptor != null 
138                            ? securityInterceptor.invoke(context)
139                            : endAuthorization(context);
140                    
141                    return returnedObject;
142            }
143            catch (AccessDeniedException e) {
144                    throw SecurityServiceException.newAccessDeniedException(e.getMessage());
145            }
146            catch (InvocationTargetException e) {
147                handleAuthorizationExceptions(e);
148                throw e;
149            }
150            finally {
151                if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
152                    // Do this only when not already filtered by Spring Security
153                            SecurityContext securityContextAfter = SecurityContextHolder.getContext();
154                            SecurityContextHolder.clearContext();
155                            saveSecurityContextInSession(securityContextAfter, securityContextHashBefore);
156                }
157            }
158        }
159    
160        public void logout() {
161            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
162            HttpSession session = context.getSession(false);
163            if (session != null && session.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY) != null)
164                    session.invalidate();
165            
166            SecurityContextHolder.clearContext();
167        }
168    
169        protected boolean isUserInRole(Authentication authentication, String role) {
170            for (GrantedAuthority ga : authentication.getAuthorities()) {
171                if (ga.getAuthority().matches(role))
172                    return true;
173            }
174            return false;
175        }
176    
177        protected boolean isAuthenticated(Authentication authentication) {
178            return authentication != null && authentication.isAuthenticated();
179        }
180    
181        protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
182            log.debug("Is authenticated as: %s", authentication.getName());
183    
184            for (String role : context.getDestination().getRoles()) {
185                if (isUserInRole(authentication, role)) {
186                    log.debug("Allowed access to %s in role %s", authentication.getName(), role);
187                    return true;
188                }
189                log.debug("Access denied for %s not in role %s", authentication.getName(), role);
190            }
191            return false;
192        }
193    
194        protected SecurityContext loadSecurityContextFromSession() {
195            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
196            HttpServletRequest request = context.getRequest();
197            return (SecurityContext)request.getSession().getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
198        }
199        
200        protected void saveSecurityContextInSession(SecurityContext securityContext, int securityContextHashBefore) {
201            if (securityContext.hashCode() != securityContextHashBefore &&
202                            !(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
203                    HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
204                    HttpServletRequest request = context.getRequest();
205                    request.getSession().setAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, securityContext);
206            }
207        }
208    
209        protected void handleAuthorizationExceptions(InvocationTargetException e) {
210            for (Throwable t = e; t != null; t = t.getCause()) {
211                // Don't create a dependency to javax.ejb in SecurityService...
212                if (t instanceof SecurityException ||
213                    t instanceof AccessDeniedException ||
214                    "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
215                    throw SecurityServiceException.newAccessDeniedException(t.getMessage());
216            }
217        }
218    
219    }