User-group + user-role plugins¶
dhis2 user-group and dhis2 user-role round out the user-administration surface that dhis2 user started. Groups own sharing (who can read/write what metadata, data-wise). Roles own authorities (what verbs a user can do — F_METADATA_EXPORT, F_USER_VIEW, ALL, …). Authorities are granted by adding a user to a role, not by editing the user directly — hence the verbs live here, not on dhis2 user.
Both plugins share the same shape: reads via the generated /api/userGroups / /api/userRoles CRUD accessors (full metadata query surface), membership edits via the dedicated single-entry endpoints that avoid the fetch-and-rewrite race.
User groups¶
- CLI:
dhis2 user-group {list,get,add-member,remove-member,sharing-get,sharing-grant-user} - MCP:
user_group_{list,get,add_member,remove_member,sharing_get} - Service:
packages/dhis2w-core/src/dhis2w_core/plugins/user_group/service.py
Membership¶
# list + inspect
dhis2 user-group list
dhis2 user-group get <group-uid>
# single-member edits — POST/DELETE /api/userGroups/<gid>/users/<uid>
# (DHIS2 v42 calls the collection `users` on UserGroup, not `members`.)
dhis2 user-group add-member <group-uid> <user-uid>
dhis2 user-group remove-member <group-uid> <user-uid>
The single-member endpoints beat PATCHing the full group because they're atomic and don't race with other edits. No need to re-fetch + re-POST the entire users[] array.
Sharing¶
Every persistable DHIS2 object has a sharing block: who can read it, who can write it, who owns it. Groups are themselves shareable — you grant access to the group metadata, which is distinct from the access the group's members collectively have on other objects.
# inspect
dhis2 user-group sharing-get <group-uid>
# grant — preserves existing grants, appends (or overwrites) the target user.
dhis2 user-group sharing-grant-user <group-uid> <user-uid> --metadata-write
dhis2 user-group sharing-grant-user <group-uid> <user-uid> --metadata-read
sharing-grant-user fetches the current sharing block first, replays every existing user and group grant, then appends the new target grant. The result is POSTed to /api/sharing (typed via dhis2w_client.apply_sharing) so no JSON-Patch juggling is needed — see docs/api/sharing.md.
User roles¶
- CLI:
dhis2 user-role {list,get,authorities,add-user,remove-user} - MCP:
user_role_{list,get,authorities,add_user,remove_user} - Service:
packages/dhis2w-core/src/dhis2w_core/plugins/user_role/service.py
Authority listing¶
dhis2 user-role list # table: id, name, #auths, #users
dhis2 user-role authority-list <role-uid> # one authority per line, sorted
authority-list is the fast way to answer "what can this role do?" without dumping the full UserRole model.
Role membership¶
# Grant / revoke a role on a user — POST/DELETE /api/userRoles/<rid>/users/<uid>.
dhis2 user-role add-user <role-uid> <user-uid>
dhis2 user-role remove-user <role-uid> <user-uid>
Same atomic-single-entry rationale as user-group members.
MCP parity¶
Every CLI verb has an MCP tool with matching arg names. Read tools (*_list, *_get, user_role_authority_list, user_group_sharing_get) are safe for agent callers. Write tools (add_member, remove_member, add_user, remove_user) mutate real DHIS2 state — scope agent profiles with read-only PATs if you don't want them triggered.
Typed models¶
dhis2w_client.generated.v42.oas.UserGroupandUserRole— the OpenAPI-derived classes. Both carry the full sharing block, full member list, and (for roles)authorities: list[str].dhis2w_client.SharingBuilder/apply_sharing/get_sharing— the typed helpers used bysharing-grant-user. See Sharing for the access-string grammar and the builder API.
What's not in scope¶
- Role creation via CLI.
/api/userRolesaccepts a fullUserRolePOST; the generatedclient.resources.user_roles.create(role)already covers it. A CLI wrapper would be thin; add when a concrete workflow needs it. dhis2 authority list— DHIS2's global authority inventory at/api/authorities. Useful for discovering authority strings when you build your own roles; out of scope here, belongs in a futuredhis2 system authoritiessub-command.