Make user session available via public API, finishes off SIDP-296
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / util / HttpServletHelper.java
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 request current HTTP request
122      */
123     public static void bindLoginContext(LoginContext loginContext, HttpServletRequest request) {
124         if (request == null) {
125             throw new IllegalArgumentException("HTTP request may not be null");
126         }
127         request.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         String parition = getContextParam(context, LOGIN_CTX_PARTITION_CTX_PARAM, DEFAULT_LOGIN_CTX_PARITION);
153         log.debug("LoginContext parition: {}", parition);
154
155         String contextKey = UUID.randomUUID().toString();
156         while (storageService.contains(parition, contextKey)) {
157             contextKey = UUID.randomUUID().toString();
158         }
159         log.debug("LoginContext key: {}", contextKey);
160
161         LoginContextEntry entry = new LoginContextEntry(loginContext, 1800000);
162         storageService.put(parition, contextKey, entry);
163
164         Cookie contextKeyCookie = new Cookie(LOGIN_CTX_KEY_NAME, contextKey);
165         contextKeyCookie.setPath("/");
166         contextKeyCookie.setSecure(httpRequest.isSecure());
167         contextKeyCookie.setMaxAge(31556926);
168         httpResponse.addCookie(contextKeyCookie);
169     }
170
171     /**
172      * Gets the {@link AttributeFilteringEngine} service bound to the Servlet context.
173      * 
174      * @param context the Servlet context
175      * 
176      * @return the service or null if there is no such service bound to the context
177      */
178     public static AttributeFilteringEngine<?> getAttributeFilterEnginer(ServletContext context) {
179         return getAttributeFilterEnginer(context, getContextParam(context, ATTRIBUTE_FILTER_ENGINE_SID_CTX_PARAM,
180                 DEFAULT_ATTRIBUTE_FILTER_ENGINE_SID));
181     }
182
183     /**
184      * Gets the {@link AttributeFilteringEngine} bound to the Servlet context.
185      * 
186      * @param context the Servlet context
187      * @param serviceId the ID under which the service bound
188      * 
189      * @return the service or null if there is no such service bound to the context
190      */
191     public static AttributeFilteringEngine<?> getAttributeFilterEnginer(ServletContext context, String serviceId) {
192         return (AttributeFilteringEngine<?>) context.getAttribute(serviceId);
193     }
194
195     /**
196      * Gets the {@link AttributeResolver} service bound to the Servlet context.
197      * 
198      * @param context the Servlet context
199      * 
200      * @return the service or null if there is no such service bound to the context
201      */
202     public static AttributeResolver<?> getAttributeResolver(ServletContext context) {
203         return getAttributeResolver(context, getContextParam(context, ATTRIBUTE_RESOLVER_SID_CTX_PARAM,
204                 DEFAULT_ATTRIBUTE_RESOLVER_SID));
205     }
206
207     /**
208      * Gets the {@link AttributeResolver} bound to the Servlet context.
209      * 
210      * @param context the Servlet context
211      * @param serviceId the ID under which the service bound
212      * 
213      * @return the service or null if there is no such service bound to the context
214      */
215     public static AttributeResolver<?> getAttributeResolver(ServletContext context, String serviceId) {
216         return (AttributeResolver<?>) context.getAttribute(serviceId);
217     }
218
219     /**
220      * Gets a value for a given context parameter. If no value is present the default value is used.
221      * 
222      * @param context the Servlet context
223      * @param name name of the context parameter
224      * @param defaultValue default value of the parameter
225      * 
226      * @return the value of the context parameter or the default value if the parameter is not set or does not contain a
227      *         value
228      */
229     public static String getContextParam(ServletContext context, String name, String defaultValue) {
230         String value = DatatypeHelper.safeTrimOrNullString(context.getInitParameter(name));
231         if (value == null) {
232             value = defaultValue;
233         }
234         return value;
235     }
236
237     /**
238      * Gets the first {@link Cookie} whose name matches the given name.
239      * 
240      * @param cookieName the cookie name
241      * @param httpRequest HTTP request from which the cookie should be extracted
242      * 
243      * @return the cookie or null if no cookie with that name was given
244      */
245     public static Cookie getCookie(HttpServletRequest httpRequest, String cookieName) {
246         Cookie[] requestCookies = httpRequest.getCookies();
247         if (requestCookies != null) {
248             for (Cookie requestCookie : requestCookies) {
249                 if (requestCookie != null && DatatypeHelper.safeEquals(requestCookie.getName(), cookieName)) {
250                     return requestCookie;
251                 }
252             }
253         }
254
255         return null;
256     }
257
258     /**
259      * Gets the login context from the current request. The login context is only in this location while the request is
260      * being transferred from the authentication engine back to the profile handler.
261      * 
262      * @param httpRequest current HTTP request
263      * 
264      * @return the login context or null if no login context is bound to the request
265      */
266     public static LoginContext getLoginContext(HttpServletRequest httpRequest) {
267         return (LoginContext) httpRequest.getAttribute(LOGIN_CTX_KEY_NAME);
268     }
269
270     /**
271      * Gets the {@link LoginContext} for the user issuing the HTTP request. Note, login contexts are only available
272      * during the authentication process.
273      * 
274      * @param context the Servlet context
275      * @param storageService storage service to use when retrieving the login context
276      * @param httpRequest current HTTP request
277      * 
278      * @return the login context or null if none is available
279      */
280     public static LoginContext getLoginContext(StorageService storageService, ServletContext context,
281             HttpServletRequest httpRequest) {
282         if (storageService == null) {
283             throw new IllegalArgumentException("Storage service may not be null");
284         }
285         if (context == null) {
286             throw new IllegalArgumentException("Servlet context may not be null");
287         }
288         if (httpRequest == null) {
289             throw new IllegalArgumentException("HTTP request may not be null");
290         }
291
292         LoginContext loginContext = getLoginContext(httpRequest);
293         if (loginContext == null) {
294             log.debug("LoginContext not bound to HTTP request, retrieving it from storage service");
295             Cookie loginContextKeyCookie = getCookie(httpRequest, LOGIN_CTX_KEY_NAME);
296             if (loginContextKeyCookie == null) {
297                 log.debug("LoginContext key cookie was not present in request");
298                 return null;
299             }
300
301             String loginContextKey = DatatypeHelper.safeTrimOrNullString(loginContextKeyCookie.getValue());
302             if (loginContextKey == null) {
303                 log.warn("Corrupted LoginContext Key cookie, it did not contain a value");
304             }
305             log.debug("LoginContext key is '{}'", loginContextKey);
306
307             String partition = getContextParam(context, LOGIN_CTX_PARTITION_CTX_PARAM, DEFAULT_LOGIN_CTX_PARITION);
308             log.debug("parition: {}", partition);
309             LoginContextEntry entry = (LoginContextEntry) storageService.get(partition, loginContextKey);
310             if (entry != null) {
311                 if (entry.isExpired()) {
312                     log.debug("LoginContext found but it was expired");
313                 } else {
314                     loginContext = entry.getLoginContext();
315                 }
316             } else {
317                 log.debug("No login context in storage service");
318             }
319         }
320
321         return loginContext;
322     }
323
324     /**
325      * Gets the {@link IdPProfileHandlerManager} service bound to the Servlet context.
326      * 
327      * @param context the Servlet context
328      * 
329      * @return the service or null if there is no such service bound to the context
330      */
331     public static IdPProfileHandlerManager getProfileHandlerManager(ServletContext context) {
332         return getProfileHandlerManager(context, getContextParam(context, PROFILE_HANDLER_MNGR_SID_CTX_PARAM,
333                 DEFAULT_PROFILE_HANDLER_MNGR_SID));
334     }
335
336     /**
337      * Gets the {@link IdPProfileHandlerManager} bound to the Servlet context.
338      * 
339      * @param context the Servlet context
340      * @param serviceId the ID under which the service bound
341      * 
342      * @return the service or null if there is no such service bound to the context
343      */
344     public static IdPProfileHandlerManager getProfileHandlerManager(ServletContext context, String serviceId) {
345         return (IdPProfileHandlerManager) context.getAttribute(serviceId);
346     }
347
348     /**
349      * Gets the {@link RelyingPartyConfigurationManager} service bound to the Servlet context.
350      * 
351      * @param context the Servlet context
352      * 
353      * @return the service or null if there is no such service bound to the context
354      */
355     public static RelyingPartyConfigurationManager getRelyingPartyConfirmationManager(ServletContext context) {
356         return getRelyingPartyConfirmationManager(context, getContextParam(context, RP_CONFIG_MNGR_SID_CTX_PARAM,
357                 DEFAULT_RP_CONFIG_MNGR_SID));
358     }
359
360     /**
361      * Gets the {@link RelyingPartyConfigurationManager} bound to the Servlet context.
362      * 
363      * @param context the Servlet context
364      * @param serviceId the ID under which the service bound
365      * 
366      * @return the service or null if there is no such service bound to the context
367      */
368     public static RelyingPartyConfigurationManager getRelyingPartyConfirmationManager(ServletContext context,
369             String serviceId) {
370         return (RelyingPartyConfigurationManager) context.getAttribute(serviceId);
371     }
372
373     /**
374      * Gets the metatdata for a given relying party.
375      * 
376      * @param relyingPartyEntityId the ID of the relying party
377      * @param rpConfigMngr relying party configuration manager
378      * 
379      * @return the metadata for the relying party or null if no SAML metadata exists for the given relying party
380      */
381     public static EntityDescriptor getRelyingPartyMetadata(String relyingPartyEntityId,
382             RelyingPartyConfigurationManager rpConfigMngr) {
383         if (rpConfigMngr instanceof SAMLMDRelyingPartyConfigurationManager) {
384             SAMLMDRelyingPartyConfigurationManager samlRpConfigMngr = (SAMLMDRelyingPartyConfigurationManager) rpConfigMngr;
385             try {
386                 return samlRpConfigMngr.getMetadataProvider().getEntityDescriptor(relyingPartyEntityId);
387             } catch (MetadataProviderException e) {
388
389             }
390         }
391
392         return null;
393     }
394
395     /**
396      * Gets the {@link SAML1AttributeAuthority} service bound to the Servlet context.
397      * 
398      * @param context the Servlet context
399      * 
400      * @return the service or null if there is no such service bound to the context
401      */
402     public static SAML1AttributeAuthority getSAML1AttributeAuthority(ServletContext context) {
403         return getSAML1AttributeAuthority(context, getContextParam(context, SAML1_AA_SID_CTX_PARAM,
404                 DEFAULT_SAML1_AA_SID));
405     }
406
407     /**
408      * Gets the {@link SAML1AttributeAuthority} bound to the Servlet context.
409      * 
410      * @param context the Servlet context
411      * @param serviceId the ID under which the service bound
412      * 
413      * @return the service or null if there is no such service bound to the context
414      */
415     public static SAML1AttributeAuthority getSAML1AttributeAuthority(ServletContext context, String serviceId) {
416         return (SAML1AttributeAuthority) context.getAttribute(serviceId);
417     }
418
419     /**
420      * Gets the {@link SAML2AttributeAuthority} service bound to the Servlet context.
421      * 
422      * @param context the Servlet context
423      * 
424      * @return the service or null if there is no such service bound to the context
425      */
426     public static SAML2AttributeAuthority getSAML2AttributeAuthority(ServletContext context) {
427         return getSAML2AttributeAuthority(context, getContextParam(context, SAML2_AA_SID_CTX_PARAM,
428                 DEFAULT_SAML2_AA_SID));
429     }
430
431     /**
432      * Gets the {@link SAML2AttributeAuthority} bound to the Servlet context.
433      * 
434      * @param context the Servlet context
435      * @param serviceId the ID under which the service bound
436      * 
437      * @return the service or null if there is no such service bound to the context
438      */
439     public static SAML2AttributeAuthority getSAML2AttributeAuthority(ServletContext context, String serviceId) {
440         return (SAML2AttributeAuthority) context.getAttribute(serviceId);
441     }
442
443     /**
444      * Gets the {@link SessionManager} service bound to the Servlet context.
445      * 
446      * @param context the Servlet context
447      * 
448      * @return the service or null if there is no such service bound to the context
449      */
450     public static SessionManager<Session> getSessionManager(ServletContext context) {
451         return getSessionManager(context,
452                 getContextParam(context, SESSION_MNGR_SID_CTX_PARAM, DEFAULT_SESSION_MNGR_SID));
453     }
454
455     /**
456      * Gets the {@link SessionManager} bound to the Servlet context.
457      * 
458      * @param context the Servlet context
459      * @param serviceId the ID under which the service bound
460      * 
461      * @return the service or null if there is no such service bound to the context
462      */
463     public static SessionManager<Session> getSessionManager(ServletContext context, String serviceId) {
464         return (SessionManager<Session>) context.getAttribute(serviceId);
465     }
466
467     /**
468      * Gets the {@link StorageService} service bound to the Servlet context.
469      * 
470      * @param context the Servlet context
471      * 
472      * @return the service or null if there is no such service bound to the context
473      */
474     public static StorageService<?, ?> getStorageService(ServletContext context) {
475         return getStorageService(context, getContextParam(context, STORAGE_SERVICE_SID_CTX_PARAM,
476                 DEFAULT_STORAGE_SERVICE_SID));
477     }
478
479     /**
480      * Gets the {@link StorageService} bound to the Servlet context.
481      * 
482      * @param context the Servlet context
483      * @param serviceId the ID under which the service bound
484      * 
485      * @return the service or null if there is no such service bound to the context
486      */
487     public static StorageService<?, ?> getStorageService(ServletContext context, String serviceId) {
488         return (StorageService<?, ?>) context.getAttribute(serviceId);
489     }
490
491     /**
492      * Gets the user session from the request. Retrieving the session in this manner does NOT update the last activity
493      * time of the session.
494      * 
495      * @param httpRequest current request
496      * 
497      * @return the users session, if one exists
498      */
499     public static Session getUserSession(HttpServletRequest httpRequest) {
500         return (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
501     }
502
503     /**
504      * Unbinds a {@link LoginContext} from the current request. The unbinding results in the destruction of the
505      * associated context key cookie and removes the context from the storage service.
506      * 
507      * @param storageService storage service holding the context
508      * @param context the Servlet context
509      * @param httpRequest current HTTP request
510      * @param httpResponse current HTTP response
511      * 
512      * @return the login context that was unbound or null if there was no bound context
513      */
514     public static LoginContext unbindLoginContext(StorageService storageService, ServletContext context,
515             HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
516         if (storageService == null || context == null || httpRequest == null || httpResponse == null) {
517             return null;
518         }
519
520         Cookie loginContextKeyCookie = getCookie(httpRequest, LOGIN_CTX_KEY_NAME);
521         if (loginContextKeyCookie == null) {
522             return null;
523         }
524
525         String loginContextKey = DatatypeHelper.safeTrimOrNullString(loginContextKeyCookie.getValue());
526         if (loginContextKey == null) {
527             log.warn("Corrupted LoginContext Key cookie, it did not contain a value");
528         }
529
530         loginContextKeyCookie.setMaxAge(0);
531         httpResponse.addCookie(loginContextKeyCookie);
532
533         LoginContextEntry entry = (LoginContextEntry) storageService.remove(getContextParam(context,
534                 LOGIN_CTX_PARTITION_CTX_PARAM, DEFAULT_LOGIN_CTX_PARITION), loginContextKey);
535         if (entry != null && !entry.isExpired()) {
536             return entry.getLoginContext();
537         }
538         return null;
539     }
540 }