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