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