1
0
mirror of https://github.com/bitwarden/server synced 2025-12-14 15:23:42 +00:00

[PM-22104] Migrate default collection when org user is removed (#6135)

* migrate default collection to a shared collection when users are removed

* remove redundant logic

* fix test

* fix tests

* fix test

* clean up

* add migrations

* run dotnet format

* clean up, refactor duplicate logic to sproc, wip integration test

* fix sql

* add migration for new sproc

* integration test wip

* integration test wip

* integration test wip

* integration test wip

* fix integration test LINQ expression

* fix using wrong Id

* wip integration test for DeleteManyAsync

* fix LINQ

* only set DefaultUserEmail when it is null in sproc

* check for null

* spelling, separate create and update request models

* fix test

* fix child class

* refactor sproc

* clean up

* more cleanup

* fix tests

* fix user email

* remove unneccesary test

* add DefaultUserCollectionEmail to EF query

* fix test

* fix EF logic to match sprocs

* clean up logic

* cleanup
This commit is contained in:
Brandon Treston
2025-08-19 14:12:34 -04:00
committed by GitHub
parent 29d6288b27
commit c189e4aaf5
16 changed files with 1001 additions and 195 deletions

View File

@@ -22,7 +22,7 @@ namespace Bit.Api.Test.Controllers;
public class CollectionsControllerTests
{
[Theory, BitAutoData]
public async Task Post_Success(Organization organization, CollectionRequestModel collectionRequest,
public async Task Post_Success(Organization organization, CreateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
Collection ExpectedCollection() => Arg.Is<Collection>(c =>
@@ -46,9 +46,10 @@ public class CollectionsControllerTests
}
[Theory, BitAutoData]
public async Task Put_Success(Collection collection, CollectionRequestModel collectionRequest,
public async Task Put_Success(Collection collection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
collection.DefaultUserCollectionEmail = null;
Collection ExpectedCollection() => Arg.Is<Collection>(c => c.Id == collection.Id &&
c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId &&
c.OrganizationId == collection.OrganizationId);
@@ -72,7 +73,7 @@ public class CollectionsControllerTests
}
[Theory, BitAutoData]
public async Task Put_WithNoCollectionPermission_ThrowsNotFound(Collection collection, CollectionRequestModel collectionRequest,
public async Task Put_WithNoCollectionPermission_ThrowsNotFound(Collection collection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<IAuthorizationService>()
@@ -484,4 +485,176 @@ public class CollectionsControllerTests
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
.AddAccessAsync(default, default, default);
}
[Theory, BitAutoData]
public async Task Put_With_NonNullName_DoesNotPreserveExistingName(Collection existingCollection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
// Arrange
var newName = "new name";
var originalName = "original name";
existingCollection.Name = originalName;
existingCollection.DefaultUserCollectionEmail = null;
collectionRequest.Name = newName;
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(existingCollection.Id)
.Returns(existingCollection);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
existingCollection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Success());
// Act
await sutProvider.Sut.Put(existingCollection.OrganizationId, existingCollection.Id, collectionRequest);
// Assert
await sutProvider.GetDependency<IUpdateCollectionCommand>()
.Received(1)
.UpdateAsync(
Arg.Is<Collection>(c => c.Id == existingCollection.Id && c.Name == newName),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
[Theory, BitAutoData]
public async Task Put_WithNullName_DoesPreserveExistingName(Collection existingCollection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
// Arrange
var originalName = "original name";
existingCollection.Name = originalName;
existingCollection.DefaultUserCollectionEmail = null;
collectionRequest.Name = null;
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(existingCollection.Id)
.Returns(existingCollection);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
existingCollection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Success());
// Act
await sutProvider.Sut.Put(existingCollection.OrganizationId, existingCollection.Id, collectionRequest);
// Assert
await sutProvider.GetDependency<IUpdateCollectionCommand>()
.Received(1)
.UpdateAsync(
Arg.Is<Collection>(c => c.Id == existingCollection.Id && c.Name == originalName),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
[Theory, BitAutoData]
public async Task Put_WithDefaultUserCollectionEmail_DoesPreserveExistingName(Collection existingCollection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
// Arrange
var originalName = "original name";
var defaultUserCollectionEmail = "user@email.com";
existingCollection.Name = originalName;
existingCollection.DefaultUserCollectionEmail = defaultUserCollectionEmail;
collectionRequest.Name = "new name";
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(existingCollection.Id)
.Returns(existingCollection);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
existingCollection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Success());
// Act
await sutProvider.Sut.Put(existingCollection.OrganizationId, existingCollection.Id, collectionRequest);
// Assert
await sutProvider.GetDependency<IUpdateCollectionCommand>()
.Received(1)
.UpdateAsync(
Arg.Is<Collection>(c => c.Id == existingCollection.Id && c.Name == originalName && c.DefaultUserCollectionEmail == defaultUserCollectionEmail),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
[Theory, BitAutoData]
public async Task Put_WithEmptyName_DoesPreserveExistingName(Collection existingCollection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
// Arrange
var originalName = "original name";
existingCollection.Name = originalName;
existingCollection.DefaultUserCollectionEmail = null;
collectionRequest.Name = ""; // Empty string
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(existingCollection.Id)
.Returns(existingCollection);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
existingCollection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Success());
// Act
await sutProvider.Sut.Put(existingCollection.OrganizationId, existingCollection.Id, collectionRequest);
// Assert
await sutProvider.GetDependency<IUpdateCollectionCommand>()
.Received(1)
.UpdateAsync(
Arg.Is<Collection>(c => c.Id == existingCollection.Id && c.Name == originalName),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
[Theory, BitAutoData]
public async Task Put_WithWhitespaceOnlyName_DoesPreserveExistingName(Collection existingCollection, UpdateCollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
// Arrange
var originalName = "original name";
existingCollection.Name = originalName;
existingCollection.DefaultUserCollectionEmail = null;
collectionRequest.Name = " "; // Whitespace only
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(existingCollection.Id)
.Returns(existingCollection);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
existingCollection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Success());
// Act
await sutProvider.Sut.Put(existingCollection.OrganizationId, existingCollection.Id, collectionRequest);
// Assert
await sutProvider.GetDependency<IUpdateCollectionCommand>()
.Received(1)
.UpdateAsync(
Arg.Is<Collection>(c => c.Id == existingCollection.Id && c.Name == originalName),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
}