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}