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
021package org.granite.messaging.service.security;
022
023import java.lang.reflect.InvocationTargetException;
024import java.util.Arrays;
025import java.util.List;
026import java.util.Map;
027
028import javax.servlet.http.HttpServletRequest;
029
030import org.acegisecurity.AbstractAuthenticationManager;
031import org.acegisecurity.AccessDeniedException;
032import org.acegisecurity.Authentication;
033import org.acegisecurity.AuthenticationException;
034import org.acegisecurity.BadCredentialsException;
035import org.acegisecurity.GrantedAuthority;
036import org.acegisecurity.context.SecurityContext;
037import org.acegisecurity.context.SecurityContextHolder;
038import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
039import org.acegisecurity.userdetails.UsernameNotFoundException;
040import org.granite.context.GraniteContext;
041import org.granite.logging.Logger;
042import org.granite.messaging.webapp.HttpGraniteContext;
043import org.springframework.beans.factory.BeanFactoryUtils;
044import org.springframework.context.ApplicationContext;
045import org.springframework.web.context.support.WebApplicationContextUtils;
046
047/**
048 * @author Francisco PEREDO
049 */
050public 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, String charset) {
064        List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset));
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                endLogin(credentials, charset);
085            } 
086            catch (AuthenticationException e) {
087                handleAuthenticationExceptions(e);
088            }
089        }
090
091        log.debug("User %s logged in", user);
092    }
093    
094    protected void handleAuthenticationExceptions(AuthenticationException e) {
095        if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
096            throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
097        
098        throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
099    }
100
101    public Object authorize(AbstractSecurityContext context) throws Exception {
102        log.debug("Authorize: %s", context);
103        log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
104
105        startAuthorization(context);
106
107        Authentication authentication = getAuthentication();
108        if (context.getDestination().isSecured()) {
109            if (!isAuthenticated(authentication)) {
110                log.debug("Is not authenticated!");
111                throw SecurityServiceException.newNotLoggedInException("User not logged in");
112            }
113            if (!userCanAccessService(context, authentication)) { 
114                log.debug("Access denied for: %s", authentication.getName());
115                throw SecurityServiceException.newAccessDeniedException("User not in required role");
116            }
117        }
118        if (isAuthenticated(authentication)) {
119            SecurityContext securityContext = SecurityContextHolder.getContext();
120            securityContext.setAuthentication(authentication);
121        }
122
123        try {
124            return endAuthorization(context);
125        } catch (InvocationTargetException e) {
126            handleAuthorizationExceptions(e);
127            throw e;
128        }
129        finally {
130                SecurityContextHolder.clearContext();
131        }
132    }
133
134    public void logout() {
135        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
136        context.getSession().invalidate();
137        SecurityContextHolder.getContext().setAuthentication(null);
138        SecurityContextHolder.clearContext();
139    }
140
141    protected boolean isUserInRole(Authentication authentication, String role) {
142        for (GrantedAuthority ga : authentication.getAuthorities()) {
143            if (ga.getAuthority().matches(role))
144                return true;
145        }
146        return false;
147    }
148
149    protected boolean isAuthenticated(Authentication authentication) {
150        return authentication != null && authentication.isAuthenticated();
151    }
152    
153    protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
154        log.debug("Is authenticated as: %s", authentication.getName());
155
156        for (String role : context.getDestination().getRoles()) {
157            if (isUserInRole(authentication, role)) {
158                log.debug("Allowed access to %s in role %s", authentication.getName(), role);
159                return true;
160            }
161            log.debug("Access denied for %s not in role %s", authentication.getName(), role);
162        }
163        return false;
164    }
165
166    protected Authentication getAuthentication() {
167        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
168        HttpServletRequest httpRequest = context.getRequest();
169        Authentication authentication = 
170            (Authentication) httpRequest.getSession().getAttribute(SPRING_AUTHENTICATION_TOKEN);
171        return authentication;
172    }
173
174    protected void handleAuthorizationExceptions(InvocationTargetException e) {
175        for (Throwable t = e; t != null; t = t.getCause()) {
176            // Don't create a dependency to javax.ejb in SecurityService...
177            if (t instanceof SecurityException ||
178                t instanceof AccessDeniedException ||
179                "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
180                throw SecurityServiceException.newAccessDeniedException(t.getMessage());
181        }
182    }
183}