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.AuthenticationException;
040    import org.springframework.security.BadCredentialsException;
041    import org.springframework.security.GrantedAuthority;
042    import org.springframework.security.context.HttpSessionContextIntegrationFilter;
043    import org.springframework.security.context.SecurityContext;
044    import org.springframework.security.context.SecurityContextHolder;
045    import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
046    import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
047    import org.springframework.security.userdetails.UsernameNotFoundException;
048    import org.springframework.web.context.support.WebApplicationContextUtils;
049    
050    /**
051     * @author Bouiaw
052     * @author wdrai
053     */
054    public class SpringSecurityService extends AbstractSecurityService {
055            
056            private static final Logger log = Logger.getLogger(SpringSecurityService.class);
057            
058        private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
059        
060            private AbstractSpringSecurityInterceptor securityInterceptor = null;
061            
062            
063            public SpringSecurityService() {
064                    log.debug("Starting Spring Security Service!");
065        }
066    
067        public void configure(Map<String, String> params) {
068            log.debug("Configuring with parameters (NOOP) %s: ", params);
069        }
070            
071            public void setSecurityInterceptor(AbstractSpringSecurityInterceptor securityInterceptor) {
072                    this.securityInterceptor = securityInterceptor;
073            }
074        
075        public void login(Object credentials) {
076            List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials));
077    
078            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
079            HttpServletRequest httpRequest = context.getRequest();
080    
081            String user = decodedCredentials.get(0);
082            String password = decodedCredentials.get(1);
083            Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
084    
085            ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
086                httpRequest.getSession().getServletContext()
087            );
088            if (ctx != null) {
089                AbstractAuthenticationManager authenticationManager =
090                    BeanFactoryUtils.beanOfTypeIncludingAncestors(ctx, AbstractAuthenticationManager.class);
091                try {
092                    Authentication authentication = authenticationManager.authenticate(auth);
093                    SecurityContext securityContext = SecurityContextHolder.getContext();
094                    securityContext.setAuthentication(authentication);
095                    SecurityContextHolder.setContext(securityContext);
096                    saveSecurityContextInSession(securityContext, 0);
097                } 
098                catch (AuthenticationException e) {
099                    handleAuthenticationExceptions(e);
100                }
101            }
102    
103            log.debug("User %s logged in", user);
104        }
105        
106        protected void handleAuthenticationExceptions(AuthenticationException e) {
107            if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
108                throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
109            
110            throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
111        }
112    
113        public Object authorize(AbstractSecurityContext context) throws Exception {
114            log.debug("Authorize: %s", context);
115            log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
116    
117            startAuthorization(context);
118    
119            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
120            
121            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
122            
123            SecurityContext securityContextBefore = null;
124            int securityContextHashBefore = 0;
125            if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
126                    securityContextBefore = loadSecurityContextFromSession();
127                    if (securityContextBefore == null)
128                            securityContextBefore = SecurityContextHolder.getContext();
129                    else
130                            securityContextHashBefore = securityContextBefore.hashCode();
131                    SecurityContextHolder.setContext(securityContextBefore);
132                    authentication = securityContextBefore.getAuthentication();
133            }
134            
135            if (context.getDestination().isSecured()) {
136                if (!isAuthenticated(authentication) || authentication instanceof AnonymousAuthenticationToken) {
137                    log.debug("Is not authenticated!");
138                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
139                }
140                if (!userCanAccessService(context, authentication)) { 
141                    log.debug("Access denied for: %s", authentication.getName());
142                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
143                }
144            }
145    
146            try {
147                    Object returnedObject = securityInterceptor != null 
148                            ? securityInterceptor.invoke(context)
149                            : endAuthorization(context);
150                    
151                    return returnedObject;
152            }
153            catch (AccessDeniedException e) {
154                    throw SecurityServiceException.newAccessDeniedException(e.getMessage());
155            }
156            catch (InvocationTargetException e) {
157                handleAuthorizationExceptions(e);
158                throw e;
159            }
160            finally {
161                if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
162                    // Do this only when not already filtered by Spring Security
163                            SecurityContext securityContextAfter = SecurityContextHolder.getContext();
164                            SecurityContextHolder.clearContext();
165                            saveSecurityContextInSession(securityContextAfter, securityContextHashBefore);
166                }
167            }
168        }
169    
170        public void logout() {
171            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
172            HttpSession session = context.getSession(false);
173            if (session != null && session.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY) != null)
174                    session.invalidate();
175            
176            SecurityContextHolder.clearContext();
177        }
178    
179        protected boolean isUserInRole(Authentication authentication, String role) {
180            for (GrantedAuthority ga : authentication.getAuthorities()) {
181                if (ga.getAuthority().matches(role))
182                    return true;
183            }
184            return false;
185        }
186    
187        protected boolean isAuthenticated(Authentication authentication) {
188            return authentication != null && authentication.isAuthenticated();
189        }
190    
191        protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
192            log.debug("Is authenticated as: %s", authentication.getName());
193    
194            for (String role : context.getDestination().getRoles()) {
195                if (isUserInRole(authentication, role)) {
196                    log.debug("Allowed access to %s in role %s", authentication.getName(), role);
197                    return true;
198                }
199                log.debug("Access denied for %s not in role %s", authentication.getName(), role);
200            }
201            return false;
202        }
203    
204        protected SecurityContext loadSecurityContextFromSession() {
205            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
206            HttpServletRequest request = context.getRequest();
207            return (SecurityContext)request.getSession().getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
208        }
209        
210        protected void saveSecurityContextInSession(SecurityContext securityContext, int securityContextHashBefore) {
211            if (securityContext.hashCode() != securityContextHashBefore &&
212                            !(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
213                    HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
214                    HttpServletRequest request = context.getRequest();
215                    request.getSession().setAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, securityContext);
216            }
217        }
218    
219        protected void handleAuthorizationExceptions(InvocationTargetException e) {
220            for (Throwable t = e; t != null; t = t.getCause()) {
221                // Don't create a dependency to javax.ejb in SecurityService...
222                if (t instanceof SecurityException ||
223                    t instanceof AccessDeniedException ||
224                    "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
225                    throw SecurityServiceException.newAccessDeniedException(t.getMessage());
226            }
227        }
228    
229    }