Skip to content

[AAP-72137] [oauth2_provider] Add RP-initiated logout support#984

Open
BrennanPaciorek wants to merge 1 commit intoansible:develfrom
BrennanPaciorek:AAP-72137-RP-Logout
Open

[AAP-72137] [oauth2_provider] Add RP-initiated logout support#984
BrennanPaciorek wants to merge 1 commit intoansible:develfrom
BrennanPaciorek:AAP-72137-RP-Logout

Conversation

@BrennanPaciorek
Copy link
Copy Markdown
Contributor

@BrennanPaciorek BrennanPaciorek commented Apr 21, 2026

Description

This PR adds support for OIDC RP-initiated logout (end_session_endpoint) to enable proper single sign-out in SSO environments.

  • What is being changed? Added the /o/logout/ endpoint using RPInitiatedLogoutView from django-oauth-toolkit 2.3.0
  • Why is this change needed? To enable applications using AAP Gateway as an OIDC provider to properly terminate user sessions and revoke tokens on logout
  • How does this change address the issue? Implements the OIDC RP-Initiated Logout specification allowing relying parties to initiate logout at the provider level

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Configuration change
  • Test update

Self-Review Checklist

  • I have performed a self-review of my code
  • I have added relevant comments to complex code sections
  • I have updated documentation where needed
  • I have considered the security impact of these changes
  • I have considered performance implications
  • I have thought about error handling and edge cases
  • I have tested the changes in my local environment

Testing Instructions

Prerequisites

  • AAP Gateway with oauth2_provider enabled
  • OIDC_ENABLED=True in settings

Steps to Test

  1. Run the test suite: TOX_DOCKER_GATEWAY=0.0.0.0 tox -e py312-sqlite -- -k test_rp_initiated_logout -n0 --no-cov
  2. Verify all 15 logout tests pass
  3. Check URL pattern alignment: TOX_DOCKER_GATEWAY=0.0.0.0 tox -e py312-sqlite -- -k test_oidc_url_patterns_match_dot -n0 --no-cov
  4. Verify OIDC discovery includes logout endpoint: Access /o/.well-known/openid-configuration/ and confirm end_session_endpoint is present

Expected Results

  • All tests pass (15/15 for logout, 3/3 for URL patterns)
  • OIDC discovery document includes: "end_session_endpoint": "http://testserver/o/logout/"
  • Logout endpoint is accessible at /o/logout/ when OIDC is enabled
  • Returns 404 when OIDC or RP-initiated logout is disabled

Additional Context

Implementation Details

Files Changed:

  • ansible_base/oauth2_provider/urls.py - Added logout URL pattern
  • ansible_base/lib/dynamic_config/settings_logic.py - Added secure default configuration
  • test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py - Comprehensive test suite

Configuration Defaults:

  • OIDC_RP_INITIATED_LOGOUT_ENABLED: True - Feature enabled
  • OIDC_RP_INITIATED_LOGOUT_DELETE_TOKENS: True - Revokes tokens on logout
  • OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS: True - Validates redirect URIs against registered URLs
  • OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT: False - Allows silent logout
  • OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS: True - Accepts expired ID tokens for logout

Security Benefits:

  • Token revocation prevents reuse after logout
  • Strict redirect validation prevents open redirect attacks
  • Centralized session termination for SSO environments
  • Proper cleanup of authentication state

Test Coverage

15 comprehensive tests covering:

  • Endpoint accessibility and configuration
  • OIDC and RP-initiated logout feature flags
  • GET/POST request handling
  • Redirect URI validation
  • State parameter preservation
  • OIDC discovery endpoint advertisement
  • Configuration defaults validation
  • Various logout scenarios

References


Note

Medium Risk
Adds a new OIDC logout endpoint and enables token/session cleanup behavior by default, affecting authentication flows and redirect validation. Risk is moderate because misconfiguration could impact SSO logout behavior or introduce redirect/token handling regressions.

Overview
Adds OIDC RP-initiated logout support by wiring RPInitiatedLogoutView to a new /o/logout/ route and ensuring it appears in OIDC discovery as end_session_endpoint.

Introduces default OAUTH2_PROVIDER settings to enable RP-initiated logout, revoke tokens on logout, enforce strict post-logout redirect URI validation, and control prompting/expired-token handling. Adds a comprehensive test file covering endpoint availability, feature-flag gating, redirect/state handling, and default configuration.

Reviewed by Cursor Bugbot for commit 0508c86. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features
    • Added logout endpoint for OAuth2/OIDC applications enabling RP-initiated logout with configurable redirect URI validation, token deletion, state preservation, and authentication prompt handling. The endpoint supports both GET and POST requests, integrates with OpenID Connect discovery documentation, and provides comprehensive configuration defaults for secure and standards-compliant logout operations.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

This change adds RP-Initiated Logout functionality for OAuth2/OIDC providers by establishing default configuration settings, adding a new logout endpoint URL route, and introducing comprehensive test coverage for logout behavior across multiple scenarios.

Changes

Cohort / File(s) Summary
Configuration Defaults
ansible_base/lib/dynamic_config/settings_logic.py
Added default OIDC RP-Initiated Logout configuration keys to oauth2_provider settings when ansible_base.oauth2_provider is enabled, including RP-initiated logout enablement, token deletion, strict redirect URI enforcement, and token expiration policies.
Endpoint Routing
ansible_base/oauth2_provider/urls.py
Added new URL route pattern ^logout/$ mapping to RPInitiatedLogoutView with route name rp-initiated-logout for OAuth2/OIDC logout operations.
Test Suite
test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py
Comprehensive test module covering RP-initiated logout behavior with 15+ test cases, including endpoint reachability, OIDC/RP-logout dependency validation, request method handling (GET/POST), redirect URI validation with strict mode, state parameter preservation, OIDC discovery document integration, and configuration default assertions. Includes fixtures for OIDC-enabled settings and JWT ID token generation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding RP-initiated logout support to the oauth2_provider. It directly reflects the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

DVCS PR Check Results:

PR appears valid (JIRA key(s) found)

@BrennanPaciorek BrennanPaciorek marked this pull request as ready for review April 22, 2026 13:42
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 0508c86. Configure here.

# In production, this would be properly signed with RS256 or HS256
jwt_string = base64.urlsafe_b64encode(json.dumps(claims).encode()).decode()

return (id_token, jwt_string, app)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused fixture with misleading docstring and invalid JWT

Low Severity

The id_token_for_user fixture is defined but never used by any of the 15 tests in this file. It also has two latent bugs: the docstring says it returns a 2-tuple (id_token_object, jwt_string) but it actually returns a 3-tuple (id_token, jwt_string, app), and the jwt_string it constructs is just raw base64-encoded JSON, not a valid JWT (header.payload.signature format), so it would fail if ever passed as an id_token_hint. This unused fixture also pulls in several otherwise-unused imports (base64, json, datetime, timedelta, timezone, OAuth2IDToken).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0508c86. Configure here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py (1)

28-61: Unused fixture id_token_for_user.

This fixture is defined but not referenced by any test in the module. The "JWT" it produces is also not a real JWT — it is just base64(json) with no header or signature — so if a future test did use it, most logout validation paths would reject it. Either wire it into the tests that actually exercise id_token_hint validation (producing a properly signed token via the provider's key) or remove it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py` around
lines 28 - 61, The id_token_for_user fixture is unused and its jwt_string is not
a real signed JWT; either remove the fixture or update tests to use it and
produce a proper signed ID token. If you choose to keep it, replace the naive
base64(json) construction in id_token_for_user with a JWT signed by the test
OIDC provider (use the project's existing signing helper or the provider's
private key) and ensure tests that exercise id_token_hint validation reference
id_token_for_user (e.g., use its returned jwt_string when calling the
RP-initiated logout view). If you choose to remove it, delete the
id_token_for_user fixture and any related unused imports (OAuth2IDToken, base64,
json) to avoid dead code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ansible_base/lib/dynamic_config/settings_logic.py`:
- Around line 288-293: The default currently sets
OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS to True via
oauth2_provider.setdefault; change this default to False so expired
id_token_hint values are not accepted by default. Update the
oauth2_provider.setdefault call for
'OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS' to False, and consider adding a
brief inline comment near OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT and
OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS explaining integrators can opt-in
to accept expired tokens if desired.

In `@test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py`:
- Around line 176-202: The test_logout_with_state_parameter currently can pass
vacuously when no redirect occurs; update the test so it arranges valid
preconditions that force the view to redirect and then assert unconditionally
that the state round-trips. Specifically, in test_logout_with_state_parameter
ensure the oauth2_application (app) has the post_logout_redirect_uri registered
(matching redirect_uri), supply a valid id_token_hint for that client and
establish an authenticated session for the client fixture so the view will
return a 302 from client.post to
get_relative_url('oauth2_provider:rp-initiated-logout'), then parse
response['Location'] and assert params['state'][0] == state without gating the
assertion behind status checks.
- Around line 64-73: Replace permissive assertions like "response.status_code !=
404" with precise, behavior-oriented checks: keep a single existence test
(test_logout_endpoint_exists) that simply resolves the URL via
get_relative_url('oauth2_provider:rp-initiated-logout') and asserts a successful
resolution, then update each behavior test
(test_logout_with_post_logout_redirect_uri,
test_logout_without_prompt_when_configured, test_logout_with_client_id_only,
test_logout_without_parameters, test_logout_url_matches_spec) to assert exact
expected statuses and responses using client.get/client.post under
override_settings(OAUTH2_PROVIDER=oidc_enabled_settings): e.g., assert 200 and
presence of the logout form/html for unauthenticated GETs (check
response.content), assert 302 and validate the Location header for successful
POST redirects, and assert 400 for invalid parameter combinations; use
get_relative_url and the named endpoint to locate the route and adjust
assertions accordingly.
- Around line 148-173: The test
test_logout_with_invalid_redirect_uri_when_strict currently allows too-broad
outcomes; tighten it to explicitly assert that the invalid redirect is not
honored by checking response is not a 302 to the malicious URL and/or that a
rejection is returned: after posting with post_logout_redirect_uri =
invalid_redirect (and client_id = app.client_id), assert either
response.status_code == 400 (and response.content does not contain the malicious
host) or assert response.status_code != 302 or, if 302, that
response['Location'] does not equal invalid_redirect; update the assertions in
test_logout_with_invalid_redirect_uri_when_strict accordingly to fail if the app
redirects to the malicious site.

---

Nitpick comments:
In `@test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py`:
- Around line 28-61: The id_token_for_user fixture is unused and its jwt_string
is not a real signed JWT; either remove the fixture or update tests to use it
and produce a proper signed ID token. If you choose to keep it, replace the
naive base64(json) construction in id_token_for_user with a JWT signed by the
test OIDC provider (use the project's existing signing helper or the provider's
private key) and ensure tests that exercise id_token_hint validation reference
id_token_for_user (e.g., use its returned jwt_string when calling the
RP-initiated logout view). If you choose to remove it, delete the
id_token_for_user fixture and any related unused imports (OAuth2IDToken, base64,
json) to avoid dead code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ce865aa-4ef4-49bc-b56b-ce20d0ba562e

📥 Commits

Reviewing files that changed from the base of the PR and between 3dd499f and 0508c86.

📒 Files selected for processing (3)
  • ansible_base/lib/dynamic_config/settings_logic.py
  • ansible_base/oauth2_provider/urls.py
  • test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py

Comment on lines +288 to +293
# RP-Initiated Logout (OIDC) settings
oauth2_provider.setdefault('OIDC_RP_INITIATED_LOGOUT_ENABLED', True)
oauth2_provider.setdefault('OIDC_RP_INITIATED_LOGOUT_DELETE_TOKENS', True)
oauth2_provider.setdefault('OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS', True)
oauth2_provider.setdefault('OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT', False)
oauth2_provider.setdefault('OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS', True)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm django-oauth-toolkit 2.3.0 behavior for these two settings
pip download django-oauth-toolkit==2.3.0 --no-deps -d /tmp/dot 2>/dev/null
cd /tmp/dot && ls *.whl | head -1 | xargs -I{} unzip -o {} -d dot_src >/dev/null
rg -nP 'OIDC_RP_INITIATED_LOGOUT_(ACCEPT_EXPIRED_TOKENS|ALWAYS_PROMPT)' /tmp/dot/dot_src

Repository: ansible/django-ansible-base

Length of output: 1072


🏁 Script executed:

# Check the exact settings in the ansible_base file
cat -n ansible_base/lib/dynamic_config/settings_logic.py | sed -n '285,295p'

Repository: ansible/django-ansible-base

Length of output: 847


🏁 Script executed:

# Look for any PR description or related context files
fd -i 'readme|changelog|description' --type f | head -20

Repository: ansible/django-ansible-base

Length of output: 195


🏁 Script executed:

# Search for git commits related to these OIDC settings
git log --all --oneline --grep='OIDC\|RP-Initiated' | head -10
git log --all -p --follow -S 'OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS' -- ansible_base/lib/dynamic_config/settings_logic.py | head -100

Repository: ansible/django-ansible-base

Length of output: 4948


🏁 Script executed:

# Search for any documentation or comments about why this was set
rg -n 'OIDC_RP_INITIATED_LOGOUT.*ACCEPT_EXPIRED' -B 5 -A 5 ansible_base/

Repository: ansible/django-ansible-base

Length of output: 1402


🏁 Script executed:

# Look for any related issues or PR references in code comments
rg -n 'ACCEPT_EXPIRED_TOKENS|secure defaults' --type py | head -20

Repository: ansible/django-ansible-base

Length of output: 649


🏁 Script executed:

# Get the full commit message for AAP-72137
git log --format=%B -n 1 0508c86ca9c3e885113f539ecfddb0bca0c4da98

Repository: ansible/django-ansible-base

Length of output: 137


🏁 Script executed:

# Look at the test file to understand expected behavior and design rationale
head -100 test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py

Repository: ansible/django-ansible-base

Length of output: 3488


🏁 Script executed:

# Search for any related issues or documentation about this feature
rg -n 'AAP-72137|RP-initiated logout' --type md | head -20

Repository: ansible/django-ansible-base

Length of output: 53


Reconsider OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS=True as a default.

Enabling OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS by default means an expired id_token_hint will still be honored for RP-initiated logout, which relaxes integrity checks and tolerates replay of old tokens. The OIDC RP-Initiated Logout spec permits this, but most providers leave it off by default. The combination of OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT=False + ACCEPT_EXPIRED_TOKENS=True enables silent logout even with expired tokens, broadening the window for token reuse. Consider defaulting ACCEPT_EXPIRED_TOKENS to False and letting integrators opt in, which aligns with least-privilege principles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ansible_base/lib/dynamic_config/settings_logic.py` around lines 288 - 293,
The default currently sets OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS to
True via oauth2_provider.setdefault; change this default to False so expired
id_token_hint values are not accepted by default. Update the
oauth2_provider.setdefault call for
'OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS' to False, and consider adding a
brief inline comment near OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT and
OIDC_RP_INITIATED_LOGOUT_ACCEPT_EXPIRED_TOKENS explaining integrators can opt-in
to accept expired tokens if desired.

Comment on lines +64 to +73
@pytest.mark.django_db
def test_logout_endpoint_exists(client, oidc_enabled_settings):
"""
Test that the /o/logout/ endpoint is accessible when OIDC is enabled.
"""
with override_settings(OAUTH2_PROVIDER=oidc_enabled_settings):
url = get_relative_url('oauth2_provider:rp-initiated-logout')
response = client.get(url)
# The endpoint should exist (not 404)
assert response.status_code != 404
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

status_code != 404 is too permissive across most tests.

Many tests in this file only assert response.status_code != 404 (e.g., test_logout_endpoint_exists, test_logout_with_post_logout_redirect_uri, test_logout_without_prompt_when_configured, test_logout_with_client_id_only, test_logout_without_parameters, test_logout_url_matches_spec). That will pass on 500s and other regressions too, which undermines the value of the suite. Please tighten these to the specific status codes / response contents you actually expect (e.g., 200 with a form for unauthenticated GET, 302 with a parsed Location for valid POST, 400 for invalid params). Consolidating the "exists" checks into a single URL-resolution test and giving the behavior-oriented tests stronger assertions will catch real regressions instead of just route deletions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py` around
lines 64 - 73, Replace permissive assertions like "response.status_code != 404"
with precise, behavior-oriented checks: keep a single existence test
(test_logout_endpoint_exists) that simply resolves the URL via
get_relative_url('oauth2_provider:rp-initiated-logout') and asserts a successful
resolution, then update each behavior test
(test_logout_with_post_logout_redirect_uri,
test_logout_without_prompt_when_configured, test_logout_with_client_id_only,
test_logout_without_parameters, test_logout_url_matches_spec) to assert exact
expected statuses and responses using client.get/client.post under
override_settings(OAUTH2_PROVIDER=oidc_enabled_settings): e.g., assert 200 and
presence of the logout form/html for unauthenticated GETs (check
response.content), assert 302 and validate the Location header for successful
POST redirects, and assert 400 for invalid parameter combinations; use
get_relative_url and the named endpoint to locate the route and adjust
assertions accordingly.

Comment on lines +148 to +173
@pytest.mark.django_db
def test_logout_with_invalid_redirect_uri_when_strict(client, oidc_enabled_settings, oauth2_application):
"""
Test that logout rejects invalid redirect URIs when STRICT_REDIRECT_URIS is enabled.
"""
app = oauth2_application[0]
invalid_redirect = 'https://malicious-site.com/callback'

strict_settings = {
**oidc_enabled_settings,
'OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS': True,
}

with override_settings(OAUTH2_PROVIDER=strict_settings):
url = get_relative_url('oauth2_provider:rp-initiated-logout')
response = client.post(
url,
{
'post_logout_redirect_uri': invalid_redirect,
'client_id': app.client_id,
},
)

# Should reject the invalid redirect URI
# The response depends on implementation - could be error or no redirect
assert response.status_code in [200, 400]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

test_logout_with_invalid_redirect_uri_when_strict does not actually assert rejection.

Allowing status_code in [200, 400] plus no check on where (or whether) the redirect happens means this test passes even if the invalid redirect URI is silently honored (e.g., a 302 to malicious-site.com would fail, but a 200 rendering a logout form that then redirects anywhere would pass). To actually validate strict-redirect enforcement, assert either:

  • the response is not a 302 to invalid_redirect, or
  • the response status is specifically the rejection code DOT emits (typically 400 with an error body), and the body does not contain the malicious host.
Suggested tightening
-        # Should reject the invalid redirect URI
-        # The response depends on implementation - could be error or no redirect
-        assert response.status_code in [200, 400]
+        # Must not redirect the user to the unregistered URI
+        assert response.status_code != 302 or invalid_redirect not in response.get('Location', '')
+        # And the malicious host must not appear in the rendered response
+        assert b'malicious-site.com' not in response.content
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py` around
lines 148 - 173, The test test_logout_with_invalid_redirect_uri_when_strict
currently allows too-broad outcomes; tighten it to explicitly assert that the
invalid redirect is not honored by checking response is not a 302 to the
malicious URL and/or that a rejection is returned: after posting with
post_logout_redirect_uri = invalid_redirect (and client_id = app.client_id),
assert either response.status_code == 400 (and response.content does not contain
the malicious host) or assert response.status_code != 302 or, if 302, that
response['Location'] does not equal invalid_redirect; update the assertions in
test_logout_with_invalid_redirect_uri_when_strict accordingly to fail if the app
redirects to the malicious site.

Comment on lines +176 to +202
@pytest.mark.django_db
def test_logout_with_state_parameter(client, oidc_enabled_settings, oauth2_application):
"""
Test that the state parameter is preserved in the redirect.
"""
app = oauth2_application[0]
redirect_uri = 'https://example.com/callback'
state = 'test-state-value-123'

with override_settings(OAUTH2_PROVIDER=oidc_enabled_settings):
url = get_relative_url('oauth2_provider:rp-initiated-logout')
response = client.post(
url,
{
'post_logout_redirect_uri': redirect_uri,
'client_id': app.client_id,
'state': state,
},
)

# If redirected, state should be in the redirect URL
if response.status_code == 302:
redirect_url = response['Location']
parsed = urlparse(redirect_url)
params = parse_qs(parsed.query)
if 'state' in params:
assert params['state'][0] == state
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

test_logout_with_state_parameter can pass vacuously.

If the response isn't a 302, or state isn't in the parsed query, the assertions are skipped and the test passes without verifying anything. Arrange the preconditions (valid id_token_hint, registered post_logout_redirect_uri on the application, authenticated session) so the view actually redirects, then assert unconditionally that params['state'][0] == state. Otherwise this test gives false confidence that state round-trips correctly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_app/tests/oauth2_provider/views/test_rp_initiated_logout.py` around
lines 176 - 202, The test_logout_with_state_parameter currently can pass
vacuously when no redirect occurs; update the test so it arranges valid
preconditions that force the view to redirect and then assert unconditionally
that the state round-trips. Specifically, in test_logout_with_state_parameter
ensure the oauth2_application (app) has the post_logout_redirect_uri registered
(matching redirect_uri), supply a valid id_token_hint for that client and
establish an authenticated session for the client fixture so the view will
return a 302 from client.post to
get_relative_url('oauth2_provider:rp-initiated-logout'), then parse
response['Location'] and assert params['state'][0] == state without gating the
assertion behind status checks.

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants