Skip to content

Commit c28390b

Browse files
committed
#63: Reject SubjectConfirmationData with no Recipient or NotOnOrAfter
- these MUST be contained in a Response and validated according to the SAML profiles spec - extracted a method to validate SubjectConfirmation in SamlResponse - added a bit more specific error messages when validating SubjectConfirmationData fails - some test refactoring
1 parent 13cab92 commit c28390b

6 files changed

Lines changed: 311 additions & 274 deletions

File tree

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

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import javax.xml.xpath.XPathExpressionException;
1616
import javax.xml.xpath.XPathFactory;
1717

18+
import org.apache.commons.lang3.StringUtils;
1819
import org.joda.time.DateTime;
1920
import org.slf4j.Logger;
2021
import org.slf4j.LoggerFactory;
@@ -242,55 +243,7 @@ public boolean isValid(String requestId) {
242243
}
243244
}
244245

245-
// Check SubjectConfirmation, at least one SubjectConfirmation must be valid
246-
boolean validSubjectConfirmation = false;
247-
NodeList subjectConfirmationNodes = this.queryAssertion("/saml:Subject/saml:SubjectConfirmation");
248-
for (int i = 0; i < subjectConfirmationNodes.getLength(); i++) {
249-
Node scn = subjectConfirmationNodes.item(i);
250-
251-
Node method = scn.getAttributes().getNamedItem("Method");
252-
if (method != null && !method.getNodeValue().equals(Constants.CM_BEARER)) {
253-
continue;
254-
}
255-
256-
NodeList subjectConfirmationDataNodes = scn.getChildNodes();
257-
for (int c = 0; c < subjectConfirmationDataNodes.getLength(); c++) {
258-
if (subjectConfirmationDataNodes.item(c).getLocalName() != null && subjectConfirmationDataNodes.item(c).getLocalName().equals("SubjectConfirmationData")) {
259-
260-
Node recipient = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("Recipient");
261-
if (recipient != null && !recipient.getNodeValue().isEmpty()
262-
&& !recipient.getNodeValue().equals(currentUrl)) {
263-
continue;
264-
}
265-
266-
Node inResponseTo = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("InResponseTo");
267-
if (inResponseTo != null && !inResponseTo.getNodeValue().equals(responseInResponseTo)) {
268-
continue;
269-
}
270-
271-
272-
Node notOnOrAfter = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("NotOnOrAfter");
273-
if (notOnOrAfter != null) {
274-
DateTime noa = Util.parseDateTime(notOnOrAfter.getNodeValue());
275-
if (noa.isEqualNow() || noa.isBeforeNow()) {
276-
continue;
277-
}
278-
}
279-
280-
Node notBefore = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("NotBefore");
281-
if (notBefore != null) {
282-
DateTime nb = Util.parseDateTime(notBefore.getNodeValue());
283-
if (nb.isAfterNow()) {
284-
continue;
285-
}
286-
}
287-
validSubjectConfirmation = true;
288-
}
289-
}
290-
}
291-
if (!validSubjectConfirmation) {
292-
throw new Exception("A valid SubjectConfirmation was not found on this Response");
293-
}
246+
validateSubjectConfirmation(responseInResponseTo);
294247

295248
if (settings.getWantAssertionsSigned() && !signedElements.contains("Assertion")) {
296249
throw new Exception("The Assertion of the Response is not signed and the SP requires it");
@@ -334,6 +287,72 @@ public boolean isValid(String requestId) {
334287
}
335288
}
336289

290+
// Check SubjectConfirmation, at least one SubjectConfirmation must be valid
291+
private void validateSubjectConfirmation(String responseInResponseTo) throws Exception {
292+
final List<String> subjectConfirmationDataIssues = new ArrayList<>();
293+
boolean validSubjectConfirmation = false;
294+
NodeList subjectConfirmationNodes = this.queryAssertion("/saml:Subject/saml:SubjectConfirmation");
295+
for (int i = 0; i < subjectConfirmationNodes.getLength(); i++) {
296+
Node scn = subjectConfirmationNodes.item(i);
297+
298+
Node method = scn.getAttributes().getNamedItem("Method");
299+
if (method != null && !method.getNodeValue().equals(Constants.CM_BEARER)) {
300+
continue;
301+
}
302+
303+
NodeList subjectConfirmationDataNodes = scn.getChildNodes();
304+
for (int c = 0; c < subjectConfirmationDataNodes.getLength(); c++) {
305+
if (subjectConfirmationDataNodes.item(c).getLocalName() != null && subjectConfirmationDataNodes.item(c).getLocalName().equals("SubjectConfirmationData")) {
306+
307+
Node recipient = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("Recipient");
308+
if (recipient == null) {
309+
subjectConfirmationDataIssues.add("SubjectConfirmationData doesn't contain a Recipient");
310+
continue;
311+
}
312+
313+
if (!recipient.getNodeValue().equals(currentUrl)) {
314+
subjectConfirmationDataIssues.add("SubjectConfirmationData doesn't match a valid Recipient");
315+
continue;
316+
}
317+
318+
Node inResponseTo = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("InResponseTo");
319+
if (inResponseTo != null && !inResponseTo.getNodeValue().equals(responseInResponseTo)) {
320+
subjectConfirmationDataIssues.add("SubjectConfirmationData has an invalid InResponseTo value");
321+
continue;
322+
}
323+
324+
325+
Node notOnOrAfter = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("NotOnOrAfter");
326+
if (notOnOrAfter == null) {
327+
subjectConfirmationDataIssues.add("SubjectConfirmationData doesn't contain a NotOnOrAfter attribute");
328+
continue;
329+
}
330+
331+
DateTime noa = Util.parseDateTime(notOnOrAfter.getNodeValue());
332+
if (noa.isEqualNow() || noa.isBeforeNow()) {
333+
subjectConfirmationDataIssues.add("SubjectConfirmationData is no longer valid");
334+
continue;
335+
}
336+
337+
Node notBefore = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("NotBefore");
338+
if (notBefore != null) {
339+
DateTime nb = Util.parseDateTime(notBefore.getNodeValue());
340+
if (nb.isAfterNow()) {
341+
subjectConfirmationDataIssues.add("SubjectConfirmationData is not yet valid");
342+
continue;
343+
}
344+
}
345+
validSubjectConfirmation = true;
346+
}
347+
}
348+
}
349+
if (!validSubjectConfirmation) {
350+
String subjectConfirmationDataIssuesMsg = subjectConfirmationDataIssues.isEmpty() ? "" :
351+
" - " + StringUtils.join(subjectConfirmationDataIssues, ", ");
352+
throw new Exception("A valid SubjectConfirmation was not found on this Response" + subjectConfirmationDataIssuesMsg);
353+
}
354+
}
355+
337356
/**
338357
* Determines if the SAML Response is valid using the certificate.
339358
*

0 commit comments

Comments
 (0)