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