37
37
import org .opensaml .core .config .ConfigurationService ;
38
38
import org .opensaml .core .xml .XMLObject ;
39
39
import org .opensaml .core .xml .config .XMLObjectProviderRegistry ;
40
+ import org .opensaml .core .xml .config .XMLObjectProviderRegistrySupport ;
40
41
import org .opensaml .core .xml .schema .XSAny ;
41
42
import org .opensaml .core .xml .schema .XSBoolean ;
42
43
import org .opensaml .core .xml .schema .XSBooleanValue ;
57
58
import org .opensaml .saml .saml2 .core .Assertion ;
58
59
import org .opensaml .saml .saml2 .core .Attribute ;
59
60
import org .opensaml .saml .saml2 .core .AttributeStatement ;
61
+ import org .opensaml .saml .saml2 .core .AuthnRequest ;
60
62
import org .opensaml .saml .saml2 .core .AuthnStatement ;
61
63
import org .opensaml .saml .saml2 .core .Condition ;
62
64
import org .opensaml .saml .saml2 .core .EncryptedAssertion ;
63
65
import org .opensaml .saml .saml2 .core .OneTimeUse ;
64
66
import org .opensaml .saml .saml2 .core .Response ;
65
67
import org .opensaml .saml .saml2 .core .StatusCode ;
68
+ import org .opensaml .saml .saml2 .core .Subject ;
66
69
import org .opensaml .saml .saml2 .core .SubjectConfirmation ;
70
+ import org .opensaml .saml .saml2 .core .SubjectConfirmationData ;
71
+ import org .opensaml .saml .saml2 .core .impl .AuthnRequestUnmarshaller ;
67
72
import org .opensaml .saml .saml2 .core .impl .ResponseUnmarshaller ;
68
73
import org .opensaml .saml .saml2 .encryption .Decrypter ;
69
74
import org .opensaml .saml .security .impl .SAMLSignatureProfileValidator ;
85
90
import org .springframework .security .saml2 .core .Saml2ErrorCodes ;
86
91
import org .springframework .security .saml2 .core .Saml2ResponseValidatorResult ;
87
92
import org .springframework .security .saml2 .provider .service .registration .RelyingPartyRegistration ;
93
+ import org .springframework .security .saml2 .provider .service .registration .Saml2MessageBinding ;
88
94
import org .springframework .util .Assert ;
89
95
import org .springframework .util .CollectionUtils ;
90
96
import org .springframework .util .StringUtils ;
@@ -349,6 +355,37 @@ public void setResponseAuthenticationConverter(
349
355
this .responseAuthenticationConverter = responseAuthenticationConverter ;
350
356
}
351
357
358
+ private static Saml2ResponseValidatorResult validateInResponseTo (AbstractSaml2AuthenticationRequest storedRequest ,
359
+ String inResponseTo ) {
360
+ if (!StringUtils .hasText (inResponseTo )) {
361
+ return Saml2ResponseValidatorResult .success ();
362
+ }
363
+ AuthnRequest request ;
364
+ try {
365
+ request = parseRequest (storedRequest );
366
+ }
367
+ catch (Exception ex ) {
368
+ String message = "The stored AuthNRequest could not be properly deserialized [" + ex .getMessage () + "]" ;
369
+ return Saml2ResponseValidatorResult
370
+ .failure (new Saml2Error (Saml2ErrorCodes .MALFORMED_REQUEST_DATA , message ));
371
+ }
372
+ if (request == null ) {
373
+ String message = "The response contained an InResponseTo attribute [" + inResponseTo + "]"
374
+ + " but no saved AuthNRequest request was found" ;
375
+ return Saml2ResponseValidatorResult
376
+ .failure (new Saml2Error (Saml2ErrorCodes .INVALID_IN_RESPONSE_TO , message ));
377
+ }
378
+ else if (!request .getID ().equals (inResponseTo )) {
379
+ String message = "The InResponseTo attribute [" + inResponseTo + "] does not match the ID of the "
380
+ + "AuthNRequest [" + request .getID () + "]" ;
381
+ return Saml2ResponseValidatorResult
382
+ .failure (new Saml2Error (Saml2ErrorCodes .INVALID_IN_RESPONSE_TO , message ));
383
+ }
384
+ else {
385
+ return Saml2ResponseValidatorResult .success ();
386
+ }
387
+ }
388
+
352
389
/**
353
390
* Construct a default strategy for validating the SAML 2.0 Response
354
391
* @return the default response validator strategy
@@ -365,6 +402,10 @@ public static Converter<ResponseToken, Saml2ResponseValidatorResult> createDefau
365
402
response .getID ());
366
403
result = result .concat (new Saml2Error (Saml2ErrorCodes .INVALID_RESPONSE , message ));
367
404
}
405
+
406
+ String inResponseTo = response .getInResponseTo ();
407
+ result = result .concat (validateInResponseTo (token .getAuthenticationRequest (), inResponseTo ));
408
+
368
409
String issuer = response .getIssuer ().getValue ();
369
410
String destination = response .getDestination ();
370
411
String location = token .getRelyingPartyRegistration ().getAssertionConsumerServiceLocation ();
@@ -447,7 +488,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
447
488
try {
448
489
Saml2AuthenticationToken token = (Saml2AuthenticationToken ) authentication ;
449
490
String serializedResponse = token .getSaml2Response ();
450
- Response response = parse (serializedResponse );
491
+ Response response = parseResponse (serializedResponse );
451
492
process (token , response );
452
493
AbstractAuthenticationToken authenticationResponse = this .responseAuthenticationConverter
453
494
.convert (new ResponseToken (response , token ));
@@ -469,7 +510,7 @@ public boolean supports(Class<?> authentication) {
469
510
return authentication != null && Saml2AuthenticationToken .class .isAssignableFrom (authentication );
470
511
}
471
512
472
- private Response parse (String response ) throws Saml2Exception , Saml2AuthenticationException {
513
+ private Response parseResponse (String response ) throws Saml2Exception , Saml2AuthenticationException {
473
514
try {
474
515
Document document = this .parserPool
475
516
.parse (new ByteArrayInputStream (response .getBytes (StandardCharsets .UTF_8 )));
@@ -481,6 +522,28 @@ private Response parse(String response) throws Saml2Exception, Saml2Authenticati
481
522
}
482
523
}
483
524
525
+ private static AuthnRequest parseRequest (AbstractSaml2AuthenticationRequest request ) throws Exception {
526
+ if (request == null ) {
527
+ return null ;
528
+ }
529
+ String samlRequest = request .getSamlRequest ();
530
+ if (!StringUtils .hasText (samlRequest )) {
531
+ return null ;
532
+ }
533
+ if (request .getBinding () == Saml2MessageBinding .REDIRECT ) {
534
+ samlRequest = Saml2Utils .samlInflate (Saml2Utils .samlDecode (samlRequest ));
535
+ }
536
+ else {
537
+ samlRequest = new String (Saml2Utils .samlDecode (samlRequest ), StandardCharsets .UTF_8 );
538
+ }
539
+ Document document = XMLObjectProviderRegistrySupport .getParserPool ()
540
+ .parse (new ByteArrayInputStream (samlRequest .getBytes (StandardCharsets .UTF_8 )));
541
+ Element element = document .getDocumentElement ();
542
+ AuthnRequestUnmarshaller unmarshaller = (AuthnRequestUnmarshaller ) XMLObjectProviderRegistrySupport
543
+ .getUnmarshallerFactory ().getUnmarshaller (AuthnRequest .DEFAULT_ELEMENT_NAME );
544
+ return (AuthnRequest ) unmarshaller .unmarshall (element );
545
+ }
546
+
484
547
private void process (Saml2AuthenticationToken token , Response response ) {
485
548
String issuer = response .getIssuer ().getValue ();
486
549
this .logger .debug (LogMessage .format ("Processing SAML response from %s" , issuer ));
@@ -685,13 +748,41 @@ private static Converter<AssertionToken, Saml2ResponseValidatorResult> createAss
685
748
};
686
749
}
687
750
751
+ private static boolean assertionContainsInResponseTo (Assertion assertion ) {
752
+ Subject subject = (assertion != null ) ? assertion .getSubject () : null ;
753
+ List <SubjectConfirmation > confirmations = (subject != null ) ? subject .getSubjectConfirmations ()
754
+ : new ArrayList <>();
755
+ return confirmations .stream ().filter ((confirmation ) -> {
756
+ SubjectConfirmationData confirmationData = confirmation .getSubjectConfirmationData ();
757
+ return confirmationData != null && StringUtils .hasText (confirmationData .getInResponseTo ());
758
+ }).findFirst ().orElse (null ) != null ;
759
+ }
760
+
761
+ private static void addRequestIdToValidationContext (AbstractSaml2AuthenticationRequest storedRequest ,
762
+ Map <String , Object > context ) {
763
+ String requestId = null ;
764
+ try {
765
+ AuthnRequest request = parseRequest (storedRequest );
766
+ requestId = (request != null ) ? request .getID () : null ;
767
+ }
768
+ catch (Exception ex ) {
769
+ }
770
+ if (StringUtils .hasText (requestId )) {
771
+ context .put (SAML2AssertionValidationParameters .SC_VALID_IN_RESPONSE_TO , requestId );
772
+ }
773
+ }
774
+
688
775
private static ValidationContext createValidationContext (AssertionToken assertionToken ,
689
776
Consumer <Map <String , Object >> paramsConsumer ) {
690
777
RelyingPartyRegistration relyingPartyRegistration = assertionToken .token .getRelyingPartyRegistration ();
691
778
String audience = relyingPartyRegistration .getEntityId ();
692
779
String recipient = relyingPartyRegistration .getAssertionConsumerServiceLocation ();
693
780
String assertingPartyEntityId = relyingPartyRegistration .getAssertingPartyDetails ().getEntityId ();
694
781
Map <String , Object > params = new HashMap <>();
782
+ Assertion assertion = assertionToken .getAssertion ();
783
+ if (assertionContainsInResponseTo (assertion )) {
784
+ addRequestIdToValidationContext (assertionToken .token .getAuthenticationRequest (), params );
785
+ }
695
786
params .put (SAML2AssertionValidationParameters .COND_VALID_AUDIENCES , Collections .singleton (audience ));
696
787
params .put (SAML2AssertionValidationParameters .SC_VALID_RECIPIENTS , Collections .singleton (recipient ));
697
788
params .put (SAML2AssertionValidationParameters .VALID_ISSUERS , Collections .singleton (assertingPartyEntityId ));
@@ -733,13 +824,6 @@ protected ValidationResult validateAddress(SubjectConfirmation confirmation, Ass
733
824
// applications should validate their own addresses - gh-7514
734
825
return ValidationResult .VALID ;
735
826
}
736
-
737
- @ Override
738
- protected ValidationResult validateInResponseTo (SubjectConfirmation confirmation , Assertion assertion ,
739
- ValidationContext context , boolean required ) {
740
- // applications should validate their own in response to
741
- return ValidationResult .VALID ;
742
- }
743
827
});
744
828
}
745
829
0 commit comments