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