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.spring.security;
022    
023    import java.lang.reflect.InvocationTargetException;
024    import java.lang.reflect.Method;
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.HttpServletResponse;
031    import javax.servlet.http.HttpSession;
032    
033    import org.granite.context.GraniteContext;
034    import org.granite.logging.Logger;
035    import org.granite.messaging.service.security.AbstractSecurityContext;
036    import org.granite.messaging.service.security.AbstractSecurityService;
037    import org.granite.messaging.service.security.SecurityServiceException;
038    import org.granite.messaging.webapp.HttpGraniteContext;
039    import org.springframework.beans.factory.BeanFactoryUtils;
040    import org.springframework.context.ApplicationContext;
041    import org.springframework.security.access.AccessDeniedException;
042    import org.springframework.security.authentication.AnonymousAuthenticationToken;
043    import org.springframework.security.authentication.AuthenticationManager;
044    import org.springframework.security.authentication.BadCredentialsException;
045    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
046    import org.springframework.security.core.Authentication;
047    import org.springframework.security.core.GrantedAuthority;
048    import org.springframework.security.core.context.SecurityContext;
049    import org.springframework.security.core.context.SecurityContextHolder;
050    import org.springframework.security.web.context.HttpRequestResponseHolder;
051    import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
052    import org.springframework.security.web.context.SecurityContextRepository;
053    import org.springframework.web.context.support.WebApplicationContextUtils;
054    
055    
056    /**
057     * @author Bouiaw
058     */
059    public class SpringSecurity3Service extends AbstractSecurityService {
060            
061            private static final Logger log = Logger.getLogger(SpringSecurity3Service.class);
062            
063        private static final String FILTER_APPLIED = "__spring_security_scpf_applied";
064            
065            private AuthenticationManager authenticationManager = null;
066            private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
067            private AbstractSpringSecurity3Interceptor securityInterceptor = null;
068            private String authenticationManagerBeanName = null;
069            private Method getRequest = null;
070            private Method getResponse = null;
071        
072            
073            public SpringSecurity3Service() {
074                    log.debug("Starting Spring 3 Security Service!");
075                    try {
076                    getRequest = HttpRequestResponseHolder.class.getDeclaredMethod("getRequest");
077                    getRequest.setAccessible(true);
078                    getResponse = HttpRequestResponseHolder.class.getDeclaredMethod("getResponse");
079                    getResponse.setAccessible(true);
080                    }
081                    catch (Exception e) {
082                            throw new RuntimeException("Could not get methods from HttpRequestResponseHolder", e);
083                    }
084        }
085            
086            
087            public void setAuthenticationManager(AuthenticationManager authenticationManager) {
088                    this.authenticationManager = authenticationManager;
089            }
090            
091            public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
092                    this.securityContextRepository = securityContextRepository;
093            }
094            
095            public void setSecurityInterceptor(AbstractSpringSecurity3Interceptor securityInterceptor) {
096                    this.securityInterceptor = securityInterceptor;
097            }
098    
099        public void configure(Map<String, String> params) {
100            log.debug("Configuring with parameters %s: ", params);
101            if (params.containsKey("authentication-manager-bean-name"))
102                    authenticationManagerBeanName = params.get("authentication-manager-bean-name");
103        }
104        
105        public void login(Object credentials) {
106            List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials));
107    
108            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
109            HttpServletRequest httpRequest = graniteContext.getRequest();
110    
111            String user = decodedCredentials.get(0);
112            String password = decodedCredentials.get(1);
113            Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
114    
115            ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
116                httpRequest.getSession().getServletContext()
117            );
118            if (ctx != null) {
119                    lookupAuthenticationManager(ctx, authenticationManagerBeanName);
120                
121                try {
122                    Authentication authentication = authenticationManager.authenticate(auth);
123                    
124                    HttpRequestResponseHolder holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse());
125                    SecurityContext securityContext = securityContextRepository.loadContext(holder);
126                    securityContext.setAuthentication(authentication);
127                    SecurityContextHolder.setContext(securityContext);
128                        try {
129                            securityContextRepository.saveContext(securityContext, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder));
130                        }
131                        catch (Exception e) {
132                            log.error(e, "Could not save context after authentication");
133                        }
134                } 
135                catch (BadCredentialsException e) {
136                    throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
137                }
138            }
139    
140            log.debug("Logged In!");
141        }
142        
143        public void lookupAuthenticationManager(ApplicationContext ctx, String authenticationManagerBeanName) throws SecurityServiceException {
144            if (this.authenticationManager != null)
145                    return;
146            
147            @SuppressWarnings("unchecked")
148            Map<String, AuthenticationManager> authManagers = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, AuthenticationManager.class);
149            
150            if (authenticationManagerBeanName != null) {
151                    this.authenticationManager = authManagers.get(authenticationManagerBeanName);
152                    if (authenticationManager == null) {
153                            log.error("AuthenticationManager bean not found " + authenticationManagerBeanName);
154                            throw SecurityServiceException.newInvalidCredentialsException("Authentication failed");
155                    }
156                    return;
157            }
158            else if (authManagers.size() > 1) {
159                    log.error("More than one AuthenticationManager beans found, specify which one to use in Spring config <graniteds:security-service authentication-manager='myAuthManager'/> or in granite-config.xml <security type='org.granite.spring.security.SpringSecurity3Service'><param name='authentication-manager-bean-name' value='myAuthManager'/></security>");
160                    throw SecurityServiceException.newInvalidCredentialsException("Authentication failed");
161            }
162            
163            this.authenticationManager = authManagers.values().iterator().next();
164        }
165    
166        
167        public Object authorize(AbstractSecurityContext context) throws Exception {
168            log.debug("Authorize: %s", context);
169            log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
170    
171            startAuthorization(context);
172            
173            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
174            
175            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
176            HttpRequestResponseHolder holder = null;
177            
178            if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
179                    holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse());
180                    SecurityContext contextBeforeChainExecution = securityContextRepository.loadContext(holder);
181                        SecurityContextHolder.setContext(contextBeforeChainExecution);
182                        if (isAuthenticated(authentication))
183                            contextBeforeChainExecution.setAuthentication(authentication);
184                        else
185                            authentication = contextBeforeChainExecution.getAuthentication();
186            }
187            
188            if (context.getDestination().isSecured()) {
189                if (!isAuthenticated(authentication) || authentication instanceof AnonymousAuthenticationToken) {
190                    log.debug("Is not authenticated!");
191                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
192                }
193                if (!userCanAccessService(context, authentication)) { 
194                    log.debug("Access denied for: %s", authentication.getName());
195                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
196                }
197            }
198    
199            try {
200                    Object returnedObject = securityInterceptor != null 
201                            ? securityInterceptor.invoke(context)
202                            : endAuthorization(context);
203                
204                return returnedObject;
205            }
206            catch (AccessDeniedException e) {
207                    throw SecurityServiceException.newAccessDeniedException(e.getMessage());
208            }
209            catch (InvocationTargetException e) {
210                handleAuthorizationExceptions(e);
211                throw e;
212            }
213            finally {
214                if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
215                        SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
216                        SecurityContextHolder.clearContext();
217                        try {
218                            securityContextRepository.saveContext(contextAfterChainExecution, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder));
219                        }
220                        catch (Exception e) {
221                            log.error(e, "Could not extract wrapped context from holder");
222                        }
223                }
224            }
225        }
226    
227        public void logout() {
228            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
229            HttpSession session = context.getSession(false);
230            if (session != null && securityContextRepository.containsContext(context.getRequest()))                 
231                    session.invalidate();
232            
233            SecurityContextHolder.clearContext();
234        }
235    
236        protected boolean isUserInRole(Authentication authentication, String role) {
237            for (GrantedAuthority ga : authentication.getAuthorities()) {
238                if (ga.getAuthority().matches(role))
239                    return true;
240            }
241            return false;
242        }
243    
244        protected boolean isAuthenticated(Authentication authentication) {
245            return authentication != null && authentication.isAuthenticated();
246        }
247    
248        protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
249            log.debug("Is authenticated as: %s", authentication.getName());
250    
251            for (String role : context.getDestination().getRoles()) {
252                if (isUserInRole(authentication, role)) {
253                    log.debug("Allowed access to %s in role %s", authentication.getName(), role);
254                    return true;
255                }
256                log.debug("Access denied for %s not in role %s", authentication.getName(), role);
257            }
258            return false;
259        }
260    
261        protected void handleAuthorizationExceptions(InvocationTargetException e) {
262            for (Throwable t = e; t != null; t = t.getCause()) {
263                // Don't create a dependency to javax.ejb in SecurityService...
264                if (t instanceof SecurityException ||
265                    t instanceof AccessDeniedException ||
266                    "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
267                    throw SecurityServiceException.newAccessDeniedException(t.getMessage());
268            }
269        }
270    
271    }