2 * Copyright [2007] [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.saml2;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
24 import javax.servlet.ServletRequest;
25 import javax.servlet.ServletResponse;
27 import org.apache.log4j.Logger;
28 import org.joda.time.DateTime;
29 import org.opensaml.common.SAMLObjectBuilder;
30 import org.opensaml.common.SAMLVersion;
31 import org.opensaml.common.impl.SAMLObjectContentReference;
32 import org.opensaml.common.xml.SAMLConstants;
33 import org.opensaml.log.Level;
34 import org.opensaml.saml2.core.Advice;
35 import org.opensaml.saml2.core.Assertion;
36 import org.opensaml.saml2.core.AttributeQuery;
37 import org.opensaml.saml2.core.AttributeStatement;
38 import org.opensaml.saml2.core.Audience;
39 import org.opensaml.saml2.core.AudienceRestriction;
40 import org.opensaml.saml2.core.AuthnRequest;
41 import org.opensaml.saml2.core.Conditions;
42 import org.opensaml.saml2.core.Issuer;
43 import org.opensaml.saml2.core.NameID;
44 import org.opensaml.saml2.core.ProxyRestriction;
45 import org.opensaml.saml2.core.RequestAbstractType;
46 import org.opensaml.saml2.core.Response;
47 import org.opensaml.saml2.core.Statement;
48 import org.opensaml.saml2.core.Status;
49 import org.opensaml.saml2.core.StatusCode;
50 import org.opensaml.saml2.core.StatusMessage;
51 import org.opensaml.saml2.core.StatusResponseType;
52 import org.opensaml.saml2.core.Subject;
53 import org.opensaml.saml2.core.SubjectConfirmation;
54 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
55 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
56 import org.opensaml.saml2.metadata.NameIDFormat;
57 import org.opensaml.saml2.metadata.PDPDescriptor;
58 import org.opensaml.saml2.metadata.RoleDescriptor;
59 import org.opensaml.saml2.metadata.SPSSODescriptor;
60 import org.opensaml.saml2.metadata.SSODescriptor;
61 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
62 import org.opensaml.xml.XMLObjectBuilder;
63 import org.opensaml.xml.security.credential.Credential;
64 import org.opensaml.xml.signature.Signature;
65 import org.opensaml.xml.signature.Signer;
66 import org.opensaml.xml.util.DatatypeHelper;
68 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
69 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
70 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
71 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
72 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
73 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAMLAttributeRequestContext;
74 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
75 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
76 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
77 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
78 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
79 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
80 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
81 import edu.internet2.middleware.shibboleth.idp.session.Session;
83 /** Common implementation details for profile handlers. */
84 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
86 /** SAML Version for this profile handler. */
87 public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
90 private Logger log = Logger.getLogger(AbstractSAML2ProfileHandler.class);
92 /** For building response. */
93 private SAMLObjectBuilder<Response> responseBuilder;
95 /** For building status. */
96 private SAMLObjectBuilder<Status> statusBuilder;
98 /** For building statuscode. */
99 private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
101 /** For building StatusMessages. */
102 private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
104 /** For building assertion. */
105 private SAMLObjectBuilder<Assertion> assertionBuilder;
107 /** For building issuer. */
108 private SAMLObjectBuilder<Issuer> issuerBuilder;
110 /** For building subject. */
111 private SAMLObjectBuilder<Subject> subjectBuilder;
113 /** For builder subject confirmation. */
114 private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
116 /** For building conditions. */
117 private SAMLObjectBuilder<Conditions> conditionsBuilder;
119 /** For building audience restriction. */
120 private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
122 /** For building proxy retrictions. */
123 private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
125 /** For building audience. */
126 private SAMLObjectBuilder<Audience> audienceBuilder;
128 /** For building advice. */
129 private SAMLObjectBuilder<Advice> adviceBuilder;
131 /** For building signature. */
132 private XMLObjectBuilder<Signature> signatureBuilder;
135 @SuppressWarnings("unchecked")
136 protected AbstractSAML2ProfileHandler() {
139 responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
140 statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
141 statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
142 StatusCode.DEFAULT_ELEMENT_NAME);
143 statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
144 StatusMessage.DEFAULT_ELEMENT_NAME);
145 issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
146 assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
147 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
148 subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
149 subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
150 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
151 conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
152 Conditions.DEFAULT_ELEMENT_NAME);
153 audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
154 AudienceRestriction.DEFAULT_ELEMENT_NAME);
155 proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
156 ProxyRestriction.DEFAULT_ELEMENT_NAME);
157 audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
158 adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
159 signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
163 * Convenience method for getting the SAML 2 advice builder.
165 * @return SAML 2 advice builder
167 public SAMLObjectBuilder<Advice> getAdviceBuilder() {
168 return adviceBuilder;
172 * Convenience method for getting the SAML 2 assertion builder.
174 * @return SAML 2 assertion builder
176 public SAMLObjectBuilder<Assertion> getAssertionBuilder() {
177 return assertionBuilder;
181 * Convenience method for getting the SAML 2 audience builder.
183 * @return SAML 2 audience builder
185 public SAMLObjectBuilder<Audience> getAudienceBuilder() {
186 return audienceBuilder;
190 * Convenience method for getting the SAML 2 audience restriction builder.
192 * @return SAML 2 audience restriction builder
194 public SAMLObjectBuilder<AudienceRestriction> getAudienceRestrictionBuilder() {
195 return audienceRestrictionBuilder;
199 * Convenience method for getting the SAML 2 conditions builder.
201 * @return SAML 2 conditions builder
203 public SAMLObjectBuilder<Conditions> getConditionsBuilder() {
204 return conditionsBuilder;
208 * Convenience method for getting the SAML 2 Issuer builder.
210 * @return SAML 2 Issuer builder
212 public SAMLObjectBuilder<Issuer> getIssuerBuilder() {
213 return issuerBuilder;
217 * Convenience method for getting the SAML 2 proxy restriction builder.
219 * @return SAML 2 proxy restriction builder
221 public SAMLObjectBuilder<ProxyRestriction> getProxyRestrictionBuilder() {
222 return proxyRestrictionBuilder;
226 * Convenience method for getting the SAML 2 response builder.
228 * @return SAML 2 response builder
230 public SAMLObjectBuilder<Response> getResponseBuilder() {
231 return responseBuilder;
235 * Convenience method for getting the Signature builder.
237 * @return signature builder
239 public XMLObjectBuilder<Signature> getSignatureBuilder() {
240 return signatureBuilder;
244 * Convenience method for getting the SAML 2 status builder.
246 * @return SAML 2 status builder
248 public SAMLObjectBuilder<Status> getStatusBuilder() {
249 return statusBuilder;
253 * Convenience method for getting the SAML 2 status code builder.
255 * @return SAML 2 status code builder
257 public SAMLObjectBuilder<StatusCode> getStatusCodeBuilder() {
258 return statusCodeBuilder;
262 * Convenience method for getting the SAML 2 status message builder.
264 * @return SAML 2 status message builder
266 public SAMLObjectBuilder<StatusMessage> getStatusMessageBuilder() {
267 return statusMessageBuilder;
271 * Convenience method for getting the SAML 2 subject builder.
273 * @return SAML 2 subject builder
275 public SAMLObjectBuilder<Subject> getSubjectBuilder() {
276 return subjectBuilder;
280 * Convenience method for getting the SAML 2 subject confirmation builder.
282 * @return SAML 2 subject confirmation builder
284 public SAMLObjectBuilder<SubjectConfirmation> getSubjectConfirmationBuilder() {
285 return subjectConfirmationBuilder;
289 * Checks that the SAML major version for a request is 2.
291 * @param requestContext current request context containing the SAML message
293 * @throws ProfileException thrown if the major version of the SAML request is not 2
295 protected void checkSamlVersion(SAML2ProfileRequestContext requestContext) throws ProfileException {
296 SAMLVersion version = requestContext.getSamlRequest().getVersion();
297 if (version.getMajorVersion() < 2) {
298 requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
299 StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
300 throw new ProfileException("SAML request version too low");
301 } else if (version.getMajorVersion() > 2) {
302 requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
303 StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
304 throw new ProfileException("SAML request version too high");
309 * Builds a response to the attribute query within the request context.
311 * @param requestContext current request context
312 * @param assertionSubject subject of the assertion within the response
313 * @param statements the statements to include in the response
315 * @return the built response
317 * @throws ProfileException thrown if there is a problem creating the SAML response
319 protected Response buildResponse(SAML2ProfileRequestContext requestContext, Subject assertionSubject,
320 List<Statement> statements) throws ProfileException {
322 DateTime issueInstant = new DateTime();
324 // create the assertion and add the attribute statement
325 Assertion assertion = buildAssertion(requestContext, issueInstant);
326 assertion.setSubject(assertionSubject);
327 if (statements != null) {
328 assertion.getStatements().addAll(statements);
331 // create the SAML response and add the assertion
332 Response samlResponse = getResponseBuilder().buildObject();
333 samlResponse.setIssueInstant(issueInstant);
334 populateStatusResponse(requestContext, samlResponse);
336 samlResponse.getAssertions().add(assertion);
338 // sign the assertion if it should be signed
339 signAssertion(requestContext, assertion);
341 Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
342 samlResponse.setStatus(status);
348 * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
350 * @param requestContext current request context
351 * @param issueInstant time to use as assertion issue instant
353 * @return the built assertion
355 protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
356 Assertion assertion = getAssertionBuilder().buildObject();
357 assertion.setID(getIdGenerator().generateIdentifier());
358 assertion.setIssueInstant(issueInstant);
359 assertion.setVersion(SAMLVersion.VERSION_20);
360 assertion.setIssuer(buildEntityIssuer(requestContext));
362 Conditions conditions = buildConditions(requestContext, issueInstant);
363 assertion.setConditions(conditions);
369 * Creates an {@link Issuer} populated with information about the relying party.
371 * @param requestContext current request context
373 * @return the built issuer
375 protected Issuer buildEntityIssuer(SAML2ProfileRequestContext requestContext) {
376 Issuer issuer = getIssuerBuilder().buildObject();
377 issuer.setFormat(Issuer.ENTITY);
378 issuer.setValue(requestContext.getRelyingPartyId());
384 * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
385 * restrictions, and proxy restrictions.
387 * @param requestContext current request context
388 * @param issueInstant timestamp the assertion was created
390 * @return constructed conditions
392 protected Conditions buildConditions(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
393 AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
395 Conditions conditions = getConditionsBuilder().buildObject();
396 conditions.setNotBefore(issueInstant);
397 conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
399 Collection<String> audiences;
401 // add audience restrictions
402 audiences = profileConfig.getAssertionAudiences();
403 if (audiences != null && audiences.size() > 0) {
404 AudienceRestriction audienceRestriction = getAudienceRestrictionBuilder().buildObject();
405 for (String audienceUri : audiences) {
406 Audience audience = getAudienceBuilder().buildObject();
407 audience.setAudienceURI(audienceUri);
408 audienceRestriction.getAudiences().add(audience);
410 conditions.getAudienceRestrictions().add(audienceRestriction);
413 // add proxy restrictions
414 audiences = profileConfig.getProxyAudiences();
415 if (audiences != null && audiences.size() > 0) {
416 ProxyRestriction proxyRestriction = getProxyRestrictionBuilder().buildObject();
418 for (String audienceUri : audiences) {
419 audience = getAudienceBuilder().buildObject();
420 audience.setAudienceURI(audienceUri);
421 proxyRestriction.getAudiences().add(audience);
424 proxyRestriction.setProxyCount(profileConfig.getProxyCount());
425 conditions.getConditions().add(proxyRestriction);
432 * Populates the response's id, in response to, issue instant, version, and issuer properties.
434 * @param requestContext current request context
435 * @param response the response to populate
437 protected void populateStatusResponse(SAML2ProfileRequestContext requestContext, StatusResponseType response) {
438 response.setID(getIdGenerator().generateIdentifier());
439 if (requestContext.getSamlRequest() != null) {
440 response.setInResponseTo(requestContext.getSamlRequest().getID());
442 response.setVersion(SAMLVersion.VERSION_20);
443 response.setIssuer(buildEntityIssuer(requestContext));
447 * Executes a query for attributes and builds a SAML attribute statement from the results.
449 * @param requestContext current request context
451 * @return attribute statement resulting from the query
453 * @throws ProfileException thrown if there is a problem making the query
455 protected AttributeStatement buildAttributeStatement(SAML2ProfileRequestContext requestContext)
456 throws ProfileException {
458 if (log.isDebugEnabled()) {
459 log.debug("Creating attribute statement in response to SAML request "
460 + requestContext.getSamlRequest().getID() + " from relying party "
461 + requestContext.getRelyingPartyId());
464 AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
465 SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
468 if (log.isDebugEnabled()) {
469 log.debug("Resolving attributes for principal " + requestContext.getPrincipalName()
470 + " of SAML request " + requestContext.getSamlRequest().getID() + " from relying party "
471 + requestContext.getRelyingPartyId());
473 Map<String, BaseAttribute> principalAttributes = attributeAuthority
474 .getAttributes(buildAttributeRequestContext(requestContext));
476 requestContext.setPrincipalAttributes(principalAttributes);
478 if (requestContext.getSamlRequest() instanceof AttributeQuery) {
479 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext.getSamlRequest(),
480 principalAttributes.values());
482 return attributeAuthority.buildAttributeStatement(null, principalAttributes.values());
484 } catch (AttributeRequestException e) {
485 log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
486 + " from relying party " + requestContext.getRelyingPartyId(), e);
487 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
488 throw new ProfileException("Error resolving attributes for SAML request "
489 + requestContext.getSamlRequest().getID() + " from relying party "
490 + requestContext.getRelyingPartyId(), e);
495 * Resolves the principal name of the subject of the request.
497 * @param requestContext current request context
499 * @throws ProfileException thrown if the principal name can not be resolved
501 protected void resolvePrincipal(SAML2ProfileRequestContext requestContext) throws ProfileException {
502 AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
503 SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
505 if (log.isDebugEnabled()) {
506 log.debug("Resolving principal name for subject of SAML request " + requestContext.getSamlRequest().getID()
507 + " from relying party " + requestContext.getRelyingPartyId());
511 String principal = attributeAuthority.getPrincipal(buildAttributeRequestContext(requestContext));
512 requestContext.setPrincipalName(principal);
513 } catch (AttributeRequestException e) {
514 log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
515 + " from relying party " + requestContext.getRelyingPartyId(), e);
516 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
517 "Error resolving principal"));
518 throw new ProfileException("Error resolving attributes for SAML request "
519 + requestContext.getSamlRequest().getID() + " from relying party "
520 + requestContext.getRelyingPartyId(), e);
525 * Creates an attribute query context from the current profile request context.
527 * @param requestContext current profile request
529 * @return created query context
531 protected ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> buildAttributeRequestContext(
532 SAML2ProfileRequestContext requestContext) {
534 ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> queryContext;
535 if (requestContext.getSamlRequest() instanceof AttributeQuery) {
536 queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
537 requestContext.getRelyingPartyConfiguration(), (AttributeQuery) requestContext.getSamlRequest());
539 queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
540 requestContext.getRelyingPartyConfiguration());
543 queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
544 queryContext.setPrincipalName(requestContext.getPrincipalName());
545 queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
546 queryContext.setRequest(requestContext.getProfileRequest());
548 Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
549 if (userSession != null) {
550 queryContext.setUserSession(userSession);
551 ServiceInformation serviceInfo = userSession.getServiceInformation(requestContext.getRelyingPartyId());
552 if (serviceInfo != null) {
553 String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
555 requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
556 queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
564 * Signs the given assertion if either the current profile configuration or the relying party configuration contains
565 * signing credentials.
567 * @param requestContext current request context
568 * @param assertion assertion to sign
570 * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
571 * required, if a signing credential is not configured
573 protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion)
574 throws ProfileException {
575 if (log.isDebugEnabled()) {
576 log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
577 + " should be signed");
580 boolean signAssertion = false;
582 RoleDescriptor relyingPartyRole;
584 relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
585 requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
586 } catch (MetadataProviderException e) {
587 throw new ProfileException("Unable to lookup entity metadata for relying party "
588 + requestContext.getRelyingPartyId());
590 AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
592 if (relyingPartyRole instanceof SPSSODescriptor) {
593 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
594 if (ssoDescriptor.getWantAssertionsSigned() != null) {
595 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
596 if (log.isDebugEnabled()) {
597 log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
598 + " indicates to sign assertions: " + signAssertion);
601 } else if (profileConfig.getSignAssertions()) {
602 signAssertion = true;
603 log.debug("IdP relying party configuration "
604 + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
605 + " indicates to sign assertions: " + signAssertion);
608 if (!signAssertion) {
612 if (log.isDebugEnabled()) {
613 log.debug("Determining signing credntial for assertion to relying party "
614 + requestContext.getRelyingPartyId());
616 Credential signatureCredential = profileConfig.getSigningCredential();
617 if (signatureCredential == null) {
618 signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
621 if (signatureCredential == null) {
622 throw new ProfileException("No signing credential is specified for relying party configuration "
623 + requestContext.getRelyingPartyConfiguration().getProviderId()
624 + " or it's SAML2 attribute query profile configuration");
627 if (log.isDebugEnabled()) {
628 log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
630 SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
631 Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
632 signature.getContentReferences().add(contentRef);
633 assertion.setSignature(signature);
635 Signer.signObject(signature);
639 * Build a status message, with an optional second-level failure message.
641 * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
642 * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
643 * null, no second-level Status element will be set.
644 * @param failureMessage An optional second-level failure message
646 * @return a Status object.
648 protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
649 Status status = getStatusBuilder().buildObject();
651 StatusCode statusCode = getStatusCodeBuilder().buildObject();
652 statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
653 status.setStatusCode(statusCode);
655 if (secondLevelCode != null) {
656 StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
657 secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
658 statusCode.setStatusCode(secondLevelStatusCode);
661 if (failureMessage != null) {
662 StatusMessage msg = getStatusMessageBuilder().buildObject();
663 msg.setMessage(failureMessage);
664 status.setStatusMessage(msg);
671 * Builds the SAML subject for the user for the service provider.
673 * @param requestContext current request context
674 * @param confirmationMethod subject confirmation method used for the subject
676 * @return SAML subject for the user for the service provider
678 * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
679 * name ID attribute or because there are no supported name formats
681 protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod)
682 throws ProfileException {
683 NameID nameID = buildNameId(requestContext);
684 requestContext.setSubjectNameID(nameID);
685 // TODO handle encryption
687 SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
688 subjectConfirmation.setMethod(confirmationMethod);
690 Subject subject = getSubjectBuilder().buildObject();
691 subject.setNameID(nameID);
692 subject.getSubjectConfirmations().add(subjectConfirmation);
698 * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
699 * picking a name format that was requested by the relying party or is mutually supported by both the relying party
700 * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
701 * the principals attributes are inspected for an attribtue supported an attribute encoder whose category is one of
702 * the supported name formats.
704 * @param requestContext current request context
706 * @return the NameID appropriate for this request
708 * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
709 * name ID attribute or because there are no supported name formats
711 protected NameID buildNameId(SAML2ProfileRequestContext requestContext) throws ProfileException {
712 if (log.isDebugEnabled()) {
713 log.debug("Building assertion NameID for principal/relying party:" + requestContext.getPrincipalName()
714 + "/" + requestContext.getRelyingPartyId());
716 Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
717 List<String> supportedNameFormats = getNameFormats(requestContext);
719 if (log.isDebugEnabled()) {
720 log.debug("Supported NameID formats: " + supportedNameFormats);
723 if (principalAttributes != null && supportedNameFormats != null) {
725 AttributeEncoder<NameID> nameIdEncoder = null;
726 for (BaseAttribute attribute : principalAttributes.values()) {
727 for (String nameFormat : supportedNameFormats) {
728 nameIdEncoder = attribute.getEncoderByCategory(nameFormat);
729 if (nameIdEncoder != null) {
730 if (log.isDebugEnabled()) {
731 log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
732 + nameFormat + " to create the NameID for principal "
733 + requestContext.getPrincipalName());
735 return nameIdEncoder.encode(attribute);
739 } catch (AttributeEncodingException e) {
740 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
741 "Unable to construct NameID"));
742 throw new ProfileException("Unable to encode NameID attribute", e);
746 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
747 "Unable to construct NameID"));
748 throw new ProfileException("No principal attributes support NameID construction");
752 * Gets the NameID format to use when creating NameIDs for the relying party.
754 * @param requestContext current request context
756 * @return list of nameID formats that may be used with the relying party
758 * @throws ProfileException thrown if there is a problem determing the NameID format to use
760 protected List<String> getNameFormats(SAML2ProfileRequestContext requestContext) throws ProfileException {
761 ArrayList<String> nameFormats = new ArrayList<String>();
764 RoleDescriptor assertingPartyRole = getMetadataProvider().getRole(requestContext.getAssertingPartyId(),
765 requestContext.getAssertingPartyRole(), SAMLConstants.SAML20P_NS);
766 List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
768 String nameFormat = null;
769 if (requestContext.getSamlRequest() instanceof AuthnRequest) {
770 AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
771 if (authnRequest.getNameIDPolicy() != null) {
772 nameFormat = authnRequest.getNameIDPolicy().getFormat();
773 if (assertingPartySupportedFormats.contains(nameFormat)) {
774 nameFormats.add(nameFormat);
776 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
777 StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + nameFormat));
778 throw new ProfileException("NameID format required by relying party is not supported");
783 if (nameFormats.isEmpty()) {
784 RoleDescriptor relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
785 requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
786 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
788 assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
789 nameFormats.addAll(assertingPartySupportedFormats);
791 if (nameFormats.isEmpty()) {
792 nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
797 } catch (MetadataProviderException e) {
798 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
799 "Unable to lookup entity metadata"));
800 throw new ProfileException("Unable to determine lookup entity metadata", e);
805 * Gets the list of NameID formats supported for a given role.
807 * @param role the role to get the list of supported NameID formats
809 * @return list of supported NameID formats
811 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
812 List<NameIDFormat> nameIDFormats = null;
814 if (role instanceof SSODescriptor) {
815 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
816 } else if (role instanceof AuthnAuthorityDescriptor) {
817 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
818 } else if (role instanceof PDPDescriptor) {
819 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
820 } else if (role instanceof AttributeAuthorityDescriptor) {
821 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
824 ArrayList<String> supportedFormats = new ArrayList<String>();
825 if (nameIDFormats != null) {
826 for (NameIDFormat format : nameIDFormats) {
827 supportedFormats.add(format.getFormat());
831 return supportedFormats;
835 * Constructs an SAML response message carrying a request error.
837 * @param requestContext current request context
839 * @return the constructed error response
841 protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext) {
842 Response samlResponse = getResponseBuilder().buildObject();
843 samlResponse.setIssueInstant(new DateTime());
844 populateStatusResponse(requestContext, samlResponse);
846 samlResponse.setStatus(requestContext.getFailureStatus());
852 * Writes an aduit log entry indicating the successful response to the attribute request.
854 * @param context current request context
856 protected void writeAuditLogEntry(SAML2ProfileRequestContext context) {
857 AuditLogEntry auditLogEntry = new AuditLogEntry();
858 auditLogEntry.setMessageProfile(getProfileId());
859 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
860 auditLogEntry.setPrincipalName(context.getPrincipalName());
861 auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
862 auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
863 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
864 auditLogEntry.setRequestId(context.getSamlRequest().getID());
865 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
866 auditLogEntry.setResponseId(context.getSamlResponse().getID());
867 getAduitLog().log(Level.CRITICAL, auditLogEntry);
871 * Contextual object used to accumlate information as profile requests are being processed.
873 * @param <RequestType> type of SAML 2 request
874 * @param <ResponseType> type of SAML 2 response
875 * @param <ProfileConfigurationType> configuration type for this profile
877 protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends StatusResponseType, ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
878 extends SAMLProfileRequestContext {
880 /** SAML request message. */
881 private RequestType samlRequest;
883 /** SAML response message. */
884 private ResponseType samlResponse;
886 /** Request profile configuration. */
887 private ProfileConfigurationType profileConfiguration;
889 /** The NameID of the subject of this request. */
890 private NameID subjectNameID;
892 /** The request failure status. */
893 private Status failureStatus;
898 * @param request current profile request
899 * @param response current profile response
901 public SAML2ProfileRequestContext(ProfileRequest<ServletRequest> request,
902 ProfileResponse<ServletResponse> response) {
903 super(request, response);
907 * Gets the NameID of the subject of this request.
909 * @return NameID of the subject of this request
911 public NameID getSubjectNameID() {
912 return subjectNameID;
916 * Sets the NameID of the subject of this request.
918 * @param nameID NameID of the subject of this request
920 public void setSubjectNameID(NameID nameID) {
921 subjectNameID = nameID;
925 * Gets the profile configuration for this request.
927 * @return profile configuration for this request
929 public ProfileConfigurationType getProfileConfiguration() {
930 return profileConfiguration;
934 * Sets the profile configuration for this request.
936 * @param configuration profile configuration for this request
938 public void setProfileConfiguration(ProfileConfigurationType configuration) {
939 profileConfiguration = configuration;
943 * Gets the SAML request message.
945 * @return SAML request message
947 public RequestType getSamlRequest() {
952 * Sets the SAML request message.
954 * @param request SAML request message
956 public void setSamlRequest(RequestType request) {
957 samlRequest = request;
961 * Gets the SAML response message.
963 * @return SAML response message
965 public ResponseType getSamlResponse() {
970 * Sets the SAML response message.
972 * @param response SAML response message
974 public void setSamlResponse(ResponseType response) {
975 samlResponse = response;
979 * Gets the status reflecting a request failure.
981 * @return status reflecting a request failure
983 public Status getFailureStatus() {
984 return failureStatus;
988 * Sets the status reflecting a request failure.
990 * @param status status reflecting a request failure
992 public void setFailureStatus(Status status) {
993 failureStatus = status;