Skip to content

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

Draft
jrmccannon wants to merge 1 commit into
mainfrom
jmccannon/ac/pm-38927-org-user-role-validation
Draft

[PM-38927] - Extract Organziation User Role Validation#7876
jrmccannon wants to merge 1 commit 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
@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.24390% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.22%. Comparing base (2c4874b) to head (81ad189).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...OrganizationUsers/OrganizationUserAction/Errors.cs 50.00% 2 Missing ⚠️
...izationUserAction/OrganizationUserActionRequest.cs 87.50% 1 Missing ⚠️
...ationUserAction/OrganizationUserActionValidator.cs 96.55% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7876   +/-   ##
=======================================
  Coverage   61.21%   61.22%           
=======================================
  Files        2217     2222    +5     
  Lines       98045    98093   +48     
  Branches     8847     8856    +9     
=======================================
+ Hits        60018    60057   +39     
- Misses      35911    35920    +9     
  Partials     2116     2116           

☔ 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.

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