oidc: Handle expired redis state data gracefully in auth_complete.
Some checks failed
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
Code scanning / CodeQL (push) Has been cancelled
Zulip production suite / Ubuntu 22.04 production build (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:bookworm, true, false, Debian 12 (Python 3.11, backend + documentation), bookworm) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:jammy, false, true, Ubuntu 22.04 (Python 3.10, backend + frontend), jammy) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:noble, false, false, Ubuntu 24.04 (Python 3.12, backend), noble) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:resolute, false, false, Ubuntu 26.04 (Python 3.14, backend), resolute) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:trixie, false, false, Debian 13 (Python 3.13, backend), trixie) (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm, --test-custom-db, Debian 12 production install with custom db name and user, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:jammy, , Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga, jammy) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble, , Ubuntu 24.04 production install, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:resolute, , Ubuntu 26.04 production install, resolute) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:trixie, , Debian 13 production install, trixie) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm-7.0, 7.0 Version Upgrade, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm-8.0, 8.0 Version Upgrade, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:jammy-6.0, 6.0 Version Upgrade, jammy) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble-10.0, 10.0 Version Upgrade, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble-9.0, 9.0 Version Upgrade, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:resolute-12.0, 12.0 Version Upgrade, resolute) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:trixie-11.0, 11.0 Version Upgrade, trixie) (push) Has been cancelled
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled

If a user takes longer than REDIS_EXPIRATION_SECONDS to return from
the IdP, the relayed_params data stored in redis at the start of the
authentication attempt has expired, and the old
`assert relayed_params is not None` crashed with a 500.

If the state data is expired, we now log at the info level and return
None, which redirects the user back to the login page to retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
PieterCK 2026-07-15 16:37:13 +07:00 committed by Mateusz Mandera
parent e2a54e5085
commit 7b3d58146f
2 changed files with 34 additions and 1 deletions

View File

@ -5375,6 +5375,31 @@ class GenericOpenIdConnectTest(SocialAuthBaseWithSyncAttrTest):
],
)
def test_social_auth_state_data_expired_in_redis(self) -> None:
account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
# Simulate the state data having expired from redis, like when
# a user takes longer than REDIS_EXPIRATION_SECONDS to return
# from the IdP.
with (
mock.patch.object(
GenericOpenIdConnectBackend, "get_data_from_redis", return_value=None
),
self.assertLogs(self.logger_string, level="INFO") as m,
):
result = self.social_auth_test(account_data_dict, subdomain="zulip")
self.assertEqual(result.status_code, 302)
self.assertEqual("/login/", result["Location"])
self.assertEqual(
m.output,
[
self.logger_output(
"State data expired in redis: authentication took too long to complete.",
"info",
)
],
)
@override
def test_social_auth_complete(self) -> None:
def mock_process_error(backend: BaseOAuth2, data: Mapping[str, object]) -> None:

View File

@ -4291,7 +4291,15 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth):
assert state
relayed_params = self.get_data_from_redis(state)
assert relayed_params is not None
if relayed_params is None:
# The relayed_params live in redis with a TTL of
# REDIS_EXPIRATION_SECONDS, so this means the user took
# too long to complete the authentication attempt.
self.logger.info(
"State data expired in redis: authentication took too long to complete."
)
return None
for param in self.standard_relay_params:
relayed_value = relayed_params.get(param)
session_value = self.strategy.session_get(param)