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    
030    import org.acegisecurity.AbstractAuthenticationManager;
031    import org.acegisecurity.AccessDeniedException;
032    import org.acegisecurity.Authentication;
033    import org.acegisecurity.AuthenticationException;
034    import org.acegisecurity.BadCredentialsException;
035    import org.acegisecurity.GrantedAuthority;
036    import org.acegisecurity.context.SecurityContext;
037    import org.acegisecurity.context.SecurityContextHolder;
038    import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
039    import org.acegisecurity.userdetails.UsernameNotFoundException;
040    import org.granite.context.GraniteContext;
041    import org.granite.logging.Logger;
042    import org.granite.messaging.webapp.HttpGraniteContext;
043    import org.springframework.beans.factory.BeanFactoryUtils;
044    import org.springframework.context.ApplicationContext;
045    import org.springframework.web.context.support.WebApplicationContextUtils;
046    
047    /**
048     * @author Francisco PEREDO
049     */
050    public class AcegiSecurityService extends AbstractSecurityService {
051    
052        private static final Logger log = Logger.getLogger(AcegiSecurityService.class);
053            private static final String SPRING_AUTHENTICATION_TOKEN = AcegiSecurityService.class.getName() + ".SPRING_AUTHENTICATION_TOKEN";
054    
055        public AcegiSecurityService() {
056            log.debug("Starting Service!");
057        }
058    
059        public void configure(Map<String, String> params) {
060            log.debug("Configuring with parameters (NOOP) %s: ", params);
061        }
062    
063        public void login(Object credentials) {
064            List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials));
065    
066            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
067            HttpServletRequest httpRequest = context.getRequest();
068    
069            String user = decodedCredentials.get(0);
070            String password = decodedCredentials.get(1);
071            Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
072    
073            ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
074                httpRequest.getSession().getServletContext()
075            );
076            if (ctx != null) {
077                AbstractAuthenticationManager authenticationManager =
078                    BeanFactoryUtils.beanOfTypeIncludingAncestors(ctx, AbstractAuthenticationManager.class);
079                try {
080                    Authentication authentication = authenticationManager.authenticate(auth);
081                    SecurityContextHolder.getContext().setAuthentication(authentication);
082                    httpRequest.getSession().setAttribute(SPRING_AUTHENTICATION_TOKEN, authentication);
083                } 
084                catch (AuthenticationException e) {
085                    handleAuthenticationExceptions(e);
086                }
087            }
088    
089            log.debug("User %s logged in", user);
090        }
091        
092        protected void handleAuthenticationExceptions(AuthenticationException e) {
093            if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
094                throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
095            
096            throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
097        }
098    
099        public Object authorize(AbstractSecurityContext context) throws Exception {
100            log.debug("Authorize: %s", context);
101            log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
102    
103            startAuthorization(context);
104    
105            Authentication authentication = getAuthentication();
106            if (context.getDestination().isSecured()) {
107                if (!isAuthenticated(authentication)) {
108                    log.debug("Is not authenticated!");
109                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
110                }
111                if (!userCanAccessService(context, authentication)) { 
112                    log.debug("Access denied for: %s", authentication.getName());
113                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
114                }
115            }
116            if (isAuthenticated(authentication)) {
117                SecurityContext securityContext = SecurityContextHolder.getContext();
118                securityContext.setAuthentication(authentication);
119            }
120    
121            try {
122                return endAuthorization(context);
123            } catch (InvocationTargetException e) {
124                handleAuthorizationExceptions(e);
125                throw e;
126            }
127            finally {
128                    SecurityContextHolder.clearContext();
129            }
130        }
131    
132        public void logout() {
133            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
134            context.getSession().invalidate();
135            SecurityContextHolder.getContext().setAuthentication(null);
136            SecurityContextHolder.clearContext();
137        }
138    
139        protected boolean isUserInRole(Authentication authentication, String role) {
140            for (GrantedAuthority ga : authentication.getAuthorities()) {
141                if (ga.getAuthority().matches(role))
142                    return true;
143            }
144            return false;
145        }
146    
147        protected boolean isAuthenticated(Authentication authentication) {
148            return authentication != null && authentication.isAuthenticated();
149        }
150        
151        protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
152            log.debug("Is authenticated as: %s", authentication.getName());
153    
154            for (String role : context.getDestination().getRoles()) {
155                if (isUserInRole(authentication, role)) {
156                    log.debug("Allowed access to %s in role %s", authentication.getName(), role);
157                    return true;
158                }
159                log.debug("Access denied for %s not in role %s", authentication.getName(), role);
160            }
161            return false;
162        }
163    
164        protected Authentication getAuthentication() {
165            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
166            HttpServletRequest httpRequest = context.getRequest();
167            Authentication authentication = 
168                (Authentication) httpRequest.getSession().getAttribute(SPRING_AUTHENTICATION_TOKEN);
169            return authentication;
170        }
171    
172        protected void handleAuthorizationExceptions(InvocationTargetException e) {
173            for (Throwable t = e; t != null; t = t.getCause()) {
174                // Don't create a dependency to javax.ejb in SecurityService...
175                if (t instanceof SecurityException ||
176                    t instanceof AccessDeniedException ||
177                    "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
178                    throw SecurityServiceException.newAccessDeniedException(t.getMessage());
179            }
180        }
181    }