Skip to content

Commit 7dde7cf

Browse files
committed
Add Status Check
Closes gh-8955
1 parent 337d24e commit 7dde7cf

File tree

4 files changed

+71
-4
lines changed

4 files changed

+71
-4
lines changed

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,13 @@ public interface Saml2ErrorCodes {
3737
*/
3838
String MALFORMED_RESPONSE_DATA = "malformed_response_data";
3939

40+
/**
41+
* Response is invalid in a general way.
42+
*
43+
* @since 5.5
44+
*/
45+
String INVALID_RESPONSE = "invalid_response";
46+
4047
/**
4148
* Response destination does not match the request URL. A SAML 2 response object was
4249
* received at a URL that did not match the URL stored in the {code Destination}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -73,6 +73,7 @@
7373
import org.opensaml.saml.saml2.core.NameID;
7474
import org.opensaml.saml.saml2.core.OneTimeUse;
7575
import org.opensaml.saml.saml2.core.Response;
76+
import org.opensaml.saml.saml2.core.StatusCode;
7677
import org.opensaml.saml.saml2.core.SubjectConfirmation;
7778
import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
7879
import org.opensaml.saml.saml2.encryption.Decrypter;
@@ -615,6 +616,12 @@ private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResp
615616
Response response = responseToken.getResponse();
616617
Saml2AuthenticationToken token = responseToken.getToken();
617618
Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success();
619+
String statusCode = getStatusCode(response);
620+
if (!StatusCode.SUCCESS.equals(statusCode)) {
621+
String message = String.format("Invalid status [%s] for SAML response [%s]", statusCode,
622+
response.getID());
623+
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, message));
624+
}
618625
String issuer = response.getIssuer().getValue();
619626
String destination = response.getDestination();
620627
String location = token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation();
@@ -637,6 +644,16 @@ private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResp
637644
};
638645
}
639646

647+
private String getStatusCode(Response response) {
648+
if (response.getStatus() == null) {
649+
return StatusCode.SUCCESS;
650+
}
651+
if (response.getStatus().getStatusCode() == null) {
652+
return StatusCode.SUCCESS;
653+
}
654+
return response.getStatus().getStatusCode().getValue();
655+
}
656+
640657
private Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionSignatureValidator() {
641658
return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> {
642659
SignatureTrustEngine engine = this.signatureTrustEngineConverter.convert(assertionToken.token);

saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@
4848
import org.opensaml.saml.saml2.core.NameID;
4949
import org.opensaml.saml.saml2.core.OneTimeUse;
5050
import org.opensaml.saml.saml2.core.Response;
51+
import org.opensaml.saml.saml2.core.StatusCode;
5152
import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder;
5253
import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder;
5354
import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
@@ -557,6 +558,25 @@ public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertio
557558
assertThat(authentication.getName()).isEqualTo("decrypted name");
558559
}
559560

561+
@Test
562+
public void authenticateWhenResponseStatusIsNotSuccessThenFails() {
563+
Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(
564+
(r) -> r.setStatus(TestOpenSamlObjects.status(StatusCode.AUTHN_FAILED)));
565+
Saml2AuthenticationToken token = token(response, verifying(registration()));
566+
assertThatExceptionOfType(Saml2AuthenticationException.class)
567+
.isThrownBy(() -> this.provider.authenticate(token))
568+
.satisfies(errorOf(Saml2ErrorCodes.INVALID_RESPONSE));
569+
}
570+
571+
@Test
572+
public void authenticateWhenResponseStatusIsSuccessThenSucceeds() {
573+
Response response = TestOpenSamlObjects
574+
.signedResponseWithOneAssertion((r) -> r.setStatus(TestOpenSamlObjects.successStatus()));
575+
Saml2AuthenticationToken token = token(response, verifying(registration()));
576+
Authentication authentication = this.provider.authenticate(token);
577+
assertThat(authentication.getName()).isEqualTo("[email protected]");
578+
}
579+
560580
private <T extends XMLObject> T build(QName qName) {
561581
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
562582
}

saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import java.util.Base64;
2222
import java.util.List;
2323
import java.util.UUID;
24+
import java.util.function.Consumer;
2425

2526
import javax.crypto.SecretKey;
2627
import javax.crypto.spec.SecretKeySpec;
@@ -59,11 +60,15 @@
5960
import org.opensaml.saml.saml2.core.Issuer;
6061
import org.opensaml.saml.saml2.core.NameID;
6162
import org.opensaml.saml.saml2.core.Response;
63+
import org.opensaml.saml.saml2.core.Status;
64+
import org.opensaml.saml.saml2.core.StatusCode;
6265
import org.opensaml.saml.saml2.core.Subject;
6366
import org.opensaml.saml.saml2.core.SubjectConfirmation;
6467
import org.opensaml.saml.saml2.core.SubjectConfirmationData;
6568
import org.opensaml.saml.saml2.core.impl.AttributeBuilder;
6669
import org.opensaml.saml.saml2.core.impl.AttributeStatementBuilder;
70+
import org.opensaml.saml.saml2.core.impl.StatusBuilder;
71+
import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder;
6772
import org.opensaml.saml.saml2.encryption.Encrypter;
6873
import org.opensaml.security.SecurityException;
6974
import org.opensaml.security.credential.BasicCredential;
@@ -118,8 +123,14 @@ static Response response(String destination, String issuerEntityId) {
118123
}
119124

120125
static Response signedResponseWithOneAssertion() {
126+
return signedResponseWithOneAssertion((response) -> {
127+
});
128+
}
129+
130+
static Response signedResponseWithOneAssertion(Consumer<Response> responseConsumer) {
121131
Response response = response();
122132
response.getAssertions().add(assertion());
133+
responseConsumer.accept(response);
123134
return signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
124135
}
125136

@@ -338,6 +349,18 @@ static List<AttributeStatement> attributeStatements() {
338349
return attributeStatements;
339350
}
340351

352+
static Status successStatus() {
353+
return status(StatusCode.SUCCESS);
354+
}
355+
356+
static Status status(String code) {
357+
Status status = new StatusBuilder().buildObject();
358+
StatusCode statusCode = new StatusCodeBuilder().buildObject();
359+
statusCode.setValue(code);
360+
status.setStatusCode(statusCode);
361+
return status;
362+
}
363+
341364
static <T extends XMLObject> T build(QName qName) {
342365
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
343366
}

0 commit comments

Comments
 (0)