1
0
mirror of https://github.com/bitwarden/server synced 2025-12-18 01:03:17 +00:00

Merge branch 'main' into SM-1571-DisableSMAdsForUsers

This commit is contained in:
cd-bitwarden
2025-11-06 10:30:56 -05:00
committed by GitHub
28 changed files with 670 additions and 59 deletions

View File

@@ -41,6 +41,10 @@
matchUpdateTypes: ["patch"], matchUpdateTypes: ["patch"],
dependencyDashboardApproval: false, dependencyDashboardApproval: false,
}, },
{
matchSourceUrls: ["https://github.com/bitwarden/sdk-internal"],
groupName: "sdk-internal",
},
{ {
matchManagers: ["dockerfile", "docker-compose"], matchManagers: ["dockerfile", "docker-compose"],
commitMessagePrefix: "[deps] BRE:", commitMessagePrefix: "[deps] BRE:",

View File

@@ -12,6 +12,7 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Context; using Bit.Core.Context;
@@ -41,8 +42,9 @@ public class PoliciesController : Controller
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory; private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IFeatureService _featureService;
private readonly ISavePolicyCommand _savePolicyCommand; private readonly ISavePolicyCommand _savePolicyCommand;
private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand;
public PoliciesController(IPolicyRepository policyRepository, public PoliciesController(IPolicyRepository policyRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
@@ -53,7 +55,9 @@ public class PoliciesController : Controller
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory, IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery, IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
ISavePolicyCommand savePolicyCommand) IFeatureService featureService,
ISavePolicyCommand savePolicyCommand,
IVNextSavePolicyCommand vNextSavePolicyCommand)
{ {
_policyRepository = policyRepository; _policyRepository = policyRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@@ -65,7 +69,9 @@ public class PoliciesController : Controller
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
_organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery; _organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery;
_featureService = featureService;
_savePolicyCommand = savePolicyCommand; _savePolicyCommand = savePolicyCommand;
_vNextSavePolicyCommand = vNextSavePolicyCommand;
} }
[HttpGet("{type}")] [HttpGet("{type}")]
@@ -221,7 +227,9 @@ public class PoliciesController : Controller
{ {
var savePolicyRequest = await model.ToSavePolicyModelAsync(orgId, _currentContext); var savePolicyRequest = await model.ToSavePolicyModelAsync(orgId, _currentContext);
var policy = await _savePolicyCommand.VNextSaveAsync(savePolicyRequest); var policy = _featureService.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor) ?
await _vNextSavePolicyCommand.SaveAsync(savePolicyRequest) :
await _savePolicyCommand.VNextSaveAsync(savePolicyRequest);
return new PolicyResponseModel(policy); return new PolicyResponseModel(policy);
} }

View File

@@ -5,11 +5,15 @@ using System.Net;
using Bit.Api.AdminConsole.Public.Models.Request; using Bit.Api.AdminConsole.Public.Models.Request;
using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.AdminConsole.Public.Models.Response;
using Bit.Api.Models.Public.Response; using Bit.Api.Models.Public.Response;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -22,18 +26,24 @@ public class PoliciesController : Controller
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService;
private readonly ISavePolicyCommand _savePolicyCommand; private readonly ISavePolicyCommand _savePolicyCommand;
private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand;
public PoliciesController( public PoliciesController(
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
IPolicyService policyService, IPolicyService policyService,
ICurrentContext currentContext, ICurrentContext currentContext,
ISavePolicyCommand savePolicyCommand) IFeatureService featureService,
ISavePolicyCommand savePolicyCommand,
IVNextSavePolicyCommand vNextSavePolicyCommand)
{ {
_policyRepository = policyRepository; _policyRepository = policyRepository;
_policyService = policyService; _policyService = policyService;
_currentContext = currentContext; _currentContext = currentContext;
_featureService = featureService;
_savePolicyCommand = savePolicyCommand; _savePolicyCommand = savePolicyCommand;
_vNextSavePolicyCommand = vNextSavePolicyCommand;
} }
/// <summary> /// <summary>
@@ -87,8 +97,17 @@ public class PoliciesController : Controller
[ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Put(PolicyType type, [FromBody] PolicyUpdateRequestModel model) public async Task<IActionResult> Put(PolicyType type, [FromBody] PolicyUpdateRequestModel model)
{ {
var policyUpdate = model.ToPolicyUpdate(_currentContext.OrganizationId!.Value, type); Policy policy;
var policy = await _savePolicyCommand.SaveAsync(policyUpdate); if (_featureService.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor))
{
var savePolicyModel = model.ToSavePolicyModel(_currentContext.OrganizationId!.Value, type);
policy = await _vNextSavePolicyCommand.SaveAsync(savePolicyModel);
}
else
{
var policyUpdate = model.ToPolicyUpdate(_currentContext.OrganizationId!.Value, type);
policy = await _savePolicyCommand.SaveAsync(policyUpdate);
}
var response = new PolicyResponseModel(policy); var response = new PolicyResponseModel(policy);
return new JsonResult(response); return new JsonResult(response);

View File

@@ -8,6 +8,8 @@ namespace Bit.Api.AdminConsole.Public.Models.Request;
public class PolicyUpdateRequestModel : PolicyBaseModel public class PolicyUpdateRequestModel : PolicyBaseModel
{ {
public Dictionary<string, object>? Metadata { get; set; }
public PolicyUpdate ToPolicyUpdate(Guid organizationId, PolicyType type) public PolicyUpdate ToPolicyUpdate(Guid organizationId, PolicyType type)
{ {
var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, type); var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, type);
@@ -21,4 +23,22 @@ public class PolicyUpdateRequestModel : PolicyBaseModel
PerformedBy = new SystemUser(EventSystemUser.PublicApi) PerformedBy = new SystemUser(EventSystemUser.PublicApi)
}; };
} }
public SavePolicyModel ToSavePolicyModel(Guid organizationId, PolicyType type)
{
var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, type);
var policyUpdate = new PolicyUpdate
{
Type = type,
OrganizationId = organizationId,
Data = serializedData,
Enabled = Enabled.GetValueOrDefault()
};
var performedBy = new SystemUser(EventSystemUser.PublicApi);
var metadata = PolicyDataValidator.ValidateAndDeserializeMetadata(Metadata, type);
return new SavePolicyModel(policyUpdate, performedBy, metadata);
}
} }

View File

@@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -24,7 +25,9 @@ public class VerifyOrganizationDomainCommand(
IEventService eventService, IEventService eventService,
IGlobalSettings globalSettings, IGlobalSettings globalSettings,
ICurrentContext currentContext, ICurrentContext currentContext,
IFeatureService featureService,
ISavePolicyCommand savePolicyCommand, ISavePolicyCommand savePolicyCommand,
IVNextSavePolicyCommand vNextSavePolicyCommand,
IMailService mailService, IMailService mailService,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@@ -131,15 +134,26 @@ public class VerifyOrganizationDomainCommand(
await SendVerifiedDomainUserEmailAsync(domain); await SendVerifiedDomainUserEmailAsync(domain);
} }
private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) => private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser)
await savePolicyCommand.SaveAsync( {
new PolicyUpdate var policyUpdate = new PolicyUpdate
{ {
OrganizationId = organizationId, OrganizationId = organizationId,
Type = PolicyType.SingleOrg, Type = PolicyType.SingleOrg,
Enabled = true, Enabled = true,
PerformedBy = actingUser PerformedBy = actingUser
}); };
if (featureService.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor))
{
var savePolicyModel = new SavePolicyModel(policyUpdate, actingUser);
await vNextSavePolicyCommand.SaveAsync(savePolicyModel);
}
else
{
await savePolicyCommand.SaveAsync(policyUpdate);
}
}
private async Task SendVerifiedDomainUserEmailAsync(OrganizationDomain domain) private async Task SendVerifiedDomainUserEmailAsync(OrganizationDomain domain)
{ {

View File

@@ -5,4 +5,18 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
public record SavePolicyModel(PolicyUpdate PolicyUpdate, IActingUser? PerformedBy, IPolicyMetadataModel Metadata) public record SavePolicyModel(PolicyUpdate PolicyUpdate, IActingUser? PerformedBy, IPolicyMetadataModel Metadata)
{ {
public SavePolicyModel(PolicyUpdate PolicyUpdate)
: this(PolicyUpdate, null, new EmptyMetadataModel())
{
}
public SavePolicyModel(PolicyUpdate PolicyUpdate, IActingUser performedBy)
: this(PolicyUpdate, performedBy, new EmptyMetadataModel())
{
}
public SavePolicyModel(PolicyUpdate PolicyUpdate, IPolicyMetadataModel metadata)
: this(PolicyUpdate, null, metadata)
{
}
} }

View File

@@ -3,9 +3,11 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
@@ -24,7 +26,9 @@ public class SsoConfigService : ISsoConfigService
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IFeatureService _featureService;
private readonly ISavePolicyCommand _savePolicyCommand; private readonly ISavePolicyCommand _savePolicyCommand;
private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand;
public SsoConfigService( public SsoConfigService(
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
@@ -32,14 +36,18 @@ public class SsoConfigService : ISsoConfigService
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IEventService eventService, IEventService eventService,
ISavePolicyCommand savePolicyCommand) IFeatureService featureService,
ISavePolicyCommand savePolicyCommand,
IVNextSavePolicyCommand vNextSavePolicyCommand)
{ {
_ssoConfigRepository = ssoConfigRepository; _ssoConfigRepository = ssoConfigRepository;
_policyRepository = policyRepository; _policyRepository = policyRepository;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_eventService = eventService; _eventService = eventService;
_featureService = featureService;
_savePolicyCommand = savePolicyCommand; _savePolicyCommand = savePolicyCommand;
_vNextSavePolicyCommand = vNextSavePolicyCommand;
} }
public async Task SaveAsync(SsoConfig config, Organization organization) public async Task SaveAsync(SsoConfig config, Organization organization)
@@ -67,13 +75,12 @@ public class SsoConfigService : ISsoConfigService
// Automatically enable account recovery, SSO required, and single org policies if trusted device encryption is selected // Automatically enable account recovery, SSO required, and single org policies if trusted device encryption is selected
if (config.GetData().MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption) if (config.GetData().MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption)
{ {
var singleOrgPolicy = new PolicyUpdate
await _savePolicyCommand.SaveAsync(new()
{ {
OrganizationId = config.OrganizationId, OrganizationId = config.OrganizationId,
Type = PolicyType.SingleOrg, Type = PolicyType.SingleOrg,
Enabled = true Enabled = true
}); };
var resetPasswordPolicy = new PolicyUpdate var resetPasswordPolicy = new PolicyUpdate
{ {
@@ -82,14 +89,27 @@ public class SsoConfigService : ISsoConfigService
Enabled = true, Enabled = true,
}; };
resetPasswordPolicy.SetDataModel(new ResetPasswordDataModel { AutoEnrollEnabled = true }); resetPasswordPolicy.SetDataModel(new ResetPasswordDataModel { AutoEnrollEnabled = true });
await _savePolicyCommand.SaveAsync(resetPasswordPolicy);
await _savePolicyCommand.SaveAsync(new() var requireSsoPolicy = new PolicyUpdate
{ {
OrganizationId = config.OrganizationId, OrganizationId = config.OrganizationId,
Type = PolicyType.RequireSso, Type = PolicyType.RequireSso,
Enabled = true Enabled = true
}); };
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor))
{
var performedBy = new SystemUser(EventSystemUser.Unknown);
await _vNextSavePolicyCommand.SaveAsync(new SavePolicyModel(singleOrgPolicy, performedBy));
await _vNextSavePolicyCommand.SaveAsync(new SavePolicyModel(resetPasswordPolicy, performedBy));
await _vNextSavePolicyCommand.SaveAsync(new SavePolicyModel(requireSsoPolicy, performedBy));
}
else
{
await _savePolicyCommand.SaveAsync(singleOrgPolicy);
await _savePolicyCommand.SaveAsync(resetPasswordPolicy);
await _savePolicyCommand.SaveAsync(requireSsoPolicy);
}
} }
await LogEventsAsync(config, oldConfig); await LogEventsAsync(config, oldConfig);

View File

@@ -143,6 +143,7 @@ public static class FeatureFlagKeys
public const string AutomaticConfirmUsers = "pm-19934-auto-confirm-organization-users"; public const string AutomaticConfirmUsers = "pm-19934-auto-confirm-organization-users";
public const string PM23845_VNextApplicationCache = "pm-24957-refactor-memory-application-cache"; public const string PM23845_VNextApplicationCache = "pm-24957-refactor-memory-application-cache";
public const string AccountRecoveryCommand = "pm-25581-prevent-provider-account-recovery"; public const string AccountRecoveryCommand = "pm-25581-prevent-provider-account-recovery";
public const string PolicyValidatorsRefactor = "pm-26423-refactor-policy-side-effects";
/* Auth Team */ /* Auth Team */
public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence"; public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence";

View File

@@ -35,4 +35,10 @@ public interface ISecurityTaskRepository : IRepository<SecurityTask, Guid>
/// <param name="organizationId">The id of the organization</param> /// <param name="organizationId">The id of the organization</param>
/// <returns>A collection of security task metrics</returns> /// <returns>A collection of security task metrics</returns>
Task<SecurityTaskMetrics> GetTaskMetricsAsync(Guid organizationId); Task<SecurityTaskMetrics> GetTaskMetricsAsync(Guid organizationId);
/// <summary>
/// Marks all tasks associated with the respective ciphers as complete.
/// </summary>
/// <param name="cipherIds">Collection of cipher IDs</param>
Task MarkAsCompleteByCipherIds(IEnumerable<Guid> cipherIds);
} }

View File

@@ -33,6 +33,7 @@ public class CipherService : ICipherService
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ICollectionCipherRepository _collectionCipherRepository; private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly ISecurityTaskRepository _securityTaskRepository;
private readonly IPushNotificationService _pushService; private readonly IPushNotificationService _pushService;
private readonly IAttachmentStorageService _attachmentStorageService; private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
@@ -53,6 +54,7 @@ public class CipherService : ICipherService
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
ICollectionCipherRepository collectionCipherRepository, ICollectionCipherRepository collectionCipherRepository,
ISecurityTaskRepository securityTaskRepository,
IPushNotificationService pushService, IPushNotificationService pushService,
IAttachmentStorageService attachmentStorageService, IAttachmentStorageService attachmentStorageService,
IEventService eventService, IEventService eventService,
@@ -71,6 +73,7 @@ public class CipherService : ICipherService
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_collectionCipherRepository = collectionCipherRepository; _collectionCipherRepository = collectionCipherRepository;
_securityTaskRepository = securityTaskRepository;
_pushService = pushService; _pushService = pushService;
_attachmentStorageService = attachmentStorageService; _attachmentStorageService = attachmentStorageService;
_eventService = eventService; _eventService = eventService;
@@ -724,6 +727,7 @@ public class CipherService : ICipherService
cipherDetails.ArchivedDate = null; cipherDetails.ArchivedDate = null;
} }
await _securityTaskRepository.MarkAsCompleteByCipherIds([cipherDetails.Id]);
await _cipherRepository.UpsertAsync(cipherDetails); await _cipherRepository.UpsertAsync(cipherDetails);
await _eventService.LogCipherEventAsync(cipherDetails, EventType.Cipher_SoftDeleted); await _eventService.LogCipherEventAsync(cipherDetails, EventType.Cipher_SoftDeleted);
@@ -750,6 +754,8 @@ public class CipherService : ICipherService
await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId); await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
} }
await _securityTaskRepository.MarkAsCompleteByCipherIds(deletingCiphers.Select(c => c.Id));
var events = deletingCiphers.Select(c => var events = deletingCiphers.Select(c =>
new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_SoftDeleted, null)); new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_SoftDeleted, null));
foreach (var eventsBatch in events.Chunk(100)) foreach (var eventsBatch in events.Chunk(100))

View File

@@ -85,4 +85,19 @@ public class SecurityTaskRepository : Repository<SecurityTask, Guid>, ISecurityT
return tasksList; return tasksList;
} }
/// <inheritdoc />
public async Task MarkAsCompleteByCipherIds(IEnumerable<Guid> cipherIds)
{
if (!cipherIds.Any())
{
return;
}
await using var connection = new SqlConnection(ConnectionString);
await connection.ExecuteAsync(
$"[{Schema}].[SecurityTask_MarkCompleteByCipherIds]",
new { CipherIds = cipherIds.ToGuidIdArrayTVP() },
commandType: CommandType.StoredProcedure);
}
} }

View File

@@ -96,4 +96,24 @@ public class SecurityTaskRepository : Repository<Core.Vault.Entities.SecurityTas
return metrics ?? new Core.Vault.Entities.SecurityTaskMetrics(0, 0); return metrics ?? new Core.Vault.Entities.SecurityTaskMetrics(0, 0);
} }
/// <inheritdoc />
public async Task MarkAsCompleteByCipherIds(IEnumerable<Guid> cipherIds)
{
if (!cipherIds.Any())
{
return;
}
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var cipherIdsList = cipherIds.ToList();
await dbContext.SecurityTasks
.Where(st => st.CipherId.HasValue && cipherIdsList.Contains(st.CipherId.Value) && st.Status != SecurityTaskStatus.Completed)
.ExecuteUpdateAsync(st => st
.SetProperty(s => s.Status, SecurityTaskStatus.Completed)
.SetProperty(s => s.RevisionDate, DateTime.UtcNow));
}
} }

View File

@@ -0,0 +1,15 @@
CREATE PROCEDURE [dbo].[SecurityTask_MarkCompleteByCipherIds]
@CipherIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[SecurityTask]
SET
[Status] = 1, -- completed
[RevisionDate] = SYSUTCDATETIME()
WHERE
[CipherId] IN (SELECT [Id] FROM @CipherIds)
AND [Status] <> 1 -- Not already completed
END

View File

@@ -0,0 +1,87 @@
using Bit.Api.AdminConsole.Public.Controllers;
using Bit.Api.AdminConsole.Public.Models.Request;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.Context;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Public.Controllers;
[ControllerCustomize(typeof(PoliciesController))]
[SutProviderCustomize]
public class PoliciesControllerTests
{
[Theory]
[BitAutoData]
public async Task Put_WhenPolicyValidatorsRefactorEnabled_UsesVNextSavePolicyCommand(
Guid organizationId,
PolicyType policyType,
PolicyUpdateRequestModel model,
Policy policy,
SutProvider<PoliciesController> sutProvider)
{
// Arrange
policy.Data = null;
sutProvider.GetDependency<ICurrentContext>()
.OrganizationId.Returns(organizationId);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
.Returns(true);
sutProvider.GetDependency<IVNextSavePolicyCommand>()
.SaveAsync(Arg.Any<SavePolicyModel>())
.Returns(policy);
// Act
await sutProvider.Sut.Put(policyType, model);
// Assert
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(m =>
m.PolicyUpdate.OrganizationId == organizationId &&
m.PolicyUpdate.Type == policyType &&
m.PolicyUpdate.Enabled == model.Enabled.GetValueOrDefault() &&
m.PerformedBy is SystemUser));
}
[Theory]
[BitAutoData]
public async Task Put_WhenPolicyValidatorsRefactorDisabled_UsesLegacySavePolicyCommand(
Guid organizationId,
PolicyType policyType,
PolicyUpdateRequestModel model,
Policy policy,
SutProvider<PoliciesController> sutProvider)
{
// Arrange
policy.Data = null;
sutProvider.GetDependency<ICurrentContext>()
.OrganizationId.Returns(organizationId);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
.Returns(false);
sutProvider.GetDependency<ISavePolicyCommand>()
.SaveAsync(Arg.Any<PolicyUpdate>())
.Returns(policy);
// Act
await sutProvider.Sut.Put(policyType, model);
// Assert
await sutProvider.GetDependency<ISavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<PolicyUpdate>(p =>
p.OrganizationId == organizationId &&
p.Type == policyType &&
p.Enabled == model.Enabled));
}
}

View File

@@ -1,10 +1,15 @@
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json; using System.Text.Json;
using Bit.Api.AdminConsole.Controllers; using Bit.Api.AdminConsole.Controllers;
using Bit.Api.AdminConsole.Models.Request;
using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Context; using Bit.Core.Context;
@@ -455,4 +460,98 @@ public class PoliciesControllerTests
Assert.Equal(enabledPolicy.Type, expectedPolicy.Type); Assert.Equal(enabledPolicy.Type, expectedPolicy.Type);
Assert.Equal(enabledPolicy.Enabled, expectedPolicy.Enabled); Assert.Equal(enabledPolicy.Enabled, expectedPolicy.Enabled);
} }
[Theory]
[BitAutoData]
public async Task PutVNext_WhenPolicyValidatorsRefactorEnabled_UsesVNextSavePolicyCommand(
SutProvider<PoliciesController> sutProvider, Guid orgId,
SavePolicyRequest model, Policy policy, Guid userId)
{
// Arrange
policy.Data = null;
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(orgId)
.Returns(true);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
.Returns(true);
sutProvider.GetDependency<IVNextSavePolicyCommand>()
.SaveAsync(Arg.Any<SavePolicyModel>())
.Returns(policy);
// Act
var result = await sutProvider.Sut.PutVNext(orgId, model);
// Assert
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(
m => m.PolicyUpdate.OrganizationId == orgId &&
m.PolicyUpdate.Type == model.Policy.Type &&
m.PolicyUpdate.Enabled == model.Policy.Enabled &&
m.PerformedBy.UserId == userId &&
m.PerformedBy.IsOrganizationOwnerOrProvider == true));
await sutProvider.GetDependency<ISavePolicyCommand>()
.DidNotReceiveWithAnyArgs()
.VNextSaveAsync(default);
Assert.NotNull(result);
Assert.Equal(policy.Id, result.Id);
Assert.Equal(policy.Type, result.Type);
}
[Theory]
[BitAutoData]
public async Task PutVNext_WhenPolicyValidatorsRefactorDisabled_UsesSavePolicyCommand(
SutProvider<PoliciesController> sutProvider, Guid orgId,
SavePolicyRequest model, Policy policy, Guid userId)
{
// Arrange
policy.Data = null;
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(orgId)
.Returns(true);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
.Returns(false);
sutProvider.GetDependency<ISavePolicyCommand>()
.VNextSaveAsync(Arg.Any<SavePolicyModel>())
.Returns(policy);
// Act
var result = await sutProvider.Sut.PutVNext(orgId, model);
// Assert
await sutProvider.GetDependency<ISavePolicyCommand>()
.Received(1)
.VNextSaveAsync(Arg.Is<SavePolicyModel>(
m => m.PolicyUpdate.OrganizationId == orgId &&
m.PolicyUpdate.Type == model.Policy.Type &&
m.PolicyUpdate.Enabled == model.Policy.Enabled &&
m.PerformedBy.UserId == userId &&
m.PerformedBy.IsOrganizationOwnerOrProvider == true));
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.DidNotReceiveWithAnyArgs()
.SaveAsync(default);
Assert.NotNull(result);
Assert.Equal(policy.Id, result.Id);
Assert.Equal(policy.Type, result.Type);
}
} }

View File

@@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -191,6 +192,37 @@ public class VerifyOrganizationDomainCommandTests
x.PerformedBy.UserId == userId)); x.PerformedBy.UserId == userId));
} }
[Theory, BitAutoData]
public async Task UserVerifyOrganizationDomainAsync_WhenPolicyValidatorsRefactorFlagEnabled_UsesVNextSavePolicyCommand(
OrganizationDomain domain, Guid userId, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationDomainRepository>()
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
.Returns([]);
sutProvider.GetDependency<IDnsResolverService>()
.ResolveAsync(domain.DomainName, domain.Txt)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.UserId.Returns(userId);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
.Returns(true);
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(m =>
m.PolicyUpdate.Type == PolicyType.SingleOrg &&
m.PolicyUpdate.OrganizationId == domain.OrganizationId &&
m.PolicyUpdate.Enabled &&
m.PerformedBy is StandardUser &&
m.PerformedBy.UserId == userId));
}
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldNotBeEnabled( public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldNotBeEnabled(
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider) OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)

View File

@@ -92,7 +92,7 @@ public class FreeFamiliesForEnterprisePolicyValidatorTests
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId) .GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
.Returns(organizationSponsorships); .Returns(organizationSponsorships);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);
@@ -120,7 +120,7 @@ public class FreeFamiliesForEnterprisePolicyValidatorTests
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId) .GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
.Returns(organizationSponsorships); .Returns(organizationSponsorships);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);

View File

@@ -32,7 +32,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(false); .Returns(false);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -58,7 +58,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(true); .Returns(true);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -84,7 +84,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(true); .Returns(true);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -110,7 +110,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
var collectionRepository = Substitute.For<ICollectionRepository>(); var collectionRepository = Substitute.For<ICollectionRepository>();
var sut = ArrangeSut(factory, policyRepository, collectionRepository); var sut = ArrangeSut(factory, policyRepository, collectionRepository);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -199,7 +199,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
var collectionRepository = Substitute.For<ICollectionRepository>(); var collectionRepository = Substitute.For<ICollectionRepository>();
var sut = ArrangeSut(factory, policyRepository, collectionRepository); var sut = ArrangeSut(factory, policyRepository, collectionRepository);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -238,7 +238,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(true); .Returns(true);
var policyRequest = new SavePolicyModel(policyUpdate, null, metadata); var policyRequest = new SavePolicyModel(policyUpdate, metadata);
// Act // Act
await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -286,7 +286,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(false); .Returns(false);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -312,7 +312,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(true); .Returns(true);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -338,7 +338,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(true); .Returns(true);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -364,7 +364,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
var collectionRepository = Substitute.For<ICollectionRepository>(); var collectionRepository = Substitute.For<ICollectionRepository>();
var sut = ArrangeSut(factory, policyRepository, collectionRepository); var sut = ArrangeSut(factory, policyRepository, collectionRepository);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -404,7 +404,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
var collectionRepository = Substitute.For<ICollectionRepository>(); var collectionRepository = Substitute.For<ICollectionRepository>();
var sut = ArrangeSut(factory, policyRepository, collectionRepository); var sut = ArrangeSut(factory, policyRepository, collectionRepository);
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
// Act // Act
await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
@@ -436,7 +436,7 @@ public class OrganizationDataOwnershipPolicyValidatorTests
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) .IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
.Returns(true); .Returns(true);
var policyRequest = new SavePolicyModel(policyUpdate, null, metadata); var policyRequest = new SavePolicyModel(policyUpdate, metadata);
// Act // Act
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);

View File

@@ -88,7 +88,7 @@ public class RequireSsoPolicyValidatorTests
.GetByOrganizationIdAsync(policyUpdate.OrganizationId) .GetByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(ssoConfig); .Returns(ssoConfig);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase); Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase);
@@ -109,7 +109,7 @@ public class RequireSsoPolicyValidatorTests
.GetByOrganizationIdAsync(policyUpdate.OrganizationId) .GetByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(ssoConfig); .Returns(ssoConfig);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.Contains("Trusted device encryption is on", result, StringComparison.OrdinalIgnoreCase); Assert.Contains("Trusted device encryption is on", result, StringComparison.OrdinalIgnoreCase);
@@ -129,7 +129,7 @@ public class RequireSsoPolicyValidatorTests
.GetByOrganizationIdAsync(policyUpdate.OrganizationId) .GetByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(ssoConfig); .Returns(ssoConfig);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.True(string.IsNullOrEmpty(result)); Assert.True(string.IsNullOrEmpty(result));

View File

@@ -94,7 +94,7 @@ public class ResetPasswordPolicyValidatorTests
.GetByOrganizationIdAsync(policyUpdate.OrganizationId) .GetByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(ssoConfig); .Returns(ssoConfig);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.Contains("Trusted device encryption is on and requires this policy.", result, StringComparison.OrdinalIgnoreCase); Assert.Contains("Trusted device encryption is on and requires this policy.", result, StringComparison.OrdinalIgnoreCase);
@@ -118,7 +118,7 @@ public class ResetPasswordPolicyValidatorTests
.GetByOrganizationIdAsync(policyUpdate.OrganizationId) .GetByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(ssoConfig); .Returns(ssoConfig);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.True(string.IsNullOrEmpty(result)); Assert.True(string.IsNullOrEmpty(result));

View File

@@ -162,7 +162,7 @@ public class SingleOrgPolicyValidatorTests
.GetByOrganizationIdAsync(policyUpdate.OrganizationId) .GetByOrganizationIdAsync(policyUpdate.OrganizationId)
.Returns(ssoConfig); .Returns(ssoConfig);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase); Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase);
@@ -186,7 +186,7 @@ public class SingleOrgPolicyValidatorTests
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId) .HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
.Returns(false); .Returns(false);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
Assert.True(string.IsNullOrEmpty(result)); Assert.True(string.IsNullOrEmpty(result));
@@ -256,7 +256,7 @@ public class SingleOrgPolicyValidatorTests
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>()) .RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
.Returns(new CommandResult()); .Returns(new CommandResult());
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);

View File

@@ -169,7 +169,7 @@ public class TwoFactorAuthenticationPolicyValidatorTests
(orgUserDetailUserWithout2Fa, false), (orgUserDetailUserWithout2Fa, false),
}); });
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy)); sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy));
@@ -228,7 +228,7 @@ public class TwoFactorAuthenticationPolicyValidatorTests
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>()) .RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
.Returns(new CommandResult()); .Returns(new CommandResult());
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
// Act // Act
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);

View File

@@ -288,7 +288,7 @@ public class SavePolicyCommandTests
{ {
// Arrange // Arrange
var sutProvider = SutProviderFactory(); var sutProvider = SutProviderFactory();
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
currentPolicy.OrganizationId = policyUpdate.OrganizationId; currentPolicy.OrganizationId = policyUpdate.OrganizationId;
sutProvider.GetDependency<IPolicyRepository>() sutProvider.GetDependency<IPolicyRepository>()
@@ -332,7 +332,7 @@ public class SavePolicyCommandTests
var sutProvider = SutProviderFactory(); var sutProvider = SutProviderFactory();
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
sutProvider.GetDependency<IPolicyRepository>() sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type)

View File

@@ -33,7 +33,7 @@ public class VNextSavePolicyCommandTests
fakePolicyValidationEvent fakePolicyValidationEvent
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var newPolicy = new Policy var newPolicy = new Policy
{ {
@@ -77,7 +77,7 @@ public class VNextSavePolicyCommandTests
fakePolicyValidationEvent fakePolicyValidationEvent
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
currentPolicy.OrganizationId = policyUpdate.OrganizationId; currentPolicy.OrganizationId = policyUpdate.OrganizationId;
sutProvider.GetDependency<IPolicyRepository>() sutProvider.GetDependency<IPolicyRepository>()
@@ -117,7 +117,7 @@ public class VNextSavePolicyCommandTests
{ {
// Arrange // Arrange
var sutProvider = SutProviderFactory(); var sutProvider = SutProviderFactory();
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
sutProvider.GetDependency<IApplicationCacheService>() sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId) .GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
@@ -137,7 +137,7 @@ public class VNextSavePolicyCommandTests
{ {
// Arrange // Arrange
var sutProvider = SutProviderFactory(); var sutProvider = SutProviderFactory();
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
sutProvider.GetDependency<IApplicationCacheService>() sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId) .GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
@@ -167,7 +167,7 @@ public class VNextSavePolicyCommandTests
new FakeSingleOrgDependencyEvent() new FakeSingleOrgDependencyEvent()
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var requireSsoPolicy = new Policy var requireSsoPolicy = new Policy
{ {
@@ -202,7 +202,7 @@ public class VNextSavePolicyCommandTests
new FakeSingleOrgDependencyEvent() new FakeSingleOrgDependencyEvent()
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var requireSsoPolicy = new Policy var requireSsoPolicy = new Policy
{ {
@@ -237,7 +237,7 @@ public class VNextSavePolicyCommandTests
new FakeSingleOrgDependencyEvent() new FakeSingleOrgDependencyEvent()
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var requireSsoPolicy = new Policy var requireSsoPolicy = new Policy
{ {
@@ -271,7 +271,7 @@ public class VNextSavePolicyCommandTests
new FakeSingleOrgDependencyEvent() new FakeSingleOrgDependencyEvent()
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
ArrangeOrganization(sutProvider, policyUpdate); ArrangeOrganization(sutProvider, policyUpdate);
sutProvider.GetDependency<IPolicyRepository>() sutProvider.GetDependency<IPolicyRepository>()
@@ -302,7 +302,7 @@ public class VNextSavePolicyCommandTests
new FakeVaultTimeoutDependencyEvent() new FakeVaultTimeoutDependencyEvent()
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
ArrangeOrganization(sutProvider, policyUpdate); ArrangeOrganization(sutProvider, policyUpdate);
sutProvider.GetDependency<IPolicyRepository>() sutProvider.GetDependency<IPolicyRepository>()
@@ -331,7 +331,7 @@ public class VNextSavePolicyCommandTests
new FakeSingleOrgDependencyEvent() new FakeSingleOrgDependencyEvent()
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
ArrangeOrganization(sutProvider, policyUpdate); ArrangeOrganization(sutProvider, policyUpdate);
sutProvider.GetDependency<IPolicyRepository>() sutProvider.GetDependency<IPolicyRepository>()
@@ -356,7 +356,7 @@ public class VNextSavePolicyCommandTests
fakePolicyValidationEvent fakePolicyValidationEvent
]); ]);
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); var savePolicyModel = new SavePolicyModel(policyUpdate);
var singleOrgPolicy = new Policy var singleOrgPolicy = new Policy
{ {

View File

@@ -1,8 +1,10 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
@@ -12,6 +14,7 @@ using Bit.Core.Auth.Services;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute; using NSubstitute;
@@ -364,4 +367,54 @@ public class SsoConfigServiceTests
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs() await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
.UpsertAsync(default); .UpsertAsync(default);
} }
[Theory, BitAutoData]
public async Task SaveAsync_Tde_WhenPolicyValidatorsRefactorEnabled_UsesVNextSavePolicyCommand(
SutProvider<SsoConfigService> sutProvider, Organization organization)
{
var ssoConfig = new SsoConfig
{
Id = default,
Data = new SsoConfigurationData
{
MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption,
}.Serialize(),
Enabled = true,
OrganizationId = organization.Id,
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor)
.Returns(true);
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(m =>
m.PolicyUpdate.Type == PolicyType.SingleOrg &&
m.PolicyUpdate.OrganizationId == organization.Id &&
m.PolicyUpdate.Enabled &&
m.PerformedBy is SystemUser));
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(m =>
m.PolicyUpdate.Type == PolicyType.ResetPassword &&
m.PolicyUpdate.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled &&
m.PolicyUpdate.OrganizationId == organization.Id &&
m.PolicyUpdate.Enabled &&
m.PerformedBy is SystemUser));
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(m =>
m.PolicyUpdate.Type == PolicyType.RequireSso &&
m.PolicyUpdate.OrganizationId == organization.Id &&
m.PolicyUpdate.Enabled &&
m.PerformedBy is SystemUser));
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
.UpsertAsync(default);
}
} }

View File

@@ -2286,6 +2286,63 @@ public class CipherServiceTests
.PushSyncCiphersAsync(deletingUserId); .PushSyncCiphersAsync(deletingUserId);
} }
[Theory]
[BitAutoData]
public async Task SoftDeleteAsync_CallsMarkAsCompleteByCipherIds(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
cipherDetails.UserId = deletingUserId;
cipherDetails.OrganizationId = null;
cipherDetails.DeletedDate = null;
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
await sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId);
await sutProvider.GetDependency<ISecurityTaskRepository>()
.Received(1)
.MarkAsCompleteByCipherIds(Arg.Is<IEnumerable<Guid>>(ids =>
ids.Count() == 1 && ids.First() == cipherDetails.Id));
}
[Theory]
[BitAutoData]
public async Task SoftDeleteManyAsync_CallsMarkAsCompleteByCipherIds(
Guid deletingUserId, List<CipherDetails> ciphers, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
foreach (var cipher in ciphers)
{
cipher.UserId = deletingUserId;
cipher.OrganizationId = null;
cipher.Edit = true;
cipher.DeletedDate = null;
}
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
await sutProvider.Sut.SoftDeleteManyAsync(cipherIds, deletingUserId, null, false);
await sutProvider.GetDependency<ISecurityTaskRepository>()
.Received(1)
.MarkAsCompleteByCipherIds(Arg.Is<IEnumerable<Guid>>(ids =>
ids.Count() == cipherIds.Length && ids.All(id => cipherIds.Contains(id))));
}
private async Task AssertNoActionsAsync(SutProvider<CipherService> sutProvider) private async Task AssertNoActionsAsync(SutProvider<CipherService> sutProvider)
{ {
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyOrganizationDetailsByOrganizationIdAsync(default); await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyOrganizationDetailsByOrganizationIdAsync(default);

View File

@@ -345,4 +345,110 @@ public class SecurityTaskRepositoryTests
Assert.Equal(0, metrics.CompletedTasks); Assert.Equal(0, metrics.CompletedTasks);
Assert.Equal(0, metrics.TotalTasks); Assert.Equal(0, metrics.TotalTasks);
} }
[DatabaseTheory, DatabaseData]
public async Task MarkAsCompleteByCipherIds_MarksPendingTasksAsCompleted(
IOrganizationRepository organizationRepository,
ICipherRepository cipherRepository,
ISecurityTaskRepository securityTaskRepository)
{
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Org",
PlanType = PlanType.EnterpriseAnnually,
Plan = "Test Plan",
BillingEmail = "billing@email.com"
});
var cipher1 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = "",
});
var cipher2 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = "",
});
var task1 = await securityTaskRepository.CreateAsync(new SecurityTask
{
OrganizationId = organization.Id,
CipherId = cipher1.Id,
Status = SecurityTaskStatus.Pending,
Type = SecurityTaskType.UpdateAtRiskCredential,
});
var task2 = await securityTaskRepository.CreateAsync(new SecurityTask
{
OrganizationId = organization.Id,
CipherId = cipher2.Id,
Status = SecurityTaskStatus.Pending,
Type = SecurityTaskType.UpdateAtRiskCredential,
});
await securityTaskRepository.MarkAsCompleteByCipherIds([cipher1.Id, cipher2.Id]);
var updatedTask1 = await securityTaskRepository.GetByIdAsync(task1.Id);
var updatedTask2 = await securityTaskRepository.GetByIdAsync(task2.Id);
Assert.Equal(SecurityTaskStatus.Completed, updatedTask1.Status);
Assert.Equal(SecurityTaskStatus.Completed, updatedTask2.Status);
}
[DatabaseTheory, DatabaseData]
public async Task MarkAsCompleteByCipherIds_OnlyUpdatesSpecifiedCiphers(
IOrganizationRepository organizationRepository,
ICipherRepository cipherRepository,
ISecurityTaskRepository securityTaskRepository)
{
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Org",
PlanType = PlanType.EnterpriseAnnually,
Plan = "Test Plan",
BillingEmail = "billing@email.com"
});
var cipher1 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = "",
});
var cipher2 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = "",
});
var taskToUpdate = await securityTaskRepository.CreateAsync(new SecurityTask
{
OrganizationId = organization.Id,
CipherId = cipher1.Id,
Status = SecurityTaskStatus.Pending,
Type = SecurityTaskType.UpdateAtRiskCredential,
});
var taskToKeep = await securityTaskRepository.CreateAsync(new SecurityTask
{
OrganizationId = organization.Id,
CipherId = cipher2.Id,
Status = SecurityTaskStatus.Pending,
Type = SecurityTaskType.UpdateAtRiskCredential,
});
await securityTaskRepository.MarkAsCompleteByCipherIds([cipher1.Id]);
var updatedTask = await securityTaskRepository.GetByIdAsync(taskToUpdate.Id);
var unchangedTask = await securityTaskRepository.GetByIdAsync(taskToKeep.Id);
Assert.Equal(SecurityTaskStatus.Completed, updatedTask.Status);
Assert.Equal(SecurityTaskStatus.Pending, unchangedTask.Status);
}
} }

View File

@@ -0,0 +1,15 @@
CREATE OR ALTER PROCEDURE [dbo].[SecurityTask_MarkCompleteByCipherIds]
@CipherIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[SecurityTask]
SET
[Status] = 1, -- Completed
[RevisionDate] = SYSUTCDATETIME()
WHERE
[CipherId] IN (SELECT [Id] FROM @CipherIds)
AND [Status] <> 1 -- Not already completed
END