Skip to content

[PM-38927] - Extract Organziation User Role Validation#7876

Open
jrmccannon wants to merge 5 commits into
mainfrom
jmccannon/ac/pm-38927-org-user-role-validation
Open

[PM-38927] - Extract Organziation User Role Validation#7876
jrmccannon wants to merge 5 commits into
mainfrom
jmccannon/ac/pm-38927-org-user-role-validation

Conversation

@jrmccannon

@jrmccannon jrmccannon commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

PM-38927

📔 Objective

Consolidates logic for Organization User Aciton validation to single class. This will be consumed when checking if an org user can perform an action or if an org user can perform an action on another org user.

@jrmccannon jrmccannon added the t:feature Change Type - Feature Development label Jun 26, 2026
@jrmccannon jrmccannon changed the title [PM-38927] - [PM-38927] - Extract Organziation User Role Validation Jun 26, 2026
@jrmccannon jrmccannon requested a review from eliykat June 26, 2026 16:17
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.36%. Comparing base (e899a9c) to head (ac100b9).

Files with missing lines Patch % Lines
...OrganizationUsers/OrganizationUserAction/Errors.cs 50.00% 1 Missing ⚠️
...ionUserAction/OrganizationUserValidationService.cs 92.30% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7876   +/-   ##
=======================================
  Coverage   61.35%   61.36%           
=======================================
  Files        2236     2238    +2     
  Lines       98547    98563   +16     
  Branches     8911     8914    +3     
=======================================
+ Hits        60468    60482   +14     
- Misses      35943    35944    +1     
- Partials     2136     2137    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

A couple of quick comments, but my main feedback - my thinking was to re-introduce a service layer for smaller shared logic like this. e.g. OrganizationUserValidationService which can be called by multiple validators associated with different flows, and which would provide a bit more flexibility. That flexibility includes multiple params instead of request objects (where reasonable) which allows it to be easily called from different contexts without them having to share or construct the same request object (which adds a lot of boilerplate).

i.e. command -> validator (1:1 relationship with the command) -> domainValidationService (shared logic only, many:many relationship with validators).

Not sure if we discussed this - I might be mixing up my conversations. I will try to document some of my thoughts here this sprint.

The case against using dedicated validator classes for this level is:

  • confusion between command-specific validators and reusable validators
  • proliferation of classes (and associated boilerplate) for small, reusable bits of logic
  • excessive request object mapping for them to be reusable

Let me know what you think and we can discuss further.

Comment on lines +21 to +32
/// <summary>
/// Validates whether the acting user can manage members at all, independent of any target. Use this where
/// authorization needs the gate without a specific target. Returns valid, or a <see cref="MissingManagePermissionError"/>.
/// </summary>
public static async Task<ValidationResult<OrganizationUserManageMembersRequest>> ValidateCanManageMembersAsync(OrganizationUserManageMembersRequest request)
{
var isProvider = !IsAuthorizedByRole(request) && await request.IsProviderUserForOrg();

return IsAuthorizedByRole(request) || isProvider
? Valid(request)
: Invalid(request, new MissingManagePermissionError());
}

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.

We can already do simple RBAC checks well at the controller layer via attributes. I don't think we need to duplicate it here. This would be limited to the escalation concern.

Comment on lines +52 to +55
// The acting user can manage members; confirm the target's role is within their authority. A provider
// user has Owner-level authority, so treat them as an Owner. The expensive provider lookup already ran
// inside the gate above, so we reuse the cheap synchronous role check here rather than repeating it.
var effectiveRole = IsAuthorizedByRole(request) ? request.ActingUserRole : OrganizationUserType.Owner;

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.

I think this could be simplified, and it's better to avoid implicit logic such as:

  • if you're not an admin then you must be a provider based on the logic of a separate private method
  • if you're a provider we're going to say you're an owner

I would just check the role first, then check provider status if required. If we are doing multiple provider lookups there are better ways to handle that - e.g. push it out to the caller to pass a memoized Func or just a concrete boolean value.

Comment on lines +25 to +31
public record OrganizationUserActionRequest(
OrganizationUserType? ActingUserRole,
Permissions? ActingUserPermissions,
Func<Permissions, bool> PermissionPicker,
Func<Task<bool>> IsProviderUserForOrg,
OrganizationUserType TargetUserRole)
: OrganizationUserManageMembersRequest(ActingUserRole, ActingUserPermissions, PermissionPicker, IsProviderUserForOrg);

@eliykat eliykat Jun 27, 2026

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.

This is really flexible but I think it degrades the interface. Simplified:

  • OrganizationUser? actingUser
  • OrganizationUser targetUser
  • bool actingUserIsProvider

Then if this is a service method - they can easily be passed as arguments.

@jrmccannon

Copy link
Copy Markdown
Contributor Author

Let me know what you think and we can discuss further.

That all makes sense and we did have the combo. Went down this path to see how it would look and got a little carried away. I've updated it to match the signature requested and hopefully cleaned up some other things.

@jrmccannon jrmccannon requested a review from eliykat June 29, 2026 19:26
@jrmccannon jrmccannon marked this pull request as ready for review June 29, 2026 19:32
@jrmccannon jrmccannon requested a review from a team as a code owner June 29, 2026 19:32
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the extraction of organization-user "can manage" authorization logic into a new reusable OrganizationUserValidationService. The change is purely additive infrastructure (DI registration plus unit tests) with no production consumers yet, consistent with the PR's stated intent. The new rank-based authority rules faithfully mirror the existing canonical logic in OrganizationService.ValidateOrganizationUserUpdatePermissions, and the privilege-escalation edge case (e.g. an Admin demoting an Owner) is explicitly documented and covered by tests.

Code Review Details

No findings.

Notes verified during review:

  • DI uses TryAddScoped, file-scoped namespaces, and nullable reference types per server conventions.
  • CanManage correctly denies Custom users with null/absent permissions via the _ => false fallthrough.
  • Test coverage spans Owner/Admin/Custom/User acting roles, provider authority, missing ManageUsers, and the current-vs-new-role escalation check.
  • The three existing review threads from eliykat reference earlier file/types (OrganizationUserActionValidator, OrganizationUserActionRequest) that no longer exist; the author reworked the design into the simpler (actingUser, targetUser, actingUserIsProvider) signature that was suggested. Those threads are outdated.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants