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     */
022    package org.granite.messaging.service.security;
023    
024    import java.lang.reflect.InvocationTargetException;
025    import java.util.Arrays;
026    import java.util.List;
027    import java.util.Map;
028    
029    import javax.servlet.http.HttpServletRequest;
030    import javax.servlet.http.HttpSession;
031    
032    import org.granite.context.GraniteContext;
033    import org.granite.logging.Logger;
034    import org.granite.messaging.webapp.HttpGraniteContext;
035    import org.springframework.beans.factory.BeanFactoryUtils;
036    import org.springframework.context.ApplicationContext;
037    import org.springframework.security.AbstractAuthenticationManager;
038    import org.springframework.security.AccessDeniedException;
039    import org.springframework.security.Authentication;
040    import org.springframework.security.AuthenticationException;
041    import org.springframework.security.BadCredentialsException;
042    import org.springframework.security.GrantedAuthority;
043    import org.springframework.security.context.HttpSessionContextIntegrationFilter;
044    import org.springframework.security.context.SecurityContext;
045    import org.springframework.security.context.SecurityContextHolder;
046    import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
047    import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
048    import org.springframework.security.userdetails.UsernameNotFoundException;
049    import org.springframework.web.context.support.WebApplicationContextUtils;
050    
051    /**
052     * @author Bouiaw
053     * @author wdrai
054     */
055    public 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    }