Skip to content

Enforce the role group allowance when users are created and modified - #754

Closed
Free-Guy-IR wants to merge 1 commit into
PasarGuard:mainfrom
Free-Guy-IR:fix/enforce-group-access-on-user-write
Closed

Enforce the role group allowance when users are created and modified#754
Free-Guy-IR wants to merge 1 commit into
PasarGuard:mainfrom
Free-Guy-IR:fix/enforce-group-access-on-user-write

Conversation

@Free-Guy-IR

@Free-Guy-IR Free-Guy-IR commented Aug 8, 2026

Copy link
Copy Markdown

What

RoleAccess.allowed_group_ids is not consulted when a user is created or modified, so an admin whose role restricts them to a subset of groups can still assign any group by calling the API directly.

Why this looks like a gap rather than intended behaviour

The allowance is already enforced in app/operation/group.py in five places — get_all_groups, get_groups_simple, _get_group_with_access, bulk_remove_groups_by_id, bulk_set_groups_disabled — all via apply_group_access. So a restricted admin cannot list, fetch, or bulk-modify a group outside their allowance.

But validate_all_groups in app/operation/__init__.py, which every user write path resolves groups through, only checks that the requested groups exist:

missing_ids = [group_id for group_id in unique_ids if group_id not in groups_by_id]
if missing_ids:
    await self.raise_error("Group not found", 404)

return [groups_by_id[group_id] for group_id in requested_group_ids]

The result is that the restriction holds in the dashboard, because the group never appears in the picker, but not against a direct request:

POST /api/user   {"username": "...", "group_ids": [<id outside the allowance>]}
→ 201 Created

The change

validate_all_groups now takes an optional acting admin and applies get_allowed_group_ids — the same function the listing is filtered by, so what an admin can be offered and what they can assign cannot drift apart. It is the single point create_user, modify_user and bulk_create_users_from_template all resolve groups through (and the Telegram handlers by way of those), so one check covers every write path.

The argument is optional because a few callers resolve groups only in order to render them; enforcement applies wherever an acting admin is passed. Owners are unaffected, and an admin with no restriction is unaffected.

The exemption, and why it is needed

Groups already present on the user being edited are exempt from the check.

Without it, restricting a role would stop its admins from saving any edit to a user who happens to sit in a group outside the allowance — not just group changes, but the data limit, the expiry, anything — because the edit form loads the user's current groups and posts them back unchanged. The check would then be refusing the state that was already there rather than the change being made.

Adding such a group is still refused, including adding it back after removing it.

Testing

Verified against a running panel with a role limited to one group and an admin with no per-admin restriction:

  • the group listing is filtered to the allowed group
  • POST /api/user with a group outside the allowance → 403 You are not allowed to use these groups: <name>
  • a mix of one allowed and one forbidden group → 403
  • the allowed group → 201
  • a user already holding a forbidden group can still be edited
  • that group can be removed, and cannot be added back afterwards
  • owners are unaffected

The panel's existing test suite passes unchanged (501 passed, 2 skipped).

Summary by CodeRabbit

  • Bug Fixes
    • Improved group assignment validation during user creation, updates, and bulk template operations.
    • Administrators can now assign only permitted groups, while approved exemptions remain available.
    • Unauthorized group assignments now return a clear access-denied error.

RoleAccess.allowed_group_ids is applied in five places in the group
operations - listing, single fetch, bulk remove, bulk disable - but nothing
consults it on the path that actually assigns groups to a user. An admin whose
role restricts them to a subset of groups is therefore only restricted in the
UI: POST /api/user or PUT /api/user/{username} with any group_id succeeds, and
the resulting user is placed in a group that admin was never granted.

validate_all_groups is the single point every write path resolves groups
through - user create, user modify, bulk create from template, and the
Telegram handlers by way of those - so the check belongs there, and one check
covers all of them. It reads get_allowed_group_ids, the same function the
listing is filtered by, so what an admin can be shown and what they can
actually assign cannot drift apart. Owners are unaffected, and an admin with
no restriction is unaffected.

The admin argument is optional because a few callers resolve groups only in
order to render them; enforcement applies wherever an acting admin is passed.

Groups already present on the user being edited are exempt. Without that,
restricting a role would block its admins from saving *any* change to a user
who happens to sit in a group outside the allowance - not the groups, but the
data limit, the expiry, anything - because the edit form posts the user's
current groups back unchanged. The check should refuse the change being made,
not the state that was already there. Adding such a group is still refused,
including adding it back after removing it.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b66173c7-3699-4289-97f9-485e055469a0

📥 Commits

Reviewing files that changed from the base of the PR and between e81877c and 7d52645.

📒 Files selected for processing (2)
  • app/operation/__init__.py
  • app/operation/user.py

Walkthrough

The operation layer now checks requested groups against an administrator’s allowed groups. User creation, modification, and bulk template creation pass administrator context to this validation. Existing user groups can remain through exemptions.

Changes

Admin group validation

Layer / File(s) Summary
Validation contract and access checks
app/operation/__init__.py
validate_all_groups now accepts administrator and exempt-group inputs. Restricted administrators can assign only allowed or exempt groups. Unauthorized groups raise a 403 error.
User operation integration
app/operation/user.py
Single-user creation, user modification, and bulk template creation now pass administrator context to group validation. User modification also passes existing group IDs as exemptions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: immohammad20000, m03ed, x0sina

Poem

A rabbit checks each group with care,
Allowed paths are marked in air.
Old groups hop past the guarded gate,
New requests meet their access state.
“403!” cries Bunny, neat and bright—
Safe assignments now take flight.

🚥 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 and concisely describes enforcing group allowances during user creation and modification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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.

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