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, 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    }