-
Notifications
You must be signed in to change notification settings - Fork 9
docs: Define api contract for User-Grouped role assignments #437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f6c3c87
fad654a
89d8470
f8ed179
c1aba85
5258a21
0d3dde8
782591f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,287 @@ | ||||||
| 0024: API Contract for User-Grouped Role Assignments | ||||||
| ##################################################### | ||||||
|
|
||||||
| Status | ||||||
| ******** | ||||||
|
|
||||||
| **Draft** | ||||||
|
|
||||||
| Context | ||||||
| ********* | ||||||
|
|
||||||
| The new `Figma design`_ and product requirements for the Team Members tab in the | ||||||
| Admin Console change how assignments are presented: instead of listing one row | ||||||
| per assignment, the tab now groups assignments by user, showing one row per user | ||||||
| with their assignments nested underneath. The existing endpoints for gathering | ||||||
| user assignment data don't return all the fields this user-grouped view needs. | ||||||
| Before we can continue building it, we need to define a contract, either for a new | ||||||
| endpoint or for a backwards-compatible change to an existing one, that provides | ||||||
| those fields. | ||||||
|
|
||||||
| Following that design, the view displays a table of users. Each row shows the | ||||||
| username, email, a single scope the related assigned role, along with a control to | ||||||
| expand the row and reveal up to three assigned roles and the user's total number | ||||||
| of assigned roles. | ||||||
|
|
||||||
| The table can be searched by username, email, or full name; sorted by username, | ||||||
| full name or email; filtered by organization, role, or scope; and is paginated. | ||||||
|
|
||||||
| Decision | ||||||
| ********** | ||||||
|
|
||||||
| Extend the existing ``/api/authz/v1/users/`` endpoint, defined in ``TeamMembersAPIView``, | ||||||
| to include a list of assignments for each user. | ||||||
|
|
||||||
| This endpoint was originally created for an earlier version of the Team Members | ||||||
| tab that was deprioritized and never implemented in a previous phase of the RBAC | ||||||
| project. At that time, the view did not expose each user's assigned roles, only a | ||||||
| total count. | ||||||
|
|
||||||
| As part of this change, the existing ``assignation_count`` field is renamed to | ||||||
| ``assignment_count``. The rest of the repository consistently uses "assignment" | ||||||
| (e.g. ``RoleAssignmentData``, ``get_visible_role_assignments_for_user``, the | ||||||
| ``/api/authz/v1/users/<username>/assignments/`` endpoint), so ``assignation_count`` | ||||||
| is an inconsistent outlier. Renaming it now keeps the new user-grouped fields | ||||||
| (``assignments`` and ``assignment_count``) aligned with that convention. The field | ||||||
| is safe to rename because the ``GET /api/authz/v1/users/`` endpoint is not called | ||||||
| at all by | ||||||
| `frontend-app-admin-console <https://github.com/openedx/frontend-app-admin-console>`_, | ||||||
| the only client of the AuthZ API. Its current Team Members table is sourced from | ||||||
| the assignment-grouped ``GET /api/authz/v1/assignments/`` endpoint, so neither the | ||||||
| endpoint nor the ``assignation_count`` field has any released consumer. | ||||||
|
|
||||||
| The embedded assignments are not paginated. Each user includes only the first n | ||||||
| assignments, where n defaults to 3 and can be overridden by the ``assignments_limit`` | ||||||
| query parameter. | ||||||
|
|
||||||
|
|
||||||
| REST API for Team Members view | ||||||
| ================================= | ||||||
|
|
||||||
| The existing ``/api/authz/v1/users/`` endpoint will be extended to return a list of | ||||||
| assignments per user. The number of assignments returned is capped by the | ||||||
| ``assignments_limit`` parameter, which defaults to 3. | ||||||
|
|
||||||
| API Definition | ||||||
| -------------- | ||||||
|
|
||||||
| GET /api/authz/v1/users/ | ||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
|
|
||||||
| Retrieve all users that have at least one role assignment (team members). Results | ||||||
| are filtered according to the calling user's scope-level view permissions. | ||||||
|
|
||||||
| Query Parameters: | ||||||
| """"""""""""""""" | ||||||
|
|
||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is missing the roles filter
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, I've added it, thanks! |
||||||
| - ``roles`` (optional): Comma-separated list of roles to filter by (e.g. ``course_auditor,library_admin``). | ||||||
| - ``scopes`` (optional): Comma-separated list of scopes to filter by (e.g. | ||||||
| ``lib:Org1:LIB1``). | ||||||
| - ``orgs`` (optional): Comma-separated list of orgs to filter by (e.g. ``Org1,Org2``). | ||||||
| - ``search`` (optional): Search term to filter users by username, full name, or email. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the edit: I see it is just the user's full name, wondering if we should allow to search by this given is not visible in the UI, it can return results where the search string isn't visible, I think it might be confusing but maybe no big deal
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not being displayed in the design, but in past discussions with Guillermo we decided to include it in the search fields. |
||||||
| - ``assignments_limit`` (optional): Maximum number of assignments to populate in | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should set an upper limit for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what would be a good number? maybe 10?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think 10 is fine. |
||||||
| the ``assignments`` array for each user. Defaults to 3. The full total is always | ||||||
| reported in ``assignment_count``. | ||||||
| - ``sort_by`` (optional): Field to sort by. Options: ``username``, ``full_name``, | ||||||
| ``email``. Defaults to ``username``. | ||||||
| - ``order`` (optional): Sort order, ``asc`` or ``desc``. Defaults to ``asc``. | ||||||
| - ``page`` (optional): Page number for pagination. | ||||||
| - ``page_size`` (optional): Number of items per page. | ||||||
|
|
||||||
| Example: | ||||||
|
|
||||||
| .. code:: | ||||||
|
|
||||||
| GET /api/authz/v1/users/?roles=library_admin&orgs=Org1&search=john&assignments_limit=3&sort_by=username&order=asc&page=1&page_size=10 | ||||||
|
|
||||||
| Response Body: | ||||||
| """""""""""""" | ||||||
|
|
||||||
| Format: | ||||||
|
|
||||||
| .. code:: ts | ||||||
|
|
||||||
| { | ||||||
| count: number | ||||||
| next: string | null | ||||||
| previous: string | null | ||||||
| results: Array<{ | ||||||
| username: string | ||||||
| full_name: string | ||||||
| email: string | ||||||
| assignment_count: number | ||||||
| assignments: Array<{ | ||||||
| is_superadmin: boolean | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I wasn't aware of this, so we are no longer showing superadmins at all? |
||||||
| role: string | ||||||
| org: string | ||||||
| scope: string | ||||||
| scope_display_name: string | ||||||
| permission_count: number | ||||||
|
Comment on lines
+114
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to confirm if these fields in assignments are sufficient to display the information in the table correctly, especially when the scope is a Glob type (org and platform). I was thinking maybe it would be good to have a cc @dcoa |
||||||
| }> | ||||||
| }> | ||||||
| } | ||||||
|
|
||||||
| The ``assignments`` array is populated with up to ``assignments_limit`` entries | ||||||
| (default 3), while ``assignment_count`` always reflects the user's total number of | ||||||
| assignments regardless of the limit. | ||||||
|
|
||||||
| Each assignment includes a ``scope_display_name`` field alongside the existing | ||||||
| ``scope`` key. This is a new field relative to the current assignment-shaped | ||||||
| endpoints (``GET /api/authz/v1/assignments/`` and | ||||||
| ``GET /api/authz/v1/users/<username>/assignments/``), which only return the | ||||||
| ``scope`` key. The UI needs the human-readable name to label each assignment, so | ||||||
| ``scope_display_name`` carries it while ``scope`` remains the stable machine | ||||||
| identifier. | ||||||
|
|
||||||
| The display name is not stored in the authorization policy store; it lives in the | ||||||
| platform models (``CourseOverview.display_name`` for courses and the library's | ||||||
| ``learning_package.title`` for content libraries). Because assignments are read | ||||||
| from the policy store, resolving names requires reading those models. Implementers | ||||||
| must fetch the names in bulk (a single batched lookup per scope type per page, | ||||||
| keyed by scope), following the batching pattern already used for user data | ||||||
| (``get_user_map``) and for scope display names in ``ScopesAPIView``. Resolving the | ||||||
| name per assignment row would introduce N+1 queries and must be avoided. | ||||||
|
|
||||||
| For assignments that have no single concrete resource, such as superadmin entries | ||||||
| or glob scopes, ``scope_display_name`` has no meaningful value and is returned as | ||||||
| an empty string. If an assignment references a scope whose backing course or | ||||||
| library no longer exists, the name cannot be resolved and is likewise returned as | ||||||
| an empty string. | ||||||
|
|
||||||
| The example below shows the different scope kinds an assignment can reference at | ||||||
| the time of writing: a specific library, a specific course, an organization-level | ||||||
| glob (all libraries or all courses in an org), and a platform-level glob (all | ||||||
| libraries or all courses platform-wide). Specific scopes resolve to a | ||||||
| ``scope_display_name``; glob scopes have no single backing resource, so | ||||||
| ``scope_display_name`` is an empty string. | ||||||
|
|
||||||
| .. code:: json | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to include glob scopes in the example (org and platform)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added examples for courses and glob scopes. Thanks for the suggestion! |
||||||
|
|
||||||
| { | ||||||
| "count": 2, | ||||||
| "next": null, | ||||||
| "previous": null, | ||||||
| "results": [ | ||||||
| { | ||||||
| "username": "jane_doe", | ||||||
| "full_name": "Jane Doe", | ||||||
| "email": "jane_doe@example.com", | ||||||
| "assignment_count": 3, | ||||||
| "assignments": [ | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing this will return all types of assignments, right? Will the UI ever have a scope type filter?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, all type of assignments, the UI as I understand it only filters by specific scopes, not by type, but let me confirm this with María de los Ángeles.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed by María de los Ángeles: it only filters by specific scopes.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought assignments would be paginated, not only truncated. EDIT: I see in the consequences that this won't be immediately supported.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, not paginated, as the current UI design only shows a "preview" of the assignments, to see more, it links to the single user page which uses a different endpoint. |
||||||
| { | ||||||
| "is_superadmin": false, | ||||||
| "role": "library_admin", | ||||||
| "org": "Org1", | ||||||
| "scope": "lib:Org1:LIB1", | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the table shows the name of the scope instead of the id
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing this out. I added a new "scope_display_name" key and documented edge cases and implementation details. |
||||||
| "scope_display_name": "Intro to CS Library", | ||||||
| "permission_count": 11 | ||||||
| }, | ||||||
| { | ||||||
| "is_superadmin": false, | ||||||
| "role": "course_staff", | ||||||
| "org": "Org1", | ||||||
| "scope": "course-v1:Org1+CS101+2024", | ||||||
| "scope_display_name": "Introduction to Computer Science", | ||||||
| "permission_count": 27 | ||||||
| }, | ||||||
| { | ||||||
| "is_superadmin": false, | ||||||
| "role": "library_admin", | ||||||
| "org": "Org1", | ||||||
| "scope": "lib:Org1:*", | ||||||
| "scope_display_name": "", | ||||||
| "permission_count": 11 | ||||||
| } | ||||||
| ] | ||||||
| }, | ||||||
| { | ||||||
| "username": "john_doe", | ||||||
| "full_name": "John Doe", | ||||||
| "email": "john_doe@example.com", | ||||||
| "assignment_count": 2, | ||||||
| "assignments": [ | ||||||
| { | ||||||
| "is_superadmin": false, | ||||||
| "role": "course_staff", | ||||||
| "org": "Org2", | ||||||
| "scope": "course-v1:Org2+*", | ||||||
| "scope_display_name": "", | ||||||
| "permission_count": 27 | ||||||
| }, | ||||||
| { | ||||||
| "is_superadmin": false, | ||||||
| "role": "library_user", | ||||||
| "org": "*", | ||||||
| "scope": "lib:*", | ||||||
| "scope_display_name": "", | ||||||
| "permission_count": 4 | ||||||
| } | ||||||
| ] | ||||||
| } | ||||||
| ] | ||||||
| } | ||||||
|
|
||||||
| Notes on the scope kinds shown above: | ||||||
|
|
||||||
| - ``lib:Org1:LIB1`` and ``course-v1:Org1+CS101+2024`` are specific scopes, so | ||||||
| their ``scope_display_name`` is resolved from the backing library/course. | ||||||
| - ``lib:Org1:*`` and ``course-v1:Org2+*`` are organization-level globs; ``org`` | ||||||
| reflects the org (``Org1``, ``Org2``) and ``scope_display_name`` is empty. | ||||||
| - ``lib:*`` is a platform-level glob; ``org`` is ``"*"`` and | ||||||
| ``scope_display_name`` is empty. | ||||||
|
|
||||||
| Possible response codes: | ||||||
| """""""""""""""""""""""" | ||||||
|
|
||||||
| - 200: Ok, includes the Response Body defined above. | ||||||
| - 400: Bad Request, happens when the request parameters are invalid. | ||||||
| - 401: Unauthorized, happens when the user is not authenticated/logged in. | ||||||
| - 403: Forbidden, happens when the user does not have the required permissions. | ||||||
|
|
||||||
| Consequences | ||||||
| ************ | ||||||
|
|
||||||
| - The existing /api/authz/v1/users/ endpoint will be extended to return the | ||||||
| additional data: a nested ``assignments`` array per user and the renamed | ||||||
| ``assignment_count`` field. | ||||||
| - Each nested assignment gains a ``scope_display_name`` field, which is an addition | ||||||
| compared to the existing assignment-shaped endpoints that return only the | ||||||
| ``scope`` key. Since display names are not held in the policy store, this field | ||||||
| requires reading the platform course/library models. Implementation must resolve | ||||||
| these names with batched, per-page lookups to avoid N+1 query performance issues. | ||||||
| - The endpoint gains a new ``roles`` query parameter that filters the returned | ||||||
| users by a comma-separated list of roles, supporting the role filter in the | ||||||
| Team Members view. | ||||||
| - Renaming ``assignation_count`` to ``assignment_count`` is technically a breaking | ||||||
| change to the response body. It is low-risk here because | ||||||
| frontend-app-admin-console does not call the ``GET /api/authz/v1/users/`` endpoint | ||||||
| at all, so no released client depends on the field. Any internal tests or | ||||||
| fixtures referencing ``assignation_count`` must still be updated. | ||||||
| - Both ``assignments`` and ``assignment_count`` reflect only the assignments the | ||||||
| calling user is permitted to see, so values may differ between viewers for the | ||||||
| same target user. | ||||||
| - The nested ``assignments`` array is truncated to ``assignments_limit`` and is | ||||||
| not paginated. A follow-up is needed if the UI requires a defined order for the | ||||||
| truncated entries, since no ordering is currently guaranteed within a user's | ||||||
| assignments. | ||||||
|
|
||||||
|
|
||||||
| Rejected Alternatives | ||||||
| ********************* | ||||||
|
|
||||||
| - Creating a new endpoint: The /api/authz/v1/users/ endpoint already provides most | ||||||
| of the required logic, and it was originally created for this use case. | ||||||
| Extending it is the simplest and most direct solution. | ||||||
|
|
||||||
| - Loading each user's assignments dynamically from the frontend after the initial | ||||||
| /api/authz/v1/users/ response: This is suboptimal because it creates N+1 requests | ||||||
| for a single page, increasing load time and placing unnecessary strain on the | ||||||
| server. | ||||||
|
|
||||||
|
|
||||||
| References | ||||||
| ********** | ||||||
|
|
||||||
| - `Figma design`_ for the Team Members tab in the Admin Console. | ||||||
|
|
||||||
| .. _Figma design: https://www.figma.com/design/xnmQJq1cTRVqNrs31R8zDM/AuthZ---v2?node-id=40000174-1081 | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think pointing here to the public figma design will illustrate it better, at least to the most stable version of the design
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been using this as reference: https://www.figma.com/design/xnmQJq1cTRVqNrs31R8zDM/AuthZ---v2?node-id=40000174-1081
I'm asking María de los Ángeles what would be the best link to add. Thanks for the suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed and added the link.