1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.web.tomcat.security;
23
24 import java.io.IOException;
25 import java.security.Principal;
26
27 import javax.naming.InitialContext;
28 import javax.naming.NamingException;
29 import javax.security.auth.Subject;
30 import javax.servlet.ServletException;
31 import javax.servlet.http.HttpSession;
32
33 import org.apache.catalina.Manager;
34 import org.apache.catalina.Session;
35 import org.apache.catalina.Wrapper;
36 import org.apache.catalina.connector.Request;
37 import org.apache.catalina.connector.Response;
38 import org.apache.catalina.valves.ValveBase;
39 import org.jboss.logging.Logger;
40 import org.jboss.metadata.javaee.jboss.RunAsIdentityMetaData;
41 import org.jboss.metadata.web.jboss.JBossWebMetaData;
42 import org.jboss.security.AuthenticationManager;
43 import org.jboss.security.RunAsIdentity;
44 import org.jboss.security.plugins.JaasSecurityManagerServiceMBean;
45 import org.jboss.servlet.http.HttpEvent;
46
47 /**
48 * A Valve that sets/clears the SecurityAssociation information associated with
49 * the request thread for identity propagation.
50 *
51 * @author Scott.Stark@jboss.org
52 * @author Thomas.Diesler@jboss.org
53 * @author Anil.Saldhana@jboss.org
54 * @version $Revision: 81037 $
55 */
56 public class SecurityAssociationValve extends ValveBase
57 {
58 private static Logger log = Logger.getLogger(SecurityAssociationValve.class);
59 public static ThreadLocal<Principal> userPrincipal = new ThreadLocal<Principal>();
60 /** Maintain the active WebMetaData for request security checks */
61 public static ThreadLocal<JBossWebMetaData> activeWebMetaData = new ThreadLocal<JBossWebMetaData>();
62 /** Maintain the Catalina Request for programmatic web login */
63 public static ThreadLocal<Request> activeRequest = new ThreadLocal<Request>();
64 /** Maintain the Catalina Response for programmatic web login */
65 public static ThreadLocal<Response> activeResponse = new ThreadLocal<Response>();
66
67 /** The web app metadata */
68 private JBossWebMetaData metaData;
69 /** The name in the session under which the Subject is stored */
70 private String subjectAttributeName = null;
71 /** The service used to flush authentication cache on session invalidation. */
72 private JaasSecurityManagerServiceMBean secMgrService;
73 private boolean trace;
74
75 public SecurityAssociationValve(JBossWebMetaData metaData,
76 JaasSecurityManagerServiceMBean secMgrService)
77 {
78 this.metaData = metaData;
79 this.secMgrService = secMgrService;
80 this.trace = log.isTraceEnabled();
81 }
82
83 /**
84 * The name of the request attribute under with the authenticated JAAS
85 * Subject is stored on successful authentication. If null or empty then
86 * the Subject will not be stored.
87 */
88 public void setSubjectAttributeName(String subjectAttributeName)
89 {
90 this.subjectAttributeName = subjectAttributeName;
91 if (subjectAttributeName != null && subjectAttributeName.length() == 0)
92 this.subjectAttributeName = null;
93 }
94
95 public void invoke(Request request, Response response)
96 throws IOException, ServletException
97 {
98 Session session = null;
99 // Get the request caller which could be set due to SSO
100 Principal caller = request.getPrincipal();
101 // The cached web container principal
102 JBossGenericPrincipal principal = null;
103 HttpSession hsession = request.getSession(false);
104
105 if( trace )
106 log.trace("Begin invoke, caller="+caller);
107 // Set the active meta data
108 activeWebMetaData.set(metaData);
109 //Set the active request and response objects
110 activeRequest.set(request);
111 activeResponse.set(response);
112
113 try
114 {
115 Wrapper servlet = null;
116 try
117 {
118 servlet = request.getWrapper();
119 if (servlet != null)
120 {
121 String name = servlet.getName();
122 RunAsIdentityMetaData identity = metaData.getRunAsIdentity(name);
123 RunAsIdentity runAsIdentity = null;
124 if(identity != null)
125 {
126 if (trace)
127 log.trace(name + ", runAs: " + identity);
128 runAsIdentity = new RunAsIdentity(identity.getRoleName(),
129 identity.getPrincipalName(), identity.getRunAsRoles());
130 }
131 SecurityAssociationActions.pushRunAsIdentity(runAsIdentity);
132 }
133 userPrincipal.set(caller);
134
135 // If there is a session, get the tomcat session for the principal
136 Manager manager = container.getManager();
137 if (manager != null && hsession != null)
138 {
139 try
140 {
141 session = manager.findSession(hsession.getId());
142 }
143 catch (IOException ignore)
144 {
145 }
146 }
147
148 if (caller == null || (caller instanceof JBossGenericPrincipal) == false)
149 {
150 // Look to the session for the active caller security context
151 if (session != null)
152 {
153 principal =
154 (JBossGenericPrincipal) session.getPrincipal();
155 }
156 }
157 else
158 {
159 // Use the request principal as the caller identity
160 principal = (JBossGenericPrincipal) caller;
161 }
162
163 // If there is a caller use this as the identity to propagate
164 if (principal != null)
165 {
166 if (trace)
167 log.trace("Restoring principal info from cache");
168 SecurityAssociationActions.setPrincipalInfo(principal.getAuthPrincipal(),
169 principal.getCredentials(), principal.getSubject());
170 }
171 // Put the authenticated subject in the session if requested
172 if (subjectAttributeName != null)
173 {
174 javax.naming.Context securityNamingCtx = getSecurityNamingContext();
175 if (securityNamingCtx != null)
176 {
177 // Get the JBoss security manager from the ENC context
178 AuthenticationManager securityMgr = (AuthenticationManager) securityNamingCtx.lookup("securityMgr");
179 Subject subject = securityMgr.getActiveSubject();
180 request.getRequest().setAttribute(subjectAttributeName, subject);
181 }
182 }
183 }
184 catch (Throwable e)
185 {
186 log.debug("Failed to determine servlet", e);
187 }
188
189 // Perform the request
190 getNext().invoke(request, response);
191 if(servlet != null)
192 {
193 SecurityAssociationActions.popRunAsIdentity();
194 }
195
196 /* If the security domain cache is to be kept in synch with the
197 session then flush the cache if the session has been invalidated.
198 */
199 if( secMgrService != null &&
200 session != null && session.isValid() == false &&
201 metaData.isFlushOnSessionInvalidation() == true )
202 {
203 if( principal != null )
204 {
205 String securityDomain = metaData.getSecurityDomain();
206 if (trace)
207 {
208 log.trace("Session is invalid, security domain: "+securityDomain
209 +", user="+principal);
210 }
211 try
212 {
213 Principal authPrincipal = principal.getAuthPrincipal();
214 secMgrService.flushAuthenticationCache(securityDomain, authPrincipal);
215 }
216 catch(Exception e)
217 {
218 log.debug("Failed to flush auth cache", e);
219 }
220 }
221 }
222 }
223 finally
224 {
225 if( trace )
226 log.trace("End invoke, caller="+caller);
227 activeWebMetaData.set(null);
228 userPrincipal.set(null);
229 activeRequest.set(null);
230 activeResponse.set(null);
231 }
232 }
233
234 private javax.naming.Context getSecurityNamingContext()
235 {
236 javax.naming.Context securityCtx = null;
237 // Get the JBoss security manager from the ENC context
238 try
239 {
240 InitialContext iniCtx = new InitialContext();
241 securityCtx = (javax.naming.Context) iniCtx.lookup("java:comp/env/security");
242 }
243 catch (NamingException e)
244 {
245 // Apparently there is no security context?
246 }
247 return securityCtx;
248 }
249
250 public void event(Request request, Response response, HttpEvent event)
251 throws IOException, ServletException
252 {
253 Session session = null;
254 // Get the request caller which could be set due to SSO
255 Principal caller = request.getPrincipal();
256 // The cached web container principal
257 JBossGenericPrincipal principal = null;
258 HttpSession hsession = request.getSession(false);
259
260 if( trace )
261 log.trace("Begin invoke, caller="+caller);
262 // Set the active meta data
263 activeWebMetaData.set(metaData);
264 //Set the active request and response objects
265 activeRequest.set(request);
266 activeResponse.set(response);
267
268 try
269 {
270 Wrapper servlet = null;
271 try
272 {
273 servlet = request.getWrapper();
274 if (servlet != null)
275 {
276 String name = servlet.getName();
277 RunAsIdentityMetaData identity = metaData.getRunAsIdentity(name);
278 RunAsIdentity runAsIdentity = null;
279 if(identity != null)
280 {
281 if (trace)
282 log.trace(name + ", runAs: " + identity);
283 runAsIdentity = new RunAsIdentity(identity.getRoleName(),
284 identity.getPrincipalName(), identity.getRunAsRoles());
285 }
286 SecurityAssociationActions.pushRunAsIdentity(runAsIdentity);
287 }
288 userPrincipal.set(caller);
289
290 // If there is a session, get the tomcat session for the principal
291 Manager manager = container.getManager();
292 if (manager != null && hsession != null)
293 {
294 try
295 {
296 session = manager.findSession(hsession.getId());
297 }
298 catch (IOException ignore)
299 {
300 }
301 }
302
303 if (caller == null || (caller instanceof JBossGenericPrincipal) == false)
304 {
305 // Look to the session for the active caller security context
306 if (session != null)
307 {
308 principal =
309 (JBossGenericPrincipal) session.getPrincipal();
310 }
311 }
312 else
313 {
314 // Use the request principal as the caller identity
315 principal = (JBossGenericPrincipal) caller;
316 }
317
318 // If there is a caller use this as the identity to propagate
319 if (principal != null)
320 {
321 if (trace)
322 log.trace("Restoring principal info from cache");
323 SecurityAssociationActions.setPrincipalInfo(principal.getAuthPrincipal(),
324 principal.getCredentials(), principal.getSubject());
325 }
326 // Put the authenticated subject in the session if requested
327 if (subjectAttributeName != null)
328 {
329 javax.naming.Context securityNamingCtx = getSecurityNamingContext();
330 if (securityNamingCtx != null)
331 {
332 // Get the JBoss security manager from the ENC context
333 AuthenticationManager securityMgr = (AuthenticationManager) securityNamingCtx.lookup("securityMgr");
334 Subject subject = securityMgr.getActiveSubject();
335 request.getRequest().setAttribute(subjectAttributeName, subject);
336 }
337 }
338 }
339 catch (Throwable e)
340 {
341 log.debug("Failed to determine servlet", e);
342 }
343
344 // Perform the request
345 getNext().event(request, response, event);
346 if(servlet != null)
347 {
348 SecurityAssociationActions.popRunAsIdentity();
349 }
350
351 /* If the security domain cache is to be kept in synch with the
352 session then flush the cache if the session has been invalidated.
353 */
354 if( secMgrService != null &&
355 session != null && session.isValid() == false &&
356 metaData.isFlushOnSessionInvalidation() == true )
357 {
358 if( principal != null )
359 {
360 String securityDomain = metaData.getSecurityDomain();
361 if (trace)
362 {
363 log.trace("Session is invalid, security domain: "+securityDomain
364 +", user="+principal);
365 }
366 try
367 {
368 Principal authPrincipal = principal.getAuthPrincipal();
369 secMgrService.flushAuthenticationCache(securityDomain, authPrincipal);
370 }
371 catch(Exception e)
372 {
373 log.debug("Failed to flush auth cache", e);
374 }
375 }
376 }
377 }
378 finally
379 {
380 if( trace )
381 log.trace("End invoke, caller="+caller);
382 activeWebMetaData.set(null);
383 userPrincipal.set(null);
384 activeRequest.set(null);
385 activeResponse.set(null);
386 }
387 }
388
389 }