Skip to content

[PM-18236] - Use Single Org Requirement#6999

Open
jrmccannon wants to merge 6 commits intomainfrom
jmccannon/ac/pm-18236-use-single-org-req
Open

[PM-18236] - Use Single Org Requirement#6999
jrmccannon wants to merge 6 commits intomainfrom
jmccannon/ac/pm-18236-use-single-org-req

Conversation

@jrmccannon
Copy link
Contributor

@jrmccannon jrmccannon commented Feb 12, 2026

🎟️ Tracking

PM-18236

📔 Objective

In order to help encapsulate all business logic around Policies, we are moving the logic into requirements that can take the raw data and provide meaningful methods to discern the state of the organization and what actions can be performed by a user.

For single organization, this captures that a user cannot be another member of an organization in order to join an exisitng org that has Single Org enabled. If a user is a member of anotehr orgh with Single Organization Policy enabled, they cannot join or create another org.

For more information see here

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@jrmccannon jrmccannon requested a review from a team as a code owner February 12, 2026 22:29
@jrmccannon jrmccannon added the ai-review Request a Claude code review label Feb 12, 2026
@claude
Copy link
Contributor

claude bot commented Feb 12, 2026

Claude finished @jrmccannon's task in 4m 21s —— View job


Re-Review: PM-18236 - Use Single Org Requirement

Scope

Re-review triggered by new commit b066c726b ("Updated xml docs and renamed to be consistent") since the last review.

Changes Since Last Review

The new commit contains only cosmetic changes:

  • Renamed UserIsAMemberOfAnotherOrganizationError to UserIsAMemberOfAnotherOrganization for naming consistency
  • Added XML documentation comments to CanCreateOrganization, CanJoinOrganization, IsCompliantWithTargetOrganization, and IsEnabledForOtherOrganizationsUserIsAPartOf
  • Reformatted SingleOrganizationPolicyRequirementFactory class declaration to a single line
  • Updated test assertions to reference the renamed type

Previous Findings Status

Previous Finding Status
⚠️ IMPORTANT - Missing "has other organizations" check in IsCompliantForOrganizationToJoin Resolved in commit 824349813 -- IsCompliantWithTargetOrganization now correctly checks both conditions
🎨 SUGGESTED - Remove development notes before merge Resolved in commit 824349813 -- Notes removed

Full PR Verification

Verified correctness across all six modified command files:

  • AcceptOrgUserCommand -- ValidateSingleOrganizationPolicyAsync correctly delegates to CanJoinOrganization with the full allOrgUsers collection
  • ConfirmOrganizationUserCommand -- CheckPoliciesAsync correctly calls CanJoinOrganization with userOrgs and short-circuits via return
  • RestoreOrganizationUserCommand -- CheckPoliciesBeforeRestoreAsync correctly combines single-org and 2FA error reporting
  • CloudOrganizationSignUpCommand, InitPendingOrganizationCommand, SelfHostedOrganizationSignUpCommand -- All three correctly use CanCreateOrganization() with early return

The SingleOrganizationPolicyRequirementFactory correctly overrides ExemptStatuses to [] (receiving all user statuses including Invited/Revoked), since the requirement class handles status-based filtering internally via HasStatus([Accepted, Confirmed]).

All changes are properly gated behind the PolicyRequirements feature flag with the old code paths preserved.

No New Findings

The code is correct and well-tested with 531 lines of unit tests covering all edge cases. No issues identified.

Comment on lines 27 to 36
public Error? IsCompliantForOrganizationToJoin(Guid targetOrganizationId, OrganizationUser organizationUser) =>
!IsEnabledForTargetOrganization(targetOrganizationId)
|| policyDetails.Single(x => x.OrganizationUserId == organizationUser.Id)
.HasRole([OrganizationUserType.Admin, OrganizationUserType.Owner])
? null
: new UserIsAMemberOfAnotherOrganizationError();

public Error? IsEnabledForOtherOrganizationsUserIsAPartOf(Guid targetOrganizationId) =>
policyDetails.Any(p => p.OrganizationId != targetOrganizationId)
? new UserIsAMemberOfAnOrganizationThatHasSingleOrgPolicy()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: Correctness regression -- missing "has other organizations" check

IsCompliantForOrganizationToJoin returns an error when the target organization has SingleOrg policy enabled and the user is not an Admin/Owner, but it never checks whether the user actually has memberships in other organizations. This changes the behavior relative to the old code path.

The old code in all three callers (AcceptOrgUserCommand, ConfirmOrganizationUserCommand, RestoreOrganizationUserCommand) guards with:

if (allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId)
    && singleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))

That is, both conditions must be true: (1) the target org has SingleOrg policy, and (2) the user belongs to at least one other org. This is correct -- SingleOrg means "you may only be in one organization," so a user joining their first and only org should be allowed.

The new code only checks condition (1). A user with zero other org memberships will now be blocked with "Member cannot join the organization until they leave or remove all other organizations" -- which is self-contradictory since they have no other organizations to leave.

This regression affects:

  • Accept flow (AcceptOrgUserCommand.ValidateSingleOrganizationPolicyAsync)
  • Confirm flow (ConfirmOrganizationUserCommand.CheckPoliciesAsync, line 220)
  • Restore flow (RestoreOrganizationUserCommand.CheckPoliciesBeforeRestoreAsync, line 302)

Additionally, the HasRole([Admin, Owner]) check on line 30 is effectively dead code because the factory's default ExemptRoles already filters out Admin/Owner entries from policyDetails before the requirement class sees them.

The test AcceptOrgUserAsync_WithPolicyRequirementsEnabled_UserJoiningOrgWithSingleOrgPolicy_ThrowsBadRequest validates this incorrect behavior -- SetupCommonAcceptOrgUserMocks returns [] for GetManyByUserAsync (no other orgs), so the old code would not throw here.

The requirement needs access to either the count of other org memberships or a way to determine whether the user is only in the target org, so it can preserve the old semantics.

Comment on lines 39 to 43
// handle all this filtering in the req

// can return specific errors for if the org applies

// think about who is joining and what their role is and what their current status is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 SUGGESTED: Remove development notes before merge

These look like working notes from design/implementation. They should be cleaned up before this ships.

Suggested change
// handle all this filtering in the req
// can return specific errors for if the org applies
// think about who is joining and what their role is and what their current status is

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

Logo
Checkmarx One – Scan Summary & Details2d365ea9-7707-4fb3-8832-9627abf2c2c5

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Feb 12, 2026

Codecov Report

❌ Patch coverage is 98.50746% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.39%. Comparing base (94f7266) to head (b066c72).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...equirements/SingleOrganizationPolicyRequirement.cs 91.30% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6999      +/-   ##
==========================================
+ Coverage   56.28%   56.39%   +0.10%     
==========================================
  Files        1986     1990       +4     
  Lines       87667    87919     +252     
  Branches     7816     7854      +38     
==========================================
+ Hits        49345    49583     +238     
- Misses      36491    36501      +10     
- Partials     1831     1835       +4     

☔ View full report in Codecov by Sentry.
📢 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.

@sonarqubecloud
Copy link

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

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant