diff --git a/src/Infrastructure.Dapper/AdminConsole/Helpers/BulkResourceCreationService.cs b/src/Infrastructure.Dapper/AdminConsole/Helpers/BulkResourceCreationService.cs index 2be33e8846..b990c891fe 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Helpers/BulkResourceCreationService.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Helpers/BulkResourceCreationService.cs @@ -220,6 +220,8 @@ public static class BulkResourceCreationService ciphersTable.Columns.Add(deletedDateColumn); var archivedDateColumn = new DataColumn(nameof(c.ArchivedDate), typeof(DateTime)); ciphersTable.Columns.Add(archivedDateColumn); + var archivesColumn = new DataColumn(nameof(c.Archives), typeof(string)); + ciphersTable.Columns.Add(archivesColumn); var repromptColumn = new DataColumn(nameof(c.Reprompt), typeof(short)); ciphersTable.Columns.Add(repromptColumn); var keyColummn = new DataColumn(nameof(c.Key), typeof(string)); @@ -250,6 +252,7 @@ public static class BulkResourceCreationService row[revisionDateColumn] = cipher.RevisionDate; row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value; row[archivedDateColumn] = cipher.ArchivedDate.HasValue ? cipher.ArchivedDate : DBNull.Value; + row[archivesColumn] = cipher.Archives; row[repromptColumn] = cipher.Reprompt.HasValue ? cipher.Reprompt.Value : DBNull.Value; row[keyColummn] = cipher.Key; diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index b196a07e9b..82f1160915 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -72,7 +72,8 @@ public class UserCipherDetailsQuery : IQuery OrganizationUseTotp = o.UseTotp, c.Reprompt, c.Key, - c.ArchivedDate + c.ArchivedDate, + c.Archives }; var query2 = from c in dbContext.Ciphers @@ -96,7 +97,8 @@ public class UserCipherDetailsQuery : IQuery OrganizationUseTotp = false, c.Reprompt, c.Key, - c.ArchivedDate + c.ArchivedDate, + c.Archives }; var union = query.Union(query2).Select(c => new CipherDetails @@ -118,7 +120,8 @@ public class UserCipherDetailsQuery : IQuery Manage = c.Manage, OrganizationUseTotp = c.OrganizationUseTotp, Key = c.Key, - ArchivedDate = c.ArchivedDate + ArchivedDate = c.ArchivedDate, + Archives = c.Archives }); return union; } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 3c45afe530..45a6db2ca6 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -808,7 +808,31 @@ public class CipherRepository : Repository { dbContext.Attach(cipher); - cipher.ArchivedDate = action == CipherStateAction.Unarchive ? null : utcNow; + + Dictionary archives; + if (string.IsNullOrWhiteSpace(cipher.Archives)) + { + archives = new Dictionary(); + } + else + { + archives = JsonSerializer.Deserialize>(cipher.Archives) + ?? new Dictionary(); + } + + if (action == CipherStateAction.Unarchive) + { + archives.Remove(userId); + } + else if (action == CipherStateAction.Archive) + { + archives[userId] = utcNow; + } + + cipher.Archives = archives.Count == 0 + ? null + : JsonSerializer.Serialize(archives); + cipher.RevisionDate = utcNow; }); diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Archive.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Archive.sql index 7e962d36ba..d0b8fd48cf 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Archive.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Archive.sql @@ -28,7 +28,7 @@ BEGIN SET [Archives] = JSON_MODIFY( COALESCE([Archives], N'{}'), - '$."' + CONVERT(NVARCHAR(36), @UserId) + '"', + CONCAT('$."', CONVERT(NVARCHAR(36), @UserId), '"'), CONVERT(NVARCHAR(30), @UtcNow, 127) ), [RevisionDate] = @UtcNow diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Unarchive.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Unarchive.sql index d3ef3df96e..6e65e88df2 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Unarchive.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Unarchive.sql @@ -28,7 +28,7 @@ BEGIN SET [Archives] = JSON_MODIFY( COALESCE([Archives], N'{}'), - '$."' + CONVERT(NVARCHAR(36), @UserId) + '"', + CONCAT('$."', CONVERT(NVARCHAR(36), @UserId), '"'), NULL ), [RevisionDate] = @UtcNow diff --git a/util/Migrator/DbScripts/2025-12-02_AddCipherArchives.sql b/util/Migrator/DbScripts/2025-12-02_AddCipherArchives.sql index 127edab452..7c555e7a0b 100644 --- a/util/Migrator/DbScripts/2025-12-02_AddCipherArchives.sql +++ b/util/Migrator/DbScripts/2025-12-02_AddCipherArchives.sql @@ -197,7 +197,7 @@ BEGIN SET [Archives] = JSON_MODIFY( COALESCE([Archives], N'{}'), - '$."' + CONVERT(NVARCHAR(36), @UserId) + '"', + CONCAT('$."', CONVERT(NVARCHAR(36), @UserId), '"'), CONVERT(NVARCHAR(30), @UtcNow, 127) ), [RevisionDate] = @UtcNow @@ -248,7 +248,7 @@ BEGIN SET [Archives] = JSON_MODIFY( COALESCE([Archives], N'{}'), - '$."' + CONVERT(NVARCHAR(36), @UserId) + '"', + CONCAT('$."', CONVERT(NVARCHAR(36), @UserId), '"'), NULL ), [RevisionDate] = @UtcNow