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.Field;
024import java.lang.reflect.InvocationTargetException;
025import java.security.Principal;
026import java.util.Map;
027
028import javax.servlet.http.HttpServletRequest;
029import javax.servlet.http.HttpServletRequestWrapper;
030import javax.servlet.http.HttpSession;
031
032import org.apache.catalina.Context;
033import org.apache.catalina.Realm;
034import org.apache.catalina.Session;
035import org.apache.catalina.authenticator.Constants;
036import org.apache.catalina.connector.Request;
037import org.apache.catalina.connector.RequestFacade;
038import org.granite.context.GraniteContext;
039import org.granite.messaging.webapp.HttpGraniteContext;
040
041/**
042 * @author Franck WOLFF
043 */
044public class Tomcat7SecurityService extends AbstractSecurityService {
045
046    private final Field requestField;
047
048    public Tomcat7SecurityService() {
049        super();
050        try {
051            // We need to access the org.apache.catalina.connector.Request field from
052            // a org.apache.catalina.connector.RequestFacade. Unfortunately there is
053            // no public getter for this field (and I don't want to create a Valve)...
054            requestField = RequestFacade.class.getDeclaredField("request");
055            requestField.setAccessible(true);
056        } catch (Exception e) {
057            throw new RuntimeException("Could not get 'request' field in Tomcat RequestFacade", e);
058        }
059    }
060
061    protected Field getRequestField() {
062        return requestField;
063    }
064
065    
066    public void configure(Map<String, String> params) {
067    }
068
069    
070    public void login(Object credentials, String charset) throws SecurityServiceException {
071        String[] decoded = decodeBase64Credentials(credentials, charset);
072
073        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
074        HttpServletRequest httpRequest = context.getRequest();
075        Request request = getRequest(httpRequest);
076        Realm realm = getRealm(request);
077
078        Principal principal = realm.authenticate(decoded[0], decoded[1]);
079        if (principal == null)
080            throw SecurityServiceException.newInvalidCredentialsException("Wrong username or password");
081
082        request.setAuthType(AUTH_TYPE);
083        request.setUserPrincipal(principal);
084
085        Session session = request.getSessionInternal();
086        session.setAuthType(AUTH_TYPE);
087        session.setPrincipal(principal);
088        session.setNote(Constants.SESS_USERNAME_NOTE, decoded[0]);
089        session.setNote(Constants.SESS_PASSWORD_NOTE, decoded[1]);
090        
091        endLogin(credentials, charset);
092    }
093
094    public Object authorize(AbstractSecurityContext context) throws Exception {
095
096        startAuthorization(context);
097        
098        HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
099        HttpServletRequest httpRequest = graniteContext.getRequest();
100        Request request = getRequest(httpRequest);
101        Session session = request.getSessionInternal(false);
102
103        Principal principal = null;
104        if (session != null) {
105            request.setAuthType(session.getAuthType());
106                principal = session.getPrincipal();
107                if (principal == null && tryRelogin())
108                        principal = session.getPrincipal();
109        }
110
111        request.setUserPrincipal(principal);
112
113        if (context.getDestination().isSecured()) {
114            if (principal == null) {
115                if (httpRequest.getRequestedSessionId() != null) {
116                    HttpSession httpSession = httpRequest.getSession(false);
117                    if (httpSession == null || httpRequest.getRequestedSessionId().equals(httpSession.getId()))
118                        throw SecurityServiceException.newSessionExpiredException("Session expired");
119                }
120                throw SecurityServiceException.newNotLoggedInException("User not logged in");
121            }
122
123            boolean accessDenied = true;
124            for (String role : context.getDestination().getRoles()) {
125                if (request.isUserInRole(role)) {
126                    accessDenied = false;
127                    break;
128                }
129            }
130            if (accessDenied)
131                throw SecurityServiceException.newAccessDeniedException("User not in required role");
132        }
133
134        try {
135            return endAuthorization(context);
136        } 
137        catch (InvocationTargetException e) {
138            for (Throwable t = e; t != null; t = t.getCause()) {
139                // Don't create a dependency to javax.ejb in SecurityService...
140                if (t instanceof SecurityException ||
141                    "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
142                    throw SecurityServiceException.newAccessDeniedException(t.getMessage());
143            }
144            throw e;
145        }
146    }
147
148    public void logout() throws SecurityServiceException {
149        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
150
151        Session session = getSession(context.getRequest(), false);
152        if (session != null && session.getPrincipal() != null) {
153            session.setAuthType(null);
154            session.setPrincipal(null);
155            session.removeNote(Constants.SESS_USERNAME_NOTE);
156            session.removeNote(Constants.SESS_PASSWORD_NOTE);
157            
158            endLogout();
159            
160            session.expire();
161        }
162    }
163
164    protected Principal getPrincipal(HttpServletRequest httpRequest) {
165        Request request = getRequest(httpRequest);
166        Session session = request.getSessionInternal(false);
167        return (session != null ? session.getPrincipal() : null);
168    }
169
170    protected Session getSession(HttpServletRequest httpRequest, boolean create) {
171        Request request = getRequest(httpRequest);
172        return request.getSessionInternal(create);
173    }
174
175    protected Request getRequest(HttpServletRequest request) {
176        while (request instanceof HttpServletRequestWrapper)
177            request = (HttpServletRequest)((HttpServletRequestWrapper)request).getRequest();
178        try {
179            return (Request)requestField.get(request);
180        } catch (Exception e) {
181            throw new RuntimeException("Could not get tomcat request", e);
182        }
183    }
184
185    protected Realm getRealm(Request request) {
186        String serverName = request.getServerName();
187        String contextPath = request.getContextPath();
188        
189        Context context = request.getContext();
190        if (context == null)
191            throw new NullPointerException("Could not find Tomcat context for: " + contextPath);
192        Realm realm = context.getRealm();
193        if (realm == null)
194            throw new NullPointerException("Could not find Tomcat realm for: " + serverName + "" + contextPath);
195
196        return realm;
197    }
198}