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