[PM-40211] Authorize Access Rule endpoints with IOrganizationRequirement - #8162
[PM-40211] Authorize Access Rule endpoints with IOrganizationRequirement#8162Hinton wants to merge 3 commits into
Conversation
a3b75df to
ae5a67e
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## pam/enable-pam-flows #8162 +/- ##
========================================================
- Coverage 67.92% 63.55% -4.38%
========================================================
Files 2338 2339 +1
Lines 101555 101566 +11
Branches 9186 9187 +1
========================================================
- Hits 68983 64547 -4436
- Misses 30265 34805 +4540
+ Partials 2307 2214 -93 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ae5a67e to
f10979c
Compare
f10979c to
0356bd4
Compare
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the declarative authorization added to the PAM access-rule routes: a group-level Code Review DetailsNo blocking findings. Notes considered and intentionally not raised as findings:
|
| /// rules gate who can lease credentials out of it, which is not theirs to change. Note this group gate is the only | ||
| /// thing keeping providers out — <see cref="ManageAccessRulesRequirement"/> derives from | ||
| /// <c>BasePermissionRequirement</c>, which falls back to authorizing a provider for the organization. Removing or | ||
| /// weakening the group requirement would silently readmit them to the write endpoints. |
There was a problem hiding this comment.
This suggests that you don't actually want to reuse BasePermissionRequirement because you don't want to authorize providers. It works for most of our custom permissions, but you can always implement IOrganizationRequirement directly if you don't want that behavior.
For example:
public class ManageAccessRulesRequirement : IOrganizationRequirement
{
public Task<bool> AuthorizeAsync(CurrentContextOrganization? organizationClaims,
Func<Task<bool>> isProviderUserForOrg)
{
var authorized = organizationClaims is
{ Type: OrganizationUserType.Owner }
or { Type: OrganizationUserType.Admin }
or { Type: OrganizationUserType.Custom, Permissions.ManageAccessRules : true };
return Task.FromResult(authorized);
}
}This can be used inside Authorize<T> in the same way, so no change to the consumers.
You can own this requirement if desired, because it reflects the authorization rules for your domain.
0356bd4 to
2d7051c
Compare
f114a8f to
6afb818
Compare
The merge-base changed after approval.
Declarative authorization on the access-rule routes (ADR-0022): the group requires
organization membership, and writes additionally require authority over rule
authorship via the new ManageAccessRulesRequirement. ASP.NET combines the group and
endpoint policies, so a write has to satisfy both.
Deviation from the story: no named-policy bridge. The story specifies policy-name
constants in Policies.cs registered in Api/Startup.cs, because Pam.csproj cannot
reference Api and so could not reach AuthorizeAttribute<T>. PM-41272 has since
extracted that authorization code into src/Libraries/OrganizationAuthorization,
which depends only on Core — so Pam references the library directly and attaches
AuthorizeAttribute<T> to the group and to the write routes. Policies.cs and
Startup.cs are untouched.
The requirements are carried as endpoint metadata, which AuthorizationMiddleware
combines with the group-level Policies.Application rather than replacing it; a test
asserts both are present on every route. OrganizationRequirementHandler resolves the
organization from the {orgId:guid} group prefix and is already DI-registered by
AddOrganizationAuthorization.
Providers are excluded from the resource entirely. The group gate is deliberately
MemberRequirement and not MemberOrProviderRequirement: providers manage an
organization's billing and configuration, but access rules gate who can lease
credentials out of it, which is not theirs to read or change. That gate is
load-bearing rather than decorative — ManageAccessRulesRequirement derives from
BasePermissionRequirement, whose final arm authorizes any provider for the
organization, so the write routes would admit providers on the permission alone.
Two tests pin this: every write carries MemberRequirement alongside the permission,
and no access-rule route carries MemberOrProviderRequirement.
Scope: this covers the Access Rule half of the story only. The remaining half — the
usage gate on access-request submission, which must check both the member's
AccessPam and the organization's UsePam, plus the CipherLeaseGate parity decision —
cannot land yet. SubmitAccessRequestCommand does not exist on main: the access-request
and cipher-lease handlers are still NotImplementedException scaffolds, so there is no
submission path to gate. That gate belongs with the request/lease behaviour slices.
For the same reason there are no imperative EnsureMemberAsync/EnsureAdminAsync checks
to delete — AccessRuleEndpointsHandler never had them on main.
Note the observable change the story flags (breakdown review note 8) holds here:
unauthorized calls now return 403 from the authorization middleware.
BasePermissionRequirement's final arm authorizes any provider managing the organization, which is right for most custom permissions but not for access rules: they gate who can lease credentials out of an organization, which is not a provider's to change. Deriving from it left the group's MemberRequirement as the only thing keeping providers off the write endpoints — coupling the previous remarks had to document rather than remove. Implement IOrganizationRequirement directly instead, and move the requirement into the PAM project, which owns the domain rule. This reverts the additions to the Admin Console's shared PermissionRequirements, so the PR no longer changes any AC-owned file. Also regenerates util/SqlServerEFScaffold's lock file, which transitively references Pam and was missed when Pam.csproj gained OrganizationAuthorization.
6afb818 to
40283ea
Compare
The endpoint-registration tests assert which requirements are attached to which route, but stop before the pipeline runs — presence in metadata is not enforcement. These cover the denials, which stay valid once the handler scaffolds are implemented.
There was a problem hiding this comment.
AC no longer owns any files in this PR, so no approval required, but the authorization implementation looks good to me.
On more of a business logic / product note:
providers manage an organization's billing and configuration, but access rules gate who can lease credentials out of it, which is not theirs to read or change.
This might be overstating it a little - today providers can assign and remove access to items by managing collection assignments. They can't access items themselves, but it's well within their purpose to set up group and collection hierarchies for their clients to make sure they're using Bitwarden properly. I can imagine a use case where providers configure PAM rules for their clients as part of setting up their organization. It's OK if they're out of scope for the MVP, just wanted to clarify.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-40211
📔 Objective
Declarative authorization on the access-rule routes (ADR-0022): reads require organization membership, writes require authority over rule authorship via the new
ManageAccessRulesRequirement.Worth flagging for review:
Policies.csregistered inApi/Startup.cs, becausePam.csprojcannot reference Api and so could not reachAuthorizeAttribute<T>. PM-41272 has since extracted that code intosrc/Libraries/OrganizationAuthorization, which depends only on Core — so Pam references the library directly and attaches requirements per route.Policies.csandStartup.csare untouched.AuthorizationMiddlewarecombines with the group-levelPolicies.Application; a test asserts both are present on every route.OrganizationRequirementHandlerresolves the org from the{orgId:guid}group prefix and is already DI-registered.AccessRuleAuthorizationTests(Api.IntegrationTest) drives the real pipeline: non-members, plain members, Custom users withoutManageAccessRules, and provider users all get 403; members and Owners get through. Presence in metadata is not enforcement, so the registration tests alone would not catch anAllowAnonymous, a group policy that replaced rather than combined, or a new route added without the attribute. Only denials are asserted, so these stay valid once the handler scaffolds are implemented.AccessPamand the org'sUsePam) and theCipherLeaseGateparity decision cannot land yet —SubmitAccessRequestCommanddoes not exist on main, since the access-request and cipher-lease handlers are stillNotImplementedExceptionscaffolds. There is no submission path to gate; that work belongs with the request/lease behaviour slices. For the same reason there were no imperativeEnsureMemberAsync/EnsureAdminAsyncchecks to delete —AccessRuleEndpointsHandlernever had them on main. PM-40211 should stay open for the remaining half.BasePermissionRequirement/MemberOrProviderRequirementauthorize provider users — is deliberately avoided: the group usesMemberRequirement, andManageAccessRulesRequirementimplementsIOrganizationRequirementdirectly rather than deriving fromBasePermissionRequirement. Providers are denied on both reads and writes — access rules gate who can lease credentials out of an organization, which is not theirs to read or change. Two tests pin this, includingMapPamEndpoints_AccessRulesNeverAuthorizeProvidersByMembership.Lockfile changes are the new project reference only — no platform churn.
Depends on #8160 and #8161 — review those first.