@@ -260,6 +260,98 @@ def test_returns_partial_when_request_token_matches_session_id(self) -> None:
260260 self .assertIsNotNone (partial )
261261 backend .strategy .partial_load .assert_called_once_with ("session-token" )
262262
263+ def test_same_session_external_resume_requires_confirmation (self ) -> None :
264+ response = object ()
265+ backend = self ._backend (
266+ request_data = {
267+ "partial_token" : "session-token" ,
268+ "verification_code" : "123456" ,
269+ },
270+ partial_data = {PARTIAL_PIPELINE_ALLOW_EXTERNAL_RESUME : True },
271+ )
272+ backend .strategy .partial_pipeline_external_resume_confirmation .return_value = (
273+ response
274+ )
275+
276+ result = partial_pipeline_result (backend )
277+
278+ self .assertIsNone (result .partial )
279+ self .assertEqual (result .response , response )
280+ self .assertFalse (result .halt )
281+ backend .strategy .session_set .assert_any_call (
282+ PARTIAL_TOKEN_PENDING_SESSION_NAME , "session-token"
283+ )
284+ backend .strategy .session_set .assert_any_call (
285+ PARTIAL_TOKEN_PENDING_REQUEST_SESSION_NAME ,
286+ {"partial_token" : "session-token" , "verification_code" : "123456" },
287+ )
288+
289+ def test_same_session_external_resume_rejects_initial_confirmation (self ) -> None :
290+ response = object ()
291+ backend = self ._backend (
292+ request_data = {
293+ "partial_token" : "session-token" ,
294+ "partial_pipeline_confirm" : "1" ,
295+ "verification_code" : "123456" ,
296+ },
297+ partial_data = {PARTIAL_PIPELINE_ALLOW_EXTERNAL_RESUME : True },
298+ )
299+ backend .strategy .partial_pipeline_external_resume_confirmation .return_value = (
300+ response
301+ )
302+
303+ result = partial_pipeline_result (backend )
304+
305+ self .assertIsNone (result .partial )
306+ self .assertEqual (result .response , response )
307+ backend .strategy .partial_pipeline_external_resume_confirmed .assert_not_called ()
308+ backend .strategy .session_set .assert_any_call (
309+ PARTIAL_TOKEN_PENDING_SESSION_NAME , "session-token"
310+ )
311+ backend .strategy .session_set .assert_any_call (
312+ PARTIAL_TOKEN_PENDING_REQUEST_SESSION_NAME ,
313+ {
314+ "partial_token" : "session-token" ,
315+ "partial_pipeline_confirm" : "1" ,
316+ "verification_code" : "123456" ,
317+ },
318+ )
319+
320+ def test_same_session_external_resume_without_request_data_resumes (self ) -> None :
321+ backend = self ._backend (
322+ partial_data = {PARTIAL_PIPELINE_ALLOW_EXTERNAL_RESUME : True },
323+ )
324+
325+ result = partial_pipeline_result (backend )
326+
327+ self .assertIsNotNone (result .partial )
328+ backend .strategy .partial_pipeline_external_resume_confirmation .assert_not_called ()
329+
330+ def test_same_session_external_resume_without_request_token_requires_confirmation (
331+ self ,
332+ ) -> None :
333+ response = object ()
334+ backend = self ._backend (
335+ request_data = {"verification_code" : "123456" },
336+ partial_data = {PARTIAL_PIPELINE_ALLOW_EXTERNAL_RESUME : True },
337+ )
338+ backend .strategy .partial_pipeline_external_resume_confirmation .return_value = (
339+ response
340+ )
341+
342+ result = partial_pipeline_result (backend )
343+
344+ self .assertIsNone (result .partial )
345+ self .assertEqual (result .response , response )
346+ self .assertFalse (result .halt )
347+ backend .strategy .session_set .assert_any_call (
348+ PARTIAL_TOKEN_PENDING_SESSION_NAME , "session-token"
349+ )
350+ backend .strategy .session_set .assert_any_call (
351+ PARTIAL_TOKEN_PENDING_REQUEST_SESSION_NAME ,
352+ {"verification_code" : "123456" },
353+ )
354+
263355 def test_request_token_without_session_match_is_halted (self ) -> None :
264356 backend = self ._backend (
265357 request_data = {"partial_token" : "attacker-token" },
@@ -406,6 +498,33 @@ def test_confirmed_external_resume_uses_pending_request_data(self) -> None:
406498 result .partial .kwargs ["request" ]["partial_pipeline_confirm" ], "1"
407499 )
408500
501+ def test_confirmed_same_session_resume_uses_pending_request_data (self ) -> None :
502+ backend = self ._backend (
503+ request_data = {
504+ "partial_token" : "session-token" ,
505+ "partial_pipeline_confirm" : "1" ,
506+ },
507+ pending_resume = {
508+ "token" : "session-token" ,
509+ "request" : {
510+ "partial_token" : "session-token" ,
511+ "verification_code" : "123456" ,
512+ },
513+ },
514+ partial_data = {PARTIAL_PIPELINE_ALLOW_EXTERNAL_RESUME : True },
515+ )
516+
517+ result = partial_pipeline_result (backend , request = object ())
518+
519+ self .assertIsNotNone (result .partial )
520+ assert result .partial is not None
521+ self .assertEqual (
522+ result .partial .kwargs ["request" ]["partial_token" ], "session-token"
523+ )
524+ self .assertEqual (
525+ result .partial .kwargs ["request" ]["verification_code" ], "123456"
526+ )
527+
409528 def test_clean_pipeline_when_uid_does_not_match (self ) -> None :
410529 backend = self ._backend ({"uid" : "foo@example.com" })
411530 backend .strategy .request_data .return_value = {backend .ID_KEY : "bar@example.com" }
0 commit comments