package mujina.saml; import org.joda.time.DateTime; import org.opensaml.Configuration; import org.opensaml.saml2.core.*; import org.opensaml.xml.XMLObject; import org.opensaml.xml.XMLObjectBuilderFactory; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.schema.XSAny; import org.opensaml.xml.schema.XSString; import org.opensaml.xml.schema.impl.XSStringBuilder; import org.opensaml.xml.security.credential.Credential; import org.opensaml.xml.signature.SignableXMLObject; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureConstants; import org.opensaml.xml.signature.SignatureException; import org.opensaml.xml.signature.Signer; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import javax.xml.namespace.QName; import java.util.List; import java.util.Optional; import java.util.UUID; import static java.util.stream.Collectors.toList; public class SAMLBuilder { private static final XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory(); @SuppressWarnings({"unused", "unchecked"}) public static <T> T buildSAMLObject(final Class<T> objectClass, QName qName) { return (T) builderFactory.getBuilder(qName).buildObject(qName); } public static Issuer buildIssuer(String issuingEntityName) { Issuer issuer = buildSAMLObject(Issuer.class, Issuer.DEFAULT_ELEMENT_NAME); issuer.setValue(issuingEntityName); issuer.setFormat(NameIDType.ENTITY); return issuer; } private static Subject buildSubject(String subjectNameId, String subjectNameIdType, String recipient, String inResponseTo) { NameID nameID = buildSAMLObject(NameID.class, NameID.DEFAULT_ELEMENT_NAME); nameID.setValue(subjectNameId); nameID.setFormat(subjectNameIdType); Subject subject = buildSAMLObject(Subject.class, Subject.DEFAULT_ELEMENT_NAME); subject.setNameID(nameID); SubjectConfirmation subjectConfirmation = buildSAMLObject(SubjectConfirmation.class, SubjectConfirmation.DEFAULT_ELEMENT_NAME); subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); SubjectConfirmationData subjectConfirmationData = buildSAMLObject(SubjectConfirmationData.class, SubjectConfirmationData.DEFAULT_ELEMENT_NAME); subjectConfirmationData.setRecipient(recipient); subjectConfirmationData.setInResponseTo(inResponseTo); subjectConfirmationData.setNotOnOrAfter(new DateTime().plusMinutes(8 * 60)); subjectConfirmationData.setAddress(recipient); subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData); subject.getSubjectConfirmations().add(subjectConfirmation); return subject; } public static Status buildStatus(String value) { Status status = buildSAMLObject(Status.class, Status.DEFAULT_ELEMENT_NAME); StatusCode statusCode = buildSAMLObject(StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME); statusCode.setValue(value); status.setStatusCode(statusCode); return status; } public static Status buildStatus(String value, String subStatus, String message) { Status status = buildStatus(value); StatusCode subStatusCode = buildSAMLObject(StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME); subStatusCode.setValue(subStatus); status.getStatusCode().setStatusCode(subStatusCode); StatusMessage statusMessage = buildSAMLObject(StatusMessage.class, StatusMessage.DEFAULT_ELEMENT_NAME); statusMessage.setMessage(message); status.setStatusMessage(statusMessage); return status; } public static Assertion buildAssertion(SAMLPrincipal principal, Status status, String entityId) { Assertion assertion = buildSAMLObject(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME); if (status.getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) { Subject subject = buildSubject(principal.getNameID(), principal.getNameIDType(), principal.getAssertionConsumerServiceURL(), principal.getRequestID()); assertion.setSubject(subject); } Issuer issuer = buildIssuer(entityId); Audience audience = buildSAMLObject(Audience.class, Audience.DEFAULT_ELEMENT_NAME); audience.setAudienceURI(principal.getServiceProviderEntityID()); AudienceRestriction audienceRestriction = buildSAMLObject(AudienceRestriction.class, AudienceRestriction.DEFAULT_ELEMENT_NAME); audienceRestriction.getAudiences().add(audience); Conditions conditions = buildSAMLObject(Conditions.class, Conditions.DEFAULT_ELEMENT_NAME); conditions.getAudienceRestrictions().add(audienceRestriction); assertion.setConditions(conditions); AuthnStatement authnStatement = buildAuthnStatement(new DateTime(), entityId); assertion.setIssuer(issuer); assertion.getAuthnStatements().add(authnStatement); assertion.getAttributeStatements().add(buildAttributeStatement(principal.getAttributes())); assertion.setID(UUID.randomUUID().toString()); assertion.setIssueInstant(new DateTime()); return assertion; } public static void signAssertion(SignableXMLObject signableXMLObject, Credential signingCredential) throws MarshallingException, SignatureException { Signature signature = buildSAMLObject(Signature.class, Signature.DEFAULT_ELEMENT_NAME); signature.setSigningCredential(signingCredential); signature.setSignatureAlgorithm(Configuration.getGlobalSecurityConfiguration().getSignatureAlgorithmURI(signingCredential)); signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); signableXMLObject.setSignature(signature); Configuration.getMarshallerFactory().getMarshaller(signableXMLObject).marshall(signableXMLObject); Signer.signObject(signature); } public static Optional<String> getStringValueFromXMLObject(XMLObject xmlObj) { if (xmlObj instanceof XSString) { return Optional.of(((XSString) xmlObj).getValue()); } else if (xmlObj instanceof XSAny) { XSAny xsAny = (XSAny) xmlObj; String textContent = xsAny.getTextContent(); if (StringUtils.hasText(textContent)) { return Optional.of(textContent); } List<XMLObject> unknownXMLObjects = xsAny.getUnknownXMLObjects(); if (!CollectionUtils.isEmpty(unknownXMLObjects)) { XMLObject xmlObject = unknownXMLObjects.get(0); if (xmlObject instanceof NameID) { NameID nameID = (NameID) xmlObject; return Optional.of(nameID.getValue()); } } } return Optional.empty(); } private static AuthnStatement buildAuthnStatement(DateTime authnInstant, String entityID) { AuthnContextClassRef authnContextClassRef = buildSAMLObject(AuthnContextClassRef.class, AuthnContextClassRef.DEFAULT_ELEMENT_NAME); authnContextClassRef.setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX); AuthenticatingAuthority authenticatingAuthority = buildSAMLObject(AuthenticatingAuthority.class, AuthenticatingAuthority.DEFAULT_ELEMENT_NAME); authenticatingAuthority.setURI(entityID); AuthnContext authnContext = buildSAMLObject(AuthnContext.class, AuthnContext.DEFAULT_ELEMENT_NAME); authnContext.setAuthnContextClassRef(authnContextClassRef); authnContext.getAuthenticatingAuthorities().add(authenticatingAuthority); AuthnStatement authnStatement = buildSAMLObject(AuthnStatement.class, AuthnStatement.DEFAULT_ELEMENT_NAME); authnStatement.setAuthnContext(authnContext); authnStatement.setAuthnInstant(authnInstant); return authnStatement; } private static AttributeStatement buildAttributeStatement(List<SAMLAttribute> attributes) { AttributeStatement attributeStatement = buildSAMLObject(AttributeStatement.class, AttributeStatement.DEFAULT_ELEMENT_NAME); attributes.forEach(entry -> attributeStatement.getAttributes().add( buildAttribute( entry.getName(), entry.getValues()))); return attributeStatement; } private static Attribute buildAttribute(String name, List<String> values) { XSStringBuilder stringBuilder = (XSStringBuilder) Configuration.getBuilderFactory().getBuilder(XSString.TYPE_NAME); Attribute attribute = buildSAMLObject(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME); attribute.setName(name); attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); List<XSString> xsStringList = values.stream().map(value -> { XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); stringValue.setValue(value); return stringValue; }).collect(toList()); attribute.getAttributeValues().addAll(xsStringList); return attribute; } }