1
0
mirror of https://github.com/bitwarden/server synced 2026-01-18 00:13:19 +00:00

[PM-27884][PM-27886][PM-27885] - Add Cipher Archives (#6578)

* add Archives column to ciphers table

* add archives column

* update cipher archive/unarchive and cipher deatils query

* add migrations

* add missing migrations

* fixes

* update tests. cleanup

* syntax fix

* fix sql syntax

* fix sql

* fix CreateWithCollections

* fix sql

* fix migration file

* fix migration

* add go

* add missing go

* fix migrations

* add missing proc

* fix migrations

* implement claude suggestions

* fix test

* update cipher service and tests

* updates to soft delete

* update UserCipherDetailsQuery and migration

* update migration

* update archive ciphers command to allow org ciphers to be archived

* updates to archivedDate

* revert change to UserCipherDetails

* updates to migration and procs

* remove archivedDate from Cipher_CreateWithCollections

* remove trailing comma

* fix syntax errors

* fix migration

* add double quotes around datetime

* fix syntax error

* remove archivedDate from cipher entity

* re-add ArchivedDate into cipher

* fix migration

* do not set Cipher.ArchivedDate in CipherRepository

* re-add ArchivedDate until removed from the db

* set defaults

* change to CREATE OR ALTER

* fix migration

* fix migration file

* quote datetime

* fix existing archiveAsync test. add additional test

* quote datetime

* update migration

* do not wrap datetime in quotes

* do not wrap datetime in quotes

* fix migration

* clean up archives and archivedDate from procs

* fix UserCipherDetailsQuery

* fix setting date in JSON_MODIFY

* prefer cast over convert

* fix cipher response model

* re-add ArchivedDate

* add new keyword

* remove ArchivedDate from entity

* use custom parameters for CipherDetails_CreateWithCollections

* remove reference to archivedDate

* add missing param

* add missing param

* fix params

* fix cipher repository

* fix migration file

* update request/response models

* update migration

* remove Archives from Cipher_CreateWithCollections

* revert last change

* clean up

* remove comment

* remove column in migration

* change language in drop

* wrap in brackets

* put drop column in separate migration

* remove archivedDate column

* re-add archivedDate

* add refresh module

* bump migration name

* fix proc and migration

* do not require edit permission for archiving ciphers

* do not require edit permission for unarchiving ciphers
This commit is contained in:
Jordan Aasen
2026-01-07 09:29:10 -08:00
committed by GitHub
parent afd47ad085
commit 02c03f4493
38 changed files with 11354 additions and 79 deletions

View File

@@ -1207,10 +1207,110 @@ public class CipherRepositoryTests
// Act
await sutRepository.ArchiveAsync(new List<Guid> { cipher.Id }, user.Id);
// Assert
var archivedCipher = await sutRepository.GetByIdAsync(cipher.Id, user.Id);
Assert.NotNull(archivedCipher);
Assert.NotNull(archivedCipher.ArchivedDate);
// Assert per-user view should show an archive date
var archivedCipherForUser = await sutRepository.GetByIdAsync(cipher.Id, user.Id);
Assert.NotNull(archivedCipherForUser);
Assert.NotNull(archivedCipherForUser.ArchivedDate);
}
[DatabaseTheory, DatabaseData]
public async Task ArchiveAsync_IsPerUserForSharedCipher(
ICipherRepository cipherRepository,
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository)
{
// Arrange: two users in the same org, both with access to the same cipher
var user1 = await userRepository.CreateAsync(new User
{
Name = "Test User 1",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var user2 = await userRepository.CreateAsync(new User
{
Name = "Test User 2",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var org = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user1.Email,
Plan = "Test",
});
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user1.Id,
OrganizationId = org.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user2.Id,
OrganizationId = org.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.User,
});
var sharedCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Shared Collection",
OrganizationId = org.Id,
});
var cipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = org.Id,
Data = "",
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(
cipher.Id,
org.Id,
new List<Guid> { sharedCollection.Id });
// Give both org users access to the shared collection
await collectionRepository.UpdateUsersAsync(sharedCollection.Id, new List<CollectionAccessSelection>
{
new()
{
Id = orgUser1.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true,
},
new()
{
Id = orgUser2.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true,
},
});
// Act: user1 archives the shared cipher
await cipherRepository.ArchiveAsync(new List<Guid> { cipher.Id }, user1.Id);
// Assert: user1 sees it as archived
var cipherForUser1 = await cipherRepository.GetByIdAsync(cipher.Id, user1.Id);
Assert.NotNull(cipherForUser1);
Assert.NotNull(cipherForUser1.ArchivedDate);
// Assert: user2 still sees it as *not* archived
var cipherForUser2 = await cipherRepository.GetByIdAsync(cipher.Id, user2.Id);
Assert.NotNull(cipherForUser2);
Assert.Null(cipherForUser2.ArchivedDate);
}
[DatabaseTheory, DatabaseData]