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
021package org.granite.messaging.service.security;
022
023import java.lang.reflect.InvocationTargetException;
024import java.util.Arrays;
025import java.util.List;
026import java.util.Map;
027
028import javax.servlet.http.HttpServletRequest;
029import javax.servlet.http.HttpSession;
030
031import org.granite.context.GraniteContext;
032import org.granite.logging.Logger;
033import org.granite.messaging.webapp.HttpGraniteContext;
034import org.springframework.beans.factory.BeanFactoryUtils;
035import org.springframework.context.ApplicationContext;
036import org.springframework.security.AbstractAuthenticationManager;
037import org.springframework.security.AccessDeniedException;
038import org.springframework.security.Authentication;
039import org.springframework.security.AuthenticationException;
040import org.springframework.security.BadCredentialsException;
041import org.springframework.security.GrantedAuthority;
042import org.springframework.security.context.HttpSessionContextIntegrationFilter;
043import org.springframework.security.context.SecurityContext;
044import org.springframework.security.context.SecurityContextHolder;
045import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
046import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
047import org.springframework.security.userdetails.UsernameNotFoundException;
048import org.springframework.web.context.support.WebApplicationContextUtils;
049
050/**
051 * @author Bouiaw
052 * @author wdrai
053 */
054public 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, String charset) {
076        List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset));
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                endLogin(credentials, charset);
099            } 
100            catch (AuthenticationException e) {
101                handleAuthenticationExceptions(e);
102            }
103        }
104
105        log.debug("User %s logged in", user);
106    }
107    
108    protected void handleAuthenticationExceptions(AuthenticationException e) {
109        if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
110            throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
111        
112        throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
113    }
114
115    public Object authorize(AbstractSecurityContext context) throws Exception {
116        log.debug("Authorize: %s", context);
117        log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
118
119        startAuthorization(context);
120
121        HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
122        
123        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
124        
125        SecurityContext securityContextBefore = null;
126        int securityContextHashBefore = 0;
127        if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
128                securityContextBefore = loadSecurityContextFromSession();
129                if (securityContextBefore == null)
130                        securityContextBefore = SecurityContextHolder.getContext();
131                else
132                        securityContextHashBefore = securityContextBefore.hashCode();
133                SecurityContextHolder.setContext(securityContextBefore);
134                authentication = securityContextBefore.getAuthentication();
135        }
136        
137        if (context.getDestination().isSecured()) {
138            if (!isAuthenticated(authentication) || authentication instanceof AnonymousAuthenticationToken) {
139                log.debug("Is not authenticated!");
140                throw SecurityServiceException.newNotLoggedInException("User not logged in");
141            }
142            if (!userCanAccessService(context, authentication)) { 
143                log.debug("Access denied for: %s", authentication.getName());
144                throw SecurityServiceException.newAccessDeniedException("User not in required role");
145            }
146        }
147
148        try {
149                Object returnedObject = securityInterceptor != null 
150                        ? securityInterceptor.invoke(context)
151                        : endAuthorization(context);
152                
153                return returnedObject;
154        }
155        catch (AccessDeniedException e) {
156                throw SecurityServiceException.newAccessDeniedException(e.getMessage());
157        }
158        catch (InvocationTargetException e) {
159            handleAuthorizationExceptions(e);
160            throw e;
161        }
162        finally {
163            if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
164                // Do this only when not already filtered by Spring Security
165                        SecurityContext securityContextAfter = SecurityContextHolder.getContext();
166                        SecurityContextHolder.clearContext();
167                        saveSecurityContextInSession(securityContextAfter, securityContextHashBefore);
168            }
169        }
170    }
171
172    public void logout() {
173        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
174        HttpSession session = context.getSession(false);
175        if (session != null && session.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY) != null)
176                session.invalidate();
177        
178        SecurityContextHolder.clearContext();
179    }
180
181    protected boolean isUserInRole(Authentication authentication, String role) {
182        for (GrantedAuthority ga : authentication.getAuthorities()) {
183            if (ga.getAuthority().matches(role))
184                return true;
185        }
186        return false;
187    }
188
189    protected boolean isAuthenticated(Authentication authentication) {
190        return authentication != null && authentication.isAuthenticated();
191    }
192
193    protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
194        log.debug("Is authenticated as: %s", authentication.getName());
195
196        for (String role : context.getDestination().getRoles()) {
197            if (isUserInRole(authentication, role)) {
198                log.debug("Allowed access to %s in role %s", authentication.getName(), role);
199                return true;
200            }
201            log.debug("Access denied for %s not in role %s", authentication.getName(), role);
202        }
203        return false;
204    }
205
206    protected SecurityContext loadSecurityContextFromSession() {
207        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
208        HttpServletRequest request = context.getRequest();
209        return (SecurityContext)request.getSession().getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
210    }
211    
212    protected void saveSecurityContextInSession(SecurityContext securityContext, int securityContextHashBefore) {
213        if (securityContext.hashCode() != securityContextHashBefore &&
214                        !(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
215                HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
216                HttpServletRequest request = context.getRequest();
217                request.getSession().setAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, securityContext);
218        }
219    }
220
221    protected void handleAuthorizationExceptions(InvocationTargetException e) {
222        for (Throwable t = e; t != null; t = t.getCause()) {
223            // Don't create a dependency to javax.ejb in SecurityService...
224            if (t instanceof SecurityException ||
225                t instanceof AccessDeniedException ||
226                "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
227                throw SecurityServiceException.newAccessDeniedException(t.getMessage());
228        }
229    }
230
231}