View Javadoc

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