001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2007-2010 ADEQUATE SYSTEMS SARL
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.util.Arrays;
025    import java.util.List;
026    import java.util.Map;
027    
028    import javax.servlet.http.HttpServletRequest;
029    
030    import org.granite.context.GraniteContext;
031    import org.granite.logging.Logger;
032    import org.granite.messaging.service.security.AbstractSecurityContext;
033    import org.granite.messaging.service.security.AbstractSecurityService;
034    import org.granite.messaging.service.security.SecurityServiceException;
035    import org.granite.messaging.webapp.HttpGraniteContext;
036    import org.springframework.beans.factory.BeanFactoryUtils;
037    import org.springframework.context.ApplicationContext;
038    import org.springframework.security.access.AccessDeniedException;
039    import org.springframework.security.authentication.AuthenticationManager;
040    import org.springframework.security.authentication.BadCredentialsException;
041    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
042    import org.springframework.security.core.Authentication;
043    import org.springframework.security.core.GrantedAuthority;
044    import org.springframework.security.core.context.SecurityContext;
045    import org.springframework.security.core.context.SecurityContextHolder;
046    import org.springframework.web.context.support.WebApplicationContextUtils;
047    
048    /**
049     * @author Bouiaw
050     */
051    public class SpringSecurity3Service extends AbstractSecurityService {
052            
053            private static final Logger log = Logger.getLogger(SpringSecurity3Service.class);
054            private static final String SPRING_AUTHENTICATION_TOKEN = SpringSecurity3Service.class.getName() + ".SPRING_AUTHENTICATION_TOKEN";
055            
056            private AuthenticationManager authenticationManager = null;
057            private String authenticationManagerBeanName = null;
058            
059            public SpringSecurity3Service() {
060                    log.debug("Starting Spring 3 Security Service!");
061        }
062            
063            public void setAuthenticationManager(AuthenticationManager authenticationManager) {
064                    this.authenticationManager = authenticationManager;
065            }
066    
067        public void configure(Map<String, String> params) {
068            log.debug("Configuring with parameters %s: ", params);
069            if (params.containsKey("authentication-manager-bean-name"))
070                    authenticationManagerBeanName = params.get("authentication-manager-bean-name");
071        }
072        
073        public void login(Object credentials) {
074            List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials));
075    
076            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
077            HttpServletRequest httpRequest = context.getRequest();
078    
079            String user = decodedCredentials.get(0);
080            String password = decodedCredentials.get(1);
081            Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
082    
083            ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
084                httpRequest.getSession().getServletContext()
085            );
086            if (ctx != null) {
087                    lookupAuthenticationManager(ctx, authenticationManagerBeanName);
088                
089                try {
090                    Authentication authentication = authenticationManager.authenticate(auth);
091                    SecurityContextHolder.getContext().setAuthentication(authentication);
092                    httpRequest.getSession().setAttribute(SPRING_AUTHENTICATION_TOKEN, authentication);
093                } catch (BadCredentialsException e) {
094                    throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
095                }
096            }
097    
098            log.debug("Logged In!");
099        }
100        
101        public void lookupAuthenticationManager(ApplicationContext ctx, String authenticationManagerBeanName) throws SecurityServiceException {
102            if (this.authenticationManager != null)
103                    return;
104            
105            @SuppressWarnings("unchecked")
106            Map<String, AuthenticationManager> authManagers = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, AuthenticationManager.class);
107            
108            if (authenticationManagerBeanName != null) {
109                    this.authenticationManager = authManagers.get(authenticationManagerBeanName);
110                    if (authenticationManager == null) {
111                            log.error("AuthenticationManager bean not found " + authenticationManagerBeanName);
112                            throw SecurityServiceException.newInvalidCredentialsException("Authentication failed");
113                    }
114                    return;
115            }
116            else if (authManagers.size() > 1) {
117                    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>");
118                    throw SecurityServiceException.newInvalidCredentialsException("Authentication failed");
119            }
120            
121            this.authenticationManager = authManagers.values().iterator().next();
122        }
123    
124        
125        public Object authorize(AbstractSecurityContext context) throws Exception {
126            log.debug("Authorize: %s", context);
127            log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
128    
129            startAuthorization(context);
130    
131            Authentication authentication = getAuthentication();
132            if (context.getDestination().isSecured()) {
133                if (!isAuthenticated(authentication)) {
134                    log.debug("Is not authenticated!");
135                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
136                }
137                if (!userCanAccessService(context, authentication)) { 
138                    log.debug("Access denied for: %s", authentication.getName());
139                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
140                }
141            }
142            if (isAuthenticated(authentication)) {
143                SecurityContext securityContext = SecurityContextHolder.getContext();
144                securityContext.setAuthentication(authentication);
145            }
146    
147            try {
148                return endAuthorization(context);
149            } catch (InvocationTargetException e) {
150                handleAuthorizationExceptions(e);
151                throw e;
152            }
153            finally {
154                    SecurityContextHolder.clearContext();
155            }
156        }
157    
158        public void logout() {
159            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
160            context.getSession().invalidate();
161            SecurityContextHolder.getContext().setAuthentication(null);
162            SecurityContextHolder.clearContext();
163        }
164    
165        protected boolean isUserInRole(Authentication authentication, String role) {
166            for (GrantedAuthority ga : authentication.getAuthorities()) {
167                if (ga.getAuthority().matches(role))
168                    return true;
169            }
170            return false;
171        }
172    
173        protected boolean isAuthenticated(Authentication authentication) {
174            return authentication != null && authentication.isAuthenticated();
175        }
176    
177        protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
178            log.debug("Is authenticated as: %s", authentication.getName());
179    
180            for (String role : context.getDestination().getRoles()) {
181                if (isUserInRole(authentication, role)) {
182                    log.debug("Allowed access to %s in role %s", authentication.getName(), role);
183                    return true;
184                }
185                log.debug("Access denied for %s not in role %s", authentication.getName(), role);
186            }
187            return false;
188        }
189    
190        protected Authentication getAuthentication() {
191            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
192            HttpServletRequest httpRequest = context.getRequest();
193            Authentication authentication = 
194                (Authentication) httpRequest.getSession().getAttribute(SPRING_AUTHENTICATION_TOKEN);
195            
196            return authentication;
197        }
198    
199        protected void handleAuthorizationExceptions(InvocationTargetException e) {
200            for (Throwable t = e; t != null; t = t.getCause()) {
201                // Don't create a dependency to javax.ejb in SecurityService...
202                if (t instanceof SecurityException ||
203                    t instanceof AccessDeniedException ||
204                    "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
205                    throw SecurityServiceException.newAccessDeniedException(t.getMessage());
206            }
207        }
208    
209    }