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