Skip to content
Draft
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
9 changes: 9 additions & 0 deletions src/Core/AdminConsole/Enums/PolicyType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ public enum PolicyType : byte
SingleOrg = 3,
RequireSso = 4,
OrganizationDataOwnership = 5,
// Deprecated: superseded by SendControls (20) when pm-31885-send-controls flag is active.
// Do not add [Obsolete] until the flag is retired.
DisableSend = 6,
// Deprecated: superseded by SendControls (20) when pm-31885-send-controls flag is active.
// Do not add [Obsolete] until the flag is retired.
SendOptions = 7,
ResetPassword = 8,
MaximumVaultTimeout = 9,
Expand All @@ -22,6 +26,10 @@ public enum PolicyType : byte
AutotypeDefaultSetting = 17,
AutomaticUserConfirmation = 18,
BlockClaimedDomainAccountCreation = 19,
/// <summary>
/// Supersedes DisableSend (6) and SendOptions (7) when the pm-31885-send-controls feature flag is active.
/// </summary>
SendControls = 20,
}

public static class PolicyTypeExtensions
Expand Down Expand Up @@ -54,6 +62,7 @@ public static string GetName(this PolicyType type)
PolicyType.AutotypeDefaultSetting => "Autotype default setting",
PolicyType.AutomaticUserConfirmation => "Automatically confirm invited users",
PolicyType.BlockClaimedDomainAccountCreation => "Block account creation for claimed domains",
PolicyType.SendControls => "Send controls",
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;

namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies;

public class SendControlsPolicyData : IPolicyDataModel
{
[Display(Name = "DisableSend")]
public bool DisableSend { get; set; }
[Display(Name = "DisableHideEmail")]
public bool DisableHideEmail { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

/// <summary>
/// Policy requirements for the Send Controls policy.
/// Supersedes DisableSend and SendOptions when the pm-31885-send-controls feature flag is active.
/// </summary>
public class SendControlsPolicyRequirement : IPolicyRequirement
{
/// <summary>
/// Indicates whether Send is disabled for the user. If true, the user should not be able to create or edit Sends.
/// They may still delete existing Sends.
/// </summary>
public bool DisableSend { get; init; }

/// <summary>
/// Indicates whether the user is prohibited from hiding their email from the recipient of a Send.
/// </summary>
public bool DisableHideEmail { get; init; }
}

public class SendControlsPolicyRequirementFactory : BasePolicyRequirementFactory<SendControlsPolicyRequirement>
{
public override PolicyType PolicyType => PolicyType.SendControls;

public override SendControlsPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails)
{
return policyDetails
.Select(p => p.GetDataModel<SendControlsPolicyData>())
.Aggregate(
new SendControlsPolicyRequirement(),
(result, data) => new SendControlsPolicyRequirement
{
DisableSend = result.DisableSend || data.DisableSend,
DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ private static void AddPolicyUpdateEvents(this IServiceCollection services)
services.AddScoped<IPolicyUpdateEvent, UriMatchDefaultPolicyValidator>();
services.AddScoped<IPolicyUpdateEvent, BlockClaimedDomainAccountCreationPolicyValidator>();
services.AddScoped<IPolicyUpdateEvent, AutomaticUserConfirmationPolicyEventHandler>();
services.AddScoped<IPolicyUpdateEvent, DisableSendSyncPolicyEvent>();
services.AddScoped<IPolicyUpdateEvent, SendOptionsSyncPolicyEvent>();
services.AddScoped<IPolicyUpdateEvent, SendControlsSyncPolicyEvent>();
}

private static void AddPolicyRequirements(this IServiceCollection services)
{
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, DisableSendPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, SendOptionsPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, SendControlsPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, ResetPasswordPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, OrganizationDataOwnershipPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, RequireSsoPolicyRequirementFactory>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Utilities;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;

/// <summary>
/// Syncs changes to the DisableSend policy into the SendControls policy row.
/// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls
/// always stays current for when the flag is eventually enabled.
/// </summary>
public class DisableSendSyncPolicyEvent(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent
{
public PolicyType Type => PolicyType.DisableSend;

public async Task ExecutePostUpsertSideEffectAsync(
SavePolicyModel policyRequest,
Policy postUpsertedPolicyState,
Policy? previousPolicyState)
{
var policyUpdate = policyRequest.PolicyUpdate;

var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync(
policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy
{
Id = CoreHelpers.GenerateComb(),
OrganizationId = policyUpdate.OrganizationId,
Type = PolicyType.SendControls,
};

var sendOptionsPolicy = await policyRepository.GetByOrganizationIdTypeAsync(
policyUpdate.OrganizationId, PolicyType.SendOptions) ?? new Policy
{
Id = CoreHelpers.GenerateComb(),
OrganizationId = policyUpdate.OrganizationId,
Type = PolicyType.SendOptions,
};

var sendControlsPolicyData =
sendControlsPolicy.GetDataModel<SendControlsPolicyData>();

var sendOptionsPolicyData = sendOptionsPolicy.GetDataModel<SendOptionsPolicyData>();

sendControlsPolicyData.DisableSend = postUpsertedPolicyState.Enabled;
sendControlsPolicyData.DisableHideEmail = sendOptionsPolicy.Enabled && sendOptionsPolicyData.DisableHideEmail;
sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail;
sendControlsPolicy.SetDataModel(sendControlsPolicyData);

await policyRepository.UpsertAsync(sendControlsPolicy);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Utilities;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;

/// <summary>
/// When the pm-31885-send-controls flag is active, syncs changes to the SendControls policy
/// back into the legacy DisableSend and SendOptions policy rows, enabling safe rollback.
/// </summary>
public class SendControlsSyncPolicyEvent(
IPolicyRepository policyRepository,
TimeProvider timeProvider) : IOnPolicyPostUpdateEvent
{
public PolicyType Type => PolicyType.SendControls;

public async Task ExecutePostUpsertSideEffectAsync(
SavePolicyModel policyRequest,
Policy postUpsertedPolicyState,
Policy? previousPolicyState)
{
var policyUpdate = policyRequest.PolicyUpdate;

var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync(
policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy
{
Id = CoreHelpers.GenerateComb(),
OrganizationId = policyUpdate.OrganizationId,
Type = PolicyType.SendControls,
};

var sendControlsPolicyData =
sendControlsPolicy.GetDataModel<SendControlsPolicyData>();

await UpsertLegacyPolicyAsync(
policyRequest.PolicyUpdate.OrganizationId,
PolicyType.DisableSend,
enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableSend,
policyData: null);

var sendOptionsData = new SendOptionsPolicyData { DisableHideEmail = sendControlsPolicyData.DisableHideEmail };
await UpsertLegacyPolicyAsync(
policyRequest.PolicyUpdate.OrganizationId,
PolicyType.SendOptions,
enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableHideEmail,
policyData: CoreHelpers.ClassToJsonData(sendOptionsData));
}

private async Task UpsertLegacyPolicyAsync(
Guid organizationId,
PolicyType type,
bool enabled,
string? policyData)
{
var existing = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, type);

var policy = existing ?? new Policy { OrganizationId = organizationId, Type = type, };

if (existing == null)
{
policy.SetNewId();
}

policy.Enabled = enabled;
policy.Data = policyData;
Comment on lines +68 to +69
Copy link
Member

Choose a reason for hiding this comment

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

Also need to bump the RevisionDate (making sure to use TimeProvider).

policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime;

await policyRepository.UpsertAsync(policy);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Feedback on the DisableSend version of this class is also relevant here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Utilities;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;

/// <summary>
/// Syncs changes to the SendOptions policy into the SendControls policy row.
/// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls
/// always stays current for when the flag is eventually enabled.
/// </summary>
public class SendOptionsSyncPolicyEvent(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent
{
public PolicyType Type => PolicyType.SendOptions;

public async Task ExecutePostUpsertSideEffectAsync(
SavePolicyModel policyRequest,
Policy postUpsertedPolicyState,
Policy? previousPolicyState)
{
var policyUpdate = policyRequest.PolicyUpdate;

var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync(
policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy
{
Id = CoreHelpers.GenerateComb(),
OrganizationId = policyUpdate.OrganizationId,
Type = PolicyType.SendControls,
};

var sendControlsPolicyData =
sendControlsPolicy.GetDataModel<SendControlsPolicyData>();

var disableSendPolicyState = await policyRepository.GetByOrganizationIdTypeAsync(
policyUpdate.OrganizationId, PolicyType.DisableSend);

sendControlsPolicyData.DisableSend = disableSendPolicyState?.Enabled ?? false;
sendControlsPolicyData.DisableHideEmail = postUpsertedPolicyState.Enabled && postUpsertedPolicyState.GetDataModel<SendOptionsPolicyData>().DisableHideEmail;
sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail;
sendControlsPolicy.SetDataModel(sendControlsPolicyData);

await policyRepository.UpsertAsync(sendControlsPolicy);
}
}
1 change: 1 addition & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ public static class FeatureFlagKeys
public const string ChromiumImporterWithABE = "pm-25855-chromium-importer-abe";
public const string SendUIRefresh = "pm-28175-send-ui-refresh";
public const string SendEmailOTP = "pm-19051-send-email-verification";
public const string SendControls = "pm-31885-send-controls";

/* Vault Team */
public const string CipherKeyEncryption = "cipher-key-encryption";
Expand Down
20 changes: 20 additions & 0 deletions src/Core/Tools/SendFeatures/Services/SendValidationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class SendValidationService : ISendValidationService
private readonly IUserRepository _userRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserService _userService;
private readonly IFeatureService _featureService;
private readonly GlobalSettings _globalSettings;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly IPricingClient _pricingClient;
Expand All @@ -26,13 +27,15 @@ public SendValidationService(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IUserService userService,
IFeatureService featureService,
IPolicyRequirementQuery policyRequirementQuery,
GlobalSettings globalSettings,
IPricingClient pricingClient)
{
_userRepository = userRepository;
_organizationRepository = organizationRepository;
_userService = userService;
_featureService = featureService;
_policyRequirementQuery = policyRequirementQuery;
_globalSettings = globalSettings;
_pricingClient = pricingClient;
Expand All @@ -47,6 +50,23 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send)
return;
}

if (_featureService.IsEnabled(FeatureFlagKeys.SendControls))
{
var sendControlsRequirement = await _policyRequirementQuery.GetAsync<SendControlsPolicyRequirement>(userId.Value);

if (sendControlsRequirement.DisableSend)
{
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
}

if (sendControlsRequirement.DisableHideEmail && send.HideEmail.GetValueOrDefault())
{
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
}

return;
}

var disableSendRequirement = await _policyRequirementQuery.GetAsync<DisableSendPolicyRequirement>(userId.Value);
if (disableSendRequirement.DisableSend)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// TODO remove after testing
// This file will be included to demonstrate testing performed to code reviewers, but should not be merged

Loading
Loading