@@ -60,6 +60,7 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
6060 self .bindings = [BINDING_HTTP_REDIRECT , BINDING_HTTP_POST ]
6161 self .discosrv = config .get ("disco_srv" )
6262 self .encryption_keys = []
63+ self .outstanding_queries = {}
6364
6465 key_file_paths = None
6566 if 'encryption_keypairs' in self .config ['sp_config' ]: # prioritize explicit encryption keypairs
@@ -120,7 +121,7 @@ def authn_request(self, context, entity_id):
120121 :type entity_id: str
121122 :rtype: satosa.response.Response
122123
123- :param context: The curretn context
124+ :param context: The current context
124125 :param entity_id: Target IDP entity id
125126 :return: response to the user agent
126127 """
@@ -139,6 +140,13 @@ def authn_request(self, context, entity_id):
139140 exc_info = True )
140141 raise SATOSAAuthenticationError (context .state , "Failed to construct the AuthnRequest" ) from exc
141142
143+ if self .sp .config .getattr ('allow_unsolicited' , 'sp' ) is False :
144+ if req_id in self .outstanding_queries :
145+ errmsg = "Request with duplicate id {}" .format (req_id )
146+ satosa_logging (logger , logging .DEBUG , errmsg , context .state )
147+ raise SATOSAAuthenticationError (context .state , errmsg )
148+ self .outstanding_queries [req_id ] = req
149+
142150 context .state [self .name ] = {"relay_state" : relay_state }
143151 return make_saml_response (binding , ht_args )
144152
@@ -158,12 +166,22 @@ def authn_response(self, context, binding):
158166 raise SATOSAAuthenticationError (context .state , "Missing Response" )
159167
160168 try :
161- authn_response = self .sp .parse_authn_request_response (context .request ["SAMLResponse" ], binding )
169+ authn_response = self .sp .parse_authn_request_response (
170+ context .request ["SAMLResponse" ],
171+ binding , outstanding = self .outstanding_queries )
162172 except Exception as err :
163173 satosa_logging (logger , logging .DEBUG , "Failed to parse authn request for state" , context .state ,
164174 exc_info = True )
165175 raise SATOSAAuthenticationError (context .state , "Failed to parse authn request" ) from err
166176
177+ if self .sp .config .getattr ('allow_unsolicited' , 'sp' ) is False :
178+ req_id = authn_response .in_response_to
179+ if req_id not in self .outstanding_queries :
180+ errmsg = "No request with id: {}" .format (req_id ),
181+ satosa_logging (logger , logging .DEBUG , errmsg , context .state )
182+ raise SATOSAAuthenticationError (context .state , errmsg )
183+ del self .outstanding_queries [req_id ]
184+
167185 # check if the relay_state matches the cookie state
168186 if context .state [self .name ]["relay_state" ] != context .request ["RelayState" ]:
169187 satosa_logging (logger , logging .DEBUG ,
@@ -218,7 +236,7 @@ def _translate_response(self, response, state):
218236
219237 internal_resp .user_id = response .get_subject ().text
220238 internal_resp .attributes = self .converter .to_internal (self .attribute_profile , response .ava )
221-
239+
222240 # The SAML response may not include a NameID
223241 try :
224242 internal_resp .name_id = response .assertion .subject .name_id
@@ -324,7 +342,7 @@ def get_metadata_desc(self):
324342
325343class SAMLInternalResponse (InternalResponse ):
326344 """
327- Like the parent InternalResponse, holds internal representation of
345+ Like the parent InternalResponse, holds internal representation of
328346 service related data, but includes additional details relevant to
329347 SAML interoperability.
330348
0 commit comments