001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.messaging.service.security;
023
024import java.lang.reflect.InvocationTargetException;
025import java.util.Arrays;
026import java.util.List;
027import java.util.Map;
028
029import javax.servlet.http.HttpServletRequest;
030
031import org.acegisecurity.AbstractAuthenticationManager;
032import org.acegisecurity.AccessDeniedException;
033import org.acegisecurity.Authentication;
034import org.acegisecurity.AuthenticationException;
035import org.acegisecurity.BadCredentialsException;
036import org.acegisecurity.GrantedAuthority;
037import org.acegisecurity.context.SecurityContext;
038import org.acegisecurity.context.SecurityContextHolder;
039import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
040import org.acegisecurity.userdetails.UsernameNotFoundException;
041import org.granite.context.GraniteContext;
042import org.granite.logging.Logger;
043import org.granite.messaging.webapp.HttpGraniteContext;
044import org.springframework.beans.factory.BeanFactoryUtils;
045import org.springframework.context.ApplicationContext;
046import org.springframework.web.context.support.WebApplicationContextUtils;
047
048/**
049 * @author Francisco PEREDO
050 */
051public class AcegiSecurityService extends AbstractSecurityService {
052
053    private static final Logger log = Logger.getLogger(AcegiSecurityService.class);
054        private static final String SPRING_AUTHENTICATION_TOKEN = AcegiSecurityService.class.getName() + ".SPRING_AUTHENTICATION_TOKEN";
055
056    public AcegiSecurityService() {
057        log.debug("Starting Service!");
058    }
059
060    public void configure(Map<String, String> params) {
061        log.debug("Configuring with parameters (NOOP) %s: ", params);
062    }
063
064    public void login(Object credentials, String charset) {
065        List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset));
066
067        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
068        HttpServletRequest httpRequest = context.getRequest();
069
070        String user = decodedCredentials.get(0);
071        String password = decodedCredentials.get(1);
072        Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
073
074        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
075            httpRequest.getSession().getServletContext()
076        );
077        if (ctx != null) {
078            AbstractAuthenticationManager authenticationManager =
079                BeanFactoryUtils.beanOfTypeIncludingAncestors(ctx, AbstractAuthenticationManager.class);
080            try {
081                Authentication authentication = authenticationManager.authenticate(auth);
082                SecurityContextHolder.getContext().setAuthentication(authentication);
083                httpRequest.getSession().setAttribute(SPRING_AUTHENTICATION_TOKEN, authentication);
084
085                endLogin(credentials, charset);
086            } 
087            catch (AuthenticationException e) {
088                handleAuthenticationExceptions(e);
089            }
090        }
091
092        log.debug("User %s logged in", user);
093    }
094    
095    protected void handleAuthenticationExceptions(AuthenticationException e) {
096        if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
097            throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
098        
099        throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
100    }
101
102    public Object authorize(AbstractSecurityContext context) throws Exception {
103        log.debug("Authorize: %s", context);
104        log.debug("Is %s secured? %b", context.getDestination().getId(), context.getDestination().isSecured());
105
106        startAuthorization(context);
107
108        Authentication authentication = getAuthentication();
109        if (context.getDestination().isSecured()) {
110            if (!isAuthenticated(authentication)) {
111                log.debug("Is not authenticated!");
112                throw SecurityServiceException.newNotLoggedInException("User not logged in");
113            }
114            if (!userCanAccessService(context, authentication)) { 
115                log.debug("Access denied for: %s", authentication.getName());
116                throw SecurityServiceException.newAccessDeniedException("User not in required role");
117            }
118        }
119        if (isAuthenticated(authentication)) {
120            SecurityContext securityContext = SecurityContextHolder.getContext();
121            securityContext.setAuthentication(authentication);
122        }
123
124        try {
125            return endAuthorization(context);
126        } catch (InvocationTargetException e) {
127            handleAuthorizationExceptions(e);
128            throw e;
129        }
130        finally {
131                SecurityContextHolder.clearContext();
132        }
133    }
134
135    public void logout() {
136        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
137        context.getSession().invalidate();
138        SecurityContextHolder.getContext().setAuthentication(null);
139        SecurityContextHolder.clearContext();
140    }
141
142    protected boolean isUserInRole(Authentication authentication, String role) {
143        for (GrantedAuthority ga : authentication.getAuthorities()) {
144            if (ga.getAuthority().matches(role))
145                return true;
146        }
147        return false;
148    }
149
150    protected boolean isAuthenticated(Authentication authentication) {
151        return authentication != null && authentication.isAuthenticated();
152    }
153    
154    protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
155        log.debug("Is authenticated as: %s", authentication.getName());
156
157        for (String role : context.getDestination().getRoles()) {
158            if (isUserInRole(authentication, role)) {
159                log.debug("Allowed access to %s in role %s", authentication.getName(), role);
160                return true;
161            }
162            log.debug("Access denied for %s not in role %s", authentication.getName(), role);
163        }
164        return false;
165    }
166
167    protected Authentication getAuthentication() {
168        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
169        HttpServletRequest httpRequest = context.getRequest();
170        Authentication authentication = 
171            (Authentication) httpRequest.getSession().getAttribute(SPRING_AUTHENTICATION_TOKEN);
172        return authentication;
173    }
174
175    protected void handleAuthorizationExceptions(InvocationTargetException e) {
176        for (Throwable t = e; t != null; t = t.getCause()) {
177            // Don't create a dependency to javax.ejb in SecurityService...
178            if (t instanceof SecurityException ||
179                t instanceof AccessDeniedException ||
180                "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
181                throw SecurityServiceException.newAccessDeniedException(t.getMessage());
182        }
183    }
184}