Skip to content

[change!] Moved password reset to openwisp-users & prevent user enumeration #754 - #757

Open
pandafy wants to merge 14 commits into
masterfrom
issues/754-saml-password-expiration
Open

[change!] Moved password reset to openwisp-users & prevent user enumeration #754#757
pandafy wants to merge 14 commits into
masterfrom
issues/754-saml-password-expiration

Conversation

@pandafy

@pandafy pandafy commented Jul 28, 2026

Copy link
Copy Markdown
Member

Checklist

Reference to Existing Issue

Closes #754

Description of Changes

  • Password reset now always returns HTTP 200 to prevent user enumeration. Previously, unknown users returned HTTP 404.
  • Password reset is now handled by openwisp-users. The local PasswordResetSerializer is deprecated and will be removed in 1.4.0.
  • SAML sessions are now marked as externally authenticated, preventing PasswordExpirationMiddleware from redirecting SAML users to the password change page.

Blockers

…ration #754

- Password reset now always returns HTTP 200 to prevent user
  enumeration. Previously, unknown users returned HTTP 404.
- Password reset is now handled by openwisp-users. The local
  PasswordResetSerializer is deprecated and will be removed in 1.4.0.
- SAML sessions are now marked as externally authenticated, preventing
  PasswordExpirationMiddleware from redirecting SAML users to the
  password change page.

Closes #754
@pandafy pandafy self-assigned this Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Password reset handling now delegates to OpenWISP Users, with organization-scoped active-user filtering, configurable reset URLs, throttling, and membership validation. Deprecated local form and serializer wrappers remain for compatibility. Password, SAML, and captive-page authentication flows now record their authentication methods, and password expiration reporting distinguishes external from password-based authentication.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PasswordResetView
  participant OrganizationMembers
  participant CurrentSite
  Client->>PasswordResetView: Submit reset identifier
  PasswordResetView->>OrganizationMembers: Find active organization members
  PasswordResetView->>CurrentSite: Read reset URL domain
  PasswordResetView-->>Client: Return reset response
Loading
sequenceDiagram
  participant PasswordLogin
  participant SAMLLogin
  participant CaptiveRedirect
  participant AuthenticationState
  participant PasswordExpirationMiddleware
  PasswordLogin->>AuthenticationState: Record PASSWORD
  SAMLLogin->>AuthenticationState: Record EXTERNAL
  CaptiveRedirect->>AuthenticationState: Record EXTERNAL
  PasswordExpirationMiddleware->>AuthenticationState: Check authentication method
  AuthenticationState-->>PasswordExpirationMiddleware: Return PASSWORD or EXTERNAL
Loading

Possibly related issues

  • openwisp/openwisp-users issue 511 — Covers the shared password-expiration and external-authentication behavior implemented here.
  • openwisp/ansible-openwisp2 issue 637 — Also updates REST password-reset serializer configuration to use OpenWISP Users.

Possibly related PRs

Suggested labels: enhancement


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 4 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❌ Error The password reset serializer, forms, templates, views, settings, and CI updates are unrelated to issue #754. Move the password-reset and OpenWISP Users migration work to a separate PR or link the corresponding issue.
Title check ⚠️ Warning The title is descriptive and matches the PR's main SAML/password-reset changes, but the required [change] prefix is written as [change!]. Use the required prefix format, e.g. [change] Moved password reset to openwisp-users & prevent user enumeration #754.
Bug Fixes ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Features ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Changes ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
General Rules ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the implemented SAML authentication and password-reset changes.
Linked Issues check ✅ Passed The SAML session marking and password-expiration behavior align with issue #754 and its testing expectations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issues/754-saml-password-expiration

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

@coderabbitai coderabbitai Bot added the bug Something isn't working label Jul 28, 2026
@openwisp-companion

Copy link
Copy Markdown

Dependency Conflict During Package Installation

Hello @pandafy,
(Analysis for commit 5a2df1d)

There is a dependency conflict between openwisp-radius and openwisp-users due to different versions being specified.

Fix:

The setup.py file needs to be updated to ensure consistent versioning for openwisp-users. Specifically, the installation requirement for openwisp-users should be changed from:

(
    "openwisp-users @"
    "https://github.com/openwisp/openwisp-users/archive/refs/heads/"
    "issues/511-passowrd-expiration-sso.tar.gz"
),

to use a version that aligns with the other openwisp-users references, likely 1.3.0a0 or a similar stable tag.

(
    "openwisp-users @"
    "https://github.com/openwisp/openwisp-users/archive/refs/heads/"
    "1.3.tar.gz"
),

This change will ensure that both openwisp-radius and openwisp-notifications depend on the same version of openwisp-users, resolving the conflict.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_radius/api/views.py`:
- Around line 564-580: Extend tests for get_password_reset_url to verify the
organization’s radius_settings.password_reset_url takes precedence over
PASSWORD_RESET_URLS, PASSWORD_RESET_URLS is used when no organization override
exists, and the default URL is used when neither is configured. Assert each
generated URL interpolates organization, uid, token, and current-site domain
values.
- Around line 564-566: Remove the unnecessary blank line immediately after the
get_password_reset_url method declaration, keeping uid =
user_pk_to_url_str(user) as the first statement in the method body.

In `@openwisp_radius/tests/test_saml/test_views.py`:
- Line 366: Remove the commented-out `@capture_any_output`() decorator near the
affected test; do not restore it unless that test explicitly requires output
capture.
- Line 375: In the test setup around the user-under-test flow, replace the broad
User.objects.update call with a direct update on the user instance so only that
user’s password_updated value is changed; leave unrelated users and fixtures
untouched.
- Around line 395-398: Strengthen the follow-up request assertion in the session
redirect test by verifying that additional_info_response redirects to the
expected destination rather than only checking status 302. Assert its Location
target is not the password-change URL, preserving the existing successful
redirect behavior.

In `@setup.py`:
- Around line 45-46: Update the OpenWISP Users dependency URL in setup.py to
reference an immutable commit SHA or a release archive instead of the mutable
issues/511-passowrd-expiration-sso branch archive, preserving the dependency’s
intended source.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 7faea65b-c4ff-4db5-8295-c4206a1b2ba3

📥 Commits

Reviewing files that changed from the base of the PR and between 41076b8 and 5a2df1d.

📒 Files selected for processing (9)
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/base/forms.py
  • openwisp_radius/saml/views.py
  • openwisp_radius/templates/custom_password_reset_email.html
  • openwisp_radius/tests/test_api/test_api.py
  • openwisp_radius/tests/test_saml/test_views.py
  • setup.py
  • tests/openwisp2/settings.py
💤 Files with no reviewable changes (2)
  • openwisp_radius/templates/custom_password_reset_email.html
  • openwisp_radius/base/forms.py
📜 Review details
⏰ Context from checks skipped due to timeout. (12)
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file. Only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready).
Avoid unnecessary blank lines inside function and method bodies.
Mark user-facing strings for translation with Django i18n helpers in Django code.
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it.

Files:

  • openwisp_radius/saml/views.py
  • tests/openwisp2/settings.py
  • setup.py
  • openwisp_radius/tests/test_api/test_api.py
  • openwisp_radius/tests/test_saml/test_views.py
  • openwisp_radius/api/views.py
  • openwisp_radius/api/serializers.py
🔇 Additional comments (6)
openwisp_radius/saml/views.py (1)

24-24: LGTM!

Also applies to: 66-66

openwisp_radius/tests/test_saml/test_views.py (1)

2-3: LGTM!

Also applies to: 12-22

openwisp_radius/api/serializers.py (1)

28-30: LGTM!

Also applies to: 59-65

tests/openwisp2/settings.py (1)

302-302: LGTM!

openwisp_radius/api/views.py (1)

56-59: LGTM!

Also applies to: 540-562, 604-605

openwisp_radius/tests/test_api/test_api.py (1)

918-933: LGTM!

Also applies to: 967-987, 1158-1165

Comment thread openwisp_radius/api/views.py
Comment thread openwisp_radius/api/views.py
Comment thread openwisp_radius/tests/test_saml/test_views.py Outdated
Comment thread openwisp_radius/tests/test_saml/test_views.py Outdated
Comment thread openwisp_radius/tests/test_saml/test_views.py
Comment thread setup.py Outdated
Comment on lines +45 to +46
"https://github.com/openwisp/openwisp-users/archive/refs/heads/"
"issues/511-passowrd-expiration-sso.tar.gz"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-remote \
  https://github.com/openwisp/openwisp-users.git \
  refs/heads/issues/511-passowrd-expiration-sso

Repository: openwisp/openwisp-radius

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '35,55p' setup.py | cat -n

Repository: openwisp/openwisp-radius

Length of output: 1041


Pin the OpenWISP Users dependency to a commit SHA. This branch archive is still mutable, so builds can drift; use an immutable commit pin or release archive instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@setup.py` around lines 45 - 46, Update the OpenWISP Users dependency URL in
setup.py to reference an immutable commit SHA or a release archive instead of
the mutable issues/511-passowrd-expiration-sso branch archive, preserving the
dependency’s intended source.

@github-project-automation github-project-automation Bot moved this from Backlog to In progress in 26.06 Release Jul 28, 2026
@pandafy
pandafy force-pushed the issues/754-saml-password-expiration branch from 5a2df1d to 57bd2ee Compare July 29, 2026 15:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
openwisp_radius/api/views.py (1)

569-575: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Read the raw organization override before applying the settings fallback.

FallbackCharField returns its fallback when the stored value is NULL; therefore Line 570 is always truthy and the PK-keyed PASSWORD_RESET_URLS lookup is unreachable. Organizations configured only through PASSWORD_RESET_URLS[str(org.pk)] receive the global default URL instead. (openwisp.io)

  • openwisp_radius/api/views.py#L569-L575: distinguish the stored password_reset_url value from its fallback, then use the PK-keyed mapping when no explicit organization URL is stored.
  • openwisp_radius/tests/test_api/test_api.py#L1043-L1086: add a regression test for a PK-keyed PASSWORD_RESET_URLS entry and assert its fully interpolated URL is emailed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_radius/api/views.py` around lines 569 - 575, In
openwisp_radius/api/views.py lines 569-575, update the password-reset URL
selection to inspect the raw stored organization value rather than the
FallbackCharField-resolved value, using the PK-keyed PASSWORD_RESET_URLS entry
when no explicit override is stored; preserve the global fallback otherwise. In
openwisp_radius/tests/test_api/test_api.py lines 1043-1086, add a regression
test configuring a PK-keyed PASSWORD_RESET_URLS entry and assert that the
emailed URL is fully interpolated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_radius/tests/test_api/test_rest_token.py`:
- Around line 306-307: Merge test_password_login_triggers_password_expiration
into test_user_auth_token_password_expired, using the latter as the single
coverage point for this endpoint and password_expired=True behavior. Move the
targeted-user setup and precondition assertion into
test_user_auth_token_password_expired, preserve the USER_PASSWORD_EXPIRATION
patch, and remove the redundant test.
- Line 315: Remove the redundant “Login via password” comment immediately before
the password-login call in the affected test, leaving the test behavior and
surrounding code unchanged.

---

Duplicate comments:
In `@openwisp_radius/api/views.py`:
- Around line 569-575: In openwisp_radius/api/views.py lines 569-575, update the
password-reset URL selection to inspect the raw stored organization value rather
than the FallbackCharField-resolved value, using the PK-keyed
PASSWORD_RESET_URLS entry when no explicit override is stored; preserve the
global fallback otherwise. In openwisp_radius/tests/test_api/test_api.py lines
1043-1086, add a regression test configuring a PK-keyed PASSWORD_RESET_URLS
entry and assert that the emailed URL is fully interpolated.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 1baceba9-4a9a-4c43-8665-eef79b49879b

📥 Commits

Reviewing files that changed from the base of the PR and between 5a2df1d and 57bd2ee.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
  • openwisp_radius/tests/test_api/test_rest_token.py
  • openwisp_radius/tests/test_saml/test_views.py
📜 Review details
⏰ Context from checks skipped due to timeout. (12)
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file. Only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready).
Avoid unnecessary blank lines inside function and method bodies.
Mark user-facing strings for translation with Django i18n helpers in Django code.
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it.

Files:

  • openwisp_radius/tests/test_api/test_rest_token.py
  • openwisp_radius/tests/test_saml/test_views.py
  • openwisp_radius/api/views.py
  • openwisp_radius/api/serializers.py
  • openwisp_radius/tests/test_api/test_api.py
🧠 Learnings (2)
📚 Learning: 2026-03-06T08:47:54.428Z
Learnt from: stktyagi
Repo: openwisp/openwisp-radius PR: 689
File: .github/workflows/bot-ci-failure.yml:60-60
Timestamp: 2026-03-06T08:47:54.428Z
Learning: In openwisp/openwisp-radius and similar OpenWISP repos, reusable workflow caller files intentionally reference upstream reusable workflows with master (e.g., openwisp/openwisp-utils/.github/workflows/reusable-bot-ci-failure.ymlmaster). This is by design to ensure callers always use the latest changes from the master branch of the upstream project. Do not flag such references as security or pinning issues during code reviews. If applying this pattern broadly, treat any workflow call that uses a remote reusable workflow from a different repository with master as an intentional design choice rather than a vulnerability.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-04-17T13:53:45.430Z
Learnt from: pandafy
Repo: openwisp/openwisp-radius PR: 698
File: .github/workflows/ci.yml:77-77
Timestamp: 2026-04-17T13:53:45.430Z
Learning: In .github/workflows/ci.yml, the CI step that runs `pip install --upgrade --no-deps --no-cache-dir --force-reinstall "https://github.com/openwisp/openwisp-users/tarball/issues/497-export-users"` is an intentionally temporary blocker dependency (to use a pre-release branch until openwisp-users PR `#498` is merged). Do not flag this pip install as an issue in future reviews as long as it appears in this workflow with the same purpose.

Applied to files:

  • .github/workflows/ci.yml
🔇 Additional comments (5)
openwisp_radius/tests/test_saml/test_views.py (1)

364-365: LGTM!

Also applies to: 374-376, 399-402

openwisp_radius/api/serializers.py (1)

2-2: LGTM!

Also applies to: 29-35, 60-75

.github/workflows/ci.yml (1)

78-78: LGTM!

openwisp_radius/api/views.py (1)

4-4: LGTM!

Also applies to: 26-26, 44-44, 523-540, 591-606

openwisp_radius/tests/test_api/test_api.py (1)

918-987: LGTM!

Also applies to: 1201-1209

Comment on lines +306 to +307
@mock.patch("openwisp_users.settings.USER_PASSWORD_EXPIRATION", 30)
def test_password_login_triggers_password_expiration(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge this with the existing expiration test.

test_user_auth_token_password_expired already exercises the same endpoint and asserts password_expired=True. Move the targeted-user setup and precondition assertion into that test instead of retaining duplicate coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_radius/tests/test_api/test_rest_token.py` around lines 306 - 307,
Merge test_password_login_triggers_password_expiration into
test_user_auth_token_password_expired, using the latter as the single coverage
point for this endpoint and password_expired=True behavior. Move the
targeted-user setup and precondition assertion into
test_user_auth_token_password_expired, preserve the USER_PASSWORD_EXPIRATION
patch, and remove the redundant test.

Comment thread openwisp_radius/tests/test_api/test_rest_token.py Outdated
@coderabbitai coderabbitai Bot removed the bug Something isn't working label Jul 29, 2026
@kilo-code-bot

kilo-code-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (8 files)
  • docs/user/rest-api.rst
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/mixins.py
  • openwisp_radius/tests/test_api/test_rest_token.py
  • openwisp_radius/tests/test_saml/test_views.py
  • openwisp_radius/tests/test_social.py
  • openwisp_radius/tests/test_users_integration.py
Previous Review Summaries (8 snapshots, latest commit 107dc46)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 107dc46)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • openwisp_radius/api/freeradius_views.py
  • openwisp_radius/tests/test_api/test_freeradius_api.py

Previous review (commit 7ea7f74)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • tests/openwisp2/sample_users/migrations/0001_initial.py

Previous review (commit cd18b7f)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • tests/openwisp2/sample_users/migrations/0001_initial.py

Previous review (commit 14fe03a)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • tests/openwisp2/sample_users/migrations/0001_initial.py

Previous review (commit 7f6b779)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_rest_token.py
  • tests/openwisp2/sample_users/migrations/0001_initial.py

Previous review (commit 8301db2)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_social.py

Fix these issues in Kilo Cloud

Previous review (commit 69a947b)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
WARNING 1
Issue Details (click to expand)

WARNING

File Line Issue
openwisp_radius/api/views.py 526 Password reset views now use DEFAULT_THROTTLE_CLASSES, which may omit ScopedRateThrottle and remove the inherited "others" rate limit from these public endpoints.
openwisp_radius/api/views.py 597 PasswordResetConfirmView changed from [ScopedRateThrottle] to DEFAULT_THROTTLE_CLASSES, removing the inherited "others" rate limit from this public endpoint.
Files Reviewed (1 file)
  • openwisp_radius/api/views.py - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit baa7724)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (10 files)
  • .github/workflows/ci.yml
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/base/forms.py
  • openwisp_radius/saml/views.py
  • openwisp_radius/templates/custom_password_reset_email.html
  • openwisp_radius/tests/test_api/test_api.py
  • openwisp_radius/tests/test_api/test_rest_token.py
  • openwisp_radius/tests/test_saml/test_views.py
  • tests/openwisp2/settings.py

Reviewed by laguna-s-2.1:free · Input: 541.1K · Output: 52.9K · Cached: 1.8M

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_radius/api/views.py`:
- Line 526: Update the password-reset view’s throttle_classes declaration to
explicitly include ScopedRateThrottle alongside the configured
DEFAULT_THROTTLE_CLASSES, preserving the inherited “others” scope limit even
when global defaults are empty or omit it.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: f15b27da-f58f-4996-9a09-0dab333234ab

📥 Commits

Reviewing files that changed from the base of the PR and between baa7724 and 69a947b.

📒 Files selected for processing (1)
  • openwisp_radius/api/views.py
📜 Review details
⏰ Context from checks skipped due to timeout. (12)
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file. Only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready).
Avoid unnecessary blank lines inside function and method bodies.
Mark user-facing strings for translation with Django i18n helpers in Django code.
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it.

Files:

  • openwisp_radius/api/views.py
🔇 Additional comments (1)
openwisp_radius/api/views.py (1)

4-6: LGTM!

Also applies to: 26-26, 43-45, 57-60, 528-533, 542-587, 609-612


class PasswordResetView(ThrottledAPIMixin, DispatchOrgMixin, BasePasswordResetView):
authentication_classes = tuple()
throttle_classes = drf_api_settings.DEFAULT_THROTTLE_CLASSES

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep password-reset throttling explicit.

DEFAULT_THROTTLE_CLASSES can be empty or omit ScopedRateThrottle, which removes the inherited "others" limit from these public endpoints. Keep ScopedRateThrottle on the view, or guarantee it is always appended globally, so password reset stays rate-limited.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_radius/api/views.py` at line 526, Update the password-reset view’s
throttle_classes declaration to explicitly include ScopedRateThrottle alongside
the configured DEFAULT_THROTTLE_CLASSES, preserving the inherited “others” scope
limit even when global defaults are empty or omit it.

@openwisp-companion

Copy link
Copy Markdown

Flake8 F401 Unused Import

Hello @pandafy,
(Analysis for commit 69a947b)

The CI failed because of an unused import detected by Flake8.

Failure:

  • openwisp_radius/api/views.py:45:1: F401 'rest_framework.throttling.ScopedRateThrottle' imported but unused

Fix:
Remove the unused import statement from openwisp_radius/api/views.py.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 29, 2026
@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Removed.

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please address the two compatibility regressions below. I pushed ace3c59 with a failing regression test for the superuser case.

and preventing user enumeration.
"""
app_label = User._meta.app_config.label
return (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please preserve the existing superuser behavior here. The previous flow called validate_membership(), which allows superusers, but this relation filter excludes a superuser without an OrganizationUser record and silently sends no email. I pushed ace3c59 with a regression test. Include active superusers in this queryset and keep the membership filter for other users.

self.fields["csvfile"].help_text = help_text


class PasswordResetForm(BasePasswordResetForm):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please keep this import path as a deprecated alias or thin subclass of openwisp_users.base.forms.PasswordResetForm. Issue #511 requires preserving RADIUS extension points and import paths, but extensions importing or subclassing openwisp_radius.base.forms.PasswordResetForm will now fail immediately.

@pandafy

pandafy commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review!

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

@pandafy: I’ll perform a full review of all changes in this PR.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/openwisp2/settings.py`:
- Line 310: Shorten the PASSWORD_RESET_SERIALIZER setting line in the settings
configuration so it satisfies the repository’s configured flake8 maximum line
length, while preserving the same serializer path and setting key.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: f25f6847-8a25-47fd-8afb-622454737d22

📥 Commits

Reviewing files that changed from the base of the PR and between fc06a1e and cd18b7f.

📒 Files selected for processing (13)
  • .github/workflows/ci.yml
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/base/forms.py
  • openwisp_radius/saml/views.py
  • openwisp_radius/social/views.py
  • openwisp_radius/templates/custom_password_reset_email.html
  • openwisp_radius/tests/test_api/test_api.py
  • openwisp_radius/tests/test_api/test_rest_token.py
  • openwisp_radius/tests/test_saml/test_views.py
  • openwisp_radius/tests/test_social.py
  • tests/openwisp2/sample_users/migrations/0001_initial.py
  • tests/openwisp2/settings.py
💤 Files with no reviewable changes (1)
  • openwisp_radius/templates/custom_password_reset_email.html
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file. Only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready).
Avoid unnecessary blank lines inside function and method bodies.
Mark user-facing strings for translation with Django i18n helpers in Django code.
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it.

Files:

  • tests/openwisp2/settings.py
  • tests/openwisp2/sample_users/migrations/0001_initial.py
  • openwisp_radius/social/views.py
  • openwisp_radius/base/forms.py
  • openwisp_radius/saml/views.py
  • openwisp_radius/tests/test_api/test_rest_token.py
  • openwisp_radius/tests/test_social.py
  • openwisp_radius/tests/test_saml/test_views.py
  • openwisp_radius/tests/test_api/test_api.py
  • openwisp_radius/api/views.py
  • openwisp_radius/api/serializers.py
🧠 Learnings (2)
📚 Learning: 2026-03-06T08:47:54.428Z
Learnt from: stktyagi
Repo: openwisp/openwisp-radius PR: 689
File: .github/workflows/bot-ci-failure.yml:60-60
Timestamp: 2026-03-06T08:47:54.428Z
Learning: In openwisp/openwisp-radius and similar OpenWISP repos, reusable workflow caller files intentionally reference upstream reusable workflows with master (e.g., openwisp/openwisp-utils/.github/workflows/reusable-bot-ci-failure.ymlmaster). This is by design to ensure callers always use the latest changes from the master branch of the upstream project. Do not flag such references as security or pinning issues during code reviews. If applying this pattern broadly, treat any workflow call that uses a remote reusable workflow from a different repository with master as an intentional design choice rather than a vulnerability.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-04-17T13:53:45.430Z
Learnt from: pandafy
Repo: openwisp/openwisp-radius PR: 698
File: .github/workflows/ci.yml:77-77
Timestamp: 2026-04-17T13:53:45.430Z
Learning: In .github/workflows/ci.yml, the CI step that runs `pip install --upgrade --no-deps --no-cache-dir --force-reinstall "https://github.com/openwisp/openwisp-users/tarball/issues/497-export-users"` is an intentionally temporary blocker dependency (to use a pre-release branch until openwisp-users PR `#498` is merged). Do not flag this pip install as an issue in future reviews as long as it appears in this workflow with the same purpose.

Applied to files:

  • .github/workflows/ci.yml
🔇 Additional comments (21)
openwisp_radius/base/forms.py (1)

1-7: LGTM!

Also applies to: 55-69

openwisp_radius/api/serializers.py (2)

61-75: LGTM!


850-855: 🩺 Stability & Availability

Confirm is_password_authenticated semantics when request is None.

RadiusUserSerializer is instantiated without a view/request in several places (e.g. RadiusUserSerializer(admin) in tests), so request is None here. The reported password_expired value for those callers depends entirely on how openwisp_users.auth.is_password_authenticated treats a missing request (fall back to user.last_login_method vs. return False). Worth verifying so token/validate endpoints don't silently stop reporting expired passwords.

#!/bin/bash
fd -t f 'auth.py' -p 'openwisp_users' --exec ast-grep outline {} --items all
python - <<'PY'
import inspect
try:
    from openwisp_users import auth
    print(inspect.getsource(auth.is_password_authenticated))
except Exception as e:
    print("unavailable:", e)
PY
.github/workflows/ci.yml (1)

78-78: LGTM!

openwisp_radius/tests/test_api/test_rest_token.py (2)

312-327: Remove the duplicate password-expiration test.

test_user_auth_token_password_expired directly above covers the same expired-password login and response assertion; retain that test and remove this duplicate.


4-4: LGTM!

Also applies to: 13-13, 34-34, 303-310, 494-506

openwisp_radius/saml/views.py (1)

24-29: LGTM!

Also applies to: 70-70, 153-156

openwisp_radius/social/views.py (1)

10-11: LGTM!

Also applies to: 73-76

tests/openwisp2/sample_users/migrations/0001_initial.py (1)

37-51: LGTM!

openwisp_radius/tests/test_social.py (1)

1-16: LGTM!

Also applies to: 139-176

openwisp_radius/tests/test_saml/test_views.py (1)

2-2: LGTM!

Also applies to: 11-21, 361-403

openwisp_radius/api/views.py (5)

531-531: Password-reset throttling still depends entirely on global defaults.

DEFAULT_THROTTLE_CLASSES may be empty or omit ScopedRateThrottle, dropping the inherited "others" scope limit on this public endpoint. Previously flagged and still unaddressed.


4-4: LGTM!

Also applies to: 26-26, 43-43, 56-60


319-323: LGTM!


549-579: LGTM!


581-601: LGTM!

Also applies to: 611-611, 625-626

openwisp_radius/tests/test_api/test_api.py (5)

18-18: LGTM!

Also applies to: 30-41


925-940: LGTM!


974-994: LGTM!


1050-1191: LGTM!


1307-1314: LGTM!

REST_AUTH = {
"SESSION_LOGIN": False,
"PASSWORD_RESET_SERIALIZER": "openwisp_radius.api.serializers.PasswordResetSerializer",
"PASSWORD_RESET_SERIALIZER": "openwisp_users.api.serializers.PasswordResetSerializer",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Check line length against the QA config.

This line is ~92 characters; if flake8 is configured with the usual 88-char limit, run-qa-checks will fail with E501 (black won't split the string literal for you).

Proposed fix
-    "PASSWORD_RESET_SERIALIZER": "openwisp_users.api.serializers.PasswordResetSerializer",
+    "PASSWORD_RESET_SERIALIZER": (
+        "openwisp_users.api.serializers.PasswordResetSerializer"
+    ),
#!/bin/bash
fd -H -t f -e cfg -e toml -e ini . -d 2 --exec grep -Hn -i -A3 'flake8\|max-line-length' {}
awk 'NR==310 {print length($0)": "$0}' tests/openwisp2/settings.py
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/openwisp2/settings.py` at line 310, Shorten the
PASSWORD_RESET_SERIALIZER setting line in the settings configuration so it
satisfies the repository’s configured flake8 maximum line length, while
preserving the same serializer path and setting key.

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

Labels

enhancement New feature or request

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[bug] Mark RADIUS SAML sessions as external for password expiration handling

2 participants