Skip to content

[PM-37469] UseRiskInsights organization ability: migration, plan mapping, admin toggle, license, and endpoint guard#7782

Open
AlexRubik wants to merge 22 commits into
mainfrom
dirt/pm-37471/map-enterprise-riskinsights
Open

[PM-37469] UseRiskInsights organization ability: migration, plan mapping, admin toggle, license, and endpoint guard#7782
AlexRubik wants to merge 22 commits into
mainfrom
dirt/pm-37471/map-enterprise-riskinsights

Conversation

@AlexRubik

@AlexRubik AlexRubik commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-37469

This PR consolidates all server-side subtasks of PM-37469 into one atomic change:

  • PM-37470 - data migration for existing Enterprise orgs
  • PM-37471 - map Enterprise plan to UseRiskInsights
  • PM-37473 - Admin Portal auto-tick
  • PM-37474 - self-host license VerifyData
  • PM-37475 - re-enable the Access Intelligence endpoint guard

Heads-up on diff size: most of the +lines are auto-generated. Of the total additions, roughly 11,300 are EF migration snapshot files (the three *.Designer.cs files for MySQL/Postgres/SQLite that EF regenerates for the PM-37470 data migration). The actual hand-written, reviewable change is only a few hundred lines across the mapping, admin toggle, license, guard, and the migration scripts. The generated snapshots can be skimmed.

📔 Objective

The UseRiskInsights organization ability gates Access Intelligence but predates the org-ability pattern, so it was never reliably set on Enterprise orgs. This PR bundles every server-side piece so the migration, the forward-going mapping, the license check, and the re-enabled guard go live in a single atomic deploy, with no window where the guard rejects an org that has not yet been backfilled. The Billing riskInsights pricing key is already live in QA and prod, so the mapping takes effect on deploy.

Each subtask below links to its commits; the code is the source of truth.

Data migration (PM-37470)

Backfills UseRiskInsights = 1 for existing Enterprise orgs (plan types 4, 5, 10, 11, 14, 15, 19, 20) via an idempotent MSSQL DbScript plus EF migrations for MySQL/Postgres/SQLite. Commits: 57d1ecc, 45f371c, 6106a03, c543345, f1d9997; re-dated to sort after main in df2c02e.

Plan mapping (PM-37471)

Adds HasRiskInsights to the Plan model (11d26a3) and populates it in PlanAdapter via HasFeature("riskInsights") (fe367f7), then maps plan.HasRiskInsights -> organization.UseRiskInsights at all eight live write sites: org creation 081f76e / provider-client 9a19e0e, upgrade 8e1e943 / ChangePlan 1a75acb / premium-to-org a5cb547, restart 4643003, and the two bitwarden_license/ paths 007099a / e9c4d55. Tests: 6c925e4. Inert until Billing registers the riskInsights key.

Admin Portal (PM-37473)

Selecting an Enterprise plan in the org edit form auto-ticks the Risk Insights checkbox (mirrors UseInviteLinks, sourced from the same Plan.HasRiskInsights). Commit: 5c0f164.

Self-host license (PM-37474)

Adds UseRiskInsights to OrganizationLicense.VerifyData() using the conditional claim pattern, so pre-existing self-host licenses that lack the claim still validate. Commits: d663eb0, test dcb950e.

Endpoint guard (PM-37475)

Re-enables the orgAbility.UseRiskInsights check in OrganizationReportsController.AuthorizeAsync() that was temporarily disabled under PM-37478. One controller serves both V1 and V2 (V2 gated by AccessIntelligenceVersion2), both routing through the same guard; the controller tests now mock the org ability. Commit: faa7129.

📸 Screenshots

No UI except PM-37473: in the Admin Portal org edit form, selecting an Enterprise plan auto-ticks the Risk Insights checkbox (and a non-Enterprise plan unticks it); a manual override still persists on save.

AlexRubik added 8 commits June 8, 2026 09:55
Adds the plan-level RiskInsights ability flag so org-creation, upgrade,
and restart paths can derive Organization.UseRiskInsights from the plan,
consistent with every other org ability.

[PM-37471]
Maps plan.HasRiskInsights onto the organization in the vNext upgrade
command (the actual upgrade ability-mapping site; the ticket referenced
CloudOrganizationSignUpCommand, which has no upgrade path).

[PM-37471]
Covers the legacy/default upgrade fallback and the Stripe webhook plan
migration path, which both flow through Organization.ChangePlan. Found
in code review; closes a cross-path gap against the other Use* flags.

[PM-37471]
Mirrors CloudOrganizationSignUpCommand so provider-created Enterprise
orgs get the ability. Found in code review.

[PM-37471]
@AlexRubik AlexRubik added the ai-review Request a Claude code review label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the new commits since the last review round: the Access Intelligence endpoint guard re-enablement, the self-host license VerifyData claim check, and the four data migrations (MSSQL DbScript plus MySQL/Postgres/SQLite EF migrations). The plan-to-organization mapping across all eight write sites was validated and resolved in earlier rounds and remains correct. Code follows established sibling-ability patterns throughout, with test coverage matching each change.

Code Review Details

No new findings.

Verified in this pass:

  • OrganizationReportsController.AuthorizeAsync() is the single authorization chokepoint invoked by every V1 and V2 endpoint, so the restored UseRiskInsights guard covers both paths. Deny test and V1/V2 setup mocks were correctly restored.
  • OrganizationLicense.VerifyData() uses the conditional HasClaim pattern, so pre-existing self-host licenses lacking the claim still validate (backward compatible). The hash-exclusion list already covered UseRiskInsights.
  • The MSSQL DbScript is batched and idempotent (WHERE UseRiskInsights = 0); the three EF migrations mirror it and guard their Up() updates. The DbScript date (2026-06-17) sorts after the merged main.
  • Plan/PlanAdapter, OrganizationPlanExtensions, and the Admin Portal toggle wire HasRiskInsights consistently with the other Use* abilities.

UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon,
UseCustomPermissions = plan.HasCustomPermissions,
UseScim = plan.HasScim,
UseRiskInsights = plan.HasRiskInsights,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ IMPORTANT: Parallel plan-to-organization site missed: UpgradePremiumToOrganizationCommand

Details and fix

The PR objective is to map plan.HasRiskInsights -> organization.UseRiskInsights at "every live plan-to-organization site." UpgradePremiumToOrganizationCommand.BuildOrganization (src/Core/Billing/Premium/Commands/UpgradePremiumToOrganizationCommand.cs:228-281) is one such site — it constructs a brand-new Organization from a target Plan and maps every other Has* ability the same way the two SignUp commands do (HasPolicies, HasMyItems, HasInviteLinks, HasSso, HasGroups, HasScim, HasCustomPermissions, etc.).

This endpoint is reachable today via POST /account/billing/vnext/upgrade (no feature flag — see AccountBillingVNextController.UpgradePremiumToOrganizationAsync) and UpgradePremiumToOrganizationRequest allows ProductTierType.Enterprise, mapping it to PlanType.EnterpriseAnnually. So a user upgrading a Premium subscription directly to Enterprise will end up with UseRiskInsights = false even though the Enterprise plan has HasRiskInsights = true.

Suggested fix — add to the BuildOrganization initializer around line 265:

UseScim = targetPlan.HasScim,
UseRiskInsights = targetPlan.HasRiskInsights,
Plan = targetPlan.Name,

This matches the exact cross-path-gap pattern the PR description calls out for ChangePlan and ProviderClientOrganizationSignUpCommand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, fixed in a5cb547. Added UseRiskInsights = targetPlan.HasRiskInsights right after UseScim in BuildOrganization, mirroring the other SignUp commands, plus a test assertion in UpgradePremiumToOrganizationCommandTests. This was the last cross-path plan-to-organization site.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude re-review

@claude claude Bot Jun 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude finished @AlexRubik's task in 1m 16s —— View job


🤖 Re-review

Overall Assessment: REQUEST CHANGES

The previously-flagged UpgradePremiumToOrganizationCommand gap is correctly closed in commit a5cb547UseRiskInsights = targetPlan.HasRiskInsights is mapped at src/Core/Billing/Premium/Commands/UpgradePremiumToOrganizationCommand.cs:266, immediately after the UseScim assignment, with a paired assertion in UpgradePremiumToOrganizationCommandTests. Confirmed all seven plan-to-organization sites in src/ now map UseRiskInsights:

Site Line
CloudOrganizationSignUpCommand 96
ProviderClientOrganizationSignUpCommand 93
UpgradeOrganizationPlanVNextCommand 178
OrganizationPlanExtensions (ChangePlan) 41
RestartSubscriptionCommand 100
UpgradePremiumToOrganizationCommand 266
PlanAdapter — populates Plan.HasRiskInsights source-of-truth

However, the two bitwarden_license/ findings from the previous review are still unaddressed. Both files map every other Use* ability from the plan but not UseRiskInsights:

  • ⚠️ bitwarden_license/src/Commercial.Core/Billing/Providers/Services/ProviderBillingService.cs:114AddExistingOrganization maps UseScim, UseKeyConnector, etc. from the managed plan but skips UseRiskInsights. Reachable via POST /providers/{providerId}/clients/existing (no feature flag). Add organization.UseRiskInsights = plan.HasRiskInsights; after the UseScim assignment. Fix this →
  • ⚠️ bitwarden_license/src/Commercial.Core/Billing/Providers/Services/BusinessUnitConverter.cs:81FinalizeConversion converts an organization to EnterpriseAnnually/EnterpriseMonthly and maps every other Use* ability from the new plan but skips UseRiskInsights. Reachable via the business-unit-conversion finalization endpoint (no feature flag). Add organization.UseRiskInsights = updatedPlan.HasRiskInsights; after the UseScim assignment. Fix this →

These are the same class of cross-path gap the PR description and prior reviews called out for ChangePlan, ProviderClientOrganizationSignUpCommand, and UpgradePremiumToOrganizationCommand — the Enterprise plan flows through these two bitwarden_license/ paths (provider-managed orgs and business-unit conversion to Enterprise) without UseRiskInsights being set, leaving the same forward-going gap the PR is intended to close. Grepping bitwarden_license/ for UseRiskInsights returns no results, confirming neither site has been touched.

Tasks

  • Verified UpgradePremiumToOrganizationCommand fix from a5cb547
  • Re-checked ProviderBillingService.AddExistingOrganization — still missing
  • Re-checked BusinessUnitConverter.FinalizeConversion — still missing
  • Scanned for other live plan-to-organization sites — all src/ sites covered
  • Posted re-review
    · Branch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both bitwarden_license/ findings are now fixed:

  • ProviderBillingService.AddExistingOrganizationorganization.UseRiskInsights = plan.HasRiskInsights; after UseScim (commit 007099a)
  • BusinessUnitConverter.FinalizeConversionorganization.UseRiskInsights = updatedPlan.HasRiskInsights; after UseScim (commit e9c4d55)

Each has a paired test assertion (Commercial.Core.Test, 66 tests pass; build clean). Root cause of why these were missed earlier: prior passes scoped only src/. I re-scoped the whole repo by grepping every non-test plan-derived .Use* = *.Has* assignment across both src/ and bitwarden_license/; all eight plan-to-organization write sites now map UseRiskInsights and no gaps remain.

UpgradePremiumToOrganizationCommand.BuildOrganization (reachable via
POST /account/billing/vnext/upgrade, which accepts Enterprise) mapped
every other Has* ability but not HasRiskInsights. Found in PR review;
closes the last cross-path gap so Premium->Enterprise upgrades get the
ability set like every other plan-to-organization site.

[PM-37471]
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.47619% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.71%. Comparing base (19cb812) to head (faa7129).

Files with missing lines Patch % Lines
...Admin/AdminConsole/Models/OrganizationEditModel.cs 0.00% 1 Missing ⚠️
.../Dirt/Controllers/OrganizationReportsController.cs 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7782      +/-   ##
==========================================
+ Coverage   61.22%   65.71%   +4.49%     
==========================================
  Files        2209     2209              
  Lines       97716    97736      +20     
  Branches     8815     8816       +1     
==========================================
+ Hits        59824    64227    +4403     
+ Misses      35768    31289    -4479     
- Partials     2124     2220      +96     

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

AlexRubik added 2 commits June 8, 2026 11:19
ProviderBillingService.AddExistingOrganization (reachable via
POST /providers/{providerId}/clients/existing) mapped every other Use*
ability from the managed plan but not UseRiskInsights. Found in PR
re-review; closes the gap for provider-managed Enterprise orgs in the
commercial-licensed code path.

[PM-37471]
BusinessUnitConverter.FinalizeConversion converts an org to Enterprise
and mapped every other Use* ability from the new plan but not
UseRiskInsights. Found in PR re-review.

[PM-37471]
@AlexRubik AlexRubik marked this pull request as ready for review June 9, 2026 14:39
@AlexRubik AlexRubik requested review from a team as code owners June 9, 2026 14:39
sven-bitwarden
sven-bitwarden previously approved these changes Jun 9, 2026

@sven-bitwarden sven-bitwarden left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good if this is all that needs done - does anything need to be added to any Views to support reading this back out? Just comparing what I've seen done for prior org abilities to this one.

@cyprain-okeke cyprain-okeke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks good for billing

cyprain-okeke
cyprain-okeke previously approved these changes Jun 9, 2026
@AlexRubik

Copy link
Copy Markdown
Contributor Author

Looks good if this is all that needs done - does anything need to be added to any Views to support reading this back out? Just comparing what I've seen done for prior org abilities to this one.

Nope, no read-side changes needed. UseRiskInsights is already wired through all the read layers (views, SPs, EF queries, OrganizationAbility, API responses). This was all accounted for when the column was added. @sven-bitwarden

Mirror Plan.HasRiskInsights into the Admin Portal org edit form: include it
in GetPlansHelper's plan projection and check the box in togglePlanFeatures,
matching every other Use* ability. Plan.HasRiskInsights is introduced by
PM-37471 in this same PR; the checkbox, edit-model property, and controller
persistence already exist on main.

[PM-37473]
@AlexRubik

Copy link
Copy Markdown
Contributor Author

Including this functionality from pm-37473: "When an admin selects an Enterprise plan in the Bitwarden Admin Portal organization form, UseRiskInsights should auto-tick alongside the other Enterprise abilities."

Small change commit: 5c0f164
@sven-bitwarden @cyprain-okeke

@AlexRubik AlexRubik changed the title [PM-37471] Map Enterprise plan to UseRiskInsights in plan definition and lifecycle commands [PM-37471][PM-37473] Map Enterprise plan to UseRiskInsights in plan definition and lifecycle commands Jun 10, 2026
sven-bitwarden
sven-bitwarden previously approved these changes Jun 10, 2026
cyprain-okeke
cyprain-okeke previously approved these changes Jun 11, 2026
AlexRubik added 10 commits June 17, 2026 09:57
Set UseRiskInsights = 1 on existing Organization rows with Enterprise
plan types (4, 5, 10, 11, 14, 15, 19, 20). Batched and idempotent.

[PM-37470]
Adds equivalent UPDATE migration for MySQL, Postgres, and SQLite
providers used by self-hosted instances. Mirrors the MSSQL script.

[PM-37470]
Keep UPDATE TOP (N) [target] (and bare UPDATE [target] in the EF
variants) as a single header line. SET and WHERE arguments still
indent onto their own line.

No functional change.

[PM-37470]
Match the MSSQL DbScript, which only updates rows where
UseRiskInsights is currently 0. Reduces write churn on
already-correct rows and keeps cloud (MSSQL) and self-host
(EF) UPDATE shape identical.

[PM-37470]
Addresses @rkac-bw's review comment on PR #7691. Matches the
convention used by 9 of the 10 most recent DbScripts.

[PM-37470]
OrganizationLicense.VerifyData() signed the UseRiskInsights claim into the
license but never compared it against the organization, so self-host could
not rely on the license to grant Access Intelligence. Add the comparison
using the conditional HasClaim pattern (PM-33980) so pre-existing licenses
that lack the claim continue to validate.

[PM-37474]
Adds absent/present-matches/present-mismatches tests mirroring the existing
UseMyItems coverage, verifying pre-existing licenses (claim absent) still
validate and that a present claim is enforced.

[PM-37474]
@AlexRubik AlexRubik dismissed stale reviews from cyprain-okeke and sven-bitwarden via faa7129 June 17, 2026 14:09
@AlexRubik AlexRubik requested review from a team as code owners June 17, 2026 14:09
@AlexRubik AlexRubik requested a review from Banrion June 17, 2026 14:09
@AlexRubik AlexRubik changed the title [PM-37471][PM-37473] Map Enterprise plan to UseRiskInsights in plan definition and lifecycle commands [PM-37469] UseRiskInsights organization ability: migration, plan mapping, admin toggle, license, and endpoint guard Jun 17, 2026
@sonarqubecloud

Copy link
Copy Markdown

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.

3 participants