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 if (profileConfiguration == null) {
504 log.error("Unable to resolve principal, no SAML 2 profile configuration for relying party "
505 + requestContext.getRelyingPartyId());
506 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
507 "Error resolving principal"));
508 throw new ProfileException(
509 "Unable to resolve principal, no SAML 2 profile configuration for relying party "
510 + requestContext.getRelyingPartyId());
512 SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
514 if (log.isDebugEnabled()) {
515 log.debug("Resolving principal name for subject of SAML request " + requestContext.getSamlRequest().getID()
516 + " from relying party " + requestContext.getRelyingPartyId());
520 String principal = attributeAuthority.getPrincipal(buildAttributeRequestContext(requestContext));
521 requestContext.setPrincipalName(principal);
522 } catch (AttributeRequestException e) {
523 log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
524 + " from relying party " + requestContext.getRelyingPartyId(), e);
525 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
526 "Error resolving principal"));
527 throw new ProfileException("Error resolving attributes for SAML request "
528 + requestContext.getSamlRequest().getID() + " from relying party "
529 + requestContext.getRelyingPartyId(), e);
534 * Creates an attribute query context from the current profile request context.
536 * @param requestContext current profile request
538 * @return created query context
540 protected ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> buildAttributeRequestContext(
541 SAML2ProfileRequestContext requestContext) {
543 ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> queryContext;
544 if (requestContext.getSamlRequest() instanceof AttributeQuery) {
545 queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
546 requestContext.getRelyingPartyConfiguration(), (AttributeQuery) requestContext.getSamlRequest());
548 queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
549 requestContext.getRelyingPartyConfiguration());
552 queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
553 queryContext.setPrincipalName(requestContext.getPrincipalName());
554 queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
555 queryContext.setRequest(requestContext.getProfileRequest());
557 Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
558 if (userSession != null) {
559 queryContext.setUserSession(userSession);
560 ServiceInformation serviceInfo = userSession.getServicesInformation().get(
561 requestContext.getRelyingPartyId());
562 if (serviceInfo != null) {
563 String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
565 requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
566 queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
574 * Signs the given assertion if either the current profile configuration or the relying party configuration contains
575 * signing credentials.
577 * @param requestContext current request context
578 * @param assertion assertion to sign
580 * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
581 * required, if a signing credential is not configured
583 protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion)
584 throws ProfileException {
585 if (log.isDebugEnabled()) {
586 log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
587 + " should be signed");
590 boolean signAssertion = false;
592 RoleDescriptor relyingPartyRole;
594 relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
595 requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
596 } catch (MetadataProviderException e) {
597 throw new ProfileException("Unable to lookup entity metadata for relying party "
598 + requestContext.getRelyingPartyId());
600 AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
602 if (relyingPartyRole instanceof SPSSODescriptor) {
603 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
604 if (ssoDescriptor.getWantAssertionsSigned() != null) {
605 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
606 if (log.isDebugEnabled()) {
607 log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
608 + " indicates to sign assertions: " + signAssertion);
611 } else if (profileConfig.getSignAssertions()) {
612 signAssertion = true;
613 log.debug("IdP relying party configuration "
614 + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
615 + " indicates to sign assertions: " + signAssertion);
618 if (!signAssertion) {
622 if (log.isDebugEnabled()) {
623 log.debug("Determining signing credntial for assertion to relying party "
624 + requestContext.getRelyingPartyId());
626 Credential signatureCredential = profileConfig.getSigningCredential();
627 if (signatureCredential == null) {
628 signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
631 if (signatureCredential == null) {
632 throw new ProfileException("No signing credential is specified for relying party configuration "
633 + requestContext.getRelyingPartyConfiguration().getProviderId()
634 + " or it's SAML2 attribute query profile configuration");
637 if (log.isDebugEnabled()) {
638 log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
640 SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
641 Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
642 signature.getContentReferences().add(contentRef);
643 assertion.setSignature(signature);
645 Signer.signObject(signature);
649 * Build a status message, with an optional second-level failure message.
651 * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
652 * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
653 * null, no second-level Status element will be set.
654 * @param failureMessage An optional second-level failure message
656 * @return a Status object.
658 protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
659 Status status = getStatusBuilder().buildObject();
661 StatusCode statusCode = getStatusCodeBuilder().buildObject();
662 statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
663 status.setStatusCode(statusCode);
665 if (secondLevelCode != null) {
666 StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
667 secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
668 statusCode.setStatusCode(secondLevelStatusCode);
671 if (failureMessage != null) {
672 StatusMessage msg = getStatusMessageBuilder().buildObject();
673 msg.setMessage(failureMessage);
674 status.setStatusMessage(msg);
681 * Builds the SAML subject for the user for the service provider.
683 * @param requestContext current request context
684 * @param confirmationMethod subject confirmation method used for the subject
686 * @return SAML subject for the user for the service provider
688 * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
689 * name ID attribute or because there are no supported name formats
691 protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod)
692 throws ProfileException {
693 NameID nameID = buildNameId(requestContext);
694 requestContext.setSubjectNameID(nameID);
695 // TODO handle encryption
697 SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
698 subjectConfirmation.setMethod(confirmationMethod);
700 Subject subject = getSubjectBuilder().buildObject();
701 subject.setNameID(nameID);
702 subject.getSubjectConfirmations().add(subjectConfirmation);
708 * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
709 * picking a name format that was requested by the relying party or is mutually supported by both the relying party
710 * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
711 * the principals attributes are inspected for an attribtue supported an attribute encoder whose category is one of
712 * the supported name formats.
714 * @param requestContext current request context
716 * @return the NameID appropriate for this request
718 * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
719 * name ID attribute or because there are no supported name formats
721 protected NameID buildNameId(SAML2ProfileRequestContext requestContext) throws ProfileException {
722 if (log.isDebugEnabled()) {
723 log.debug("Building assertion NameID for principal/relying party:" + requestContext.getPrincipalName()
724 + "/" + requestContext.getRelyingPartyId());
726 Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
727 List<String> supportedNameFormats = getNameFormats(requestContext);
729 if (log.isDebugEnabled()) {
730 log.debug("Supported NameID formats: " + supportedNameFormats);
733 if (principalAttributes != null && supportedNameFormats != null) {
735 AttributeEncoder<NameID> nameIdEncoder = null;
736 for (BaseAttribute attribute : principalAttributes.values()) {
737 for (String nameFormat : supportedNameFormats) {
738 nameIdEncoder = attribute.getEncoderByCategory(nameFormat);
739 if (nameIdEncoder != null) {
740 if (log.isDebugEnabled()) {
741 log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
742 + nameFormat + " to create the NameID for principal "
743 + requestContext.getPrincipalName());
745 return nameIdEncoder.encode(attribute);
749 } catch (AttributeEncodingException e) {
750 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
751 "Unable to construct NameID"));
752 throw new ProfileException("Unable to encode NameID attribute", e);
756 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
757 "Unable to construct NameID"));
758 throw new ProfileException("No principal attributes support NameID construction");
762 * Gets the NameID format to use when creating NameIDs for the relying party.
764 * @param requestContext current request context
766 * @return list of nameID formats that may be used with the relying party
768 * @throws ProfileException thrown if there is a problem determing the NameID format to use
770 protected List<String> getNameFormats(SAML2ProfileRequestContext requestContext) throws ProfileException {
771 ArrayList<String> nameFormats = new ArrayList<String>();
774 RoleDescriptor assertingPartyRole = getMetadataProvider().getRole(requestContext.getAssertingPartyId(),
775 requestContext.getAssertingPartyRole(), SAMLConstants.SAML20P_NS);
776 List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
778 String nameFormat = null;
779 if (requestContext.getSamlRequest() instanceof AuthnRequest) {
780 AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
781 if (authnRequest.getNameIDPolicy() != null) {
782 nameFormat = authnRequest.getNameIDPolicy().getFormat();
783 if (assertingPartySupportedFormats.contains(nameFormat)) {
784 nameFormats.add(nameFormat);
786 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
787 StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + nameFormat));
788 throw new ProfileException("NameID format required by relying party is not supported");
793 if (nameFormats.isEmpty()) {
794 RoleDescriptor relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
795 requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
796 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
798 assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
799 nameFormats.addAll(assertingPartySupportedFormats);
801 if (nameFormats.isEmpty()) {
802 nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
807 } catch (MetadataProviderException e) {
808 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
809 "Unable to lookup entity metadata"));
810 throw new ProfileException("Unable to determine lookup entity metadata", e);
815 * Gets the list of NameID formats supported for a given role.
817 * @param role the role to get the list of supported NameID formats
819 * @return list of supported NameID formats
821 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
822 List<NameIDFormat> nameIDFormats = null;
824 if (role instanceof SSODescriptor) {
825 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
826 } else if (role instanceof AuthnAuthorityDescriptor) {
827 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
828 } else if (role instanceof PDPDescriptor) {
829 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
830 } else if (role instanceof AttributeAuthorityDescriptor) {
831 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
834 ArrayList<String> supportedFormats = new ArrayList<String>();
835 if (nameIDFormats != null) {
836 for (NameIDFormat format : nameIDFormats) {
837 supportedFormats.add(format.getFormat());
841 return supportedFormats;
845 * Constructs an SAML response message carrying a request error.
847 * @param requestContext current request context
849 * @return the constructed error response
851 protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext) {
852 Response samlResponse = getResponseBuilder().buildObject();
853 samlResponse.setIssueInstant(new DateTime());
854 populateStatusResponse(requestContext, samlResponse);
856 samlResponse.setStatus(requestContext.getFailureStatus());
862 * Writes an aduit log entry indicating the successful response to the attribute request.
864 * @param context current request context
866 protected void writeAuditLogEntry(SAML2ProfileRequestContext context) {
867 AuditLogEntry auditLogEntry = new AuditLogEntry();
868 auditLogEntry.setMessageProfile(getProfileId());
869 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
870 auditLogEntry.setPrincipalName(context.getPrincipalName());
871 auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
872 auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
873 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
874 auditLogEntry.setRequestId(context.getSamlRequest().getID());
875 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
876 auditLogEntry.setResponseId(context.getSamlResponse().getID());
877 getAduitLog().log(Level.CRITICAL, auditLogEntry);
881 * Contextual object used to accumlate information as profile requests are being processed.
883 * @param <RequestType> type of SAML 2 request
884 * @param <ResponseType> type of SAML 2 response
885 * @param <ProfileConfigurationType> configuration type for this profile
887 protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends StatusResponseType, ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
888 extends SAMLProfileRequestContext {
890 /** SAML request message. */
891 private RequestType samlRequest;
893 /** SAML response message. */
894 private ResponseType samlResponse;
896 /** Request profile configuration. */
897 private ProfileConfigurationType profileConfiguration;
899 /** The NameID of the subject of this request. */
900 private NameID subjectNameID;
902 /** The request failure status. */
903 private Status failureStatus;
908 * @param request current profile request
909 * @param response current profile response
911 public SAML2ProfileRequestContext(ProfileRequest<ServletRequest> request,
912 ProfileResponse<ServletResponse> response) {
913 super(request, response);
917 * Gets the NameID of the subject of this request.
919 * @return NameID of the subject of this request
921 public NameID getSubjectNameID() {
922 return subjectNameID;
926 * Sets the NameID of the subject of this request.
928 * @param nameID NameID of the subject of this request
930 public void setSubjectNameID(NameID nameID) {
931 subjectNameID = nameID;
935 * Gets the profile configuration for this request.
937 * @return profile configuration for this request
939 public ProfileConfigurationType getProfileConfiguration() {
940 return profileConfiguration;
944 * Sets the profile configuration for this request.
946 * @param configuration profile configuration for this request
948 public void setProfileConfiguration(ProfileConfigurationType configuration) {
949 profileConfiguration = configuration;
953 * Gets the SAML request message.
955 * @return SAML request message
957 public RequestType getSamlRequest() {
962 * Sets the SAML request message.
964 * @param request SAML request message
966 public void setSamlRequest(RequestType request) {
967 samlRequest = request;
971 * Gets the SAML response message.
973 * @return SAML response message
975 public ResponseType getSamlResponse() {
980 * Sets the SAML response message.
982 * @param response SAML response message
984 public void setSamlResponse(ResponseType response) {
985 samlResponse = response;
989 * Gets the status reflecting a request failure.
991 * @return status reflecting a request failure
993 public Status getFailureStatus() {
994 return failureStatus;
998 * Sets the status reflecting a request failure.
1000 * @param status status reflecting a request failure
1002 public void setFailureStatus(Status status) {
1003 failureStatus = status;