Auth/PM-42167 - SSO - Add Staged Org User Support - #8228
Auth/PM-42167 - SSO - Add Staged Org User Support#8228JaredSnider-Bitwarden wants to merge 10 commits into
Conversation
Promotes a Staged OrgUser row to Invited when a brand-new user JIT-provisions against it via SSO (AutoProvisionUserAsync Scenario 3). Behind the PM34423StagedStatus feature flag. Extracts the pre-existing seat-availability check into a private helper and calls it from both the fresh-JIT site and the new Staged-promotion site. Staged rows don't count against occupied seats but Invited does, so the promotion must run the same "enough seats or autoscale" check. Integration tests cover: the Staged-promotion happy path, promotion blocked at seat cap (self-hosted throw and cloud autoscale-fails throw), and all six branches of the seat-availability check as lock-in for the extraction.
…tations Scenario 3 of AutoProvisionUserAsync mutates the pre-existing OrganizationUser row (UserId always, Status when the Staged promotion fires). The OrganizationUser_Update sproc uses whatever RevisionDate the entity carries, so callers must bump it explicitly — otherwise the row's watermark stays at its Staged-creation timestamp and watermark-driven consumers miss the change. Also updates the Staged-promotion integration test to capture the seeded row's initial RevisionDate and assert the refreshed value has advanced.
…ged OrgUsers When an existing BW user attempts SSO against a Staged OrganizationUser row matching their email, promote the row to Invited, send the standard invite email, and redirect the SSO callback to /login with InviteAcceptanceRequired so the user completes acceptance via the normal token flow. Behind the PM34423StagedStatus feature flag. Extracted into a PromoteStagedOrgUserAndSendInviteAsync helper that verifies seat capacity (autoscales on cloud when possible), flips status, bumps RevisionDate, logs OrganizationUser_Invited, and dispatches ISendOrganizationInvitesCommand. UserId stays null to match the standard admin-invite shape. Integration tests cover: promote+invite happy path, self-hosted seat cap throws NoSeatsAvailable (no invite sent, row unmutated), cloud autoscale fails NoSeatsAvailable (same). TODOs left inline for product/AC to weigh in on (a) whether OrganizationUser_Invited is the right event type when the trigger is a user SSO login rather than an admin action and (b) whether the client redirect should carry a Staged-specific error code distinct from InviteAcceptanceRequired.
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Re-reviewed at head Code Review DetailsNo findings at or above the reporting threshold. Considered and dropped: the seat cap grown by |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8228 +/- ##
==========================================
+ Coverage 63.29% 63.33% +0.03%
==========================================
Files 2401 2402 +1
Lines 104043 104113 +70
Branches 9426 9431 +5
==========================================
+ Hits 65857 65938 +81
+ Misses 35930 35921 -9
+ Partials 2256 2254 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…motion Introduces SsoAuthnStagedOrgUserRequiresInviteAcceptanceException + a StagedOrgUserInviteAcceptanceRequired redirect error code so the web client can prompt the user to check their email for the freshly-sent invite, rather than reusing ssoOrgInviteAcceptanceRequired which implies the user should already have an invite in hand.
Product confirmed no user event should be emitted when an SSO login promotes a Staged OrganizationUser to Invited; drop the OrganizationUser_Invited event log call and its pending TODO.
…ment Drops the stale OrganizationUser_Invited event log claim (removed in 3575878) and rewrites the summary to describe only what this method mutates, not the internals of EnsureSeatAvailableAsync, ISendOrganizationInvitesCommand, or the accept endpoint.
…d promotions When JIT-provisioning a BW User against a Staged OrganizationUser row and the org is at seat cap without autoscale headroom, the seat check previously ran after RegisterSSOAutoProvisionedUserAsync — leaving a password-less BW User row plus a welcome email for a login that never completed. Move the seat check to the pre-user-creation gate so a rejection preserves prior state. Adds regression coverage: at-cap-autoscale-succeeds branch, feature-flag-off boundary at seat cap, and a two-phase test verifying rejection preserves state and retry after cap increase provisions cleanly.
…at guard Adds OrganizationUserStatusType.Staged to the existing Key Connector guard in AccountController.ExternalCallback so a Key Connector user who hits a Staged OrganizationUser row in the SSO-target org is cleanly rejected before the Staged-promotion branch runs. Previously the Staged case bypassed the guard and fell through to PromoteStagedOrgUserAndSendInviteAsync, consuming a seat and mailing an invite the KC user could not accept. Adds integration test ExternalCallback_WithExistingKeyConnectorUser_AndStagedOrgUser_ReturnsError alongside the two existing KC guard tests, asserting the KC error message, that no invite email is sent, and that the Staged row is not mutated.
…nd failure If SendInvitesAsync throws after the Staged row has been flipped to Invited, revert the row so a seat isn't consumed for an invite the user never received and the next SSO attempt cleanly re-runs the promotion instead of dead-ending on the "accept your invite" redirect.
ike-kottlowski
left a comment
There was a problem hiding this comment.
Out of band finding of the email failure seat consumption issue has been addressed.
Transition of User from staged to invited provided auto scale is enabled is accomplished. All other flows for JIT registration can complete with the promotion of the user to invited.
Everything is well tested, and code readability improvements are sound.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-42167
Clients PR: bitwarden/clients#22562
📔 Objective
Adds SSO support for organization users in the
Stagedstatus (PM34423StagedStatusflag-gated).StagedOrgUserInviteAcceptanceRequiredSSO redirect error code (client PR renders a "check your email" terminal page).Also extracts a shared seat-availability check for both paths and adds
Stagedto the existing Key Connector guard so KC users hitting a Staged row get the KC error instead of falling through to promotion.📸 Screenshots
See Clients PR: bitwarden/clients#22562