2 * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.idp.profile.saml1;
19 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
20 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
22 import java.io.IOException;
23 import java.net.URLEncoder;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.util.List;
28 import javax.servlet.RequestDispatcher;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.ServletResponse;
32 import javax.servlet.http.Cookie;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 import javax.servlet.http.HttpSession;
37 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
38 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
39 import edu.internet2.middleware.shibboleth.common.relyingparty.saml1.ShibbolethSSOConfiguration;
40 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
42 import org.apache.log4j.Logger;
43 import org.bouncycastle.util.encoders.Hex;
44 import org.joda.time.DateTime;
45 import org.opensaml.Configuration;
46 import org.opensaml.common.SAMLVersion;
47 import org.opensaml.common.binding.artifact.SAMLArtifactMap;
48 import org.opensaml.common.binding.artifact.SAMLArtifact;
49 import org.opensaml.common.binding.artifact.SAMLArtifactFactory;
50 import org.opensaml.saml1.core.Assertion;
51 import org.opensaml.saml1.core.AttributeStatement;
52 import org.opensaml.saml1.core.Audience;
53 import org.opensaml.saml1.core.AudienceRestrictionCondition;
54 import org.opensaml.saml1.core.AuthenticationStatement;
55 import org.opensaml.saml1.core.Conditions;
56 import org.opensaml.saml1.core.ConfirmationMethod;
57 import org.opensaml.saml1.core.NameIdentifier;
58 import org.opensaml.saml1.core.Response;
59 import org.opensaml.saml1.core.Status;
60 import org.opensaml.saml1.core.StatusCode;
61 import org.opensaml.saml1.core.StatusMessage;
62 import org.opensaml.saml1.core.Subject;
63 import org.opensaml.saml1.core.SubjectConfirmation;
64 import org.opensaml.saml1.core.impl.AuthenticationStatementBuilder;
65 import org.opensaml.saml2.metadata.AssertionConsumerService;
66 import org.opensaml.saml2.metadata.EntityDescriptor;
67 import org.opensaml.saml2.metadata.SPSSODescriptor;
68 import org.opensaml.saml2.metadata.provider.MetadataProvider;
69 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
70 import org.opensaml.xml.ConfigurationException;
71 import org.opensaml.xml.XMLObjectBuilder;
72 import org.opensaml.xml.signature.KeyInfo;
73 import org.opensaml.xml.signature.SignableXMLObject;
74 import org.opensaml.xml.signature.Signature;
77 * Shibboleth, version 1.X, single sign-on profile handler.
79 * This profile implements the SSO profile from "Shibboleth Architecture Protocols and Profiles" - 10 September 2005.
81 public class ShibbolethSSO extends AbstractSAML1ProfileHandler {
84 private static final Logger log = Logger.getLogger(ShibbolethSSO.class);
86 /** SAML 1 bearer confirmation method URI. */
87 protected static final String BEARER_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:bearer";
89 /** SAML 1 artifact confirmation method URI */
90 protected static final String ARTIFACT_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
92 /** SAML 1.1 SPSSO protocol URI */
93 protected static final String SAML11_PROTOCOL_URI = "urn:oasis:names:tc:SAML:1.1:protocol";
95 /** SAML 1 Browser/POST protocol URI. */
96 protected static final String PROFILE_BROWSER_POST_URI = "urn:oasis:names:tc:SAML:1.0:profiles:browser-post";
98 /** SAML 1 Artifact protocol URI. */
99 protected static final String PROFILE_ARTIFACT_URI = "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
101 /** The digest algorithm for generating SP cookies. */
102 protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
104 /** The RelyingPartyManager. */
105 protected RelyingPartyConfigurationManager rpManager;
108 * Backing store for artifacts. This must be shared between ShibbolethSSO and AttributeQuery.
110 protected SAMLArtifactMap artifactMap;
112 /** The path to the IdP's AuthenticationManager servlet */
113 protected String authnMgrURL;
115 /** The URI of the default authentication method */
116 protected String authenticationMethodURI;
118 /** Builder for AuthenticationStatement objects. */
119 protected XMLObjectBuilder authnStmtBuilder;
121 /** Builder for Subject elements. */
122 protected XMLObjectBuilder subjectbuilder;
124 /** Builder for SubjectConfirmation objects. */
125 protected XMLObjectBuilder subjConfBuilder;
127 /** Builder for SubjectConfirmationMethod objects. */
128 protected XMLObjectBuilder confMethodBuilder;
130 /** Builder for Artifacts. */
131 protected XMLObjectBuilder artifactBuilder;
133 /** Builder for NameIdentifiers. */
134 protected XMLObjectBuilder nameIdentifierBuilder;
136 /** Builder for Audience elements. */
137 protected XMLObjectBuilder audienceBuilder;
139 /** Builder for AudienceRestrictionCondition elements. */
140 protected XMLObjectBuilder audienceRestrictionBuilder;
142 /** Builder for Assertions. */
143 protected XMLObjectBuilder assertionBuilder;
145 /** Builder for Status objects. */
146 protected XMLObjectBuilder statusBuilder;
148 /** Builder for StatusCode objects. */
149 protected XMLObjectBuilder statusCodeBuilder;
151 /** Builder for StatusMessage objects. */
152 protected XMLObjectBuilder statusMessageBuilder;
154 /** Builder for Response objects. */
155 protected XMLObjectbuilder responseBuilder;
157 /** Block stale requests. */
158 protected boolean blockStaleRequests = false;
160 /** Blame the SP if requests are malformed. */
161 protected boolean blameSP = false;
164 * Time after which an authn request is considered stale(in seconds). Defaults to 30 minutes.
166 protected int requestTTL = 1800;
168 /** Protocol binding to use to the Authentication Assertion */
169 protected enum ENDPOINT_BINDING {
170 BROWSER_POST, ARTIFACT
173 /** ArtifactFactory used to make artifacts. */
174 protected SAMLArtifactFactory artifactFactory;
176 /** PRNG for Artifact assertionHandles. */
177 protected SecureRandom prng;
180 * Default constructor.
182 public ShibbolethSSO() {
184 // setup SAML object builders
186 assertionBuilder = getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
187 authnStmtBuilder = getBuilderFactory().getBuilder(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
188 subjectbuilder = getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
189 subjConfBuilder = getBuilderFactory().getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
190 confMethodBuilder = getBuilderFactory().getBuilder(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
191 nameIdentifierBuilder = getBuilderFactory().getBuilder(NameIdentifier.DEFAULT_ELEMENT_NAME);
192 audienceBuilder = getBuilderFactory().getBbuilder(Audience.DEFAULT_ELEMENT_NAME);
193 audienceRestrictionBuilder = getBuilderFactory().getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
194 statusBuilder = getBuilderFactory().getBuidler(Status.DEFAULT_ELEMENT_NAME);
195 statusCodeBuilder = getBuilderFactory().getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
196 statusMessageBuilder = getBuilderFactory().getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
197 responseBuilder = getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
199 artifactFactory = new SAMLArtifactFactory();
203 * Set the authentication manager.
205 * @param authnManagerURL The URL of the IdP's AuthenticationManager servlet
207 public void setAuthenticationManager(String authnManagerURL) {
208 authnMgrURL = authnManagerURL;
212 * Set the RelyingPartyManager.
214 * @param rpManager A RelyingPartyManager.
216 public void setRelyingPartyManager(RelyingPartyConfigurationManager rpManager) {
217 this.rpManager = rpManager;
221 * Set the authentication method URI.
223 * The URI SHOULD come from oasis-sstc-saml-core-1.1, section 7.1
225 * @param authMethod The authentication method's URI
227 public void setAuthenticationMethodURI(String authMethod) {
228 authenticationMethodURI = authMethod;
232 * Set if old requests should be blocked.
234 * @param blockStaleRequests boolean flag.
236 public void setBlockStaleRequests(boolean blockStaleRequests) {
237 this.blockStaleRequests = blockStaleRequests;
241 * Return if stale requests are blocked.
243 * @return <code>true</code> if old requests are blocked.
245 public boolean getBlockStaleRequests() {
246 return blockStaleRequests;
252 * @param ttl Request timeout (in seconds).
254 public void setRequestTTL(int ttl) {
259 * Get Request TTL. This is the time after which a request is considered stale.
261 * @return request timeout (in seconds).
263 public int getRequestTTL() {
268 * Set the artifact map backing store.
270 * @param artifactMap the Artifact mapping backing store.
272 public void setArtifactMap(SAMLArtifactMap artifactMap) {
273 this.artifactMap = artifactMap;
277 * Get the artifact map backing store.
279 * @return An ArtifactMap instance.
281 public SAMLArtifactMap getArtifactMap() {
286 public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
288 // Only http servlets are supported for now.
289 if (!(request.getRequest() instanceof HttpServletRequest)) {
290 log.error("Received a non-HTTP request from " + request.getRequest().getRemoteHost());
294 HttpServletRequest httpReq = (HttpServletRequest) request.getRequest();
295 HttpServletResponse httpResp = (HttpServletResponse) response.getResponse();
296 HttpSession httpSession = httpReq.getSession();
297 LoginContext loginCtx;
300 String target = null;
301 String providerId = null;
302 String remoteAddr = null;
304 // extract the (mandatory) request parameters.
305 if (!getRequestParameters(httpReq, shire, target, providerId, remoteAddr)) {
308 httpReq.setAttribute("errorPage", "/IdPErrorBlameSP.jsp");
309 // XXX: flesh this out more.
315 // check for stale requests
316 if (blockStaleRequests) {
317 String cookieName = getRPCookieName(providerName);
318 if (!validateFreshness(httpReq, httpResp, cookieName)) {
322 writeFreshnessCookie(httpReq, httpResp, cookieName);
325 // check if the user has already been authenticated
326 Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
329 // the user hasn't been authenticated, so forward the request
330 // to the AuthenticationManager. When the AuthenticationManager
331 // is done it will forward the request back to this servlet.
333 // don't force reauth or passive auth
334 loginCtx = new LoginContext(false, false);
335 loginCtx.setProfileHandlerURL(httpReq.getPathInfo());
336 httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
338 RequestDispatcher dispatcher = request.getRequest().getRequestDispatcher(authnMgrURL);
339 dispatcher.forward(request.getRequest(), response.getResponse());
340 } catch (IOException ex) {
341 log.error("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
346 // The user has been authenticated.
347 // Process the SAML 1 authn request.
349 if (!(o instanceof LoginContext)) {
350 log.error("Invalid login context object -- object is not an instance of LoginContext.");
354 loginCtx = (LoginContext) o;
356 if (!loginCtx.getAuthenticationOK()) {
357 // issue error message.
358 String failureMessage = loginCtx.getAuthenticationFailureMessage();
360 // generate SAML failure message
365 // The user successfully authenticated,
366 // so build the appropriate AuthenticationStatement.
368 DateTime now = new DateTime();
369 RelyingPartyConfiguration relyingParty = rpManager.getRelyingPartyConfiguration(providerId);
370 ShibbolethSSOConfiguration ssoConfig = relyingParty.getProfileConfigurations().get(
371 ShibbolethSSOConfiguration.PROFILE_ID);
372 SPSSODescriptor spDescriptor;
375 spDescriptor = rpManager.getMetadataProvider().getEntityDescriptor(relyingParty.getRelyingPartyID())
376 .getSPSSODescriptor(SAML11_PROTOCOL_URI);
377 } catch (MetadataProviderException ex) {
378 log.error("Unable to locate metadata for SP " + providerId + " for protocol " + SAML11_PROTOCOL_URI, ex);
382 if (spDescriptor == null) {
383 log.error("Unable to locate metadata for SP " + providerId + " for protocol " + SAML11_PROTOCOL_URI);
388 // validate the AssertionConsumer URL
389 List<AssertionConsumerService> consumerEndpoints = validateAssertionConsumerURL(spDescriptor, shire);
390 if (consumerEndpoints.length == 0) {
395 ENDPOINT_BINDING endpointBinding = getProtocolBinding(spDescriptor, consumerEndpoints, shire);
398 if (endpointBinding = ENDPOINT_BINDING.BROWSER_POST) {
399 confMethod = BEARER_CONF_METHOD_URI;
400 } else if (endpointBinding = ENDPOINT_BINDING.ARTIFACT) {
401 confMethod = ARTIFACT_CONF_METHOD_URI;
404 Assertion authenticationAssertion = generateAuthenticationAssertion(loginCtx, relyingParty, ssoConfig,
405 providerId, spDescriptor, confMethod, now);
406 if (authenticationAssertion == null) {
411 if (endpointBinging == ENDPOINT_BINDING.BROWSER_POST) {
413 } else if (endpointBinding == ENDPOINT_BINDING.ARTIFACT) {
414 respondWithArtifact(httpReq, httpResp, shire, target, new Assertion[] { authenticationAssertion });
421 * Respond with a SAML Artifact.
423 * @param request The HttpServletRequest.
424 * @param response The HttpServletResponse.
425 * @param shire The AssertionConsumerService URL.
426 * @parma target The target parameter from the request.
427 * @param assertions One or more SAML assertions.
429 protected void respondWithArtifact(HttpServletRequest request, HttpServletResponse response, String shire,
430 String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ServletException,
431 NoSuchProviderException {
433 if (assertions.length < 1) {
437 StringBuilder buf = new StringBuilder(shire);
438 buf.append("?TARGET=");
439 buf.append(URLEncoder.encode(target), "UTF-8");;
441 // We construct the type 1 Artifact's sourceID by SHA-1 hashing the
443 // This is legacy holdover from Shib 1.x.
444 MessageDigest digester = MessageDigest.getInstance("SHA-1");
445 byte[] sourceID = digester.digest(relyingParty.getProviderID);
447 for (Assertion assertion : assertions) {
449 // XXX: todo: log the assertion to log4j @ debug level.
451 byte artifactType = (byte) relyingParty.getDefaultArtifactType();
453 SAMLArtifact artifact = artifactFactory.buildArtifact(SAML_VERSION, new byte[] { 0, artifactType },
454 relyingParty.getProviderID());
456 String artifactID = artifact.hexEncode();
457 artifactMap.put(artifact, assertion);
459 log.debug("encoding assertion " + assertion.getID() + " into artifact " + artifactID);
460 log.debug("appending artifact " + artifactID + " for URL " + shire);
461 buf.append("&SAMLArt=");
462 buf.append(URLEncoder.encode(artifact.base64Encode(), "UTF-8"));
465 String url = buf.toString();
466 response.sendRedirect(url);
470 * Respond with the SAML 1 Browser/POST profile.
472 * @param request The HttpServletRequest.
473 * @param response The HttpServletResponse.
474 * @param shire The AssertionConsumerService URL.
475 * @parma target The target parameter from the request.
476 * @param assertions One or more SAML assertions.
478 protected void respondWithPOST(HttpServletRequest request, HttpServletResponse response, String shire,
479 String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ServletException {
481 Response samlResponse = (Response) responseBuilder.buildObject(Response.DEFAULT_ELEMENT_NAME);
482 Status status = buildStatus("Success", null);
483 samlResponse.setStatus(status);
484 samlResponse.setIssueInstant(new DateTime());
485 samlResponse.setVersion(SAML_VERSION);
486 samlResponse.setID(getIdGenerator().generateIdentifier());
487 samlResponse.setRecipient(relyingParty.getRelyingPartyID());
489 List<Assertion> assertionList = samlResponse.getAssertions();
490 for (Assertion assertion : assertions) {
491 assertionList.add(assertion);
494 request.setAttribute("acceptanceURL", shire);
495 request.setAttribute("target", target);
497 RequestDispatcher dispatcher = request.getRequestDispatcher("/IdP_SAML1_POST.jdp");
498 dispatcher.forward(request, response);
502 * Get the Shibboleth profile-specific request parameters. The shire, target, providerId and remoteAddr parameters
503 * will be populated upon successful return.
505 * @param request The servlet request from the SP.
506 * @param shire The AttributeConsumerService URL
507 * @param target The location to which to POST the response.
508 * @param providerId The SP's provider ID in the metadata.
509 * @param remoteAddr The address of the requestor.
511 * @return <code>true</code> if the request contains valid parameters.
513 protected boolean getRequestParameters(HttpServletRequest request, String shire, String target, String providerId,
516 target = request.getParameter("target");
517 providerId = request.getParameter("providerId");
518 shire = request.getParameter("shire");
519 remoteAddr = request.getRemoteAddr();
521 if (target == null || target.equals("")) {
522 log.error("Shib 1 SSO request is missing or contains an invalid target parameter");
526 if (providerId == null || providerId.equals("")) {
527 log.error("Shib 1 SSO request is missing or contains an invalid provierId parameter");
531 if (shire == null || providerId.equals("")) {
532 log.error("Shib 1 SSO request is missing or contains an invalid shire parameter");
536 if (remoteAddr == null || remoteAddr.equals("")) {
537 log.error("Unable to obtain requestor address when processing Shib 1 SSO request");
545 * Generate a SAML 1 AuthenticationStatement.
547 * @param loginCtx The LoginContext.
548 * @param relyingParty The Replying Party configuration for the SP.
549 * @param ssoConfig The ShibbolethSSOConfiguration data.
550 * @param spID The providerID of the SP that sent the request.
551 * @param spDescriptor The SPSSO Descriptor from the metadata.
552 * @param subjectConfirmationMethod The SubjectConfirmationMethod. If <code>null</code> no
553 * SubjectConfirmationMethod element will be generated.
554 * @param now The current timestamp
556 * @return A SAML 1 Authentication Assertion or <code>null</code> on error.
558 protected Assertion generateAuthenticationAssertion(final LoginContext loginCtx,
559 final RelyingPartyConfiguration relyingParty, final ShibbolethSSOConfiguration ssoConfig, String spID,
560 final SPSSODescriptor spDescriptor, String subjectConfirmationMethod, final DateTime now) {
562 Assertion authenticationAssertion = (Assertion) assertionBuilder.build(Assertion.DEFAULT_ELEMENT_NAME);
564 authenticationAssertion.setIssueInstant(now);
565 authenticationAssertion.setVersion(SAMLVersion.VERSION_11);
566 authenticationAssertion.setIssuer(relyingParty.getProviderID());
567 authenticationAssertion.setID(getIdGenerator().generateIdentifier());
568 authenticationAssertion.setIssuer(relyingParty.getProviderID());
570 Conditions conditions = authenticationAssertion.getConditions();
571 conditions.setNotBefore(now.minusSeconds(30)); // for now, clock skew
572 // is hard-coded to 30
574 conditions.setNotOnOrAfter(now.plusMillis(ssoConfig.getAssertionLifetime()));
576 List<AudienceRestrictionCondition> audiences = conditions.getAudienceRestrictionConditions();
577 AudienceRestrictionCondition restrictionCondition = (AudienceRestrictionCondition) audienceRestrictionBuilder
578 .buildObject(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
579 Audience rpAudience = (Audience) audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
580 rpAudience.setURI(relyingParty.getProviderID());
581 audiences.add(rpAudience);
582 if (!relyingParty.getProviderID().equals(spID)) {
583 Audience spAudience = (Audience) audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
584 spAudience.setURI(spID);
585 audiences.add(spAudience);
588 AuthenticationStatement authenticationStatement = (AuthenticationStatement) authnStmtBuilder
589 .buildObject(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
591 authenticationStatement.setSubject(buildSubject(loginCtx, subjectConfirmationMethod, relyingParty));
592 authenticationStatement.setAuthenticationInstant(loginCtx.getAuthenticationInstant());
593 authenticationStatement.setAuthenticationMethod(authenticationMethodURI);
595 authenticationAssertion.getAuthenticationStatements().add(authenticationStatement);
597 if (spDescriptor.getWantAssertionsSigned()) {
598 // sign the assertion
601 return authenticationStatement;
605 * Get the protocol binding to use for sending the authentication assertion. Currently, only Browser/POST and
606 * Artifact are supported. This method will return the first recognized binding that it locates.
608 * @param spDescriptor The SP's SPSSODescriptor
609 * @param endpoints The list of AssertionConsumerEndpoints with the "shire" URL as their location.
610 * @param shireURL The "shire" url from the authn request.
612 * @return The protocol binding for a given SPSSODescriptor.
614 * @throws MetadataException if no Browswer/POST or Artifact binding can be found.
616 protected ENDPOINT_BINDING getProtocolBinding(final SPSSODescriptor spDecsriptor,
617 final List<AssertionConsumerService> endpoints, String shireURL) throws MetadataException {
619 // check the default AssertionConsumerService first.
620 AssertionConsumerService defaultConsumer = spDescriptor.getDefaultAssertionConsumerService();
622 if (defaultConsumer != null && defaultConsumer.getLocation().equals(acceptanceURL)) {
624 if (defaultConsumer.getBinding().equals(PROFILE_ARTIFACT_URI)) {
625 return ENDPOINT_BINDING.ARTIFACT;
626 } else if (defaultConsumer.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
627 return ENDPOINT_BINDING.BROWSER_POST;
631 // check the (already filtered) list of AssertionConsumer endpoints
632 for (AssertionConsumerService endpoint : endpoints) {
633 if (endpoint.getBinding().equals(PROFILE_ARTIFACT_URI)) {
634 return ENDPOINT_BINDING.ARTIFACT;
635 } else if (endpoint.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
636 return ENDPOINT_BINDING.BROWSER_POST;
640 // no AssertionConsumerServices were found, or none had a recognized
642 log.error("Unable to find a Browswer/POST or Artifact binding " + " for an AssertionConsumerService in "
643 + spDecsriptor.getID());
645 throw new MetadataException("Unable to find a Browswer/POST or Artifact binding "
646 + " for an AssertionConsumerService in " + spDecsriptor.getID());
652 * @param object The XMLObject to be signed
654 protected void SignXMLObject(final SignableXMLObject object) throws KeyException {
659 * Validate the AssertionConsumer ("shire") URL against the metadata.
661 * @param spDescriptor The SPSSO element from the metadata
662 * @param URL The "shire" URL.
664 * @return a {@link List} of AssertionConsumerServices which have <code>url</code> as their location.
666 protected List<AssertionConsumerService> validateAssertionConsumerURL(final SPSSODescriptor spDescriptor, String url) {
668 // spDescriptor returns a reference to an internal mutable copy, so make
670 List<AssertionConsumerService> consumerURLs = new FastList<AssertionConsumerService>();
672 // filter out any list elements that don't have the correct location
674 // copy any consumerURLs with the correct location
675 for (AssertionConsumerService service : spDescriptor.getAssertionConsumerServices()) {
676 if (service.getLocation().equals(url)) {
677 consumerURLs.add(service);
685 * Validate the "freshness" of an authn request. If the reqeust is more than 30 minutes old, reject it.
687 * @param request The HttpServletRequest
688 * @param response The HttpServletResponse
689 * @param cookieName The name of the RP's cookie.
691 * @return <code>true</code> if the cookie is fresh; otherwise <code>false</code>
693 protected boolean validateFreshness(HttpServletRequest request, HttpServletResponse response, String cookieName)
694 throws IOException, ServletException {
696 if (cookieName == null) {
700 String timestamp = request.getParameter("time");
701 if (timestamp == null || timestamp.equals("")) {
707 reqtime = Long.parseLong(timestamp);
708 } catch (NumberFormatException ex) {
709 log.error("Unable to parse Authentication Request's timestamp", ex);
713 if (reqtime * 1000 < System.currentTimeMillis() - requestTTL * 1000) {
714 RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
715 rd.forward(request, response);
719 for (Cookie cookie : request.getCookies()) {
720 if (cookieName.equals(cookie.getName())) {
722 long cookieTime = Long.parseLong(cookie.getValue());
723 if (reqtime <= cookieTime) {
724 RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
725 rd.forward(request, response);
728 } catch (NumberFormatException ex) {
729 log.error("Unable to parse freshness cookie's timestamp", ex);
739 * Generate the RP's cookie name
741 * @param providerID The RP's providerID
743 * @throws NoSuchAlgorithmException If unable to find a JCE provider for SHA-1
745 * @return the RP's cookie name
747 protected String getRPCookieName(String providerID) throws NoSuchAlgorithmException {
749 MessageDigest digester = MessageDigest.getInstance(RP_COOKIE_DIGEST_ALG);
750 return "shib_sp_" + new String(Hex.encode(digester.digest(providerID.getBytes("UTF-8"))));
754 * Write the current time into the freshness cookie.
756 protected void writeFreshnessCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
758 String timestamp = request.getParameter("time");
759 if (timestamp == null || timestamp.equals("")) {
763 Cookie cookie = new Cookie(cookieName, timestamp);
764 cookie.setSecure(true);
765 response.addCookie(cookie);
769 * Generate a SAML 1 Subject element.
771 * @param loginContext The LoginContext for an authenticated user.
772 * @param confirmationMethod The SubjectConfirmationMethod URI, or <code>null</code> is none is to be set.
773 * @param relyingParty The RelyingPartyConfiguration for the request.
775 * @return a Subject object.
777 protected Subject buildSubject(final LoginContext loginCtx, String confirmationMethod,
778 final RelyingPartyConfiguration relyingParty) {
780 Subject subject = (Subject) subjectBuilder.buildObject(Subject.DEFAULT_ELEMENT_NAME);
782 NameIdentifier nameID = (NameIdentifier) nameIdentifierBuilder.buildObject(NameIdentifier.DEFAULT_ELEMENT_NAME);
783 nameID.setFormat(relyingParty.getDefaultNameIDFormat());
784 String username = loginCtx.getUserID();
785 // XXX: todo: map the username onto an appropriate format
786 nameID.setNameQualifier(username);
788 if (subjectConfirmationMethod != null) {
790 SubjectConfirmation subjConf = (SubjectConfirmation) subjConfBuilder
791 .buildObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
793 ConfirmationMethod m = (ConfirmationMethod) confMethodBuilder
794 .buildObject(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
796 m.setConfirmationMethod(subjectConfirmationMethod);
797 subjConf.getConfirmationMethods().add(m);
798 subject.setSubjectConfirmation(subjConf);
805 * Build a SAML 1 Status element.
807 * @param statusCode The status code - see oasis-sstc-saml-core-1.1, section 3.4.3.1.
808 * @param statusMessage The status message, or <code>null</code> if none is to be set.
810 * @return The Status object, or <code>null</code> on error.
812 protected Status buildStatus(String statusCode, String statusMessage) {
814 if (statusCode == null || statusCode.equals("")) {
818 Status status = (Status) statusBuilder.buildObject(Status.DEFAULT_ELEMENT_NAME);
819 StatusCode sc = (StatusCode) statusCodeBuilder.buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
820 sc.setValue(statusCode);
821 status.setStatusCode(sc);
823 if (statusMessage != null || !(statusMessage.equals(""))) {
825 StatusMessage sm = (StatusMessage) statusMessageBuilder.buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
826 sm.setMessage(statusMessage);
827 status.setStatusMessage(sm);
834 * Get an Attribute Statement.
836 * @param rpConfig The RelyingPartyConfiguration for the request.
837 * @param subject The Subject of the request.
838 * @param request The ServletRequest.
840 * @return An AttributeStatement.
842 * @throws ServletException On error.
844 protected AttributeStatement getAttributeStatement(RelyingPartyConfiguration rpConfig, Subject subject,
845 ServletRequest request) throws ServletException {
847 // build a dummy AttributeQuery object for the AA.
849 AttributeAuthority aa = new AttributeAuthority();
850 aa.setAttributeResolver(getAttributeResolver());
851 aa.setFilteringEngine(getFilteringEngine());
852 // aa.setSecurityPolicy(getDecoder().getSecurityPolicy()); //
853 // super.getDecoder() will need to change.
854 aa.setRequest(request);
855 aa.setRelyingPartyConfiguration(rpConfig);
856 AttributeStatement statement = null;
858 statement = aa.performAttributeQuery(message);
859 } catch (AttributeResolutionException e) {
860 log.error("Error resolving attributes", e);
861 throw new ServletException("Error resolving attributes");
862 } catch (FilteringException e) {
863 log.error("Error filtering attributes", e);
864 throw new ServletException("Error filtering attributes");