/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.joda.time.DateTime; import org.opensaml.Configuration; import org.opensaml.common.SAMLException; import org.opensaml.common.SAMLObjectBuilder; import org.opensaml.common.SAMLVersion; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AttributeStatement; import org.opensaml.saml2.core.AttributeValue; import org.opensaml.saml2.core.Audience; import org.opensaml.saml2.core.AudienceRestriction; import org.opensaml.saml2.core.AuthnContext; import org.opensaml.saml2.core.AuthnContextClassRef; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.AuthnStatement; import org.opensaml.saml2.core.Conditions; import org.opensaml.saml2.core.NameID; import org.opensaml.saml2.core.NameIDType; import org.opensaml.saml2.core.Response; import org.opensaml.saml2.core.Status; import org.opensaml.saml2.core.StatusCode; import org.opensaml.saml2.core.Subject; import org.opensaml.saml2.core.SubjectConfirmation; import org.opensaml.saml2.core.SubjectConfirmationData; import org.opensaml.saml2.metadata.AssertionConsumerService; import org.opensaml.saml2.metadata.Endpoint; import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.xml.XMLObjectBuilder; import org.opensaml.xml.io.Marshaller; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.schema.XSString; import org.opensaml.xml.security.SecurityException; import org.opensaml.xml.security.SecurityHelper; import org.opensaml.xml.security.credential.Credential; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureException; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.signature.impl.SignatureBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.websso.WebSSOProfileImpl; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class IdpWebSsoProfileImpl extends WebSSOProfileImpl implements IdpWebSsoProfile { @Override public void sendResponse(Authentication authentication, SAMLMessageContext context, IdpWebSSOProfileOptions options) throws SAMLException, MetadataProviderException, MessageEncodingException, SecurityException, MarshallingException, SignatureException { buildResponse(authentication, context, options); sendMessage(context, false); } @SuppressWarnings("unchecked") protected void buildResponse(Authentication authentication, SAMLMessageContext context, IdpWebSSOProfileOptions options) throws MetadataProviderException, SecurityException, MarshallingException, SignatureException, SAMLException { IDPSSODescriptor idpDescriptor = (IDPSSODescriptor) context.getLocalEntityRoleMetadata(); SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getPeerEntityRoleMetadata(); AuthnRequest authnRequest = (AuthnRequest) context.getInboundSAMLMessage(); AssertionConsumerService assertionConsumerService = getAssertionConsumerService(options, idpDescriptor, spDescriptor); context.setPeerEntityEndpoint(assertionConsumerService); Assertion assertion = buildAssertion(authentication, authnRequest, options, context.getPeerEntityId(), context.getLocalEntityId()); if (options.isAssertionsSigned() || spDescriptor.getWantAssertionsSigned()) { signAssertion(assertion, context.getLocalSigningCredential()); } Response samlResponse = createResponse(context, assertionConsumerService, assertion, authnRequest); context.setOutboundMessage(samlResponse); context.setOutboundSAMLMessage(samlResponse); } private Response createResponse(SAMLMessageContext context, AssertionConsumerService assertionConsumerService, Assertion assertion, AuthnRequest authnRequest) { @SuppressWarnings("unchecked") SAMLObjectBuilder<Response> responseBuilder = (SAMLObjectBuilder<Response>) builderFactory .getBuilder(Response.DEFAULT_ELEMENT_NAME); Response response = responseBuilder.buildObject(); buildCommonAttributes(context.getLocalEntityId(), response, assertionConsumerService, authnRequest); response.getAssertions().add(assertion); buildStatusSuccess(response); return response; } private void buildCommonAttributes(String localEntityId, Response response, Endpoint service, AuthnRequest authnRequest) { response.setID(generateID()); response.setIssuer(getIssuer(localEntityId)); response.setInResponseTo(authnRequest.getID()); response.setVersion(SAMLVersion.VERSION_20); response.setIssueInstant(new DateTime()); if (service != null) { response.setDestination(service.getLocation()); } } private Assertion buildAssertion(Authentication authentication, AuthnRequest authnRequest, IdpWebSSOProfileOptions options, String audienceURI, String issuerEntityId) throws SAMLException{ @SuppressWarnings("unchecked") SAMLObjectBuilder<Assertion> assertionBuilder = (SAMLObjectBuilder<Assertion>) builderFactory .getBuilder(Assertion.DEFAULT_ELEMENT_NAME); Assertion assertion = assertionBuilder.buildObject(); assertion.setID(generateID()); assertion.setIssueInstant(new DateTime()); assertion.setVersion(SAMLVersion.VERSION_20); assertion.setIssuer(getIssuer(issuerEntityId)); buildAssertionAuthnStatement(assertion); buildAssertionConditions(assertion, options.getAssertionTimeToLiveSeconds(), audienceURI); buildAssertionSubject(assertion, authnRequest, options.getAssertionTimeToLiveSeconds(), (UaaPrincipal) authentication.getPrincipal()); buildAttributeStatement(assertion, authentication); return assertion; } private void buildAssertionAuthnStatement(Assertion assertion) { @SuppressWarnings("unchecked") SAMLObjectBuilder<AuthnStatement> authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) builderFactory .getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME); AuthnStatement authnStatement = authnStatementBuilder.buildObject(); authnStatement.setAuthnInstant(new DateTime()); authnStatement.setSessionIndex(generateID()); @SuppressWarnings("unchecked") SAMLObjectBuilder<AuthnContext> authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) builderFactory .getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME); AuthnContext authnContext = authnContextBuilder.buildObject(); @SuppressWarnings("unchecked") SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) builderFactory .getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(); authnContextClassRef.setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX); authnContext.setAuthnContextClassRef(authnContextClassRef); authnStatement.setAuthnContext(authnContext); assertion.getAuthnStatements().add(authnStatement); } private void buildAssertionConditions(Assertion assertion, int assertionTtlSeconds, String audienceURI) { @SuppressWarnings("unchecked") SAMLObjectBuilder<Conditions> conditionsBuilder = (SAMLObjectBuilder<Conditions>) builderFactory .getBuilder(Conditions.DEFAULT_ELEMENT_NAME); Conditions conditions = conditionsBuilder.buildObject(); conditions.setNotBefore(new DateTime()); conditions.setNotOnOrAfter(new DateTime().plusSeconds(assertionTtlSeconds)); @SuppressWarnings("unchecked") SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) builderFactory .getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME); AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject(); @SuppressWarnings("unchecked") SAMLObjectBuilder<Audience> audienceBuilder = (SAMLObjectBuilder<Audience>) builderFactory .getBuilder(Audience.DEFAULT_ELEMENT_NAME); Audience audience = audienceBuilder.buildObject(); audience.setAudienceURI(audienceURI); audienceRestriction.getAudiences().add(audience); conditions.getAudienceRestrictions().add(audienceRestriction); assertion.setConditions(conditions); } private void buildAssertionSubject(Assertion assertion, AuthnRequest authnRequest, int assertionTtlSeconds, UaaPrincipal uaaPrincipal) throws SAMLException { @SuppressWarnings("unchecked") SAMLObjectBuilder<Subject> subjectBuilder = (SAMLObjectBuilder<Subject>) builderFactory .getBuilder(Subject.DEFAULT_ELEMENT_NAME); Subject subject = subjectBuilder.buildObject(); @SuppressWarnings("unchecked") SAMLObjectBuilder<NameID> nameIdBuilder = (SAMLObjectBuilder<NameID>) builderFactory .getBuilder(NameID.DEFAULT_ELEMENT_NAME); NameID nameID = nameIdBuilder.buildObject(); String nameIDFormat = NameIDType.UNSPECIFIED; String nameIdStr = uaaPrincipal.getName(); if(null != authnRequest.getSubject() && null != authnRequest.getSubject().getNameID() && null != authnRequest.getSubject().getNameID().getFormat()){ nameIDFormat = authnRequest.getSubject().getNameID().getFormat(); switch (nameIDFormat) { case NameIDType.EMAIL: nameIdStr = uaaPrincipal.getEmail(); break; case NameIDType.PERSISTENT: nameIdStr = uaaPrincipal.getId(); break; case NameIDType.UNSPECIFIED: nameIdStr = uaaPrincipal.getName(); break; default: throw new SAMLException("The NameIDType '" + nameIDFormat + "' is not supported."); } } nameID.setValue(nameIdStr); nameID.setFormat(nameIDFormat); subject.setNameID(nameID); @SuppressWarnings("unchecked") SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) builderFactory .getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME); SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject(); subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); @SuppressWarnings("unchecked") SAMLObjectBuilder<SubjectConfirmationData> subjectConfirmationDataBuilder = (SAMLObjectBuilder<SubjectConfirmationData>) builderFactory .getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); SubjectConfirmationData subjectConfirmationData = subjectConfirmationDataBuilder.buildObject(); subjectConfirmationData.setNotOnOrAfter(new DateTime().plusSeconds(assertionTtlSeconds)); subjectConfirmationData.setInResponseTo(authnRequest.getID()); subjectConfirmationData.setRecipient(authnRequest.getAssertionConsumerServiceURL()); subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData); subject.getSubjectConfirmations().add(subjectConfirmation); assertion.setSubject(subject); } private void buildAttributeStatement(Assertion assertion, Authentication authentication) { @SuppressWarnings("unchecked") SAMLObjectBuilder<AttributeStatement> attributeStatementBuilder = (SAMLObjectBuilder<AttributeStatement>) builderFactory .getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME); AttributeStatement attributeStatement = attributeStatementBuilder.buildObject(); List<String> authorities = new ArrayList<>(); for (GrantedAuthority authority : authentication.getAuthorities()) { authorities.add(authority.getAuthority()); } Attribute authoritiesAttribute = buildStringAttribute("authorities", authorities); attributeStatement.getAttributes().add(authoritiesAttribute); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); Attribute emailAttribute = buildStringAttribute("email", Arrays.asList(new String[] { principal.getEmail() })); attributeStatement.getAttributes().add(emailAttribute); Attribute idAttribute = buildStringAttribute("id", Arrays.asList(new String[] { principal.getId() })); attributeStatement.getAttributes().add(idAttribute); Attribute nameAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getName() })); attributeStatement.getAttributes().add(nameAttribute); Attribute originAttribute = buildStringAttribute("origin", Arrays.asList(new String[] { principal.getOrigin() })); attributeStatement.getAttributes().add(originAttribute); Attribute zoneAttribute = buildStringAttribute("zoneId", Arrays.asList(new String[] { principal.getZoneId() })); attributeStatement.getAttributes().add(zoneAttribute); assertion.getAttributeStatements().add(attributeStatement); } public Attribute buildStringAttribute(String name, List<String> values) { @SuppressWarnings("unchecked") SAMLObjectBuilder<Attribute> attributeBuilder = (SAMLObjectBuilder<Attribute>) builderFactory .getBuilder(Attribute.DEFAULT_ELEMENT_NAME); Attribute attribute = (Attribute) attributeBuilder.buildObject(); attribute.setName(name); @SuppressWarnings("unchecked") XMLObjectBuilder<XSString> xsStringBuilder = (XMLObjectBuilder<XSString>) builderFactory .getBuilder(XSString.TYPE_NAME); for (String value : values) { // Set custom Attributes XSString attributeValue = xsStringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); attributeValue.setValue(value); attribute.getAttributeValues().add(attributeValue); } return attribute; } private void buildStatusSuccess(Response response) { buildStatus(response, StatusCode.SUCCESS_URI); } private void buildStatus(Response response, String statusCodeStr) { @SuppressWarnings("unchecked") SAMLObjectBuilder<StatusCode> statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) builderFactory .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME); StatusCode statusCode = statusCodeBuilder.buildObject(); statusCode.setValue(statusCodeStr); @SuppressWarnings("unchecked") SAMLObjectBuilder<Status> statusBuilder = (SAMLObjectBuilder<Status>) builderFactory .getBuilder(Status.DEFAULT_ELEMENT_NAME); Status status = statusBuilder.buildObject(); status.setStatusCode(statusCode); response.setStatus(status); } private void signAssertion(Assertion assertion, Credential credential) throws SecurityException, MarshallingException, SignatureException { SignatureBuilder signatureBuilder = (SignatureBuilder) builderFactory .getBuilder(Signature.DEFAULT_ELEMENT_NAME); Signature signature = signatureBuilder.buildObject(); signature.setSigningCredential(credential); SecurityHelper.prepareSignatureParams(signature, credential, null, null); assertion.setSignature(signature); Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(assertion); marshaller.marshall(assertion); Signer.signObject(signature); } }