/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.messaging.service.security; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.Principal; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpSession; import com.sun.web.security.RealmAdapter; import org.apache.catalina.Engine; import org.apache.catalina.Realm; import org.apache.catalina.Server; import org.apache.catalina.ServerFactory; import org.apache.catalina.Service; import org.apache.catalina.Session; import org.apache.catalina.authenticator.Constants; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.Request; import org.apache.catalina.core.StandardContext; import org.granite.context.GraniteContext; import org.granite.logging.Logger; import org.granite.messaging.webapp.HttpGraniteContext; import org.granite.messaging.webapp.ServletGraniteContext; /** * @author Franck WOLFF */ public class GlassFishV3SecurityService extends AbstractSecurityService { private static final Logger log = Logger.getLogger(GlassFishV3SecurityService.class); private static Method authenticate = null; static { // GlassFish V3.0 try { authenticate = Realm.class.getMethod("authenticate", String.class, String.class); log.info("Detected GlassFish v3.0 authentication"); } catch (NoSuchMethodException e) { } catch (NoSuchMethodError e) { } // GlassFish V3.1+ if (authenticate == null) try { authenticate = Realm.class.getMethod("authenticate", String.class, char[].class); log.info("Detected GlassFish v3.1+ authentication"); } catch (NoSuchMethodException e) { } catch (NoSuchMethodError e) { } if (authenticate == null) throw new ExceptionInInitializerError("Could not find any supported Realm.authenticate method"); } private static Principal authenticate(Realm realm, String username, String password) { try { if (authenticate.getParameterTypes()[1].equals(String.class)) return (Principal)authenticate.invoke(realm, username, password); return (Principal)authenticate.invoke(realm, username, password.toCharArray()); } catch (Exception e) { throw new RuntimeException(e); } } private final Field requestField; private Engine engine = null; public GlassFishV3SecurityService() { super(); try { // We need to access the org.apache.catalina.connector.Request field from // a org.apache.catalina.connector.RequestFacade. Unfortunately there is // no public getter for this field (and I don't want to create a Valve)... requestField = RequestFacade.class.getDeclaredField("request"); requestField.setAccessible(true); } catch (Exception e) { throw new RuntimeException("Could not get 'request' field in GlassFish V3 RequestFacade", e); } } protected Field getRequestField() { return requestField; } protected Engine getEngine() { return engine; } public void configure(Map<String, String> params) { String serviceId = params.get("service"); Server server = ServerFactory.getServer(); if (server == null) throw new NullPointerException("Could not get GlassFish V3 server"); Service service = null; if (serviceId != null) service = server.findService(serviceId); else { Service[] services = server.findServices(); if (services != null && services.length > 0) service = services[0]; } if (service == null) throw new NullPointerException("Could not find GlassFish V3 service for: " + (serviceId != null ? serviceId : "(default)")); engine = (Engine)service.getContainer(); if (engine == null) throw new NullPointerException("Could not find GlassFish V3 container for: " + (serviceId != null ? serviceId : "(default)")); } @Override public void prelogin(HttpSession session, Object httpRequest, String servletName) { if (session == null) // Cannot prelogin() without a session return; if (session.getAttribute(AuthenticationContext.class.getName()) instanceof GlassFishV3AuthenticationContext) return; HttpServletRequest request = null; RealmAdapter realm = null; if (httpRequest.getClass().getName().equals("com.sun.grizzly.websockets.ServerNetworkHandler$WSServletRequestImpl")) { Field f = null; try { f = httpRequest.getClass().getDeclaredField("glassfishSupport"); f.setAccessible(true); Object gfs = f.get(httpRequest); f = gfs.getClass().getDeclaredField("context"); f.setAccessible(true); Object ctx = f.get(gfs); f = ctx.getClass().getDeclaredField("context"); f.setAccessible(true); StandardContext sc = (StandardContext)f.get(ctx); realm = (RealmAdapter)sc.getRealm(); } catch (Exception e) { throw new RuntimeException("Could not get internal glassfish v3 request", e); } } else { if (httpRequest instanceof HttpServletRequest) request = (HttpServletRequest)httpRequest; else if (httpRequest.getClass().getName().equals("org.glassfish.tyrus.core.RequestContext")) { // Websocket/Tyrus Field f = null; try { f = httpRequest.getClass().getDeclaredField("isUserInRoleDelegate"); f.setAccessible(true); Object delegate = f.get(httpRequest); f = delegate.getClass().getDeclaredField("val$httpServletRequest"); f.setAccessible(true); request = (HttpServletRequest)f.get(delegate); } catch (Exception e) { throw new RuntimeException("Could not get internal glassfish v3 / tyrus request", e); } } servletName = getRequest(request).getWrapper().getServletName(); realm = getRealm(request); } GlassFishV3AuthenticationContext authorizationContext = new GlassFishV3AuthenticationContext(servletName, realm); session.setAttribute(AuthenticationContext.class.getName(), authorizationContext); } public Principal login(Object credentials, String charset) throws SecurityServiceException { String[] decoded = decodeBase64Credentials(credentials, charset); ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance(); Principal principal = null; Request request = null; if (graniteContext instanceof HttpGraniteContext) { HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance(); HttpServletRequest httpRequest = context.getRequest(); request = getRequest(httpRequest); RealmAdapter realm = (RealmAdapter)request.getContext().getRealm(); GlassFishV3AuthenticationContext authenticationContext = new GlassFishV3AuthenticationContext(request.getWrapper().getServletName(), realm); principal = authenticationContext.authenticate(decoded[0], decoded[1]); if (principal != null) graniteContext.getSession().setAttribute(AuthenticationContext.class.getName(), authenticationContext); } else { AuthenticationContext authenticationContext = (AuthenticationContext)graniteContext.getSession().getAttribute(AuthenticationContext.class.getName()); if (authenticationContext != null) principal = authenticationContext.authenticate(decoded[0], decoded[1]); else return null; } if (principal == null) throw SecurityServiceException.newInvalidCredentialsException("Wrong username or password"); graniteContext.setPrincipal(principal); if (graniteContext instanceof HttpGraniteContext) { request.setAuthType(AUTH_TYPE); request.setUserPrincipal(principal); Session session = request.getSessionInternal(); session.setAuthType(AUTH_TYPE); session.setPrincipal(principal); session.setNote(Constants.SESS_USERNAME_NOTE, decoded[0]); session.setNote(Constants.SESS_PASSWORD_NOTE, decoded[1]); } endLogin(credentials, charset); return principal; } public Object authorize(AbstractSecurityContext context) throws Exception { startAuthorization(context); ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance(); HttpServletRequest httpRequest = null; AuthenticationContext authenticationContext = null; Principal principal = null; if (graniteContext instanceof HttpGraniteContext) { httpRequest = graniteContext.getRequest(); Request request = getRequest(httpRequest); Session session = request.getSessionInternal(false); if (session != null) { request.setAuthType(session.getAuthType()); principal = session.getPrincipal(); if (principal == null && tryRelogin()) principal = session.getPrincipal(); } request.setUserPrincipal(principal); } else { HttpSession session = graniteContext.getSession(false); if (session != null) { authenticationContext = (AuthenticationContext)session.getAttribute(AuthenticationContext.class.getName()); if (authenticationContext != null) principal = authenticationContext.getPrincipal(); } } graniteContext.setPrincipal(principal); if (context.getDestination().isSecured()) { if (principal == null) { if (httpRequest != null && httpRequest.getRequestedSessionId() != null) { HttpSession httpSession = httpRequest.getSession(false); if (httpSession == null || !httpRequest.getRequestedSessionId().equals(httpSession.getId())) throw SecurityServiceException.newSessionExpiredException("Session expired"); } throw SecurityServiceException.newNotLoggedInException("User not logged in"); } if (httpRequest == null && authenticationContext == null) throw SecurityServiceException.newNotLoggedInException("No authorization context"); boolean accessDenied = true; for (String role : context.getDestination().getRoles()) { if (httpRequest != null && httpRequest.isUserInRole(role)) { accessDenied = false; break; } if (authenticationContext != null && authenticationContext.isUserInRole(role)) { accessDenied = false; break; } } if (accessDenied) throw SecurityServiceException.newAccessDeniedException("User not in required role"); } try { return endAuthorization(context); } catch (InvocationTargetException e) { for (Throwable t = e; t != null; t = t.getCause()) { // Don't create a dependency to javax.ejb in SecurityService... if (t instanceof SecurityException || "javax.ejb.EJBAccessException".equals(t.getClass().getName())) throw SecurityServiceException.newAccessDeniedException(t.getMessage()); } throw e; } } public void logout() throws SecurityServiceException { ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance(); if (graniteContext instanceof HttpGraniteContext) { Session session = getSession(graniteContext.getRequest(), false); if (session != null && session.getPrincipal() != null) { session.setAuthType(null); session.setPrincipal(null); session.removeNote(Constants.SESS_USERNAME_NOTE); session.removeNote(Constants.SESS_PASSWORD_NOTE); endLogout(); session.expire(); } } else { HttpSession session = graniteContext.getSession(); if (session != null) { session.removeAttribute(AuthenticationContext.class.getName()); endLogout(); session.invalidate(); } } } protected Session getSession(HttpServletRequest httpRequest, boolean create) { Request request = getRequest(httpRequest); return request.getSessionInternal(create); } protected Request getRequest(HttpServletRequest request) { while (request instanceof HttpServletRequestWrapper) request = (HttpServletRequest)((HttpServletRequestWrapper)request).getRequest(); try { return (Request)requestField.get(request); } catch (Exception e) { throw new RuntimeException("Could not get GlassFish V3 request", e); } } protected RealmAdapter getRealm(HttpServletRequest request) { Request creq = getRequest(request); return (RealmAdapter)creq.getContext().getRealm(); } public static class GlassFishV3AuthenticationContext implements AuthenticationContext { private static final long serialVersionUID = 1L; private final String securityServletName; private transient final RealmAdapter realm; private transient Principal principal; public GlassFishV3AuthenticationContext(String securityServletName, RealmAdapter realm) { this.securityServletName = securityServletName; this.realm = realm; } public Principal authenticate(String username, String password) { if (realm == null) throw SecurityServiceException.newAuthenticationFailedException("Invalid authentication"); principal = GlassFishV3SecurityService.authenticate(realm, username, password); return principal; } public Principal getPrincipal() { return principal; } public boolean isUserInRole(String role) { return realm.hasRole(securityServletName, principal, role); } public void logout() { realm.logout(); } } }