[PM-30448] - remove edit requirement for cipher archiving (#6830)

* remove edit requirement for cipher archiving

* update cipher_archive/unarchive sql

* update cipher_archive/unarchive sql

* fix sql

* update sql

* update sql
This commit is contained in:
Jordan Aasen
2026-01-14 15:55:09 -08:00
committed by GitHub
parent ed5419c767
commit b86a31160a
5 changed files with 195 additions and 17 deletions

View File

@@ -801,7 +801,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
var query = from ucd in await userCipherDetailsQuery.Run(dbContext).ToListAsync()
join c in cipherEntitiesToCheck
on ucd.Id equals c.Id
where ucd.Edit && FilterArchivedDate(action, ucd)
where FilterArchivedDate(action, ucd)
select c;
var utcNow = DateTime.UtcNow;

View File

@@ -13,14 +13,13 @@ BEGIN
INSERT INTO #Temp
SELECT
[Id],
[UserId]
ucd.[Id],
ucd.[UserId]
FROM
[dbo].[UserCipherDetails](@UserId)
[dbo].[UserCipherDetails](@UserId) ucd
INNER JOIN @Ids ids ON ids.Id = ucd.[Id]
WHERE
[Edit] = 1
AND [ArchivedDate] IS NULL
AND [Id] IN (SELECT * FROM @Ids)
ucd.[ArchivedDate] IS NULL
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
UPDATE
@@ -32,8 +31,9 @@ BEGIN
CONVERT(NVARCHAR(30), @UtcNow, 127)
),
[RevisionDate] = @UtcNow
WHERE
[Id] IN (SELECT [Id] FROM #Temp)
FROM [dbo].[Cipher] AS c
INNER JOIN #Temp AS t
ON t.[Id] = c.[Id];
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId

View File

@@ -13,14 +13,13 @@ BEGIN
INSERT INTO #Temp
SELECT
[Id],
[UserId]
ucd.[Id],
ucd.[UserId]
FROM
[dbo].[UserCipherDetails](@UserId)
[dbo].[UserCipherDetails](@UserId) ucd
INNER JOIN @Ids ids ON ids.Id = ucd.[Id]
WHERE
[Edit] = 1
AND [ArchivedDate] IS NOT NULL
AND [Id] IN (SELECT * FROM @Ids)
ucd.[ArchivedDate] IS NOT NULL
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
UPDATE
@@ -32,8 +31,9 @@ BEGIN
NULL
),
[RevisionDate] = @UtcNow
WHERE
[Id] IN (SELECT [Id] FROM #Temp)
FROM [dbo].[Cipher] AS c
INNER JOIN #Temp AS t
ON t.[Id] = c.[Id];
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId

View File

@@ -2,6 +2,7 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data;
using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Core.Utilities;
using Bit.Core.Vault.Entities;
using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
@@ -279,4 +280,92 @@ public class CipherRepositoryTests
Assert.Equal(Core.Vault.Enums.CipherRepromptType.Password, savedCipher.Reprompt);
}
}
[CiSkippedTheory, EfUserCipherCustomize, BitAutoData]
public async Task ArchiveAsync_SetsArchivesJsonAndBumpsUserAccountRevisionDate(
Cipher cipher,
User user,
List<EfVaultRepo.CipherRepository> suts,
List<EfRepo.UserRepository> efUserRepos)
{
foreach (var sut in suts)
{
var i = suts.IndexOf(sut);
var efUser = await efUserRepos[i].CreateAsync(user);
efUserRepos[i].ClearChangeTracking();
cipher.UserId = efUser.Id;
cipher.OrganizationId = null;
var createdCipher = await sut.CreateAsync(cipher);
sut.ClearChangeTracking();
var archiveUtcNow = await sut.ArchiveAsync(new[] { createdCipher.Id }, efUser.Id);
sut.ClearChangeTracking();
var savedCipher = await sut.GetByIdAsync(createdCipher.Id);
Assert.NotNull(savedCipher);
Assert.Equal(archiveUtcNow, savedCipher.RevisionDate);
Assert.False(string.IsNullOrWhiteSpace(savedCipher.Archives));
var archives = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, DateTime>>(savedCipher.Archives);
Assert.NotNull(archives);
Assert.True(archives.ContainsKey(efUser.Id));
Assert.Equal(archiveUtcNow, archives[efUser.Id]);
var bumpedUser = await efUserRepos[i].GetByIdAsync(efUser.Id);
Assert.Equal(DateTime.UtcNow.ToShortDateString(), bumpedUser.AccountRevisionDate.ToShortDateString());
}
}
[CiSkippedTheory, EfUserCipherCustomize, BitAutoData]
public async Task UnarchiveAsync_RemovesUserFromArchivesJsonAndBumpsUserAccountRevisionDate(
Cipher cipher,
User user,
List<EfVaultRepo.CipherRepository> suts,
List<EfRepo.UserRepository> efUserRepos)
{
foreach (var sut in suts)
{
var i = suts.IndexOf(sut);
var efUser = await efUserRepos[i].CreateAsync(user);
efUserRepos[i].ClearChangeTracking();
cipher.UserId = efUser.Id;
cipher.OrganizationId = null;
var createdCipher = await sut.CreateAsync(cipher);
sut.ClearChangeTracking();
// Precondition: archived
await sut.ArchiveAsync(new[] { createdCipher.Id }, efUser.Id);
sut.ClearChangeTracking();
var unarchiveUtcNow = await sut.UnarchiveAsync(new[] { createdCipher.Id }, efUser.Id);
sut.ClearChangeTracking();
var savedCipher = await sut.GetByIdAsync(createdCipher.Id);
Assert.NotNull(savedCipher);
Assert.Equal(unarchiveUtcNow, savedCipher.RevisionDate);
// Archives should be null or not contain this user (repo clears string when map empty)
if (!string.IsNullOrWhiteSpace(savedCipher.Archives))
{
var archives = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, DateTime>>(savedCipher.Archives)
?? new Dictionary<Guid, DateTime>();
Assert.False(archives.ContainsKey(efUser.Id));
}
else
{
Assert.Null(savedCipher.Archives);
}
var bumpedUser = await efUserRepos[i].GetByIdAsync(efUser.Id);
Assert.Equal(DateTime.UtcNow.ToShortDateString(), bumpedUser.AccountRevisionDate.ToShortDateString());
}
}
}

View File

@@ -0,0 +1,89 @@
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Unarchive]
@Ids AS [dbo].[GuidIdArray] READONLY,
@UserId AS UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #Temp
(
[Id] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL
)
INSERT INTO #Temp
SELECT
ucd.[Id],
ucd.[UserId]
FROM
[dbo].[UserCipherDetails](@UserId) ucd
INNER JOIN @Ids ids ON ids.Id = ucd.[Id]
WHERE
ucd.[ArchivedDate] IS NOT NULL
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
UPDATE
[dbo].[Cipher]
SET
[Archives] = JSON_MODIFY(
COALESCE([Archives], N'{}'),
CONCAT('$."', @UserId, '"'),
NULL
),
[RevisionDate] = @UtcNow
FROM [dbo].[Cipher] AS c
INNER JOIN #Temp AS t
ON t.[Id] = c.[Id];
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
DROP TABLE #Temp
SELECT @UtcNow
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Archive]
@Ids AS [dbo].[GuidIdArray] READONLY,
@UserId AS UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #Temp
(
[Id] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL
)
INSERT INTO #Temp
SELECT
ucd.[Id],
ucd.[UserId]
FROM
[dbo].[UserCipherDetails](@UserId) ucd
INNER JOIN @Ids ids ON ids.Id = ucd.[Id]
WHERE
ucd.[ArchivedDate] IS NULL
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
UPDATE
[dbo].[Cipher]
SET
[Archives] = JSON_MODIFY(
COALESCE([Archives], N'{}'),
CONCAT('$."', @UserId, '"'),
CONVERT(NVARCHAR(30), @UtcNow, 127)
),
[RevisionDate] = @UtcNow
FROM [dbo].[Cipher] AS c
INNER JOIN #Temp AS t
ON t.[Id] = c.[Id];
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
DROP TABLE #Temp
SELECT @UtcNow
END
GO