Skip to content

Commit b323f6c

Browse files
committed
fix: skip hinted login if pipeline already running
1 parent 15db36a commit b323f6c

File tree

2 files changed

+33
-14
lines changed

2 files changed

+33
-14
lines changed

openedx/core/djangoapps/user_authn/views/login_form.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ def login_and_registration_form(request, initial_mode="login"):
161161
# Retrieve the form descriptions from the user API
162162
form_descriptions = _get_form_descriptions(request)
163163

164+
# Detect a running TPA pipeline early so it can guard against redirect loops below.
165+
saml_provider = False
166+
running_pipeline = pipeline.get(request)
167+
if running_pipeline:
168+
saml_provider, __ = third_party_auth.utils.is_saml_provider(
169+
running_pipeline.get('backend'), running_pipeline.get('kwargs')
170+
)
171+
164172
# Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check.
165173
# If present, we display a login page focused on third-party auth with that provider.
166174
third_party_auth_hint = None
@@ -172,9 +180,10 @@ def login_and_registration_form(request, initial_mode="login"):
172180
provider_id = next_args['tpa_hint'][0]
173181
tpa_hint_provider = third_party_auth.provider.Registry.get(provider_id=provider_id)
174182
if tpa_hint_provider:
175-
if tpa_hint_provider.skip_hinted_login_dialog:
183+
if tpa_hint_provider.skip_hinted_login_dialog and not running_pipeline:
176184
# Forward the user directly to the provider's login URL when the provider is configured
177-
# to skip the dialog.
185+
# to skip the dialog. Do not redirect if a TPA pipeline is already running, as that
186+
# would cause an infinite loop (e.g. new SAML users dispatched back to /login).
178187
if initial_mode == "register":
179188
auth_entry = pipeline.AUTH_ENTRY_REGISTER
180189
else:
@@ -194,18 +203,6 @@ def login_and_registration_form(request, initial_mode="login"):
194203
# tpa_hint_provider is not available
195204
# AND
196205
# user is not coming from a SAML IDP.
197-
saml_provider = False
198-
running_pipeline = pipeline.get(request)
199-
if running_pipeline:
200-
backend_name = running_pipeline.get('backend')
201-
if backend_name == 'tpa-saml':
202-
# Directly detect SAML backend to avoid registry lookup failures
203-
# (e.g. when pipeline kwargs lack response['idp_name'] at this point).
204-
saml_provider = True
205-
else:
206-
saml_provider, __ = third_party_auth.utils.is_saml_provider(
207-
backend_name, running_pipeline.get('kwargs')
208-
)
209206

210207
enterprise_customer = enterprise_customer_for_request(request)
211208

openedx/core/djangoapps/user_authn/views/tests/test_logistration.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,28 @@ def test_hinted_login_dialog_disabled(self, url_name, auth_entry):
418418
target_status_code=302
419419
)
420420

421+
@ddt.data(
422+
('signin_user', 'login'),
423+
('register_user', 'register'),
424+
)
425+
@ddt.unpack
426+
def test_hinted_login_dialog_disabled_with_running_pipeline(self, url_name, auth_entry): # pylint: disable=unused-argument
427+
"""
428+
Test that skip_hinted_login_dialog does NOT redirect to the provider when a TPA
429+
pipeline is already running. Without this guard, new SAML users dispatched back
430+
to /login or /register by ensure_user_information would be sent straight back to
431+
the IdP, creating an infinite redirect loop that eventually results in a 429 from
432+
the IdP (e.g. Auth0).
433+
"""
434+
self.google_provider.skip_hinted_login_dialog = True
435+
self.google_provider.save()
436+
params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
437+
pipeline_target = "openedx.core.djangoapps.user_authn.views.login_form.third_party_auth.pipeline"
438+
with simulate_running_pipeline(pipeline_target, "tpa-saml"):
439+
response = self.client.get(reverse(url_name), params, HTTP_ACCEPT="text/html")
440+
# The form should be rendered (200), not a redirect to the provider's auth URL.
441+
self.assertEqual(response.status_code, 200)
442+
421443
@override_settings(FEATURES=dict(settings.FEATURES, THIRD_PARTY_AUTH_HINT='oa2-google-oauth2'))
422444
@ddt.data(
423445
'signin_user',

0 commit comments

Comments
 (0)