Skip to content

[PM-43734] Don't re-enable sends for excluded roles/members - #8469

Open
BTreston wants to merge 4 commits into
mainfrom
ac/pm-43734
Open

BTreston wants to merge 4 commits into
mainfrom
ac/pm-43734

Conversation

@BTreston

Copy link
Copy Markdown
Contributor

🎟️ Tracking

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

📔 Objective

Saving the SendControls organization policy sets Disabled = 0 over every Send owned by
every user who has an OrganizationUser row in that organization, enabling sends that should remain disabled. (i.e. sends of revoked members or personal sends that were manually disabled by its owner.)

📸 Screenshots

@BTreston BTreston added ai-review Request a Claude code review t:bugfix Change Type - Bugfix labels Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Claude Code is reviewing this pull request...

If this comment does not update with results, check the Actions log.

@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.11111% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.05%. Comparing base (55f79c4) to head (80dd77d).

Files with missing lines Patch % Lines
...t/Services/NoopImplementations/NoopEventService.cs 0.00% 3 Missing ⚠️
...Core/Dirt/Services/Implementations/EventService.cs 95.45% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8469      +/-   ##
==========================================
+ Coverage   65.43%   71.05%   +5.62%     
==========================================
  Files        2527     2527              
  Lines      108488   108508      +20     
  Branches     9871     9875       +4     
==========================================
+ Hits        70988    77103    +6115     
+ Misses      35150    28928    -6222     
- Partials     2350     2477     +127     

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

{
OrganizationId = organizationId,
UserId = e.send.UserId,
ActingUserId = _currentContext?.UserId,

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.

♻️ DEBT: ProviderId is not populated, so these rows are invisible in the provider event log.

Details and fix

Every other organization-scoped log method in this file sets ProviderId = await GetProviderIdAsync(orgId) — LogPolicyEventAsync (line 278), CreateLogCipherEvent (line 166), LogCollectionEventsAsync, LogGroupEventsAsync. That field is what Event_ReadPageByProviderId filters on (IEventRepository.GetManyByProviderAsync).

When a provider user saves the SendControls policy for a managed client organization, the Policy_Updated row gets the provider attribution but these Send_PolicyDisabled / Send_PolicyEnabled rows do not, so the provider's event log shows the policy change with none of the resulting Send changes.

                OrganizationId = organizationId,
                UserId = e.send.UserId,
                ActingUserId = _currentContext?.UserId,
                ProviderId = providerId, // resolved once before the Select via GetProviderIdAsync(organizationId)

If the omission is deliberate — keeping member-scoped Send rows out of provider-visible logs — a short comment here would keep the next reader from treating it as an oversight.

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.

I would check to see if these events should turn up in the Provider event log. @BTreston

.Where(s => !s.Disabled && postUpsertedPolicyState.Enabled && !IsIgnored(s) && SendIsNonCompliant(s, sendControlsPolicyData))
.ToList();
var toEnable = sendsChunk
.Where(s => s.Disabled && !postUpsertedPolicyState.Enabled)

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.

❓ QUESTION: Relaxing the policy without disabling it never restores Sends the policy itself disabled — is manual re-enable the intended recovery?

Details and fix

toEnable requires !postUpsertedPolicyState.Enabled, so re-enabling only happens when SendControls is switched off entirely. Consider an org that enables SendControls with DisableSend = true (every member Send gets Disabled = 1), then clears DisableSend but keeps the policy enabled for DisableHideEmail. Those Sends are now compliant, but the sweep leaves them disabled — and the legacy path does not help either, since DisableSendSyncPolicyEvent writes the SendControls row through IPolicyRepository.UpsertAsync and runs no sweep at all. Previously the else enabled.Add(send.Id) branch restored them.

ExecutePostUpsertSideEffectAsync_DoesNotReEnableOwnerDisabledCompliantSend documents the owner-disabled case as intentional, and the two are indistinguishable without recording why a Send was disabled. Owners can still re-enable their own Sends (nothing in NonAnonymousSendCommand blocks it), so this is recoverable rather than broken — but the relaxed-policy case has no test and is not mentioned in the description, so flagging it in case it was not weighed.

@BTreston
BTreston marked this pull request as ready for review September 29, 2026 16:02
@BTreston
BTreston requested review from a team as code owners September 29, 2026 16:02
@BTreston
BTreston requested review from a team, JaredScar, jrmccannon and lastbestdev and removed request for JaredScar September 29, 2026 16:02
WHERE
[OrganizationId] = @OrganizationId
AND [UserId] IS NOT NULL
AND [Status] IN (1, 2) -- 1 = Accepted, 2 = Confirmed

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.

Should we include staged here?

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.

No. Staged users are less than invited. @sven-bitwarden can confirm.

@@ -95,34 +97,32 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send
var orgOwnerAndAdminUserIds = (await orgUserRepository.GetManyByMinimumRoleAsync(postUpsertedPolicyState.OrganizationId, Core.Enums.OrganizationUserType.Admin)).Select(oud => oud.GetUserId());
foreach (var sendIdsChunk in orgSendIds.Chunk(50))

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.

Which system has the limiting that causes the 50 record chunking?

{
OrganizationId = organizationId,
UserId = e.send.UserId,
ActingUserId = _currentContext?.UserId,

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.

I would check to see if these events should turn up in the Provider event log. @BTreston

This branch has not been deployed

No deployments
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 t:bugfix Change Type - Bugfix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants