Skip to content

Commit

Permalink
Do not include assertion it the status is not OK
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Nov 10, 2023
1 parent 4e74d71 commit ee51c98
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 96 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.openconext</groupId>
<artifactId>saml-idp</artifactId>
<version>0.0.5-SNAPSHOT</version>
<version>0.0.6-SNAPSHOT</version>
<name>saml-idp</name>

<properties>
Expand Down
165 changes: 87 additions & 78 deletions src/main/java/saml/DefaultSAMLIdPService.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,17 @@ private void validateSignature(SignableSAMLObject target, Credential credential)
throw new SignatureException("Signature element not found.");
}
} else {

SignatureValidator.validate(signature, credential);
}
}

private SAMLServiceProvider getSAMLServiceProvider(String entityId) {
return this.serviceProviders
.computeIfAbsent(entityId, key -> this.resolveSigningCredential(this.configuration.getServiceProviders().stream()
.filter(samlServiceProvider -> samlServiceProvider.getEntityId().equals(entityId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown SP entity: " + entityId))));
.computeIfAbsent(entityId, key -> this.resolveSigningCredential(
this.configuration.getServiceProviders().stream()
.filter(samlServiceProvider -> samlServiceProvider.getEntityId().equals(entityId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown SP entity: " + entityId))));
}

@SneakyThrows
Expand Down Expand Up @@ -280,7 +280,14 @@ public void sendResponse(String spEntityID,

org.opensaml.saml.saml2.core.Status newStatus = buildSAMLObject(org.opensaml.saml.saml2.core.Status.class);
StatusCode statusCode = buildSAMLObject(StatusCode.class);
statusCode.setValue(status.getStatus());
if (status.equals(SAMLStatus.NO_AUTHN_CONTEXT)) {
statusCode.setValue("urn:oasis:names:tc:SAML:2.0:status:Responder");
StatusCode innerStatusCode = buildSAMLObject(StatusCode.class);
innerStatusCode.setValue(SAMLStatus.NO_AUTHN_CONTEXT.getStatus());
statusCode.setStatusCode(innerStatusCode);
} else {
statusCode.setValue(status.getStatus());
}
newStatus.setStatusCode(statusCode);
if (StringUtils.isNotEmpty(optionalMessage)) {
StatusMessage statusMessage = buildSAMLObject(StatusMessage.class);
Expand All @@ -296,79 +303,81 @@ public void sendResponse(String spEntityID,
}
response.setStatus(newStatus);

Assertion assertion = buildSAMLObject(Assertion.class);
// Can't re-use, because it is already the child of another XML Object
Issuer newIssuer = buildSAMLObject(Issuer.class);
newIssuer.setValue(idpEntityID);
newIssuer.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
assertion.setIssuer(newIssuer);
assertion.setID("A" + UUID.randomUUID());
assertion.setIssueInstant(now);
assertion.setVersion(SAMLVersion.VERSION_20);

Subject subject = buildSAMLObject(Subject.class);
NameID nameID = buildSAMLObject(NameID.class);
nameID.setValue(nameId);
nameID.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
subject.setNameID(nameID);

SubjectConfirmation subjectConfirmation = buildSAMLObject(SubjectConfirmation.class);
subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer");
SubjectConfirmationData subjectConfirmationData = buildSAMLObject(SubjectConfirmationData.class);
subjectConfirmationData.setInResponseTo(inResponseTo);
subjectConfirmationData.setNotOnOrAfter(notOnOrAfter);
subjectConfirmationData.setNotBefore(notBefore);
subjectConfirmationData.setRecipient(acsLocation);
subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData);
subject.getSubjectConfirmations().add(subjectConfirmation);
assertion.setSubject(subject);

Conditions conditions = buildSAMLObject(Conditions.class);
conditions.setNotBefore(notBefore);
conditions.setNotOnOrAfter(notOnOrAfter);
AudienceRestriction audienceRestriction = buildSAMLObject(AudienceRestriction.class);
Audience audience = buildSAMLObject(Audience.class);
audience.setURI(spEntityID);
audienceRestriction.getAudiences().add(audience);
conditions.getAudienceRestrictions().add(audienceRestriction);
assertion.setConditions(conditions);

AuthnStatement authnStatement = buildSAMLObject(AuthnStatement.class);
authnStatement.setAuthnInstant(now);
authnStatement.setSessionIndex("IDX" + UUID.randomUUID());
authnStatement.setSessionNotOnOrAfter(notOnOrAfter);

AuthnContext authnContext = buildSAMLObject(AuthnContext.class);
AuthnContextClassRef authnContextClassRef = buildSAMLObject(AuthnContextClassRef.class);
authnContextClassRef.setURI(authnContextClassRefValue);
authnContext.setAuthnContextClassRef(authnContextClassRef);

AuthenticatingAuthority authenticatingAuthority = buildSAMLObject(AuthenticatingAuthority.class);
authenticatingAuthority.setURI(idpEntityID);
authnContext.getAuthenticatingAuthorities().add(authenticatingAuthority);
authnStatement.setAuthnContext(authnContext);
assertion.getAuthnStatements().add(authnStatement);

AttributeStatement attributeStatement = buildSAMLObject(AttributeStatement.class);
List<Attribute> attributes = attributeStatement.getAttributes();
Map<String, List<SAMLAttribute>> groupedSAMLAttributes = samlAttributes.stream().collect(Collectors.groupingBy(SAMLAttribute::getName));
XSStringBuilder stringBuilder = (XSStringBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(XSString.TYPE_NAME);

groupedSAMLAttributes.forEach((name, values) -> {
Attribute attribute = buildSAMLObject(Attribute.class);
attribute.setName(name);
attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
attribute.getAttributeValues().addAll(values.stream().map(value -> {
XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
stringValue.setValue(value.getValue());
return stringValue;
}).collect(Collectors.toList()));
attributes.add(attribute);
});
assertion.getAttributeStatements().add(attributeStatement);
if (status.equals(SAMLStatus.SUCCESS)) {
Assertion assertion = buildSAMLObject(Assertion.class);
// Can't re-use, because it is already the child of another XML Object
Issuer newIssuer = buildSAMLObject(Issuer.class);
newIssuer.setValue(idpEntityID);
newIssuer.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
assertion.setIssuer(newIssuer);
assertion.setID("A" + UUID.randomUUID());
assertion.setIssueInstant(now);
assertion.setVersion(SAMLVersion.VERSION_20);

Subject subject = buildSAMLObject(Subject.class);
NameID nameID = buildSAMLObject(NameID.class);
nameID.setValue(nameId);
nameID.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
subject.setNameID(nameID);

SubjectConfirmation subjectConfirmation = buildSAMLObject(SubjectConfirmation.class);
subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer");
SubjectConfirmationData subjectConfirmationData = buildSAMLObject(SubjectConfirmationData.class);
subjectConfirmationData.setInResponseTo(inResponseTo);
subjectConfirmationData.setNotOnOrAfter(notOnOrAfter);
subjectConfirmationData.setNotBefore(notBefore);
subjectConfirmationData.setRecipient(acsLocation);
subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData);
subject.getSubjectConfirmations().add(subjectConfirmation);
assertion.setSubject(subject);

Conditions conditions = buildSAMLObject(Conditions.class);
conditions.setNotBefore(notBefore);
conditions.setNotOnOrAfter(notOnOrAfter);
AudienceRestriction audienceRestriction = buildSAMLObject(AudienceRestriction.class);
Audience audience = buildSAMLObject(Audience.class);
audience.setURI(spEntityID);
audienceRestriction.getAudiences().add(audience);
conditions.getAudienceRestrictions().add(audienceRestriction);
assertion.setConditions(conditions);

AuthnStatement authnStatement = buildSAMLObject(AuthnStatement.class);
authnStatement.setAuthnInstant(now);
authnStatement.setSessionIndex("IDX" + UUID.randomUUID());
authnStatement.setSessionNotOnOrAfter(notOnOrAfter);

AuthnContext authnContext = buildSAMLObject(AuthnContext.class);
AuthnContextClassRef authnContextClassRef = buildSAMLObject(AuthnContextClassRef.class);
authnContextClassRef.setURI(authnContextClassRefValue);
authnContext.setAuthnContextClassRef(authnContextClassRef);

AuthenticatingAuthority authenticatingAuthority = buildSAMLObject(AuthenticatingAuthority.class);
authenticatingAuthority.setURI(idpEntityID);
authnContext.getAuthenticatingAuthorities().add(authenticatingAuthority);
authnStatement.setAuthnContext(authnContext);
assertion.getAuthnStatements().add(authnStatement);

AttributeStatement attributeStatement = buildSAMLObject(AttributeStatement.class);
List<Attribute> attributes = attributeStatement.getAttributes();
Map<String, List<SAMLAttribute>> groupedSAMLAttributes = samlAttributes.stream().collect(Collectors.groupingBy(SAMLAttribute::getName));
XSStringBuilder stringBuilder = (XSStringBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(XSString.TYPE_NAME);

this.signObject(assertion, this.signingCredential);
response.getAssertions().add(assertion);
groupedSAMLAttributes.forEach((name, values) -> {
Attribute attribute = buildSAMLObject(Attribute.class);
attribute.setName(name);
attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
attribute.getAttributeValues().addAll(values.stream().map(value -> {
XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
stringValue.setValue(value.getValue());
return stringValue;
}).collect(Collectors.toList()));
attributes.add(attribute);
});
assertion.getAttributeStatements().add(attributeStatement);

this.signObject(assertion, this.signingCredential);
response.getAssertions().add(assertion);
}

this.signObject(response, this.signingCredential);

Expand Down
6 changes: 2 additions & 4 deletions src/main/java/saml/parser/OpenSamlVelocityEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ public OpenSamlVelocityEngine() {
velocityEngine.init();
}

public void process(Map<String, Object> model,
Writer out) {
VelocityContext context = new VelocityContext(model);
velocityEngine.mergeTemplate(templateId, UTF_8.name(), context, out);
public void process(Map<String, Object> model, Writer out) {
velocityEngine.mergeTemplate(templateId, UTF_8.name(), new VelocityContext(model), out);
}

}
54 changes: 44 additions & 10 deletions src/test/java/saml/DefaultSAMLIdPServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.schema.XSString;
import org.opensaml.core.xml.util.XMLObjectSupport;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.credential.impl.KeyStoreCredentialResolver;
Expand Down Expand Up @@ -72,6 +75,12 @@ class DefaultSAMLIdPServiceTest {
}
}

@BeforeEach
void beforeEach() {
SAMLConfiguration samlConfiguration = getSamlConfiguration(false);
samlIdPService = new DefaultSAMLIdPService(samlConfiguration);
}

private String getSPMetaData() {
SAMLConfiguration samlConfiguration = new SAMLConfiguration(
new SAMLIdentityProvider(
Expand All @@ -88,12 +97,6 @@ private String getSPMetaData() {
return tempSamlIdPService.serviceProviderMetaData(serviceProvider);
}

@BeforeEach
void beforeEach() {
SAMLConfiguration samlConfiguration = getSamlConfiguration(false);
samlIdPService = new DefaultSAMLIdPService(samlConfiguration);
}

private SAMLConfiguration getSamlConfiguration(boolean requiresSignedAuthnRequest) {
String metaData = getSPMetaData();
stubFor(get(urlPathMatching("/sp-metadata.xml")).willReturn(aResponse()
Expand Down Expand Up @@ -209,7 +212,7 @@ void sendResponse() {
"urn:specified",
SAMLStatus.SUCCESS,
"relayState😀",
"Ok",
null,
DefaultSAMLIdPService.authnContextClassRefPassword,
List.of(
new SAMLAttribute("group", "riders"),
Expand All @@ -230,9 +233,6 @@ void sendResponse() {
String statusCode = response.getStatus().getStatusCode().getValue();
assertEquals(statusCode, "urn:oasis:names:tc:SAML:2.0:status:Success");

assertEquals("Ok", response.getStatus().getStatusMessage().getValue());
assertEquals("Ok", ((XSString) response.getStatus().getStatusDetail().getUnknownXMLObjects().get(0)).getValue());

List<String> group = response
.getAssertions().get(0)
.getAttributeStatements().get(0)
Expand All @@ -254,6 +254,40 @@ void sendResponse() {
assertTrue(notOnOrAfter.isAfter(now));
}

@SneakyThrows
@Test
void sendResponseNoAuthnContext() {
String inResponseTo = UUID.randomUUID().toString();
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
samlIdPService.sendResponse(
spEntityId,
inResponseTo,
"urn:specified",
SAMLStatus.NO_AUTHN_CONTEXT,
null,
"Not Ok",
DefaultSAMLIdPService.authnContextClassRefPassword,
List.of(),
httpServletResponse
);
String html = httpServletResponse.getContentAsString();
Document document = Jsoup.parse(html);
String samlResponse = document.select("input[name=\"SAMLResponse\"]").first().attr("value");
//Convenient way to make simple assertions
Response response = samlIdPService.parseResponse(samlResponse);

StatusCode statusCode = response.getStatus().getStatusCode();
StatusCode innerStatusCode = statusCode.getStatusCode();
assertEquals("urn:oasis:names:tc:SAML:2.0:status:Responder", statusCode.getValue() );
assertEquals(SAMLStatus.NO_AUTHN_CONTEXT.getStatus(), innerStatusCode.getValue());

assertEquals("Not Ok", response.getStatus().getStatusMessage().getValue());
assertEquals("Not Ok", ((XSString) response.getStatus().getStatusDetail().getUnknownXMLObjects().get(0)).getValue());

List<Assertion> assertions = response.getAssertions();
assertTrue(assertions.isEmpty());
}

@Test
void metadata() {
String singleSignOnServiceURI = "https://single.sign.on";
Expand Down
3 changes: 0 additions & 3 deletions src/test/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
</encoder>
</appender>

<!--<logger name="mujina" level="DEBUG"/>-->
<!--<logger name="org.springframework.security" level="DEBUG"/>-->

<root level="WARN">
<appender-ref ref="STDOUT"/>
</root>
Expand Down

0 comments on commit ee51c98

Please sign in to comment.