Skip to content

Commit 7692f56

Browse files
committed
Split Response and Assertion Issuer retrieval
The SamlResponse.getIssuers() contract is quite controversial. For a valid response, it will always return just one element. For an invalid response, depending on the cause it may: - fail if no Assertion is present: this means, in particular, that if the status code is not a success one, it's impossible to retrieve the Response issuer with this method (although it may be a reasonable requirement, for logging purposes for instance) - fail if multiple Assertions are present: again, the Response Issuer cannot be retrieved in this case either - fail in the unlikely event that multiple Response Issuers were found - return up to 2 issuers at most, if different issuers were set on the Response and on the Assertion (which will make isValid() fail), with the inability to determine which is the Response Issuer and which is the Assertion Issuer (indeed: the former will be the first one in the list, the latter will be the second, but this contract is a bit weak) For these reasons, two different methods were provided to retrieve the Response and the Assertion Issuers, with the former that will succeed even when the status code is not a successful one. Also, because of the above reasons, the getIssuers() method was deprecated in favour of the two new ones.
1 parent 119f7fe commit 7692f56

File tree

2 files changed

+125
-27
lines changed

2 files changed

+125
-27
lines changed

core/src/main/java/com/onelogin/saml2/authn/SamlResponse.java

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -706,38 +706,89 @@ public List<String> getAudiences() throws XPathExpressionException {
706706
}
707707

708708
/**
709-
* Gets the Issuers (from Response and Assertion).
709+
* Gets the Response Issuer.
710710
*
711-
* @return the issuers of the assertion/response
711+
* @return the Response Issuer, or <code>null</code> if not specified
712712
*
713713
* @throws XPathExpressionException
714714
* @throws ValidationError
715+
* if multiple Response issuers were found
716+
* @see #getAssertionIssuer()
717+
* @see #getIssuers()
715718
*/
716-
public List<String> getIssuers() throws XPathExpressionException, ValidationError {
717-
List<String> issuers = new ArrayList<String>();
718-
String value;
719+
public String getResponseIssuer() throws XPathExpressionException, ValidationError {
719720
NodeList responseIssuer = Util.query(samlResponseDocument, "/samlp:Response/saml:Issuer");
720721
if (responseIssuer.getLength() > 0) {
721722
if (responseIssuer.getLength() == 1) {
722-
value = responseIssuer.item(0).getTextContent();
723-
if (!issuers.contains(value)) {
724-
issuers.add(value);
725-
}
723+
return responseIssuer.item(0).getTextContent();
726724
} else {
727725
throw new ValidationError("Issuer of the Response is multiple.", ValidationError.ISSUER_MULTIPLE_IN_RESPONSE);
728726
}
729727
}
730-
728+
return null;
729+
}
730+
731+
/**
732+
* Gets the Assertion Issuer.
733+
*
734+
* @return the Assertion Issuer
735+
*
736+
* @throws XPathExpressionException
737+
* @throws ValidationError
738+
* if no Assertion Issuer could be found, or if multiple Assertion
739+
* issuers were found
740+
* @see #getResponseIssuer()
741+
* @see #getIssuers()
742+
*/
743+
public String getAssertionIssuer() throws XPathExpressionException, ValidationError {
731744
NodeList assertionIssuer = this.queryAssertion("/saml:Issuer");
732745
if (assertionIssuer.getLength() == 1) {
733-
value = assertionIssuer.item(0).getTextContent();
734-
if (!issuers.contains(value)) {
735-
issuers.add(value);
736-
}
746+
return assertionIssuer.item(0).getTextContent();
737747
} else {
738748
throw new ValidationError("Issuer of the Assertion not found or multiple.", ValidationError.ISSUER_NOT_FOUND_IN_ASSERTION);
739749
}
740-
750+
}
751+
752+
/**
753+
* Gets the Issuers (from Response and Assertion). If the same issuer appears
754+
* both in the Response and in the Assertion (as it should), the returned list
755+
* will contain it just once. Hence, the returned list should always return one
756+
* element and in particular:
757+
* <ul>
758+
* <li>it will never contain zero elements (it means an Assertion Issuer could
759+
* not be found, hence a {@link ValidationError} will be thrown instead)
760+
* <li>if it contains more than one element, it means that the response is
761+
* invalid and one of the returned issuers won't pass the check performed by
762+
* {@link #isValid(String)} (which requires both issuers to be equal to the
763+
* Identity Provider entity id)
764+
* </ul>
765+
* <p>
766+
* Warning: as a consequence of the above, if this response status code is not a
767+
* successful one, this method will throw a {@link ValidationError} because it
768+
* won't find any Assertion Issuer. In this case, if you need to retrieve the
769+
* Response Issuer any way, you must use {@link #getResponseIssuer()} instead.
770+
*
771+
* @return the issuers of the assertion/response
772+
*
773+
* @throws XPathExpressionException
774+
* @throws ValidationError
775+
* if multiple Response Issuers or multiple Assertion Issuers were
776+
* found, or if no Assertion Issuer could be found
777+
* @see #getResponseIssuer()
778+
* @see #getAssertionIssuer()
779+
* @deprecated use {@link #getResponseIssuer()} and/or
780+
* {@link #getAssertionIssuer()}; the contract of this method is
781+
* quite controversial
782+
*/
783+
@Deprecated
784+
public List<String> getIssuers() throws XPathExpressionException, ValidationError {
785+
List<String> issuers = new ArrayList<String>();
786+
String responseIssuer = getResponseIssuer();
787+
if(responseIssuer != null)
788+
issuers.add(responseIssuer);
789+
String assertionIssuer = getAssertionIssuer();
790+
if(!issuers.contains(assertionIssuer))
791+
issuers.add(assertionIssuer);
741792
return issuers;
742793
}
743794

core/src/test/java/com/onelogin/saml2/test/authn/AuthnResponseTest.java

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ public void testGetAudiences() throws IOException, Error, XPathExpressionExcepti
822822
}
823823

824824
/**
825-
* Tests the getIssuers method of SamlResponse
825+
* Tests the getIssuers methods of SamlResponse
826826
*
827827
* @throws Error
828828
* @throws IOException
@@ -837,46 +837,61 @@ public void testGetAudiences() throws IOException, Error, XPathExpressionExcepti
837837
@Test
838838
public void testGetIssuers() throws IOException, Error, XPathExpressionException, ParserConfigurationException, SAXException, SettingsException, ValidationError {
839839
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
840-
String samlResponseEncoded = Util.getFileAsString("data/responses/response1.xml.base64");
840+
String samlResponseEncoded = Util.getFileAsString("data/responses/valid_encrypted_assertion.xml.base64");
841841
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
842+
String expectedIssuer = "http://idp.example.com/";
842843
List<String> expectedIssuers = new ArrayList<String>();
843-
expectedIssuers.add("http://idp.example.com/");
844-
samlResponseEncoded = Util.getFileAsString("data/responses/valid_encrypted_assertion.xml.base64");
845-
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
844+
expectedIssuers.add(expectedIssuer);
845+
assertEquals(expectedIssuer, samlResponse.getResponseIssuer());
846+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
846847
assertEquals(expectedIssuers, samlResponse.getIssuers());
847848

848849
expectedIssuers.remove(0);
849-
expectedIssuers.add("https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php");
850+
expectedIssuer = "https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php";
851+
expectedIssuers.add(expectedIssuer);
850852

851853
samlResponseEncoded = Util.getFileAsString("data/responses/signed_message_encrypted_assertion.xml.base64");
852854
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
855+
assertEquals(expectedIssuer, samlResponse.getResponseIssuer());
856+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
853857
assertEquals(expectedIssuers, samlResponse.getIssuers());
854858

855859
samlResponseEncoded = Util.getFileAsString("data/responses/double_signed_encrypted_assertion.xml.base64");
856860
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
861+
assertEquals(expectedIssuer, samlResponse.getResponseIssuer());
862+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
857863
assertEquals(expectedIssuers, samlResponse.getIssuers());
858864

859865
samlResponseEncoded = Util.getFileAsString("data/responses/signed_encrypted_assertion.xml.base64");
860866
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
867+
assertEquals(expectedIssuer, samlResponse.getResponseIssuer());
868+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
861869
assertEquals(expectedIssuers, samlResponse.getIssuers());
862870

863871
samlResponseEncoded = Util.getFileAsString("data/responses/double_signed_response.xml.base64");
864872
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
873+
assertEquals(expectedIssuer, samlResponse.getResponseIssuer());
874+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
865875
assertEquals(expectedIssuers, samlResponse.getIssuers());
866876

867877
samlResponseEncoded = Util.getFileAsString("data/responses/signed_assertion_response.xml.base64");
868878
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
879+
assertEquals(expectedIssuer, samlResponse.getResponseIssuer());
880+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
869881
assertEquals(expectedIssuers, samlResponse.getIssuers());
870882

883+
expectedIssuer = "https://app.onelogin.com/saml/metadata/13590";
871884
expectedIssuers = new ArrayList<String>();
872-
expectedIssuers.add("https://app.onelogin.com/saml/metadata/13590");
885+
expectedIssuers.add(expectedIssuer);
873886
samlResponseEncoded = Util.getFileAsString("data/responses/invalids/no_issuer_response.xml.base64");
874887
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
888+
assertNull(expectedIssuer, samlResponse.getResponseIssuer());
889+
assertEquals(expectedIssuer, samlResponse.getAssertionIssuer());
875890
assertEquals(expectedIssuers, samlResponse.getIssuers());
876891
}
877892

878893
/**
879-
* Tests the getIssuers method of SamlResponse
894+
* Tests the getIssuers methods of SamlResponse
880895
* <p>
881896
* Case: different issuers for response and assertion
882897
*
@@ -896,13 +911,44 @@ public void testGetIssuersDifferentIssuers() throws IOException, Error, XPathExp
896911
String samlResponseEncoded = Util.getFileAsString("data/responses/invalids/different_issuers.xml.base64");
897912
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
898913
List<String> expectedIssuers = new ArrayList<String>();
899-
expectedIssuers.add("https://response-issuer.com");
900-
expectedIssuers.add("https://assertion-issuer.com");
914+
String expectedResponseIssuer = "https://response-issuer.com";
915+
String expectedAssertionIssuer = "https://assertion-issuer.com";
916+
expectedIssuers.add(expectedResponseIssuer);
917+
expectedIssuers.add(expectedAssertionIssuer);
918+
assertEquals(expectedResponseIssuer, samlResponse.getResponseIssuer());
919+
assertEquals(expectedAssertionIssuer, samlResponse.getAssertionIssuer());
901920
assertEquals(expectedIssuers, samlResponse.getIssuers());
902921
}
903922

904923
/**
905-
* Tests the getIssuers method of SamlResponse
924+
* Tests the getAssertionIssuer method of SamlResponse
925+
* <p>
926+
* Case: Issuer of the assertion not found
927+
*
928+
* @throws Error
929+
* @throws IOException
930+
* @throws ValidationError
931+
* @throws SettingsException
932+
* @throws SAXException
933+
* @throws ParserConfigurationException
934+
* @throws XPathExpressionException
935+
*
936+
* @see com.onelogin.saml2.authn.SamlResponse#getIssuers
937+
*/
938+
@Test
939+
public void testGetAssertionIssuerNoInAssertion() throws IOException, Error, XPathExpressionException, ParserConfigurationException, SAXException, SettingsException, ValidationError {
940+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
941+
String samlResponseEncoded = Util.getFileAsString("data/responses/invalids/no_issuer_assertion.xml.base64");
942+
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
943+
944+
expectedEx.expect(ValidationError.class);
945+
expectedEx.expectMessage("Issuer of the Assertion not found or multiple.");
946+
samlResponse.getAssertionIssuer();
947+
}
948+
949+
/**
950+
* Tests the getIssuers methods of SamlResponse
951+
* <p>
906952
* Case: Issuer of the assertion not found
907953
*
908954
* @throws Error
@@ -921,11 +967,12 @@ public void testGetIssuersNoInAssertion() throws IOException, Error, XPathExpres
921967
String samlResponseEncoded = Util.getFileAsString("data/responses/invalids/no_issuer_assertion.xml.base64");
922968
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
923969

970+
samlResponse.getResponseIssuer(); // this should not fail
924971
expectedEx.expect(ValidationError.class);
925972
expectedEx.expectMessage("Issuer of the Assertion not found or multiple.");
926973
samlResponse.getIssuers();
927974
}
928-
975+
929976
/**
930977
* Tests the getSessionIndex method of SamlResponse
931978
*

0 commit comments

Comments
 (0)