Skip to content

core: actually omit users from the groups API response when include_users=false#21690

Open
SAY-5 wants to merge 1 commit intogoauthentik:mainfrom
SAY-5:fix/groups-include-users-omit
Open

core: actually omit users from the groups API response when include_users=false#21690
SAY-5 wants to merge 1 commit intogoauthentik:mainfrom
SAY-5:fix/groups-include-users-omit

Conversation

@SAY-5
Copy link
Copy Markdown

@SAY-5 SAY-5 commented Apr 18, 2026

What

GroupSerializer in authentik/core/api/groups.py gates the users_obj SerializerMethodField on _should_include_users correctly (returns None when ?include_users=false). The sibling users field is not a SerializerMethodField — it's the implicit PrimaryKeyRelatedField(many=True) that DRF derives from the Meta.fields list — and nothing turned it off. So every list response still serialises the group-users M2M as an array of PKs.

Per #21278, on tenants with large groups this makes:

GET /api/v3/core/groups/?include_users=false

indistinguishable from include_users=true in wall-clock time, which is the opposite of what the caller asked for and what the OpenAPI parameter doc implies. The viewset already drops the users prefetch when _should_include_users is false (see the prefetch_related("users", ...) guard), so each group now takes a cold round-trip to walk the M2M. That makes the N+1 shape in this mode arguably worse than with include_users=true.

Fix

Override GroupSerializer.get_fields to drop the users binding when the include flag is off:

def get_fields(self):
    fields = super().get_fields()
    if not self._should_include_users:
        fields.pop("users", None)
    return fields

After this:

  • GET /core/groups/?include_users=false returns neither users nor users_obj. The M2M is never walked.
  • GET /core/groups/ (default) still returns both.
  • Write paths (POST /core/groups/, PATCH /core/groups/{uuid}/) do not set include_users in their query string, so _should_include_users stays at its True default and users remains a writable field. Create/update semantics for assigning users are unaffected.

Test plan

  • Baseline on main: GET /core/groups/?include_users=false&page_size=20 on a tenant with large groups. Response body still contains users: [pk, pk, ...] per group; p95 latency unchanged vs include_users=true.
  • Apply patch. Same request returns group objects with no users or users_obj keys. p95 drops back to the "no user walks" curve.
  • GET /core/groups/ (no query param) unchanged: both fields present.
  • POST /core/groups/ {"name": "...", "users": [1, 2]} still assigns users correctly (test_groups_api covers this).

Fixes #21278

…sers=false

GroupSerializer's `include_users=false` query flag only controlled the
`users_obj` SerializerMethodField, which correctly returned None. The
companion `users` field (implicit PrimaryKeyRelatedField(many=True)
from the Meta.fields list) was untouched, so every list response still
walked the group.users M2M to collect user PKs. On tenants with large
groups this makes /api/v3/core/groups/?include_users=false indistinguishable
from include_users=true in response time, which is the opposite of what
the caller asked for and what the OpenAPI doc implies.

The viewset already skips the `users` prefetch when
_should_include_users is false, but that only means the M2M walk is now
a cold DB round-trip per group rather than a cached one, so the N+1
pattern is actually worse than with include_users=true in that mode.

Override GroupSerializer.get_fields to drop `users` when
_should_include_users is false. GET /core/groups/?include_users=false
now returns neither `users` nor `users_obj`. Write paths (POST / PATCH)
do not set the query param so _should_include_users stays at its True
default and `users` remains writable exactly as before.

Fixes goauthentik#21278

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
@SAY-5 SAY-5 requested a review from a team as a code owner April 18, 2026 07:14
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 18, 2026

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit e27ebad
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69e32f45cfcebe0008992e43
😎 Deploy Preview https://deploy-preview-21690--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 18, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.17%. Comparing base (08832b8) to head (e27ebad).

Files with missing lines Patch % Lines
authentik/core/api/groups.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21690      +/-   ##
==========================================
- Coverage   92.21%   92.17%   -0.05%     
==========================================
  Files        1033     1033              
  Lines       59947    59952       +5     
  Branches     2695     2695              
==========================================
- Hits        55283    55259      -24     
- Misses       4593     4620      +27     
- Partials       71       73       +2     
Flag Coverage Δ
conformance 36.97% <20.00%> (+<0.01%) ⬆️
e2e 42.90% <80.00%> (+0.01%) ⬆️
integration 33.38% <20.00%> (-0.52%) ⬇️
rust 60.55% <ø> (-0.08%) ⬇️
unit 91.92% <80.00%> (+<0.01%) ⬆️
unit-migrate 92.02% <80.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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.

API: include_users=false on Groups endpoint still returns user PKs in users field

1 participant