View Javadoc

1   /*
2    * Copyright 2009 University Corporation for Advanced Internet Development, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.shibboleth.idp.util;
18  
19  import java.util.UUID;
20  
21  import javax.servlet.ServletContext;
22  import javax.servlet.http.Cookie;
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpServletResponse;
25  
26  import org.opensaml.saml2.metadata.EntityDescriptor;
27  import org.opensaml.saml2.metadata.provider.MetadataProviderException;
28  import org.opensaml.util.storage.StorageService;
29  import org.opensaml.xml.util.DatatypeHelper;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  import edu.internet2.middleware.shibboleth.common.attribute.filtering.AttributeFilteringEngine;
34  import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
35  import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
36  import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolver;
37  import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
38  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
39  import edu.internet2.middleware.shibboleth.common.session.SessionManager;
40  import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
41  import edu.internet2.middleware.shibboleth.idp.authn.LoginContextEntry;
42  import edu.internet2.middleware.shibboleth.idp.profile.IdPProfileHandlerManager;
43  import edu.internet2.middleware.shibboleth.idp.session.Session;
44  
45  /** A helper class that provides access to internal state from Servlets and hence also JSPs. */
46  public class HttpServletHelper {
47  
48      /** Name of the cookie containing the IdP session ID: {@value} . */
49      public static final String IDP_SESSION_COOKIE = "_idp_session";
50  
51      /** Name of the key to the current authentication login context: {@value} . */
52      public static final String LOGIN_CTX_KEY_NAME = "_idp_authn_lc_key";
53  
54      /** {@link ServletContext} parameter name bearing the ID of the {@link AttributeFilteringEngine} service: {@value} . */
55      public static final String ATTRIBUTE_FILTER_ENGINE_SID_CTX_PARAM = "AttributeFilterEngineId";
56  
57      /** {@link ServletContext} parameter name bearing the ID of the {@link AttributeResolver} service: {@value} . */
58      public static final String ATTRIBUTE_RESOLVER_SID_CTX_PARAM = "AttributeResolverId";
59  
60      /**
61       * {@link ServletContext} parameter name bearing the name of the {@link StorageService} partition into which
62       * {@link LoginContext}s are stored: {@value} .
63       */
64      public static final String LOGIN_CTX_PARTITION_CTX_PARAM = "loginContextPartitionName";
65  
66      /** {@link ServletContext} parameter name bearing the ID of the {@link IdPProfileHandlerManager} service: {@value} . */
67      public static final String PROFILE_HANDLER_MNGR_SID_CTX_PARAM = "ProfileHandlerMngrId";
68  
69      /**
70       * {@link ServletContext} parameter name bearing the ID of the {@link RelyingPartyConfigurationManager} service: * *
71       * * {@value} .
72       */
73      public static final String RP_CONFIG_MNGR_SID_CTX_PARAM = "RelyingPartyConfigurationManagerId";
74  
75      /** {@link ServletContext} parameter name bearing the ID of the {@link SAML1AttributeAuthority} service: {@value} . */
76      public static final String SAML1_AA_SID_CTX_PARAM = "SAML1AttributeAuthorityId";
77  
78      /** {@link ServletContext} parameter name bearing the ID of the {@link SAML2AttributeAuthority} service: {@value} . */
79      public static final String SAML2_AA_SID_CTX_PARAM = "SAML2AttributeAuthorityId";
80  
81      /** {@link ServletContext} parameter name bearing the ID of the {@link SessionManager} service: {@value} . */
82      public static final String SESSION_MNGR_SID_CTX_PARAM = "SessionManagerId";
83  
84      /** {@link ServletContext} parameter name bearing the ID of the {@link SAML1AttributeAuthority} service: {@value} . */
85      public static final String STORAGE_SERVICE_SID_CTX_PARAM = "StorageServiceId";
86  
87      /** Default ID by which the {@link AttributeFilteringEngine} is know within the Servlet context: {@value} . */
88      public static final String DEFAULT_ATTRIBUTE_FILTER_ENGINE_SID = "shibboleth.AttributeFilterEngine";
89  
90      /** Default ID by which the {@link AttributeResolver} is know within the Servlet context: {@value} . */
91      public static final String DEFAULT_ATTRIBUTE_RESOLVER_SID = "shibboleth.AttributeResolver";
92  
93      /** Default name for the {@link StorageService} partition which holds {@link LoginContext}s: {@value} . */
94      public static final String DEFAULT_LOGIN_CTX_PARITION = "loginContexts";
95  
96      /** Default ID by which the {@link IdPProfileHandlerManager} is know within the Servlet context: {@value} . */
97      public static final String DEFAULT_PROFILE_HANDLER_MNGR_SID = "shibboleth.HandlerManager";
98  
99      /** Default ID by which the {@link RelyingPartyConfigurationManager} is know within the Servlet context: {@value} . */
100     public static final String DEFAULT_RP_CONFIG_MNGR_SID = "shibboleth.RelyingPartyConfigurationManager";
101 
102     /** Default ID by which the {@link SAML1AttributeAuthority} is know within the Servlet context: {@value} . */
103     public static final String DEFAULT_SAML1_AA_SID = "shibboleth.SAML1AttributeAuthority";
104 
105     /** Default ID by which the {@link SAML2AttributeAuthority} is know within the Servlet context: {@value} . */
106     public static final String DEFAULT_SAML2_AA_SID = "shibboleth.SAML2AttributeAuthority";
107 
108     /** Default ID by which the {@link SessionManager} is know within the Servlet context: {@value} . */
109     public static final String DEFAULT_SESSION_MNGR_SID = "shibboleth.SessionManager";
110 
111     /** Default ID by which the {@link StorageService} is know within the Servlet context: {@value} . */
112     public static final String DEFAULT_STORAGE_SERVICE_SID = "shibboleth.StorageService";
113 
114     /** Class logger. */
115     private static final Logger log = LoggerFactory.getLogger(HttpServletHelper.class);
116 
117     /**
118      * Binds a {@link LoginContext} to the current request.
119      * 
120      * @param loginContext login context to be bound
121      * @param httpRequest current HTTP request
122      */
123     public static void bindLoginContext(LoginContext loginContext, HttpServletRequest httpRequest) {
124         if (httpRequest == null) {
125             throw new IllegalArgumentException("HTTP request may not be null");
126         }
127         httpRequest.setAttribute(LOGIN_CTX_KEY_NAME, loginContext);
128     }
129 
130     /**
131      * Binds a {@link LoginContext} to the issuer of the current request. The binding is done by creating a random UUID,
132      * placing that in a cookie in the request, and storing the context in to the storage service under that key.
133      * 
134      * @param loginContext the login context to be bound
135      * @param storageService the storage service which will hold the context
136      * @param context the Servlet context
137      * @param httpRequest the current HTTP request
138      * @param httpResponse the current HTTP response
139      */
140     public static void bindLoginContext(LoginContext loginContext, StorageService storageService,
141             ServletContext context, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
142         if (storageService == null) {
143             throw new IllegalArgumentException("Storage service may not be null");
144         }
145         if (httpRequest == null) {
146             throw new IllegalArgumentException("HTTP request may not be null");
147         }
148         if (loginContext == null) {
149             return;
150         }
151 
152         bindLoginContext(loginContext, httpRequest);
153 
154         String parition = getContextParam(context, LOGIN_CTX_PARTITION_CTX_PARAM, DEFAULT_LOGIN_CTX_PARITION);
155         log.debug("LoginContext parition: {}", parition);
156 
157         String contextKey = UUID.randomUUID().toString();
158         while (storageService.contains(parition, contextKey)) {
159             contextKey = UUID.randomUUID().toString();
160         }
161         log.debug("LoginContext key: {}", contextKey);
162 
163         LoginContextEntry entry = new LoginContextEntry(loginContext, 1800000);
164         storageService.put(parition, contextKey, entry);
165 
166         Cookie contextKeyCookie = new Cookie(LOGIN_CTX_KEY_NAME, contextKey);
167         contextKeyCookie.setVersion(1);
168         contextKeyCookie.setPath("".equals(httpRequest.getContextPath()) ? "/" : httpRequest.getContextPath());
169         contextKeyCookie.setSecure(httpRequest.isSecure());
170         httpResponse.addCookie(contextKeyCookie);
171     }
172 
173     /**
174      * Gets the {@link AttributeFilteringEngine} service bound to the Servlet context.
175      * 
176      * @param context the Servlet context
177      * 
178      * @return the service or null if there is no such service bound to the context
179      */
180     public static AttributeFilteringEngine<?> getAttributeFilterEnginer(ServletContext context) {
181         return getAttributeFilterEnginer(context, getContextParam(context, ATTRIBUTE_FILTER_ENGINE_SID_CTX_PARAM,
182                 DEFAULT_ATTRIBUTE_FILTER_ENGINE_SID));
183     }
184 
185     /**
186      * Gets the {@link AttributeFilteringEngine} bound to the Servlet context.
187      * 
188      * @param context the Servlet context
189      * @param serviceId the ID under which the service bound
190      * 
191      * @return the service or null if there is no such service bound to the context
192      */
193     public static AttributeFilteringEngine<?> getAttributeFilterEnginer(ServletContext context, String serviceId) {
194         return (AttributeFilteringEngine<?>) context.getAttribute(serviceId);
195     }
196 
197     /**
198      * Gets the {@link AttributeResolver} service bound to the Servlet context.
199      * 
200      * @param context the Servlet context
201      * 
202      * @return the service or null if there is no such service bound to the context
203      */
204     public static AttributeResolver<?> getAttributeResolver(ServletContext context) {
205         return getAttributeResolver(context, getContextParam(context, ATTRIBUTE_RESOLVER_SID_CTX_PARAM,
206                 DEFAULT_ATTRIBUTE_RESOLVER_SID));
207     }
208 
209     /**
210      * Gets the {@link AttributeResolver} bound to the Servlet context.
211      * 
212      * @param context the Servlet context
213      * @param serviceId the ID under which the service bound
214      * 
215      * @return the service or null if there is no such service bound to the context
216      */
217     public static AttributeResolver<?> getAttributeResolver(ServletContext context, String serviceId) {
218         return (AttributeResolver<?>) context.getAttribute(serviceId);
219     }
220 
221     /**
222      * Gets a value for a given context parameter. If no value is present the default value is used.
223      * 
224      * @param context the Servlet context
225      * @param name name of the context parameter
226      * @param defaultValue default value of the parameter
227      * 
228      * @return the value of the context parameter or the default value if the parameter is not set or does not contain a
229      *         value
230      */
231     public static String getContextParam(ServletContext context, String name, String defaultValue) {
232         String value = DatatypeHelper.safeTrimOrNullString(context.getInitParameter(name));
233         if (value == null) {
234             value = defaultValue;
235         }
236         return value;
237     }
238 
239     /**
240      * Gets the first {@link Cookie} whose name matches the given name.
241      * 
242      * @param cookieName the cookie name
243      * @param httpRequest HTTP request from which the cookie should be extracted
244      * 
245      * @return the cookie or null if no cookie with that name was given
246      */
247     public static Cookie getCookie(HttpServletRequest httpRequest, String cookieName) {
248         Cookie[] requestCookies = httpRequest.getCookies();
249         if (requestCookies != null) {
250             for (Cookie requestCookie : requestCookies) {
251                 if (requestCookie != null && DatatypeHelper.safeEquals(requestCookie.getName(), cookieName)) {
252                     return requestCookie;
253                 }
254             }
255         }
256 
257         return null;
258     }
259 
260     /**
261      * Gets the login context from the current request. The login context is only in this location while the request is
262      * being transferred from the authentication engine back to the profile handler.
263      * 
264      * @param httpRequest current HTTP request
265      * 
266      * @return the login context or null if no login context is bound to the request
267      */
268     public static LoginContext getLoginContext(HttpServletRequest httpRequest) {
269         return (LoginContext) httpRequest.getAttribute(LOGIN_CTX_KEY_NAME);
270     }
271 
272     /**
273      * Gets the {@link LoginContext} for the user issuing the HTTP request. Note, login contexts are only available
274      * during the authentication process.
275      * 
276      * @param context the Servlet context
277      * @param storageService storage service to use when retrieving the login context
278      * @param httpRequest current HTTP request
279      * 
280      * @return the login context or null if none is available
281      */
282     public static LoginContext getLoginContext(StorageService storageService, ServletContext context,
283             HttpServletRequest httpRequest) {
284         if (storageService == null) {
285             throw new IllegalArgumentException("Storage service may not be null");
286         }
287         if (context == null) {
288             throw new IllegalArgumentException("Servlet context may not be null");
289         }
290         if (httpRequest == null) {
291             throw new IllegalArgumentException("HTTP request may not be null");
292         }
293 
294         LoginContext loginContext = getLoginContext(httpRequest);
295         if (loginContext == null) {
296             log.debug("LoginContext not bound to HTTP request, retrieving it from storage service");
297             Cookie loginContextKeyCookie = getCookie(httpRequest, LOGIN_CTX_KEY_NAME);
298             if (loginContextKeyCookie == null) {
299                 log.debug("LoginContext key cookie was not present in request");
300                 return null;
301             }
302 
303             String loginContextKey = DatatypeHelper.safeTrimOrNullString(loginContextKeyCookie.getValue());
304             if (loginContextKey == null) {
305                 log.warn("Corrupted LoginContext Key cookie, it did not contain a value");
306             }
307             log.debug("LoginContext key is '{}'", loginContextKey);
308 
309             String partition = getContextParam(context, LOGIN_CTX_PARTITION_CTX_PARAM, DEFAULT_LOGIN_CTX_PARITION);
310             log.debug("parition: {}", partition);
311             LoginContextEntry entry = (LoginContextEntry) storageService.get(partition, loginContextKey);
312             if (entry != null) {
313                 if (entry.isExpired()) {
314                     log.debug("LoginContext found but it was expired");
315                 } else {
316                     loginContext = entry.getLoginContext();
317                 }
318             } else {
319                 log.debug("No login context in storage service");
320             }
321         }
322 
323         return loginContext;
324     }
325 
326     /**
327      * Gets the {@link IdPProfileHandlerManager} service bound to the Servlet context.
328      * 
329      * @param context the Servlet context
330      * 
331      * @return the service or null if there is no such service bound to the context
332      */
333     public static IdPProfileHandlerManager getProfileHandlerManager(ServletContext context) {
334         return getProfileHandlerManager(context, getContextParam(context, PROFILE_HANDLER_MNGR_SID_CTX_PARAM,
335                 DEFAULT_PROFILE_HANDLER_MNGR_SID));
336     }
337 
338     /**
339      * Gets the {@link IdPProfileHandlerManager} bound to the Servlet context.
340      * 
341      * @param context the Servlet context
342      * @param serviceId the ID under which the service bound
343      * 
344      * @return the service or null if there is no such service bound to the context
345      */
346     public static IdPProfileHandlerManager getProfileHandlerManager(ServletContext context, String serviceId) {
347         return (IdPProfileHandlerManager) context.getAttribute(serviceId);
348     }
349 
350     /**
351      * Gets the {@link RelyingPartyConfigurationManager} service bound to the Servlet context.
352      * 
353      * @param context the Servlet context
354      * 
355      * @return the service or null if there is no such service bound to the context
356      */
357     public static RelyingPartyConfigurationManager getRelyingPartyConfirmationManager(ServletContext context) {
358         return getRelyingPartyConfirmationManager(context, getContextParam(context, RP_CONFIG_MNGR_SID_CTX_PARAM,
359                 DEFAULT_RP_CONFIG_MNGR_SID));
360     }
361 
362     /**
363      * Gets the {@link RelyingPartyConfigurationManager} bound to the Servlet context.
364      * 
365      * @param context the Servlet context
366      * @param serviceId the ID under which the service bound
367      * 
368      * @return the service or null if there is no such service bound to the context
369      */
370     public static RelyingPartyConfigurationManager getRelyingPartyConfirmationManager(ServletContext context,
371             String serviceId) {
372         return (RelyingPartyConfigurationManager) context.getAttribute(serviceId);
373     }
374 
375     /**
376      * Gets the metatdata for a given relying party.
377      * 
378      * @param relyingPartyEntityId the ID of the relying party
379      * @param rpConfigMngr relying party configuration manager
380      * 
381      * @return the metadata for the relying party or null if no SAML metadata exists for the given relying party
382      */
383     public static EntityDescriptor getRelyingPartyMetadata(String relyingPartyEntityId,
384             RelyingPartyConfigurationManager rpConfigMngr) {
385         if (rpConfigMngr instanceof SAMLMDRelyingPartyConfigurationManager) {
386             SAMLMDRelyingPartyConfigurationManager samlRpConfigMngr = (SAMLMDRelyingPartyConfigurationManager) rpConfigMngr;
387             try {
388                 return samlRpConfigMngr.getMetadataProvider().getEntityDescriptor(relyingPartyEntityId);
389             } catch (MetadataProviderException e) {
390 
391             }
392         }
393 
394         return null;
395     }
396 
397     /**
398      * Gets the {@link SAML1AttributeAuthority} service bound to the Servlet context.
399      * 
400      * @param context the Servlet context
401      * 
402      * @return the service or null if there is no such service bound to the context
403      */
404     public static SAML1AttributeAuthority getSAML1AttributeAuthority(ServletContext context) {
405         return getSAML1AttributeAuthority(context, getContextParam(context, SAML1_AA_SID_CTX_PARAM,
406                 DEFAULT_SAML1_AA_SID));
407     }
408 
409     /**
410      * Gets the {@link SAML1AttributeAuthority} bound to the Servlet context.
411      * 
412      * @param context the Servlet context
413      * @param serviceId the ID under which the service bound
414      * 
415      * @return the service or null if there is no such service bound to the context
416      */
417     public static SAML1AttributeAuthority getSAML1AttributeAuthority(ServletContext context, String serviceId) {
418         return (SAML1AttributeAuthority) context.getAttribute(serviceId);
419     }
420 
421     /**
422      * Gets the {@link SAML2AttributeAuthority} service bound to the Servlet context.
423      * 
424      * @param context the Servlet context
425      * 
426      * @return the service or null if there is no such service bound to the context
427      */
428     public static SAML2AttributeAuthority getSAML2AttributeAuthority(ServletContext context) {
429         return getSAML2AttributeAuthority(context, getContextParam(context, SAML2_AA_SID_CTX_PARAM,
430                 DEFAULT_SAML2_AA_SID));
431     }
432 
433     /**
434      * Gets the {@link SAML2AttributeAuthority} bound to the Servlet context.
435      * 
436      * @param context the Servlet context
437      * @param serviceId the ID under which the service bound
438      * 
439      * @return the service or null if there is no such service bound to the context
440      */
441     public static SAML2AttributeAuthority getSAML2AttributeAuthority(ServletContext context, String serviceId) {
442         return (SAML2AttributeAuthority) context.getAttribute(serviceId);
443     }
444 
445     /**
446      * Gets the {@link SessionManager} service bound to the Servlet context.
447      * 
448      * @param context the Servlet context
449      * 
450      * @return the service or null if there is no such service bound to the context
451      */
452     public static SessionManager<Session> getSessionManager(ServletContext context) {
453         return getSessionManager(context,
454                 getContextParam(context, SESSION_MNGR_SID_CTX_PARAM, DEFAULT_SESSION_MNGR_SID));
455     }
456 
457     /**
458      * Gets the {@link SessionManager} bound to the Servlet context.
459      * 
460      * @param context the Servlet context
461      * @param serviceId the ID under which the service bound
462      * 
463      * @return the service or null if there is no such service bound to the context
464      */
465     public static SessionManager<Session> getSessionManager(ServletContext context, String serviceId) {
466         return (SessionManager<Session>) context.getAttribute(serviceId);
467     }
468 
469     /**
470      * Gets the {@link StorageService} service bound to the Servlet context.
471      * 
472      * @param context the Servlet context
473      * 
474      * @return the service or null if there is no such service bound to the context
475      */
476     public static StorageService<?, ?> getStorageService(ServletContext context) {
477         return getStorageService(context, getContextParam(context, STORAGE_SERVICE_SID_CTX_PARAM,
478                 DEFAULT_STORAGE_SERVICE_SID));
479     }
480 
481     /**
482      * Gets the {@link StorageService} bound to the Servlet context.
483      * 
484      * @param context the Servlet context
485      * @param serviceId the ID under which the service bound
486      * 
487      * @return the service or null if there is no such service bound to the context
488      */
489     public static StorageService<?, ?> getStorageService(ServletContext context, String serviceId) {
490         return (StorageService<?, ?>) context.getAttribute(serviceId);
491     }
492 
493     /**
494      * Gets the user session from the request. Retrieving the session in this manner does NOT update the last activity
495      * time of the session.
496      * 
497      * @param httpRequest current request
498      * 
499      * @return the users session, if one exists
500      */
501     public static Session getUserSession(HttpServletRequest httpRequest) {
502         return (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
503     }
504 
505     /**
506      * Unbinds a {@link LoginContext} from the current request. The unbinding results in the destruction of the
507      * associated context key cookie and removes the context from the storage service.
508      * 
509      * @param storageService storage service holding the context
510      * @param context the Servlet context
511      * @param httpRequest current HTTP request
512      * @param httpResponse current HTTP response
513      * 
514      * @return the login context that was unbound or null if there was no bound context
515      */
516     public static LoginContext unbindLoginContext(StorageService storageService, ServletContext context,
517             HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
518         if (storageService == null || context == null || httpRequest == null || httpResponse == null) {
519             return null;
520         }
521 
522         Cookie loginContextKeyCookie = getCookie(httpRequest, LOGIN_CTX_KEY_NAME);
523         if (loginContextKeyCookie == null) {
524             return null;
525         }
526 
527         String loginContextKey = DatatypeHelper.safeTrimOrNullString(loginContextKeyCookie.getValue());
528         if (loginContextKey == null) {
529             log.warn("Corrupted LoginContext Key cookie, it did not contain a value");
530         }
531 
532         httpRequest.setAttribute(LOGIN_CTX_KEY_NAME, null);
533         loginContextKeyCookie.setMaxAge(0);
534         loginContextKeyCookie.setPath("".equals(httpRequest.getContextPath()) ? "/" : httpRequest.getContextPath());
535         loginContextKeyCookie.setVersion(1);
536         httpResponse.addCookie(loginContextKeyCookie);
537 
538         LoginContextEntry entry = (LoginContextEntry) storageService.remove(getContextParam(context,
539                 LOGIN_CTX_PARTITION_CTX_PARAM, DEFAULT_LOGIN_CTX_PARITION), loginContextKey);
540         if (entry != null && !entry.isExpired()) {
541             return entry.getLoginContext();
542         }
543         return null;
544     }
545 }