Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
Expand All @@ -21,7 +22,8 @@ public class SendControlsSyncPolicyEvent(
IPolicyRepository policyRepository,
TimeProvider timeProvider,
ISendRepository sendRepository,
IFeatureService featureService) : IOnPolicyPostUpdateEvent, IPolicyValidationEvent
IFeatureService featureService,
IOrganizationUserRepository orgUserRepository) : IOnPolicyPostUpdateEvent, IPolicyValidationEvent
{
public PolicyType Type => PolicyType.SendControls;

Expand Down Expand Up @@ -88,6 +90,9 @@ public Task<string> ValidateAsync(SavePolicyModel policyRequest, Policy? current
private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, SendControlsPolicyData sendControlsPolicyData)
{
var orgSendIds = await sendRepository.GetIdsByOrganizationIdAsync(postUpsertedPolicyState.OrganizationId);
// We fetch all of the owners and admins in the org so we can ignore their Sends when enforcing policy compliance
// This could be a heavy call in theory but in practice owners and admins should be a minority of org users
var orgOwnerAndAdminUserIds = (await orgUserRepository.GetManyByMinimumRoleAsync(postUpsertedPolicyState.OrganizationId, Core.Enums.OrganizationUserType.Admin)).Select(oud => oud.GetUserId());
Comment thread
mcamirault marked this conversation as resolved.

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.

πŸ’­ At some point it would be good to collapse the verification of whether a Send is compliant, to the PolicyRequirement itself, which owns the concept of "given a policy and some domain data (the user trying to Send, the Send itself), is this valid?". This way if we change how the policy works, it'll "just work" for this side effect

foreach (var sendIdsChunk in orgSendIds.Chunk(50))
{
var enabled = new List<Guid>();
Expand All @@ -97,7 +102,8 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send
{
if (
// If the policy is disabled then we want to re-enable any Sends that were previously disabled
postUpsertedPolicyState.Enabled && SendIsNonCompliant(send, sendControlsPolicyData))
// If the Send was created by an Owner or an Admin in the organization we ignore it
postUpsertedPolicyState.Enabled && !orgOwnerAndAdminUserIds.Contains(send.UserId) && SendIsNonCompliant(send, sendControlsPolicyData))
{
disabled.Add(send.Id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,8 @@ public async Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRole
.Select(e => new OrganizationUserUserDetails()
{
Id = e.Id,
Email = e.Email ?? e.User.Email
Email = e.Email ?? e.User.Email,
UserId = e.UserId ?? e.User.Id
});
return await query.ToListAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Tools.Entities;
Expand Down Expand Up @@ -575,4 +577,71 @@ await sutProvider.GetDependency<ISendRepository>()
.Received(1)
.UpdateManyDisabledAsync(Arg.Is<List<Guid>>(l => l.Count() == 1 && l.ElementAt(0) == nonCompliantSend.Id), true);
}

[Theory, BitAutoData]
public async Task ExecutePostUpsertSideEffectAsync_IgnoresOwnersAndAdminsNonCompliantSends(
[PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate,
[Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy,
[Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy,
[Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy,
SutProvider<SendControlsSyncPolicyEvent> sutProvider)
{
postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId;
existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId;
existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId;
postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { AllowedSendTypes = [SendType.Text] });

sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend)
.Returns(existingDisableSendPolicy);
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions)
.Returns(existingSendOptionsPolicy);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.SendControlsExistingSends)
.Returns(true);

var adminOrganizationUser = new OrganizationUserUserDetails
{
UserId = Guid.NewGuid(),
Type = Enums.OrganizationUserType.Admin,
};
var adminNoncompliantSend = new Send
{
Id = Guid.NewGuid(),
Type = SendType.File,
UserId = adminOrganizationUser.UserId,
};
var ownerOrganizationUser = new OrganizationUserUserDetails
{
UserId = Guid.NewGuid(),
Type = Enums.OrganizationUserType.Owner,
};
var ownerNoncompliantSend = new Send
{
Id = Guid.NewGuid(),
Type = SendType.File,
UserId = ownerOrganizationUser.UserId,
};
var sendIds = new List<Guid>([adminNoncompliantSend.Id, ownerNoncompliantSend.Id]);
sutProvider.GetDependency<ISendRepository>()
.GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(sendIds);
sutProvider.GetDependency<ISendRepository>()
.GetManyByIdsAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([adminNoncompliantSend, ownerNoncompliantSend]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByMinimumRoleAsync(policyUpdate.OrganizationId, Enums.OrganizationUserType.Admin)
.Returns([adminOrganizationUser, ownerOrganizationUser]);

await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(
new SavePolicyModel(policyUpdate), postUpsertedPolicy, null);

await sutProvider.GetDependency<ISendRepository>()
.Received(1)
.UpdateManyDisabledAsync(Arg.Is<List<Guid>>(l => l.Count == 2 && l.Contains(adminNoncompliantSend.Id) && l.Contains(ownerNoncompliantSend.Id)), false);
await sutProvider.GetDependency<ISendRepository>()
.Received(0)
.UpdateManyDisabledAsync(Arg.Any<List<Guid>>(), true);
}
}
Loading