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 java.io.IOException;
20 import java.net.URLEncoder;
21 import java.security.KeyException;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.security.NoSuchProviderException;
25 import java.security.SecureRandom;
26 import java.util.ArrayList;
27 import java.util.List;
29 import javax.servlet.RequestDispatcher;
30 import javax.servlet.ServletException;
31 import javax.servlet.ServletRequest;
32 import javax.servlet.ServletResponse;
33 import javax.servlet.http.Cookie;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36 import javax.servlet.http.HttpSession;
38 import edu.internet2.middleware.shibboleth.common.attribute.AttributeAuthority;
39 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
40 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
41 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
42 import edu.internet2.middleware.shibboleth.common.relyingparty.ProfileConfiguration;
43 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
44 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
45 import edu.internet2.middleware.shibboleth.common.relyingparty.saml1.ShibbolethSSOConfiguration;
46 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
47 import java.io.UnsupportedEncodingException;
49 import org.apache.log4j.Logger;
50 import org.bouncycastle.util.encoders.Hex;
51 import org.joda.time.DateTime;
52 import org.opensaml.common.SAMLObjectBuilder;
53 import org.opensaml.common.SAMLVersion;
54 import org.opensaml.saml1.core.Assertion;
55 import org.opensaml.saml1.core.AttributeStatement;
56 import org.opensaml.saml1.core.Audience;
57 import org.opensaml.saml1.core.AudienceRestrictionCondition;
58 import org.opensaml.saml1.core.AuthenticationStatement;
59 import org.opensaml.saml1.core.Conditions;
60 import org.opensaml.saml1.core.ConfirmationMethod;
61 import org.opensaml.saml1.core.NameIdentifier;
62 import org.opensaml.saml1.core.Response;
63 import org.opensaml.saml1.core.Status;
64 import org.opensaml.saml1.core.StatusCode;
65 import org.opensaml.saml1.core.StatusMessage;
66 import org.opensaml.saml1.core.Subject;
67 import org.opensaml.saml1.core.SubjectConfirmation;
68 import org.opensaml.saml2.metadata.AssertionConsumerService;
69 import org.opensaml.saml2.metadata.SPSSODescriptor;
70 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
71 import org.opensaml.xml.signature.SignableXMLObject;
74 * Shibboleth, version 1.X, single sign-on profile handler.
76 * This profile implements the SSO profile from "Shibboleth Architecture Protocols and Profiles" - 10 September 2005.
78 public class ShibbolethSSO extends AbstractSAML1ProfileHandler {
81 private static final Logger log = Logger.getLogger(ShibbolethSSO.class);
83 /** SAML 1 bearer confirmation method URI. */
84 protected static final String BEARER_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:bearer";
86 /** SAML 1 artifact confirmation method URI */
87 protected static final String ARTIFACT_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
89 /** SAML 1.1 SPSSO protocol URI */
90 protected static final String SAML11_PROTOCOL_URI = "urn:oasis:names:tc:SAML:1.1:protocol";
92 /** SAML 1 Browser/POST protocol URI. */
93 protected static final String PROFILE_BROWSER_POST_URI = "urn:oasis:names:tc:SAML:1.0:profiles:browser-post";
95 /** SAML 1 Artifact protocol URI. */
96 protected static final String PROFILE_ARTIFACT_URI = "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
98 /** The digest algorithm for generating SP cookies. */
99 protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
101 /** Profile ID for this handler. */
102 protected static final String PROFILE_ID = "urn:mace:shibboleth:1.0:profiles:AuthnRequest";
104 /** The path to the IdP's AuthenticationManager servlet */
105 protected String authnMgrURL;
107 /** The URI of the default authentication method */
108 protected String authenticationMethodURI;
110 /** Builder for AuthenticationStatement objects. */
111 protected SAMLObjectBuilder<AuthenticationStatement> authnStmtBuilder;
113 /** Builder for Subject elements. */
114 protected SAMLObjectBuilder<Subject> subjectBuilder;
116 /** Builder for SubjectConfirmation objects. */
117 protected SAMLObjectBuilder<SubjectConfirmation> subjConfBuilder;
119 /** Builder for SubjectConfirmationMethod objects. */
120 protected SAMLObjectBuilder<ConfirmationMethod> confMethodBuilder;
122 /** Builder for NameIdentifiers. */
123 protected SAMLObjectBuilder<NameIdentifier> nameIdentifierBuilder;
125 /** Builder for Audience elements. */
126 protected SAMLObjectBuilder<Audience> audienceBuilder;
128 /** Builder for AudienceRestrictionCondition elements. */
129 protected SAMLObjectBuilder<AudienceRestrictionCondition> audienceRestrictionBuilder;
131 /** Builder for Assertions. */
132 protected SAMLObjectBuilder<Assertion> assertionBuilder;
134 /** Builder for Status objects. */
135 protected SAMLObjectBuilder<Status> statusBuilder;
137 /** Builder for StatusCode objects. */
138 protected SAMLObjectBuilder<StatusCode> statusCodeBuilder;
140 /** Builder for StatusMessage objects. */
141 protected SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
143 /** Builder for Response objects. */
144 protected SAMLObjectBuilder<Response> responseBuilder;
146 /** Block stale requests. */
147 protected boolean blockStaleRequests = false;
149 /** Blame the SP if requests are malformed. */
150 protected boolean blameSP = false;
153 * Time after which an authn request is considered stale(in seconds). Defaults to 30 minutes.
155 protected int requestTTL = 1800;
157 /** Protocol binding to use to the Authentication Assertion */
158 protected enum ENDPOINT_BINDING {
159 BROWSER_POST, ARTIFACT
162 /** PRNG for Artifact assertionHandles. */
163 protected SecureRandom prng;
166 * Default constructor.
168 public ShibbolethSSO() {
170 // setup SAML object builders
172 assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
173 authnStmtBuilder = (SAMLObjectBuilder<AuthenticationStatement>) getBuilderFactory().getBuilder(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
174 subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
175 subjConfBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
176 confMethodBuilder = (SAMLObjectBuilder<ConfirmationMethod>) getBuilderFactory().getBuilder(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
177 nameIdentifierBuilder = (SAMLObjectBuilder<NameIdentifier>) getBuilderFactory().getBuilder(NameIdentifier.DEFAULT_ELEMENT_NAME);
178 audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
179 audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestrictionCondition>) getBuilderFactory().getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
180 statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
181 statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
182 statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
183 responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
188 public String getProfileId() {
193 * Set the authentication manager.
195 * @param authnManagerURL The URL of the IdP's AuthenticationManager servlet
197 public void setAuthenticationManager(String authnManagerURL) {
198 authnMgrURL = authnManagerURL;
202 * Set the authentication method URI.
204 * The URI SHOULD come from oasis-sstc-saml-core-1.1, section 7.1
206 * @param authMethod The authentication method's URI
208 public void setAuthenticationMethodURI(String authMethod) {
209 authenticationMethodURI = authMethod;
213 * Set if old requests should be blocked.
215 * @param blockStaleRequests boolean flag.
217 public void setBlockStaleRequests(boolean blockStaleRequests) {
218 this.blockStaleRequests = blockStaleRequests;
222 * Return if stale requests are blocked.
224 * @return <code>true</code> if old requests are blocked.
226 public boolean getBlockStaleRequests() {
227 return blockStaleRequests;
233 * @param ttl Request timeout (in seconds).
235 public void setRequestTTL(int ttl) {
240 * Get Request TTL. This is the time after which a request is considered stale.
242 * @return request timeout (in seconds).
244 public int getRequestTTL() {
249 public void processRequest(final ProfileRequest<ServletRequest> request, final ProfileResponse<ServletResponse> response) throws ProfileException {
251 // Only http servlets are supported for now.
252 if (!(request.getRawRequest() instanceof HttpServletRequest)) {
253 log.error("Received a non-HTTP request.");
254 // xxx: throw exception
258 HttpServletRequest httpRequest = (HttpServletRequest) request.getRawRequest();
259 HttpServletResponse httpResponse = (HttpServletResponse) response.getRawResponse();
260 HttpSession httpSession = httpRequest.getSession();
261 LoginContext loginCtx;
264 String target = null;
265 String providerId = null;
266 String remoteAddr = null;
268 // extract the (mandatory) request parameters.
269 if (!getRequestParameters(httpRequest, shire, target, providerId, remoteAddr)) {
272 httpRequest.setAttribute("errorPage", "/IdPErrorBlameSP.jsp");
273 // XXX: flesh this out more.
276 // xxx: throw exception;
280 // check for stale requests
281 if (blockStaleRequests) {
282 String cookieName = getRPCookieName(providerId);
283 if (!validateFreshness(httpRequest, httpResponse, cookieName)) {
284 // xxX: log error and throw exception
288 writeFreshnessCookie(httpRequest,httpResponse, cookieName);
291 // check if the user has already been authenticated
292 Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
295 // the user hasn't been authenticated, so forward the request
296 // to the AuthenticationManager. When the AuthenticationManager
297 // is done it will forward the request back to this servlet.
299 // don't force reauth or passive auth
300 loginCtx = new LoginContext(false, false);
301 loginCtx.setProfileHandlerURL(httpRequest.getPathInfo());
302 httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
304 RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(authnMgrURL);
305 dispatcher.forward(httpRequest, httpResponse);
306 } catch (IOException ex) {
307 log.error("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
308 throw new ProfileException("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
309 } catch (ServletException ex) {
310 log.error("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
311 throw new ProfileException("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
315 // The user has been authenticated.
316 // Process the SAML 1 authn request.
318 if (!(o instanceof LoginContext)) {
319 log.error("Invalid login context object -- object is not an instance of LoginContext.");
320 // xxx: throw exception
324 loginCtx = (LoginContext) o;
326 if (!loginCtx.getAuthenticationOK()) {
327 // issue error message.
328 String failureMessage = loginCtx.getAuthenticationFailureMessage();
330 // generate SAML failure message
335 // The user successfully authenticated,
336 // so build the appropriate AuthenticationStatement.
338 DateTime now = new DateTime();
339 RelyingPartyConfiguration relyingParty = getRelyingPartyConfigurationManager().getRelyingPartyConfiguration(providerId);
340 ProfileConfiguration temp = relyingParty.getProfileConfigurations().get(ShibbolethSSOConfiguration.PROFILE_ID);
342 log.error("No profile configuration registered for " + ShibbolethSSOConfiguration.PROFILE_ID);
343 throw new ProfileException("No profile configuration registered for " + ShibbolethSSOConfiguration.PROFILE_ID);
346 ShibbolethSSOConfiguration ssoConfig = (ShibbolethSSOConfiguration) temp;
347 SPSSODescriptor spDescriptor;
350 spDescriptor = getMetadataProvider().getEntityDescriptor(relyingParty.getRelyingPartyId()).getSPSSODescriptor(SAML11_PROTOCOL_URI);
351 } catch (MetadataProviderException ex) {
352 log.error("Unable to locate metadata for SP " + providerId + " for protocol " + SAML11_PROTOCOL_URI, ex);
353 // xxx: throw exception
357 if (spDescriptor == null) {
358 log.error("Unable to locate metadata for SP " + providerId + " for protocol " + SAML11_PROTOCOL_URI);
363 // validate the AssertionConsumer URL
364 List<AssertionConsumerService> consumerEndpoints = validateAssertionConsumerURL(spDescriptor, shire);
365 if (consumerEndpoints.size() == 0) {
370 ENDPOINT_BINDING endpointBinding = getProtocolBinding(spDescriptor, consumerEndpoints, shire);
372 String confMethod = null;
373 if (endpointBinding.equals(ENDPOINT_BINDING.BROWSER_POST)) {
374 confMethod = BEARER_CONF_METHOD_URI;
375 } else if (endpointBinding.equals(ENDPOINT_BINDING.ARTIFACT)) {
376 confMethod = ARTIFACT_CONF_METHOD_URI;
379 Assertion authenticationAssertion = generateAuthenticationAssertion(loginCtx, relyingParty, ssoConfig,
380 providerId, spDescriptor, confMethod, now);
381 if (authenticationAssertion == null) {
386 if (endpointBinding.equals(ENDPOINT_BINDING.BROWSER_POST)) {
388 } else if (endpointBinding.equals(ENDPOINT_BINDING.ARTIFACT)) {
389 //respondWithArtifact(httpRequest, httpResponse, shire, target, new Assertion[] { authenticationAssertion });
396 * Respond with a SAML Artifact.
398 * @param request The HttpServletRequest.
399 * @param response The HttpServletResponse.
400 * @param shire The AssertionConsumerService URL.
401 * @parma target The target parameter from the request.
402 * @param assertions One or more SAML assertions.
404 protected void respondWithArtifact(HttpServletRequest request, HttpServletResponse response, String shire,
405 String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ProfileException,
406 NoSuchProviderException {
408 // if (assertions.length < 1) {
412 // StringBuilder buf = new StringBuilder(shire);
413 // buf.append("?TARGET=");
414 // buf.append(URLEncoder.encode(target), "UTF-8");;
416 // // We construct the type 1 Artifact's sourceID by SHA-1 hashing the
417 // // IdP's providerID.
418 // // This is legacy holdover from Shib 1.x.
419 // MessageDigest digester = MessageDigest.getInstance("SHA-1");
420 // byte[] sourceID = digester.digest(relyingParty.getProviderID);
422 // for (Assertion assertion : assertions) {
424 // // XXX: todo: log the assertion to log4j @ debug level.
426 // byte artifactType = (byte) relyingParty.getDefaultArtifactType();
428 // SAMLArtifact artifact = artifactFactory.buildArtifact(SAML_VERSION, new byte[] { 0, artifactType },
429 // relyingParty.getProviderID());
431 // String artifactID = artifact.hexEncode();
432 // artifactMap.put(artifact, assertion);
434 // log.debug("encoding assertion " + assertion.getID() + " into artifact " + artifactID);
435 // log.debug("appending artifact " + artifactID + " for URL " + shire);
436 // buf.append("&SAMLArt=");
437 // buf.append(URLEncoder.encode(artifact.base64Encode(), "UTF-8"));
440 // String url = buf.toString();
441 // response.sendRedirect(url);
445 * Respond with the SAML 1 Browser/POST profile.
447 * @param request The HttpServletRequest.
448 * @param response The HttpServletResponse.
449 * @param shire The AssertionConsumerService URL.
450 * @parma target The target parameter from the request.
451 * @param assertions One or more SAML assertions.
453 protected void respondWithPOST(HttpServletRequest request, HttpServletResponse response, String shire,
454 String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ProfileException {
456 // Response samlResponse = (Response) responseBuilder.buildObject(Response.DEFAULT_ELEMENT_NAME);
457 // Status status = buildStatus("Success", null);
458 // samlResponse.setStatus(status);
459 // samlResponse.setIssueInstant(new DateTime());
460 // samlResponse.setVersion(SAML_VERSION);
461 // samlResponse.setID(getIdGenerator().generateIdentifier());
462 // samlResponse.setRecipient(relyingParty.getRelyingPartyID());
464 // List<Assertion> assertionList = samlResponse.getAssertions();
465 // for (Assertion assertion : assertions) {
466 // assertionList.add(assertion);
469 // request.setAttribute("acceptanceURL", shire);
470 // request.setAttribute("target", target);
472 // RequestDispatcher dispatcher = request.getRequestDispatcher("/IdP_SAML1_POST.jdp");
473 // dispatcher.forward(request, response);
477 * Get the Shibboleth profile-specific request parameters. The shire, target, providerId and remoteAddr parameters
478 * will be populated upon successful return.
480 * @param request The servlet request from the SP.
481 * @param shire The AttributeConsumerService URL
482 * @param target The location to which to POST the response.
483 * @param providerId The SP's provider ID in the metadata.
484 * @param remoteAddr The address of the requestor.
486 * @return <code>true</code> if the request contains valid parameters.
488 protected boolean getRequestParameters(HttpServletRequest request, String shire, String target, String providerId,
491 target = request.getParameter("target");
492 providerId = request.getParameter("providerId");
493 shire = request.getParameter("shire");
494 remoteAddr = request.getRemoteAddr();
496 if (target == null || target.equals("")) {
497 log.error("Shib 1 SSO request is missing or contains an invalid target parameter");
501 if (providerId == null || providerId.equals("")) {
502 log.error("Shib 1 SSO request is missing or contains an invalid provierId parameter");
506 if (shire == null || providerId.equals("")) {
507 log.error("Shib 1 SSO request is missing or contains an invalid shire parameter");
511 if (remoteAddr == null || remoteAddr.equals("")) {
512 log.error("Unable to obtain requestor address when processing Shib 1 SSO request");
520 * Generate a SAML 1 AuthenticationStatement.
522 * @param loginCtx The LoginContext.
523 * @param relyingParty The Replying Party configuration for the SP.
524 * @param ssoConfig The ShibbolethSSOConfiguration data.
525 * @param spID The providerID of the SP that sent the request.
526 * @param spDescriptor The SPSSO Descriptor from the metadata.
527 * @param subjectConfirmationMethod The SubjectConfirmationMethod. If <code>null</code> no
528 * SubjectConfirmationMethod element will be generated.
529 * @param now The current timestamp
531 * @return A SAML 1 Authentication Assertion or <code>null</code> on error.
533 protected Assertion generateAuthenticationAssertion(final LoginContext loginCtx,
534 final RelyingPartyConfiguration relyingParty, final ShibbolethSSOConfiguration ssoConfig, String spID,
535 final SPSSODescriptor spDescriptor, String subjectConfirmationMethod, final DateTime now) {
537 Assertion authenticationAssertion = assertionBuilder.buildObject();
538 authenticationAssertion.setIssueInstant(now);
539 authenticationAssertion.setVersion(SAMLVersion.VERSION_11);
540 authenticationAssertion.setIssuer(relyingParty.getProviderId());
541 authenticationAssertion.setID(getIdGenerator().generateIdentifier());
543 Conditions conditions = authenticationAssertion.getConditions();
544 conditions.setNotBefore(now.minusSeconds(30)); // for now, clock skew is hard-coded to 30 seconds.
545 conditions.setNotOnOrAfter(now.plusMillis((int)ssoConfig.getAssertionLifetime()));
547 List<AudienceRestrictionCondition> audienceRestrictions = conditions.getAudienceRestrictionConditions();
548 AudienceRestrictionCondition restrictionCondition = audienceRestrictionBuilder.buildObject();
549 audienceRestrictions.add(restrictionCondition);
551 // add the RelyingParty to the audience.
552 Audience rpAudience = audienceBuilder.buildObject();
553 rpAudience.setUri(relyingParty.getProviderId());
554 restrictionCondition.getAudiences().add(rpAudience);
556 // if necessary, explicitely add the SP to the audience.
557 if (!relyingParty.getProviderId().equals(spID)) {
558 Audience spAudience = (Audience) audienceBuilder.buildObject();
559 spAudience.setUri(spID);
560 restrictionCondition.getAudiences().add(spAudience);
563 AuthenticationStatement authenticationStatement = authnStmtBuilder.buildObject();
564 authenticationStatement.setSubject(buildSubject(loginCtx, subjectConfirmationMethod, ssoConfig));
565 authenticationStatement.setAuthenticationInstant(loginCtx.getAuthenticationInstant());
566 authenticationStatement.setAuthenticationMethod(authenticationMethodURI);
568 authenticationAssertion.getAuthenticationStatements().add(authenticationStatement);
570 if (spDescriptor.getWantAssertionsSigned()) {
571 // xxx: sign the assertion
574 return authenticationAssertion;
578 * Get the protocol binding to use for sending the authentication assertion. Currently, only Browser/POST and
579 * Artifact are supported. This method will return the first recognized binding that it locates.
581 * @param spDescriptor The SP's SPSSODescriptor
582 * @param endpoints The list of AssertionConsumerEndpoints with the "shire" URL as their location.
583 * @param shireURL The "shire" url from the authn request.
585 * @return The protocol binding for a given SPSSODescriptor.
587 * @throws ProfileException if no Browswer/POST or Artifact binding can be found.
589 protected ENDPOINT_BINDING getProtocolBinding(final SPSSODescriptor spDescriptor,
590 final List<AssertionConsumerService> endpoints, String shireURL) throws ProfileException {
592 // check the default AssertionConsumerService first.
593 AssertionConsumerService defaultConsumer = spDescriptor.getDefaultAssertionConsumerService();
595 if (defaultConsumer != null && defaultConsumer.getLocation().equals(shireURL)) {
597 if (defaultConsumer.getBinding().equals(PROFILE_ARTIFACT_URI)) {
598 return ENDPOINT_BINDING.ARTIFACT;
599 } else if (defaultConsumer.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
600 return ENDPOINT_BINDING.BROWSER_POST;
604 // check the (already filtered) list of AssertionConsumer endpoints
605 for (AssertionConsumerService endpoint : endpoints) {
606 if (endpoint.getBinding().equals(PROFILE_ARTIFACT_URI)) {
607 return ENDPOINT_BINDING.ARTIFACT;
608 } else if (endpoint.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
609 return ENDPOINT_BINDING.BROWSER_POST;
613 // no AssertionConsumerServices were found, or none had a recognized
615 log.error("Unable to find a Browswer/POST or Artifact binding " + " for an AssertionConsumerService in "
616 + spDescriptor.getID());
618 throw new ProfileException("Unable to find a Browswer/POST or Artifact binding "
619 + " for an AssertionConsumerService in " + spDescriptor.getID());
623 * Sign an {@link XMLObject}.
625 * @param object The XMLObject to be signed.
627 * @throws KeyException On error.
629 protected void signXMLObject(final SignableXMLObject object) throws KeyException {
634 * Validate the AssertionConsumer ("shire") URL against the metadata.
636 * @param spDescriptor The SPSSO element from the metadata
637 * @param url The "shire" URL.
639 * @return a {@link List} of AssertionConsumerServices which have <code>url</code> as their location.
641 protected List<AssertionConsumerService> validateAssertionConsumerURL(final SPSSODescriptor spDescriptor, String url) {
643 // spDescriptor returns a reference to an
644 // internal mutable copy, so make a copy of it.
645 List<AssertionConsumerService> consumerURLs =
646 new ArrayList<AssertionConsumerService>(spDescriptor.getAssertionConsumerServices().size());
648 // filter out any list elements that don't have the correct location field.
649 // copy any consumerURLs with the correct location
650 for (AssertionConsumerService service : spDescriptor.getAssertionConsumerServices()) {
651 if (service.getLocation().equals(url)) {
652 consumerURLs.add(service);
660 * Validate the "freshness" of an authn request. If the reqeust is more than 30 minutes old, reject it.
662 * @param request The HttpServletRequest
663 * @param response The HttpServletResponse
664 * @param cookieName The name of the RP's cookie.
666 * @return <code>true</code> if the cookie is fresh; otherwise <code>false</code>
668 * @throws ProfileException On error.
670 protected boolean validateFreshness(HttpServletRequest request, HttpServletResponse response, String cookieName)
671 throws ProfileException {
674 if (cookieName == null) {
678 String timestamp = request.getParameter("time");
679 if (timestamp == null || timestamp.equals("")) {
685 reqtime = Long.parseLong(timestamp);
686 } catch (NumberFormatException ex) {
687 log.error("Unable to parse Authentication Request's timestamp", ex);
691 if (reqtime * 1000 < System.currentTimeMillis() - requestTTL * 1000) {
692 RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
693 rd.forward(request, response);
697 for (Cookie cookie : request.getCookies()) {
698 if (cookieName.equals(cookie.getName())) {
700 long cookieTime = Long.parseLong(cookie.getValue());
701 if (reqtime <= cookieTime) {
702 RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
703 rd.forward(request, response);
706 } catch (NumberFormatException ex) {
707 log.error("Unable to parse freshness cookie's timestamp", ex);
714 } catch (IOException ex) {
715 throw new ProfileException("Error validating freshness cookie", ex);
716 } catch (ServletException ex) {
717 throw new ProfileException("Error validating freshness cookie", ex);
722 * Generate the RP's cookie name
724 * @param providerID The RP's providerID
726 * @throws ProfileException If unable to find a JCE provider for SHA-1
728 * @return the RP's cookie name
730 protected String getRPCookieName(String providerID) throws ProfileException {
733 MessageDigest digester = MessageDigest.getInstance(RP_COOKIE_DIGEST_ALG);
734 return "shib_sp_" + new String(Hex.encode(digester.digest(providerID.getBytes("UTF-8"))));
735 } catch (NoSuchAlgorithmException ex) {
736 throw new ProfileException("Unabel to create RPCookie", ex);
737 } catch (UnsupportedEncodingException ex) {
738 // this should never happen. UTF-8 encoding should always be supported.
739 throw new ProfileException("Unable to locate UTF-8 encoder", ex);
744 * Write the current time into the freshness cookie.
746 protected void writeFreshnessCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
748 String timestamp = request.getParameter("time");
749 if (timestamp == null || timestamp.equals("")) {
753 Cookie cookie = new Cookie(cookieName, timestamp);
754 cookie.setSecure(true);
755 response.addCookie(cookie);
759 * Generate a SAML 1 Subject element.
761 * @param loginContext The LoginContext for an authenticated user.
762 * @param confirmationMethod The SubjectConfirmationMethod URI, or <code>null</code> is none is to be set.
763 * @param ssoConfig The ShibbolethSSO configuration for the request.
765 * @return a Subject object.
767 protected Subject buildSubject(final LoginContext loginCtx, String confirmationMethod,
768 final ShibbolethSSOConfiguration ssoConfig) {
770 Subject subject = subjectBuilder.buildObject();
772 NameIdentifier nameID = nameIdentifierBuilder.buildObject();
773 nameID.setFormat(ssoConfig.getDefaultNameIDFormat());
774 String username = loginCtx.getUserID();
775 // XXX: todo: map the username onto an appropriate format
776 nameID.setNameQualifier(username);
778 if (confirmationMethod != null) {
780 SubjectConfirmation subjConf = (SubjectConfirmation) subjConfBuilder
781 .buildObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
783 ConfirmationMethod m = (ConfirmationMethod) confMethodBuilder
784 .buildObject(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
786 m.setConfirmationMethod(confirmationMethod);
787 subjConf.getConfirmationMethods().add(m);
788 subject.setSubjectConfirmation(subjConf);
795 * Build a SAML 1 Status element.
797 * @param statusCode The status code - see oasis-sstc-saml-core-1.1, section 3.4.3.1.
798 * @param statusMessage The status message, or <code>null</code> if none is to be set.
800 * @return The Status object, or <code>null</code> on error.
802 protected Status buildStatus(String statusCode, String statusMessage) {
804 if (statusCode == null || statusCode.equals("")) {
808 Status status = (Status) statusBuilder.buildObject(Status.DEFAULT_ELEMENT_NAME);
809 StatusCode sc = (StatusCode) statusCodeBuilder.buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
810 sc.setValue(statusCode);
811 status.setStatusCode(sc);
813 if (statusMessage != null || !(statusMessage.equals(""))) {
815 StatusMessage sm = (StatusMessage) statusMessageBuilder.buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
816 sm.setMessage(statusMessage);
817 status.setStatusMessage(sm);
824 * Get an Attribute Statement.
826 * @param rpConfig The RelyingPartyConfiguration for the request.
827 * @param subject The Subject of the request.
828 * @param request The ServletRequest.
830 * @return An AttributeStatement.
832 * @throws ProfileException On error.
834 protected AttributeStatement getAttributeStatement(RelyingPartyConfiguration rpConfig, Subject subject,
835 ServletRequest request) throws ProfileException {
837 // // build a dummy AttributeQuery object for the AA.
839 // AttributeAuthority aa = new AttributeAuthority();
840 // aa.setAttributeResolver(getAttributeResolver());
841 // aa.setFilteringEngine(getFilteringEngine());
842 // // aa.setSecurityPolicy(getDecoder().getSecurityPolicy()); //
843 // // super.getDecoder() will need to change.
844 // aa.setRequest(request);
845 // aa.setRelyingPartyConfiguration(rpConfig);
846 // AttributeStatement statement = null;
848 // statement = aa.performAttributeQuery(message);
849 // } catch (AttributeResolutionException e) {
850 // log.error("Error resolving attributes", e);
851 // throw new ServletException("Error resolving attributes");
852 // } catch (FilteringException e) {
853 // log.error("Error filtering attributes", e);
854 // throw new ServletException("Error filtering attributes");