All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.oauth2.sdk.assertions.saml2.SAML2AssertionDetails Maven / Gradle / Ivy

/*
 * oauth2-oidc-sdk
 *
 * Copyright 2012-2016, Connect2id Ltd and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.nimbusds.oauth2.sdk.assertions.saml2;


import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.SerializeException;
import com.nimbusds.oauth2.sdk.assertions.AssertionDetails;
import com.nimbusds.oauth2.sdk.id.Audience;
import com.nimbusds.oauth2.sdk.id.Identifier;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.oauth2.sdk.id.Subject;
import com.nimbusds.openid.connect.sdk.claims.ACR;
import net.jcip.annotations.Immutable;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.saml2.core.*;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.schema.XSString;
import org.opensaml.xml.schema.impl.XSStringBuilder;


/**
 * SAML 2.0 bearer assertion details for OAuth 2.0 client authentication and
 * authorisation grants.
 *
 * 

Used for {@link com.nimbusds.oauth2.sdk.SAML2BearerGrant SAML 2.0 bearer * assertion grants}. * *

Example SAML 2.0 assertion: * *

 * <Assertion IssueInstant="2010-10-01T20:07:34.619Z"
 *            ID="ef1xsbZxPV2oqjd7HTLRLIBlBb7"
 *            Version="2.0"
 *            xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
 *     <Issuer>https://saml-idp.example.com</Issuer>
 *     <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
 *         [...omitted for brevity...]
 *     </ds:Signature>
 *     <Subject>
 *         <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
 *             [email protected]
 *         </NameID>
 *         <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
 *             <SubjectConfirmationData NotOnOrAfter="2010-10-01T20:12:34.619Z"
 *                                      Recipient="https://authz.example.net/token.oauth2"/>
 *         </SubjectConfirmation>
 *     </Subject>
 *     <Conditions>
 *         <AudienceRestriction>
 *             <Audience>https://saml-sp.example.net</Audience>
 *         </AudienceRestriction>
 *     </Conditions>
 *     <AuthnStatement AuthnInstant="2010-10-01T20:07:34.371Z">
 *         <AuthnContext>
 *             <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</AuthnContextClassRef>
 *         </AuthnContext>
 *     </AuthnStatement>
 * </Assertion>
 * 
* *

Related specifications: * *

    *
  • Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 * Client Authentication and Authorization Grants (RFC 7522), section * 3. *
*/ @Immutable public class SAML2AssertionDetails extends AssertionDetails { /** * The subject format (optional). */ private final String subjectFormat; /** * The subject authentication time (optional). */ private final Date subjectAuthTime; /** * The subject Authentication Context Class Reference (ACR) (optional). */ private final ACR subjectACR; /** * The time before which this assertion must not be accepted for * processing (optional). */ private final Date nbf; /** * The client IPv4 or IPv6 address (optional). */ private final InetAddress clientAddress; /** * The attribute statement (optional). */ private final Map> attrStatement; /** * Creates a new SAML 2.0 bearer assertion details instance. The * expiration time is set to five minutes from the current system time. * Generates a default identifier for the assertion. The issue time is * set to the current system time. * * @param issuer The issuer. Must not be {@code null}. * @param subject The subject. Must not be {@code null}. * @param audience The audience, typically the URI of the authorisation * server's token endpoint. Must not be {@code null}. */ public SAML2AssertionDetails(final Issuer issuer, final Subject subject, final Audience audience) { this(issuer, subject, null, null, null, audience.toSingleAudienceList(), new Date(new Date().getTime() + 5*60*1000L), null, new Date(), new Identifier(), null, null); } /** * Creates a new SAML 2.0 bearer assertion details instance. * * @param issuer The issuer. Must not be {@code null}. * @param subject The subject. Must not be {@code null}. * @param subjectFormat The subject format, {@code null} if not * specified. * @param subjectAuthTime The subject authentication time, {@code null} * if not specified. * @param subjectACR The subject Authentication Context Class * Reference (ACR), {@code null} if not * specified. * @param audience The audience, typically including the URI of the * authorisation server's token endpoint. Must not be * {@code null}. * @param exp The expiration time. Must not be {@code null}. * @param nbf The time before which the assertion must not * be accepted for processing, {@code null} if * not specified. * @param iat The time at which the assertion was issued. * Must not be {@code null}. * @param id Unique identifier for the assertion. Must not * be {@code null}. * @param clientAddress The client address, {@code null} if not * specified. * @param attrStatement The attribute statement (in simplified form), * {@code null} if not specified. */ public SAML2AssertionDetails(final Issuer issuer, final Subject subject, final String subjectFormat, final Date subjectAuthTime, final ACR subjectACR, final List audience, final Date exp, final Date nbf, final Date iat, final Identifier id, final InetAddress clientAddress, final Map> attrStatement) { super(issuer, subject, audience, iat, exp, id); if (iat == null) { throw new IllegalArgumentException("The issue time must not be null"); } if (id == null) { throw new IllegalArgumentException("The assertion identifier must not be null"); } this.subjectFormat = subjectFormat; this.subjectAuthTime = subjectAuthTime; this.subjectACR = subjectACR; this.clientAddress = clientAddress; this.nbf = nbf; this.attrStatement = attrStatement; } /** * Returns the optional subject format. * * @return The subject format, {@code null} if not specified. */ public String getSubjectFormat() { return subjectFormat; } /** * Returns the optional subject authentication time. * * @return The subject authentication time, {@code null} if not * specified. */ public Date getSubjectAuthenticationTime() { return subjectAuthTime; } /** * Returns the optional subject Authentication Context Class Reference * (ACR). * * @return The subject ACR, {@code null} if not specified. */ public ACR getSubjectACR() { return subjectACR; } /** * Returns the optional not-before time. * * @return The not-before time, {@code null} if not specified. */ public Date getNotBeforeTime() { return nbf; } /** * Returns the optional client address to which this assertion is * bound. * * @return The client address, {@code null} if not specified. */ public InetAddress getClientInetAddress() { return clientAddress; } /** * Returns the optional attribute statement. * * @return The attribute statement (in simplified form), {@code null} * if not specified. */ public Map> getAttributeStatement() { return attrStatement; } /** * Returns a SAML 2.0 assertion (unsigned) representation of this * assertion details instance. * * @return The SAML 2.0 assertion (with no signature element). * * @throws SerializeException If serialisation failed. */ public Assertion toSAML2Assertion() throws SerializeException { try { DefaultBootstrap.bootstrap(); } catch (ConfigurationException e) { throw new SerializeException(e.getMessage(), e); } final XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory(); // Top level assertion element SAMLObjectBuilder assertionBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Assertion.DEFAULT_ELEMENT_NAME); Assertion a = assertionBuilder.buildObject(); a.setID(getID().getValue()); a.setIssueInstant(new DateTime(getIssueTime())); // Issuer SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(org.opensaml.saml2.core.Issuer.DEFAULT_ELEMENT_NAME); org.opensaml.saml2.core.Issuer iss = issuerBuilder.buildObject(); iss.setValue(getIssuer().getValue()); a.setIssuer(iss); // Conditions SAMLObjectBuilder conditionsBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Conditions.DEFAULT_ELEMENT_NAME); Conditions conditions = conditionsBuilder.buildObject(); // Audience restriction SAMLObjectBuilder audRestrictionBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME); AudienceRestriction audRestriction = audRestrictionBuilder.buildObject(); // ... with single audience - the authz server SAMLObjectBuilder audBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(org.opensaml.saml2.core.Audience.DEFAULT_ELEMENT_NAME); for (Audience audItem: getAudience()) { org.opensaml.saml2.core.Audience aud = audBuilder.buildObject(); aud.setAudienceURI(audItem.getValue()); audRestriction.getAudiences().add(aud); } conditions.getAudienceRestrictions().add(audRestriction); a.setConditions(conditions); // Subject elements SAMLObjectBuilder subBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(org.opensaml.saml2.core.Subject.DEFAULT_ELEMENT_NAME); org.opensaml.saml2.core.Subject sub = subBuilder.buildObject(); SAMLObjectBuilder subIDBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(NameID.DEFAULT_ELEMENT_NAME); NameID nameID = subIDBuilder.buildObject(); nameID.setFormat(subjectFormat); nameID.setValue(getSubject().getValue()); sub.setNameID(nameID); SAMLObjectBuilder subCmBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME); SubjectConfirmation subCm = subCmBuilder.buildObject(); subCm.setMethod(SubjectConfirmation.METHOD_BEARER); SAMLObjectBuilder subCmDataBuilder= (SAMLObjectBuilder) builderFactory.getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); SubjectConfirmationData subCmData = subCmDataBuilder.buildObject(); subCmData.setNotOnOrAfter(new DateTime(getExpirationTime())); subCmData.setNotBefore(getNotBeforeTime() != null ? new DateTime(getNotBeforeTime()) : null); subCmData.setRecipient(getAudience().get(0).getValue()); // recipient is single-valued if (clientAddress != null) { subCmData.setAddress(clientAddress.getHostAddress()); } subCm.setSubjectConfirmationData(subCmData); sub.getSubjectConfirmations().add(subCm); a.setSubject(sub); // Auth time and class? if (subjectAuthTime != null || subjectACR != null) { SAMLObjectBuilder authnStmtBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME); AuthnStatement authnStmt = authnStmtBuilder.buildObject(); if (subjectAuthTime != null) { authnStmt.setAuthnInstant(new DateTime(subjectAuthTime)); } if (subjectACR != null) { SAMLObjectBuilder authnCtxBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME); AuthnContext authnCtx = authnCtxBuilder.buildObject(); SAMLObjectBuilder acrBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); AuthnContextClassRef acr = acrBuilder.buildObject(); acr.setAuthnContextClassRef(subjectACR.getValue()); authnCtx.setAuthnContextClassRef(acr); authnStmt.setAuthnContext(authnCtx); } a.getAuthnStatements().add(authnStmt); } // Attributes? if (MapUtils.isNotEmpty(attrStatement)) { SAMLObjectBuilder attrContainerBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME); AttributeStatement attrSet = attrContainerBuilder.buildObject(); SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME); for (Map.Entry> entry: attrStatement.entrySet()) { Attribute attr = attrBuilder.buildObject(); attr.setName(entry.getKey()); XSStringBuilder stringBuilder = (XSStringBuilder) Configuration.getBuilderFactory().getBuilder(XSString.TYPE_NAME); for (String v: entry.getValue()) { XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); stringValue.setValue(v); attr.getAttributeValues().add(stringValue); } attrSet.getAttributes().add(attr); } a.getAttributeStatements().add(attrSet); } return a; } /** * Parses a SAML 2.0 bearer assertion details instance from the * specified assertion object. * * @param assertion The assertion. Must not be {@code null}. * * @return The SAML 2.0 bearer assertion details. * * @throws ParseException If the assertion couldn't be parsed to a * SAML 2.0 bearer assertion details instance. */ public static SAML2AssertionDetails parse(final Assertion assertion) throws ParseException { // Assertion > Issuer if (assertion.getIssuer() == null) { throw new ParseException("Missing Assertion Issuer element"); } final Issuer issuer = new Issuer(assertion.getIssuer().getValue()); // Assertion > Subject if (assertion.getSubject() == null) { throw new ParseException("Missing Assertion Subject element"); } if (assertion.getSubject().getNameID() == null) { throw new ParseException("Missing Assertion Subject NameID element"); } // Assertion > Subject > NameID final Subject subject = new Subject(assertion.getSubject().getNameID().getValue()); // Assertion > Subject > NameID : Format final String subjectFormat = assertion.getSubject().getNameID().getFormat(); // Assertion > AuthnStatement : AuthnInstant Date subjectAuthTime = null; // Assertion > AuthnStatement > AuthnContext > AuthnContextClassRef ACR subjectACR = null; if (CollectionUtils.isNotEmpty(assertion.getAuthnStatements())) { for (AuthnStatement authStmt: assertion.getAuthnStatements()) { if (authStmt == null) { continue; // skip } if (authStmt.getAuthnInstant() != null) { subjectAuthTime = authStmt.getAuthnInstant().toDate(); } if (authStmt.getAuthnContext() != null && authStmt.getAuthnContext().getAuthnContextClassRef() != null) { subjectACR = new ACR(authStmt.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()); } } } List subCms = assertion.getSubject().getSubjectConfirmations(); if (CollectionUtils.isEmpty(subCms)) { throw new ParseException("Missing SubjectConfirmation element"); } // Assertion > Subject > SubjectConfirmation : Method boolean bearerMethodFound = false; for (SubjectConfirmation subCm: subCms) { if (SubjectConfirmation.METHOD_BEARER.equals(subCm.getMethod())) { bearerMethodFound = true; break; } } if (! bearerMethodFound) { throw new ParseException("Missing SubjectConfirmation Method " + SubjectConfirmation.METHOD_BEARER + " attribute"); } Conditions conditions = assertion.getConditions(); if (conditions == null) { throw new ParseException("Missing Conditions element"); } List audRestrictions = conditions.getAudienceRestrictions(); if (CollectionUtils.isEmpty(audRestrictions)) { throw new ParseException("Missing AudienceRestriction element"); } // Assertion > Conditions > AudienceRestriction > Audience final Set audSet = new HashSet<>(); // ensure no duplicates for (AudienceRestriction audRestriction: audRestrictions) { if (CollectionUtils.isEmpty(audRestriction.getAudiences())) { continue; // skip } for (org.opensaml.saml2.core.Audience aud: audRestriction.getAudiences()) { audSet.add(new Audience(aud.getAudienceURI())); } } // Optional recipient in // Assertion > Subject > SubjectConfirmation > SubjectConfirmationData for (SubjectConfirmation subCm: subCms) { if (subCm.getSubjectConfirmationData() == null) { continue; // skip } if (subCm.getSubjectConfirmationData().getRecipient() == null) { throw new ParseException("Missing SubjectConfirmationData Recipient attribute"); } audSet.add(new Audience(subCm.getSubjectConfirmationData().getRecipient())); } // Set expiration and not-before times, try first in // Assertion > Conditions Date exp = conditions.getNotOnOrAfter() != null ? conditions.getNotOnOrAfter().toDate() : null; Date nbf = conditions.getNotBefore() != null ? conditions.getNotBefore().toDate() : null; if (exp == null) { // Try in Assertion > Subject > SubjectConfirmation > SubjectConfirmationData for (SubjectConfirmation subCm: subCms) { if (subCm.getSubjectConfirmationData() == null) { continue; // skip } exp = subCm.getSubjectConfirmationData().getNotOnOrAfter() != null ? subCm.getSubjectConfirmationData().getNotOnOrAfter().toDate() : null; nbf = subCm.getSubjectConfirmationData().getNotBefore() != null ? subCm.getSubjectConfirmationData().getNotBefore().toDate() : null; } } // Assertion : ID if (assertion.getID() == null) { throw new ParseException("Missing Assertion ID attribute"); } final Identifier id = new Identifier(assertion.getID()); // Assertion : IssueInstant if (assertion.getIssueInstant() == null) { throw new ParseException("Missing Assertion IssueInstant attribute"); } final Date iat = assertion.getIssueInstant().toDate(); // Assertion > Subject > SubjectConfirmation > SubjectConfirmationData > Address InetAddress clientAddress = null; for (SubjectConfirmation subCm: subCms) { if (subCm.getSubjectConfirmationData() != null && subCm.getSubjectConfirmationData().getAddress() != null) { try { clientAddress = InetAddress.getByName(subCm.getSubjectConfirmationData().getAddress()); } catch (UnknownHostException e) { throw new ParseException("Invalid Address: " + e.getMessage(), e); } } } // Assertion > AttributeStatement > Attribute (: Name, > AttributeValue) Map> attrStatement = null; if (CollectionUtils.isNotEmpty(assertion.getAttributeStatements())) { attrStatement = new HashMap<>(); for (AttributeStatement attrStmt: assertion.getAttributeStatements()) { if (attrStmt == null) { continue; // skip } for (Attribute attr: attrStmt.getAttributes()) { String name = attr.getName(); List values = new LinkedList<>(); for (XMLObject v: attr.getAttributeValues()) { values.add(v.getDOM().getTextContent()); } attrStatement.put(name, values); } } } return new SAML2AssertionDetails(issuer, subject, subjectFormat, subjectAuthTime, subjectACR, new ArrayList<>(audSet), exp, nbf, iat, id, clientAddress, attrStatement); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy