Skip to content

Conversation

@jrmccannon
Copy link
Contributor

@jrmccannon jrmccannon commented Nov 26, 2025

🎟️ Tracking

PM-27131
PM-28187

📔 Objective

This will add the auto confirm policy requirement and enforcement query for enforcing the auto confirm policy for a user and organization.

This mainly is a different version of the Single Organzation Policy Requirement, except that it enforces that the user may not be a member of other organizations regardless of their status or type (Single org exempts admin, owners and providers and users in the status of invited and revoked.)

This also blocks providers from joining organizations with the auto confirm policy on and enforces its own single org policy when users attempt to join the organization.

⏰ 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

@github-actions
Copy link
Contributor

github-actions bot commented Nov 26, 2025

Logo
Checkmarx One – Scan Summary & Details40e96438-1f01-48e1-bb46-e0959a6fe921

New Issues (2)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 1519
detailsMethod at line 1519 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
ID: dMGF5qNfAN72zlvQcA1MgbhHv%2Fc%3D
Attack Vector
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 1395
detailsMethod at line 1395 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
ID: iOCFr11iI9znjDnv46yLfiS4aDY%3D
Attack Vector
Fixed Issues (3)

Great job! The following issues were fixed in this Pull Request

Severity Issue Source File / Package
MEDIUM CSRF /src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs: 97
MEDIUM CSRF /src/Api/AdminConsole/Public/Controllers/MembersController.cs: 207
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 300

@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

❌ Patch coverage is 73.36957% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.66%. Comparing base (b1390c9) to head (8878b8e).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...s/RestoreUser/v1/RestoreOrganizationUserCommand.cs 11.76% 14 Missing and 1 partial ⚠️
...es/Organizations/InitPendingOrganizationCommand.cs 38.46% 7 Missing and 1 partial ⚠️
...ganizations/SelfHostedOrganizationSignUpCommand.cs 38.46% 7 Missing and 1 partial ⚠️
...ents/AutomaticUserConfirmationPolicyRequirement.cs 54.54% 5 Missing ⚠️
...Features/OrganizationUsers/AcceptOrgUserCommand.cs 80.00% 3 Missing and 1 partial ⚠️
...es/Organizations/CloudOrganizationSignUpCommand.cs 63.63% 3 Missing and 1 partial ⚠️
.../AutomaticallyConfirmOrganizationUsersValidator.cs 78.57% 1 Missing and 2 partials ⚠️
...tomaticUserConfirmationPolicyEnforcementRequest.cs 92.30% 1 Missing ⚠️
...maticUserConfirmationPolicyEnforcementValidator.cs 95.83% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6649      +/-   ##
==========================================
+ Coverage   53.65%   53.66%   +0.01%     
==========================================
  Files        1926     1929       +3     
  Lines       85720    85875     +155     
  Branches     7687     7705      +18     
==========================================
+ Hits        45989    46089     +100     
- Misses      37958    38008      +50     
- Partials     1773     1778       +5     

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

@jrmccannon jrmccannon marked this pull request as ready for review November 26, 2025 17:40
@jrmccannon jrmccannon requested a review from a team as a code owner November 26, 2025 17:40
@jrmccannon jrmccannon requested a review from eliykat November 26, 2025 17:40
@claude
Copy link
Contributor

claude bot commented Nov 26, 2025

Claude finished @jrmccannon's task —— View job


Code Review Complete

I've completed a comprehensive review of PR #6649. Here are my findings:

Tasks

  • Launch bitwarden-code-reviewer agent to analyze PR changes
  • Review findings and compile feedback
  • Post comprehensive review results

Critical Issues ❌

1. Null Reference Exception in RestoreOrganizationUserCommand

File: src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RestoreUser/v1/RestoreOrganizationUserCommand.cs:285-314

The code retrieves a user at line 285 but doesn't validate it's non-null before accessing user.Email in multiple places (lines 289, 294, 298, 302, 314):

var user = await userRepository.GetByIdAsync(userId);

if (!singleOrgCompliant && !twoFactorCompliant)
{
    throw new BadRequestException(user.Email + // NULL REFERENCE POSSIBLE
                                  " is not compliant with the single organization and two-step login policy");
}

Impact: Runtime NullReferenceException if a revoked organization user's associated user account was deleted.

Fix: Add null check immediately after line 285:

var user = await userRepository.GetByIdAsync(userId);
if (user == null)
{
    throw new BadRequestException("User not found.");
}

Important Issues ⚠️

2. Inefficient Provider Check Ordering

File: src/Core/AdminConsole/OrganizationFeatures/Policies/Enforcement/AutoConfirm/AutomaticUserConfirmationPolicyEnforcementValidator.cs:17-32

The validator queries the policy requirement (database call) before checking if the user is a provider. If the user is a provider, the policy requirement query result is never used.

Fix: Reorder checks to fail fast on provider status before querying policy requirements.

3. Error Message Construction Issues

File: src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RestoreUser/v1/RestoreOrganizationUserCommand.cs:289,294,298,302,314

Error messages concatenate user email with string literals using + operator:

throw new BadRequestException(user.Email + " is not compliant with the single organization policy");

Issues:

  • Not internationalization (i18n) friendly
  • Exposes PII (user email) in exception messages that may be logged
  • Inconsistent with Bitwarden security guidelines about not logging sensitive data

Fix: Use parameterized error messages or error codes that don't include PII directly in the exception message.

4. Inconsistent Null Safety

Several files explicitly disable nullable reference types with #nullable disable:

  • RestoreOrganizationUserCommand.cs:2
  • ConfirmOrganizationUserCommand.cs:2
  • CloudOrganizationSignUpCommand.cs:2

Impact: Missing compile-time null safety checks as per ADR 0024 (Use .NET nullable reference types)

Fix: Remove #nullable disable and properly annotate nullable parameters/returns.


Suggested Improvements 🎨

5. Logic Duplication Across Commands

Files:

  • AcceptOrgUserCommand.cs:190-217
  • ConfirmOrganizationUserCommand.cs:189-228
  • RestoreOrganizationUserCommand.cs:244-324

The automatic user confirmation policy validation is duplicated across three commands with slight variations. This violates DRY principle and creates maintenance burden.

6. Provider Status Check Location

File: src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/AutomaticUserConfirmationPolicyRequirement.cs

The policy requirement has a CannotJoinProvider() method (line 24), but the actual provider status check is performed in the enforcement validator. Consider if AutomaticUserConfirmationPolicyRequirement should encapsulate provider status directly.


Security ✅

7. Zero-Knowledge Compliance - PASSED

The auto-confirm policy enforcement operates on organizational metadata and user relationships, not vault data. No violations of zero-knowledge principles detected.

Verified:

  • No encryption keys are logged or exposed
  • No vault data is accessed during policy enforcement
  • User PII handling needs improvement (Issue .NET Core Runtime #3) but doesn't violate zero-knowledge

8. Provider Isolation - CORRECT

The PR correctly blocks providers from joining organizations with auto-confirm policy enabled. This prevents privilege escalation where providers could bypass single-org restrictions.

Verified in:

  • ProviderService.AcceptUserAsync:254-267
  • ProviderService.ConfirmUsersAsync:311-324

Code Quality 👍

9. Clear Error Messages - GOOD

The new error types in Errors.cs provide clear, actionable messages with better semantic naming than previous implementation.

10. Validation Result Pattern - EXCELLENT

The use of ValidationResult<T> pattern follows functional programming principles and avoids exception-based control flow, making the code more testable.

11. Feature Flag Gating - CORRECT

All new policy enforcement is properly gated behind FeatureFlagKeys.AutomaticConfirmUsers, allowing safe deployment and toggling.


Test Coverage 🧪

Comprehensive Test Coverage - GOOD

The PR includes extensive test coverage with new test files and comprehensive scenarios:

New test files:

  • AutomaticUserConfirmationPolicyEnforcementValidatorTests.cs - 241 lines
  • Updates to ProviderServiceTests.cs - 252 new lines
  • Updates to ConfirmOrganizationUserCommandTests.cs - 255 new lines

Coverage gaps (per Codecov):

  • RestoreOrganizationUserCommand.cs - 11.76% patch coverage
  • InitPendingOrganizationCommand.cs - 38.46% patch coverage
  • SelfHostedOrganizationSignUpCommand.cs - 38.46% patch coverage

Recommendation: Add integration tests for restore user scenarios with auto-confirm policy enabled.


Summary

Overall Assessment: REQUEST CHANGES

Critical Issues: 1 (null reference exception)
Important Issues: 3 (performance, PII in errors, nullable disable)
Code Quality: Generally good with comprehensive tests

Blocking issues:

  1. Fix null reference exception in RestoreOrganizationUserCommand (Issue Identity Server  #1)
  2. Address PII exposure in error messages (Issue .NET Core Runtime #3) per Bitwarden security guidelines

Recommended before merge:

  1. Reorder provider check for performance (Issue Password Requirements Validation #2)
  2. Add test coverage for restore user scenarios
  3. Consider removing #nullable disable directives (Issue Licence #4)

Security: No zero-knowledge violations detected. Provider isolation correctly implemented.


Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

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

This is a good approach to a difficult requirement. I think the tricky thing here is making sure we've covered all flows that can land a user/org in a prohibited state.

Comment on lines 90 to 103
private async Task<Error?> OrganizationUserConformsToSingleOrgPolicyAsync(
AutomaticallyConfirmOrganizationUserValidationRequest request)
private async Task<Error?> OrganizationUserConformsToAutomaticUserConfirmationPolicyAsync(AutomaticallyConfirmOrganizationUserValidationRequest request)
Copy link
Member

Choose a reason for hiding this comment

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

Check my thinking - and if you agree, please add a comment...
For this organization: we know that autoconfirm is enabled, because we check it above; otherwise we wouldn't be in the autoconfirm flow. So we don't need to check the (regular) Single Org Policy, only autoconfirm (which is a stricter set of requirements).
For other organizations: we don't need to enforce their policies (whether Single Org or Autoconfirm), because if there are any other organizations, the prior check will always fail.
Is that right? I am just making sure that removing the current single org check is OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I've now isolated single org from auto confirm as much as possible.

Copy link
Member

Choose a reason for hiding this comment

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

The logic here looks good to me, but please add a comment explaining why we're not checking single org. (basically my explanation above.) I ask because this is a difference between the autoconfirm flow and the regular flow, it may otherwise look like an oversight.

Copy link
Member

Choose a reason for hiding this comment

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

Are these changes necessary if we're now calling the new AutoConfirmPolicyRequirement in all these flows? This is effectively modifying Single Org behaviour which isn't the approach in the rest of this PR. I may be missing something though - are there places where this policyService method is called and the new policyRequirement/enforce query is not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are not, but I have a question about what happens when someone later tries to call PolicyService with Auto Confirm policy type?

Should we throw exceptions or call the requirement here or here?

Anyone who calls those methods with the Auto Confirm policy type would not get the correct answer. Hopefully we would be involved if anyone is checking for auto confirm, but should we throw and inform them of the requirement or enforcement classes or just call the requirement?

I don't really think we have the context to answer the question they're attempting to solve for in the future so throwing might be a safe bet.

Copy link
Member

@eliykat eliykat Dec 6, 2025

Choose a reason for hiding this comment

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

That's a fair point. Given how we are treating it here - as a separate policy that should be checked separately - I think the answer is that the future caller should know to check autoconfirm as well, and this method goes unchanged.

That is maybe fragile. But the idea is that we now have more policies that affect these flows, Autoconfirm becomes just another policy that should be checked when a user tries to do an action affected by the policy. The caller needs to know to check it, but that's the case with all policies. (I think this is an argument for having another layer of abstraction for policy enforcement like we've discussed, but out of scope here.)

Also note that I am going to return to the PolicyRequirements migration in the next few sprints so we can remove this old code - which should help with PolicyService confusion: https://bitwarden.atlassian.net/browse/PM-28958

@jrmccannon jrmccannon requested a review from a team as a code owner December 5, 2025 00:46
# Conflicts:
#	src/Sql/dbo/Stored Procedures/ProviderUser_ReadManyByManyUserIds.sql
#	util/Migrator/DbScripts/2025-12-03_00_ProviderUserGetManyByUserIds.sql
@jrmccannon jrmccannon removed the request for review from a team December 5, 2025 15:01
@jrmccannon jrmccannon requested a review from eliykat December 5, 2025 15:54
Comment on lines 90 to 103
private async Task<Error?> OrganizationUserConformsToSingleOrgPolicyAsync(
AutomaticallyConfirmOrganizationUserValidationRequest request)
private async Task<Error?> OrganizationUserConformsToAutomaticUserConfirmationPolicyAsync(AutomaticallyConfirmOrganizationUserValidationRequest request)
Copy link
Member

Choose a reason for hiding this comment

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

The logic here looks good to me, but please add a comment explaining why we're not checking single org. (basically my explanation above.) I ask because this is a difference between the autoconfirm flow and the regular flow, it may otherwise look like an oversight.

Comment on lines 94 to 100
var allOrganizationUsersForUser = await organizationUserRepository
.GetManyByUserAsync(request.OrganizationUser!.UserId!.Value);

if (allOrganizationUsersForUser.Count == 1)
{
return null;
}
Copy link
Member

Choose a reason for hiding this comment

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

This early return could be moved inside the automaticUserConfirmationPolicyEnforcementValidator to better encapsulate it.

public bool IsEnabled(Guid organizationId) => policyDetails.Any(p => p.OrganizationId == organizationId);
public bool CanBeGrantedEmergencyAccess() => policyDetails.Any();

public bool UserBelongsToOrganizationWithAutomaticUserConfirmationEnabled() => policyDetails.Any();
Copy link
Member

Choose a reason for hiding this comment

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

IPolicyRequirements work best when they express what the policy requires in business logic terms. This isn't always possible, but here this could be (for example) CanJoinProvider(). Then that's the interface that the ProviderService calls.

{
var requirement = await policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);

if (requirement.UserBelongsToOrganizationWithAutomaticUserConfirmationEnabled())
Copy link
Member

Choose a reason for hiding this comment

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

Similarly, here this interface could be CanCreateNewOrganization().

The idea being that this command doesn't need to know about org membership and policies. Only that this requirement doesn't block what the user is trying to do.

Copy link
Member

@eliykat eliykat Dec 6, 2025

Choose a reason for hiding this comment

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

That's a fair point. Given how we are treating it here - as a separate policy that should be checked separately - I think the answer is that the future caller should know to check autoconfirm as well, and this method goes unchanged.

That is maybe fragile. But the idea is that we now have more policies that affect these flows, Autoconfirm becomes just another policy that should be checked when a user tries to do an action affected by the policy. The caller needs to know to check it, but that's the case with all policies. (I think this is an argument for having another layer of abstraction for policy enforcement like we've discussed, but out of scope here.)

Also note that I am going to return to the PolicyRequirements migration in the next few sprints so we can remove this old code - which should help with PolicyService confusion: https://bitwarden.atlassian.net/browse/PM-28958

@jrmccannon jrmccannon requested a review from eliykat December 8, 2025 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants