Skip to content

#37233: feat(users): return direct roles per user on GET /v1/users/filter - #37236

Open
hassandotcms wants to merge 1 commit into
mainfrom
37233-users-filter-direct-roles
Open

#37233: feat(users): return direct roles per user on GET /v1/users/filter#37236
hassandotcms wants to merge 1 commit into
mainfrom
37233-users-filter-direct-roles

Conversation

@hassandotcms

@hassandotcms hassandotcms commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Proposed Changes

  • New includeRoles query param on GET /v1/users/filter. When true, each item gains a roles array of the user's directly assigned roles as {id, name, roleKey}; inherited roles and the personal role are excluded (legacy Roles-tab parity). Lets the Users list drop its per-row GET /v1/roles/users/{id} calls.
  • Opt-in: without the flag the response is byte-identical to today.
  • includeRoles=true requires CMS Administrator or Roles + Users portlet access (same gate as GET /v1/roles/users/{id}), otherwise 403.
  • OpenAPI updated; openapi.yaml regenerated.

Checklist

  • Tests — UserResourceIntegrationTest (7 new: opt-out parity, direct roles, inherited excluded, keyless role, roleKey + paging, 403 gate, root role named "User…" kept), UserPaginatorTest, UserResourceTest

This PR fixes: #37233

@hassandotcms hassandotcms self-assigned this Aug 26, 2026
@hassandotcms
hassandotcms force-pushed the 37233-users-filter-direct-roles branch from 3be0b4d to c211f73 Compare August 26, 2026 14:47
@hassandotcms
hassandotcms marked this pull request as ready for review August 26, 2026 14:50
@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Aug 26, 2026
@claude

claude Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Claude finished @hassandotcms's task in 2m 32s —— View job


Code Review — #37233 direct roles on GET /v1/users/filter

  • Read PR diff and changed files
  • Verify SmallRoleView / loadRolesForUser signatures
  • Confirm permission-gate parity with GET /v1/roles/users/{id}
  • Cross-check personal-role exclusion vs legacy UserAjax#getUserRoles
  • Review test coverage

I reviewed the implementation against the legacy behavior it mirrors and the existing role endpoint it claims parity with. The design is sound: the opt-in flag keeps the default payload byte-identical, the permission gate (isRoleAdministrator) is a verbatim match of RoleResource#loadUserRoles (RoleResource.java:1209-1213), and the personal-role exclusion faithfully reproduces the legacy DBFQN check in UserAjax#getUserRoles (UserAjax.java:454-463) rather than the fragile name-based Role#isUser(). Role loading is correctly lifted out of addRequestPasswordAttr so a lookup failure surfaces as a 500 rather than silently emptying the item.

New Issues

  • 🟡 Medium: dotCMS/src/main/java/com/dotcms/util/pagination/UserPaginator.java:181directRolesOf calls roleAPI.loadRolesForUser(userId, false) once per user in the page, so a page of N users issues N role lookups (an N+1 pattern). This is still a net win over the per-row GET /v1/roles/users/{id} fan-out it replaces (in-process + role cache vs. N HTTP round-trips), so it's not a blocker — but worth noting there is no batch variant, and large perPage values will scale linearly. If a bulk loadRolesForUser for a set of user IDs exists or can be added later, this is the spot to use it.

Notes (non-blocking, no action required)

  • isPersonalRole uses DBFQN.contains(usersRootRoleId) — substring match on role UUIDs. This is exactly the legacy semantics (UserAjax.java:459), and UUID collision within a DBFQN path is not a realistic concern, so preserving it is the right call for parity.
  • Degradation path is well-handled: if the cms_users root role can't be resolved, usersRootRoleId() logs a warning and returns null, and isPersonalRole short-circuits to false (personal roles simply not excluded) rather than throwing. Logger.warn is used correctly.
  • The asset/permission branch passes adminRoleIds = null into toItem, preserving the pre-PR userToMap behavior (no requestPassword attr). ✅
  • Test coverage is strong: opt-out parity + never() verification that the lookup is skipped, keyed/keyless/personal/User Managers-named-root discrimination, and includeImplicitRoles=false assertion. The isUser()==true stub on the "User Managers" mock nicely pins the exact regression the DBFQN approach avoids.

Overall this looks correct and well-tested. No blocking issues.

· branch 37233-users-filter-direct-roles

…lter (opt-in)

New `includeRoles` query param on GET /v1/users/filter. When true, each item
carries a `roles` array of the user's DIRECT memberships as {id, name, roleKey}
(SmallRoleView); inherited roles and the user's personal role are excluded,
matching the legacy Users portlet Roles tab (UserAjax#getUserRoles). Lets the
Angular Users list drop its per-row GET /v1/roles/users/{id} fan-out.

Default (flag absent) response is byte-identical: the flag is only added to the
paginator params when true, so neither the items nor the Link header change.

Requesting roles requires the same privilege as GET /v1/roles/users/{id}
(CMS Administrator, or Roles+Users portlet access), otherwise 403 -- the flag
must not widen who can read role membership.

Personal roles are recognized the legacy way -- ID-based DBFQN under the
cms_users root role -- not via Role#isUser(), whose name-based FQN check would
also drop any ordinary root role whose name starts with "User".

Roles are loaded outside UserPaginator#addRequestPasswordAttr so a lookup
failure surfaces as an error instead of silently emptying the item.

Tests: ITs for opt-out payload parity, direct roles, inherited-child exclusion,
keyless role, roleKey filter + paging, the 403 gate, and a "User Managers" root
role being kept; unit tests for the paginator and resource param plumbing.
UserPaginatorTest now extends UnitTestBase -- it only passed when another
UnitTestBase subclass ran first in the reused fork. openapi.yaml regenerated.
@wezell

wezell commented Aug 26, 2026

Copy link
Copy Markdown
Member

Is there a way to get inherited roles with the API?

@wezell wezell 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.

Is there any way or flag to include inherited roles with this api?

@hassandotcms

Copy link
Copy Markdown
Contributor Author

Is there any way or flag to include inherited roles with this api?

currently no, in this api.

if consumer need inherited roles per user as well, it can use GET /v1/roles/users/{id}.

@wezell

@hassandotcms
hassandotcms requested a review from wezell August 27, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Task] Users API: GET /v1/users/filter should be able to return each user's direct roles

2 participants