Repository navigation
[PM-44796] feat: Require scoped policies on the Admin Console public API - #8564
withinfocus wants to merge 5 commits into
Conversation
Replace the class-level Organization policy on the public Members,
Groups, Collections, Policies and Organization import controllers with
per-action policies: reads require the resource's read policy and
writes its write policy, each of which also accepts the legacy
api.organization scope. PUT public/policies/{type} keeps the legacy
Organization policy because there is no policies.write scope, and
POST public/organization/import requires both members.write and
groups.write.
Add tests that evaluate each action's combined authorization metadata
in the running Api host against legacy and scoped principals.
Public API commands run as a system user, and system users skip the organization role hierarchy, so a members.write scope could otherwise promote anyone to Owner or act on Owners, Admins and Custom members. Add ICurrentContext.IsScopedOrganizationApiKey, true for an organization client token without the legacy api.organization scope, and enforce in Core that a scoped key only acts on members with the User role: - role change validation (V2 update) rejects elevated current or new roles - invite rejects any invite that isn't for the User role - revoke, remove, restore and reinvite reject non-User targets - import does not remove, or link an external ID to, non-User members Legacy api.organization tokens keep their current behavior.
…affected
IsScopedOrganizationApiKey was true for any organization.* client
without the api.organization scope. The SCIM host builds exactly that
principal (organization.{orgId}, scope api.scim), so SCIM delete, PUT
deactivate and restore were rejected for Admin, Owner and Custom
members by the User-only rule.
Detect a scoped key by the client id Identity issues for it,
organization.{organizationId}.{keyId} with both parts GUIDs. Legacy
keys and the SCIM principal are not scoped whatever their scopes.
The SCIM integration test auth handler now emits the same claims as
ApiKeyAuthenticationHandler, and new tests cover deleting and
deactivating an Admin member.
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE This PR replaces the class-level |
A scoped organization API key works at the User access level, but it
could still change which groups Owner, Admin and Custom members belong
to, which changes their collection access and their membership.
Extend the User-only rule to group membership for scoped keys:
- PUT public/members/{id}/group-ids rejects members who aren't User
(UpdateOrganizationUserGroupsCommand)
- group create and update with a member list, PUT
public/groups/{id}/member-ids and DELETE public/groups/{id} reject any
change that would add or remove a non-User member; elevated members
already in the group can stay (new ScopedApiKeyGroupMemberValidator)
- import group sync leaves non-User members' group memberships as they
are instead of adding or removing them
The public member-ids and delete endpoints write through the group
repository directly, so they call the Core validator before writing.
Legacy api.organization keys and user tokens keep their behavior.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## arch/pm-44795/org-api-scopes #8564 +/- ##
================================================================
+ Coverage 66.86% 66.96% +0.09%
================================================================
Files 2591 2593 +2
Lines 111158 111258 +100
Branches 10123 10144 +21
================================================================
+ Hits 74328 74505 +177
+ Misses 34350 34271 -79
- Partials 2480 2482 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
🎟️ Tracking
PM-44796, part of epic PM-28993. Third of nine stacked PRs.
📔 Objective
Moves the public Members, Groups, Collections, Policies and import endpoints to scoped policies, and limits scoped keys to members with the User role.
PUT public/policies/{type}stays on the legacy policy. Import needs both members write and groups write.PUT public/members/{id}/group-ids, group create and update,PUT public/groups/{id}/member-idsand deleting a group that contains one are rejected. A group can keep elevated members it already has while Users are added or removed. Group member-ids and delete write straight to the repository today, so the controller calls the Core validator before writing; routing them through the group commands would add events and checks the legacy key doesn't have.ICurrentContext.IsScopedOrganizationApiKeytells Core a scoped key is acting. It's true only for the scoped client ID shape,organization.{organizationId}.{keyId}, so legacy keys and the SCIM principal (organization.{organizationId}withapi.scim) are unaffected. The commands read it directly, so enforcement doesn't depend on each controller passing the right actor.