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