1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-12 06:00:36 -05:00

[PM-17154] Limit item deletion feature flag logic removal (#5925)

* Refactor CiphersController and related tests by removing unused IFeatureService dependency and associated feature flag checks. Cleaned up tests to reflect these changes, ensuring they focus on manage permissions without reliance on feature flags.

* Refactor CipherService and related tests by removing feature flag checks for item deletion permissions. Updated tests to focus on user manage permissions without reliance on feature flags, ensuring cleaner and more maintainable code.

* Enhance CiphersControllerTests by adding user retrieval and organization ability checks. Updated test cases to ensure proper handling of item deletion permissions based on user roles and organization settings, improving test coverage and reliability.
This commit is contained in:
Rui Tomé 2025-06-10 09:57:29 +01:00 committed by GitHub
parent fbdd62fadd
commit 021e69bc5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 322 additions and 820 deletions

View File

@ -42,7 +42,6 @@ public class CiphersController : Controller
private readonly ICurrentContext _currentContext;
private readonly ILogger<CiphersController> _logger;
private readonly GlobalSettings _globalSettings;
private readonly IFeatureService _featureService;
private readonly IOrganizationCiphersQuery _organizationCiphersQuery;
private readonly IApplicationCacheService _applicationCacheService;
private readonly ICollectionRepository _collectionRepository;
@ -57,7 +56,6 @@ public class CiphersController : Controller
ICurrentContext currentContext,
ILogger<CiphersController> logger,
GlobalSettings globalSettings,
IFeatureService featureService,
IOrganizationCiphersQuery organizationCiphersQuery,
IApplicationCacheService applicationCacheService,
ICollectionRepository collectionRepository)
@ -71,7 +69,6 @@ public class CiphersController : Controller
_currentContext = currentContext;
_logger = logger;
_globalSettings = globalSettings;
_featureService = featureService;
_organizationCiphersQuery = organizationCiphersQuery;
_applicationCacheService = applicationCacheService;
_collectionRepository = collectionRepository;
@ -375,11 +372,6 @@ public class CiphersController : Controller
private async Task<bool> CanDeleteOrRestoreCipherAsAdminAsync(Guid organizationId, IEnumerable<Guid> cipherIds)
{
if (!_featureService.IsEnabled(FeatureFlagKeys.LimitItemDeletion))
{
return await CanEditCipherAsAdminAsync(organizationId, cipherIds);
}
var org = _currentContext.GetOrganization(organizationId);
// If we're not an "admin" or if we're a provider user we don't need to check the ciphers

View File

@ -821,11 +821,6 @@ public class CipherService : ICipherService
private async Task<bool> UserCanDeleteAsync(CipherDetails cipher, Guid userId)
{
if (!_featureService.IsEnabled(FeatureFlagKeys.LimitItemDeletion))
{
return await UserCanEditAsync(cipher, userId);
}
var user = await _userService.GetUserByIdAsync(userId);
var organizationAbility = cipher.OrganizationId.HasValue ?
await _applicationCacheService.GetOrganizationAbilityAsync(cipher.OrganizationId.Value) : null;
@ -835,11 +830,6 @@ public class CipherService : ICipherService
private async Task<bool> UserCanRestoreAsync(CipherDetails cipher, Guid userId)
{
if (!_featureService.IsEnabled(FeatureFlagKeys.LimitItemDeletion))
{
return await UserCanEditAsync(cipher, userId);
}
var user = await _userService.GetUserByIdAsync(userId);
var organizationAbility = cipher.OrganizationId.HasValue ?
await _applicationCacheService.GetOrganizationAbilityAsync(cipher.OrganizationId.Value) : null;
@ -1059,17 +1049,11 @@ public class CipherService : ICipherService
}
// This method is used to filter ciphers based on the user's permissions to delete them.
// It supports both the old and new logic depending on the feature flag.
private async Task<List<T>> FilterCiphersByDeletePermission<T>(
IEnumerable<T> ciphers,
HashSet<Guid> cipherIdsSet,
Guid userId) where T : CipherDetails
{
if (!_featureService.IsEnabled(FeatureFlagKeys.LimitItemDeletion))
{
return ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).ToList();
}
var user = await _userService.GetUserByIdAsync(userId);
var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();

View File

@ -4,7 +4,6 @@ using Bit.Api.Vault.Controllers;
using Bit.Api.Vault.Models;
using Bit.Api.Vault.Models.Request;
using Bit.Api.Vault.Models.Response;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
@ -169,6 +168,7 @@ public class CiphersControllerTests
}
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
@ -197,65 +197,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteAdmin_WithOwnerOrAdmin_WithEditPermission_DeletesCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = true;
cipherDetails.Manage = false;
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails>
{
cipherDetails
});
await sutProvider.Sut.DeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).DeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteAdmin_WithOwnerOrAdmin_WithoutEditPermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = false;
cipherDetails.Manage = false;
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails>
{
cipherDetails
});
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipherDetails.Id));
await sutProvider.GetDependency<ICipherService>().DidNotReceive().DeleteAsync(Arg.Any<CipherDetails>(), Arg.Any<Guid>(), Arg.Any<bool>());
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithManagePermission_DeletesCipher(
public async Task DeleteAdmin_WithOwnerOrAdmin_WithManagePermission_DeletesCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -266,7 +208,6 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -293,7 +234,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
public async Task DeleteAdmin_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -304,7 +245,6 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -339,11 +279,22 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(organization.Id)
.Returns(new List<CipherOrganizationDetails> { new() { Id = cipherDetails.Id } });
.Returns(new List<CipherOrganizationDetails>
{
new() { Id = cipherDetails.Id, OrganizationId = cipherDetails.OrganizationId }
});
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
await sutProvider.Sut.DeleteAdmin(cipherDetails.Id);
@ -426,10 +377,14 @@ public class CiphersControllerTests
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteManyAdmin_WithOwnerOrAdmin_WithEditPermission_DeletesCiphers(
public async Task DeleteManyAdmin_WithOwnerOrAdmin_WithManagePermission_DeletesCiphers(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -437,74 +392,6 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organization.Id,
Edit = true
}).ToList());
await sutProvider.Sut.DeleteManyAdmin(model);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.DeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organization.Id, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteManyAdmin_WithOwnerOrAdmin_WithoutEditPermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = organization.Id.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>()
.GetProperUserId(default)
.ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>()
.GetOrganization(new Guid(model.OrganizationId))
.Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByOrganizationIdAsync(new Guid(model.OrganizationId))
.Returns(ciphers);
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(new Guid(model.OrganizationId))
.Returns(new OrganizationAbility
{
Id = new Guid(model.OrganizationId),
AllowAdminAccessToAllCollectionItems = false,
});
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteManyAdmin(model));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteManyAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithManagePermission_DeletesCiphers(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = organization.Id.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -540,7 +427,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task DeleteManyAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
public async Task DeleteManyAdmin_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -548,7 +435,6 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -586,10 +472,18 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(organization.Id)
.Returns(ciphers.Select(c => new CipherOrganizationDetails { Id = c.Id }).ToList());
.Returns(ciphers.Select(c => new CipherOrganizationDetails { Id = c.Id, OrganizationId = organization.Id }).ToList());
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
await sutProvider.Sut.DeleteManyAdmin(model);
@ -688,67 +582,14 @@ public class CiphersControllerTests
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteManyAdmin(model));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteAdmin_WithOwnerOrAdmin_WithEditPermission_SoftDeletesCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = true;
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails>
{
cipherDetails
});
await sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).SoftDeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteAdmin_WithOwnerOrAdmin_WithoutEditPermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = false;
cipherDetails.Manage = false;
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails>
{
cipherDetails
});
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id));
await sutProvider.GetDependency<ICipherService>().DidNotReceive().SoftDeleteAsync(Arg.Any<CipherDetails>(), Arg.Any<Guid>(), Arg.Any<bool>());
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithManagePermission_SoftDeletesCipher(
public async Task PutDeleteAdmin_WithOwnerOrAdmin_WithManagePermission_SoftDeletesCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -759,7 +600,6 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -786,7 +626,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
public async Task PutDeleteAdmin_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -797,7 +637,6 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -833,12 +672,20 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(organization.Id)
.Returns(new List<CipherOrganizationDetails> { new() { Id = cipherDetails.Id } });
.Returns(new List<CipherOrganizationDetails> { new() { Id = cipherDetails.Id, OrganizationId = organization.Id } });
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
await sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id);
@ -856,6 +703,7 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organization.Id).Returns(new List<Cipher> { cipherDetails });
@ -890,6 +738,70 @@ public class CiphersControllerTests
await sutProvider.GetDependency<ICipherService>().Received(1).SoftDeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteAdmin_WithOwnerOrAdmin_WithEditPermission_WithLimitItemDeletionFalse_SoftDeletesCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = true;
cipherDetails.Manage = false; // Only Edit permission, not Manage
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails> { cipherDetails });
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = false
});
await sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).SoftDeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteAdmin_WithOwnerOrAdmin_WithEditPermission_WithLimitItemDeletionTrue_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = true;
cipherDetails.Manage = false; // Only Edit permission, not Manage
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails> { cipherDetails });
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id));
}
[Theory]
[BitAutoData]
public async Task PutDeleteAdmin_WithCustomUser_WithEditAnyCollectionFalse_ThrowsNotFoundException(
@ -922,10 +834,14 @@ public class CiphersControllerTests
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipher.Id));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteManyAdmin_WithOwnerOrAdmin_WithEditPermission_SoftDeletesCiphers(
public async Task PutDeleteManyAdmin_WithOwnerOrAdmin_WithManagePermission_SoftDeletesCiphers(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -933,65 +849,6 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organization.Id,
Edit = true
}).ToList());
await sutProvider.Sut.PutDeleteManyAdmin(model);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.SoftDeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organization.Id, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteManyAdmin_WithOwnerOrAdmin_WithoutEditPermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = organization.Id.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organization.Id,
Edit = false
}).ToList());
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteManyAdmin(model));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteManyAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithManagePermission_SoftDeletesCiphers(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = organization.Id.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -1027,7 +884,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutDeleteManyAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
public async Task PutDeleteManyAdmin_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherBulkDeleteRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -1035,7 +892,6 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -1073,10 +929,18 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(organization.Id)
.Returns(ciphers.Select(c => new CipherOrganizationDetails { Id = c.Id }).ToList());
.Returns(ciphers.Select(c => new CipherOrganizationDetails { Id = c.Id, OrganizationId = organization.Id }).ToList());
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
await sutProvider.Sut.PutDeleteManyAdmin(model);
@ -1099,7 +963,14 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
// Set organization ID on ciphers to avoid "Cipher needs to belong to a user or an organization" error
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organization.Id;
}
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organization.Id).Returns(ciphers);
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organization.Id).Returns(new OrganizationAbility
@ -1130,7 +1001,14 @@ public class CiphersControllerTests
organization.Type = OrganizationUserType.Custom;
organization.Permissions.EditAnyCollection = true;
// Set organization ID on ciphers to avoid "Cipher needs to belong to a user or an organization" error
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organization.Id;
}
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organization.Id).Returns(ciphers);
@ -1175,68 +1053,14 @@ public class CiphersControllerTests
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteManyAdmin(model));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreAdmin_WithOwnerOrAdmin_WithEditPermission_RestoresCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Type = CipherType.Login;
cipherDetails.Data = JsonSerializer.Serialize(new CipherLoginData());
cipherDetails.Edit = true;
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails>
{
cipherDetails
});
var result = await sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id);
Assert.IsType<CipherMiniResponseModel>(result);
await sutProvider.GetDependency<ICipherService>().Received(1).RestoreAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreAdmin_WithOwnerOrAdmin_WithoutEditPermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Edit = false;
cipherDetails.Manage = false;
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails>
{
cipherDetails
});
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithManagePermission_RestoresCipher(
public async Task PutRestoreAdmin_WithOwnerOrAdmin_WithManagePermission_RestoresCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -1249,7 +1073,6 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -1277,7 +1100,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
public async Task PutRestoreAdmin_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -1288,7 +1111,6 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -1323,11 +1145,19 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(organization.Id)
.Returns(new List<CipherOrganizationDetails> { new() { Id = cipherDetails.Id } });
.Returns(new List<CipherOrganizationDetails> { new() { Id = cipherDetails.Id, OrganizationId = organization.Id } });
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
var result = await sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id);
@ -1386,6 +1216,75 @@ public class CiphersControllerTests
await sutProvider.GetDependency<ICipherService>().Received(1).RestoreAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreAdmin_WithOwnerOrAdmin_WithEditPermission_LimitItemDeletionFalse_RestoresCipher(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Type = CipherType.Login;
cipherDetails.Data = JsonSerializer.Serialize(new CipherLoginData());
cipherDetails.Edit = true;
cipherDetails.Manage = false; // Only Edit permission, not Manage
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails> { cipherDetails });
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = false // Permissive mode - Edit permission should work
});
var result = await sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id);
Assert.IsType<CipherMiniResponseModel>(result);
await sutProvider.GetDependency<ICipherService>().Received(1).RestoreAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreAdmin_WithOwnerOrAdmin_WithEditPermission_LimitItemDeletionTrue_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherDetails cipherDetails, Guid userId,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
cipherDetails.UserId = null;
cipherDetails.OrganizationId = organization.Id;
cipherDetails.Type = CipherType.Login;
cipherDetails.Data = JsonSerializer.Serialize(new CipherLoginData());
cipherDetails.Edit = true;
cipherDetails.Manage = false; // Only Edit permission, not Manage
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(new List<CipherDetails> { cipherDetails });
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true // Restrictive mode - Edit permission should NOT work
});
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id));
}
[Theory]
[BitAutoData]
public async Task PutRestoreAdmin_WithCustomUser_WithEditAnyCollectionFalse_ThrowsNotFoundException(
@ -1420,10 +1319,14 @@ public class CiphersControllerTests
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreManyAdmin_WithOwnerOrAdmin_WithEditPermission_RestoresCiphers(
public async Task PutRestoreManyAdmin_WithOwnerOrAdmin_WithManagePermission_RestoresCiphers(
OrganizationUserType organizationUserType, CipherBulkRestoreRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -1431,77 +1334,6 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organization.Id,
Edit = true
}).ToList());
var cipherOrgDetails = ciphers.Select(c => new CipherOrganizationDetails
{
Id = c.Id,
OrganizationId = organization.Id
}).ToList();
sutProvider.GetDependency<ICipherService>()
.RestoreManyAsync(Arg.Is<HashSet<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count == model.Ids.Count()),
userId, organization.Id, true)
.Returns(cipherOrgDetails);
var result = await sutProvider.Sut.PutRestoreManyAdmin(model);
await sutProvider.GetDependency<ICipherService>().Received(1)
.RestoreManyAsync(
Arg.Is<HashSet<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count == model.Ids.Count()),
userId, organization.Id, true);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreManyAdmin_WithOwnerOrAdmin_WithoutEditPermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherBulkRestoreRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = organization.Id;
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId)
.Returns(ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organization.Id,
Edit = false,
Type = CipherType.Login,
Data = JsonSerializer.Serialize(new CipherLoginData())
}).ToList());
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreManyAdmin(model));
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreManyAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithManagePermission_RestoresCiphers(
OrganizationUserType organizationUserType, CipherBulkRestoreRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = organization.Id;
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -1553,7 +1385,7 @@ public class CiphersControllerTests
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task PutRestoreManyAdmin_WithLimitItemDeletionEnabled_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
public async Task PutRestoreManyAdmin_WithOwnerOrAdmin_WithoutManagePermission_ThrowsNotFoundException(
OrganizationUserType organizationUserType, CipherBulkRestoreRequestModel model, Guid userId, List<Cipher> ciphers,
CurrentContextOrganization organization, SutProvider<CiphersController> sutProvider)
{
@ -1561,7 +1393,6 @@ public class CiphersControllerTests
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
organization.Type = organizationUserType;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitItemDeletion).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
@ -1599,6 +1430,7 @@ public class CiphersControllerTests
organization.Type = organizationUserType;
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId });
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var cipherOrgDetails = ciphers.Select(c => new CipherOrganizationDetails
@ -1614,9 +1446,16 @@ public class CiphersControllerTests
.Returns(cipherOrgDetails);
sutProvider.GetDependency<ICipherService>()
.RestoreManyAsync(Arg.Is<HashSet<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString()) && ids.Count == model.Ids.Count())),
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organization.Id, true)
.Returns(cipherOrgDetails);
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(organization.Id)
.Returns(new OrganizationAbility
{
Id = organization.Id,
LimitItemDeletion = true
});
var result = await sutProvider.Sut.PutRestoreManyAdmin(model);

View File

@ -673,13 +673,21 @@ public class CipherServiceTests
[BitAutoData]
public async Task RestoreAsync_UpdatesUserCipher(Guid restoringUserId, CipherDetails cipher, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>().GetCanEditByIdAsync(restoringUserId, cipher.Id).Returns(true);
cipher.UserId = restoringUserId;
cipher.OrganizationId = null;
var initialRevisionDate = new DateTime(1970, 1, 1, 0, 0, 0);
cipher.DeletedDate = initialRevisionDate;
cipher.RevisionDate = initialRevisionDate;
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId, cipher.OrganizationId.HasValue);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(restoringUserId)
.Returns(new User
{
Id = restoringUserId,
});
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId);
Assert.Null(cipher.DeletedDate);
Assert.NotEqual(initialRevisionDate, cipher.RevisionDate);
@ -688,15 +696,28 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreAsync_UpdatesOrganizationCipher(Guid restoringUserId, CipherDetails cipher, SutProvider<CipherService> sutProvider)
public async Task RestoreAsync_UpdatesOrganizationCipher(Guid restoringUserId, CipherDetails cipher, User user, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>().GetCanEditByIdAsync(restoringUserId, cipher.Id).Returns(true);
cipher.OrganizationId = Guid.NewGuid();
cipher.Edit = false;
cipher.Manage = true;
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(restoringUserId)
.Returns(user);
sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilityAsync(cipher.OrganizationId.Value)
.Returns(new OrganizationAbility
{
Id = cipher.OrganizationId.Value,
LimitItemDeletion = true
});
var initialRevisionDate = new DateTime(1970, 1, 1, 0, 0, 0);
cipher.DeletedDate = initialRevisionDate;
cipher.RevisionDate = initialRevisionDate;
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId, cipher.OrganizationId.HasValue);
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId);
Assert.Null(cipher.DeletedDate);
Assert.NotEqual(initialRevisionDate, cipher.RevisionDate);
@ -724,24 +745,12 @@ public class CipherServiceTests
cipherDetails.UserId = Guid.NewGuid();
cipherDetails.OrganizationId = null;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RestoreAsync(cipherDetails, restoringUserId));
Assert.Contains("do not have permissions", exception.Message);
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().UpsertAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCipherEventAsync(default, default);
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCipherUpdateAsync(default, default);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreAsync_WithOrgCipherLackingEditPermission_ThrowsBadRequestException(
Guid restoringUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(restoringUserId, cipherDetails.Id)
.Returns(false);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(restoringUserId)
.Returns(new User
{
Id = restoringUserId,
});
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RestoreAsync(cipherDetails, restoringUserId));
@ -752,28 +761,6 @@ public class CipherServiceTests
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCipherUpdateAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task RestoreAsync_WithEditPermission_RestoresCipherDetails(
Guid restoringUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(restoringUserId, cipherDetails.Id)
.Returns(true);
var initialRevisionDate = new DateTime(1970, 1, 1, 0, 0, 0);
cipherDetails.DeletedDate = initialRevisionDate;
cipherDetails.RevisionDate = initialRevisionDate;
await sutProvider.Sut.RestoreAsync(cipherDetails, restoringUserId);
Assert.Null(cipherDetails.DeletedDate);
Assert.NotEqual(initialRevisionDate, cipherDetails.RevisionDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpsertAsync(cipherDetails);
await sutProvider.GetDependency<IEventService>().Received(1).LogCipherEventAsync(cipherDetails, EventType.Cipher_Restored);
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCipherUpdateAsync(cipherDetails, null);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
@ -794,7 +781,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreAsync_WithLimitItemDeletionEnabled_WithManagePermission_RestoresCipher(
public async Task RestoreAsync_WithManagePermission_RestoresCipher(
Guid restoringUserId, CipherDetails cipherDetails, User user, SutProvider<CipherService> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
@ -802,9 +789,6 @@ public class CipherServiceTests
cipherDetails.Edit = false;
cipherDetails.Manage = true;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(restoringUserId)
.Returns(user);
@ -828,7 +812,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreAsync_WithLimitItemDeletionEnabled_WithoutManagePermission_ThrowsBadRequestException(
public async Task RestoreAsync_WithoutManagePermission_ThrowsBadRequestException(
Guid restoringUserId, CipherDetails cipherDetails, User user, SutProvider<CipherService> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
@ -836,9 +820,6 @@ public class CipherServiceTests
cipherDetails.Edit = true;
cipherDetails.Manage = false;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(restoringUserId)
.Returns(user);
@ -859,32 +840,7 @@ public class CipherServiceTests
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCipherUpdateAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task RestoreManyAsync_UpdatesCiphers(ICollection<CipherDetails> ciphers,
SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
var restoringUserId = ciphers.First().UserId.Value;
var previousRevisionDate = DateTime.UtcNow;
foreach (var cipher in ciphers)
{
cipher.Edit = true;
cipher.RevisionDate = previousRevisionDate;
}
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(restoringUserId).Returns(ciphers);
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId).Returns(revisionDate);
await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId);
foreach (var cipher in ciphers)
{
Assert.Null(cipher.DeletedDate);
Assert.Equal(revisionDate, cipher.RevisionDate);
}
}
[Theory]
[BitAutoData]
@ -971,90 +927,14 @@ public class CipherServiceTests
.PushSyncCiphersAsync(restoringUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreManyAsync_WithOrgCipherAndEditPermission_RestoresCiphers(
Guid restoringUserId, List<CipherDetails> ciphers, Guid organizationId, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
var previousRevisionDate = DateTime.UtcNow;
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
cipher.Edit = true;
cipher.DeletedDate = DateTime.UtcNow;
cipher.RevisionDate = previousRevisionDate;
}
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(restoringUserId)
.Returns(ciphers);
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
sutProvider.GetDependency<ICipherRepository>()
.RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId)
.Returns(revisionDate);
var result = await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId);
Assert.Equal(ciphers.Count, result.Count);
foreach (var cipher in result)
{
Assert.Null(cipher.DeletedDate);
Assert.Equal(revisionDate, cipher.RevisionDate);
}
await sutProvider.GetDependency<ICipherRepository>()
.Received(1)
.RestoreAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == cipherIds.Count() &&
ids.All(id => cipherIds.Contains(id))), restoringUserId);
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogCipherEventsAsync(Arg.Any<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>());
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncCiphersAsync(restoringUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreManyAsync_WithOrgCipherLackingEditPermission_DoesNotRestoreCiphers(
Guid restoringUserId, List<Cipher> ciphers, Guid organizationId, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
var cipherDetailsList = ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organizationId,
Edit = false,
DeletedDate = DateTime.UtcNow
}).ToList();
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(restoringUserId)
.Returns(cipherDetailsList);
var result = await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId);
Assert.Empty(result);
await sutProvider.GetDependency<ICipherRepository>()
.Received(1)
.RestoreAsync(Arg.Is<IEnumerable<Guid>>(ids => !ids.Any()), restoringUserId);
await sutProvider.GetDependency<IEventService>()
.DidNotReceiveWithAnyArgs()
.LogCipherEventsAsync(Arg.Any<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>());
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncCiphersAsync(restoringUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreManyAsync_WithLimitItemDeletionEnabled_WithManagePermission_RestoresCiphers(
public async Task RestoreManyAsync_WithManagePermission_RestoresCiphers(
Guid restoringUserId, List<CipherDetails> ciphers, User user, SutProvider<CipherService> sutProvider)
{
var organizationId = Guid.NewGuid();
@ -1070,9 +950,6 @@ public class CipherServiceTests
cipher.RevisionDate = previousRevisionDate;
}
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(restoringUserId)
.Returns(ciphers);
@ -1121,7 +998,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task RestoreManyAsync_WithLimitItemDeletionEnabled_WithoutManagePermission_DoesNotRestoreCiphers(
public async Task RestoreManyAsync_WithoutManagePermission_DoesNotRestoreCiphers(
Guid restoringUserId, List<CipherDetails> ciphers, User user, SutProvider<CipherService> sutProvider)
{
var organizationId = Guid.NewGuid();
@ -1135,9 +1012,6 @@ public class CipherServiceTests
cipher.DeletedDate = DateTime.UtcNow;
}
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(restoringUserId)
.Returns(ciphers);
@ -1502,23 +1376,12 @@ public class CipherServiceTests
cipherDetails.UserId = deletingUserId;
cipherDetails.OrganizationId = null;
await sutProvider.Sut.DeleteAsync(cipherDetails, deletingUserId);
await sutProvider.GetDependency<ICipherRepository>().Received(1).DeleteAsync(cipherDetails);
await sutProvider.GetDependency<IAttachmentStorageService>().Received(1).DeleteAttachmentsForCipherAsync(cipherDetails.Id);
await sutProvider.GetDependency<IEventService>().Received(1).LogCipherEventAsync(cipherDetails, EventType.Cipher_Deleted);
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCipherDeleteAsync(cipherDetails);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteAsync_WithOrgCipherAndEditPermission_DeletesCipher(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
await sutProvider.Sut.DeleteAsync(cipherDetails, deletingUserId);
@ -1528,6 +1391,8 @@ public class CipherServiceTests
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCipherDeleteAsync(cipherDetails);
}
[Theory]
[BitAutoData]
public async Task DeleteAsync_WithPersonalCipherBelongingToDifferentUser_ThrowsBadRequestException(
@ -1536,25 +1401,12 @@ public class CipherServiceTests
cipherDetails.UserId = Guid.NewGuid();
cipherDetails.OrganizationId = null;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteAsync(cipherDetails, deletingUserId));
Assert.Contains("do not have permissions", exception.Message);
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
await sutProvider.GetDependency<IAttachmentStorageService>().DidNotReceiveWithAnyArgs().DeleteAttachmentsForCipherAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCipherEventAsync(default, default);
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCipherDeleteAsync(default);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteAsync_WithOrgCipherLackingEditPermission_ThrowsBadRequestException(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(false);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteAsync(cipherDetails, deletingUserId));
@ -1583,16 +1435,13 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteAsync_WithLimitItemDeletionEnabled_WithManagePermission_DeletesCipher(
public async Task DeleteAsync_WithManagePermission_DeletesCipher(
Guid deletingUserId, CipherDetails cipherDetails, User user, SutProvider<CipherService> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
cipherDetails.Edit = false;
cipherDetails.Manage = true;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(user);
@ -1615,16 +1464,13 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteAsync_WithLimitItemDeletionEnabled_WithoutManagePermission_ThrowsBadRequestException(
public async Task DeleteAsync_WithoutManagePermission_ThrowsBadRequestException(
Guid deletingUserId, CipherDetails cipherDetails, User user, SutProvider<CipherService> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
cipherDetails.Edit = true;
cipherDetails.Manage = false;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(user);
@ -1691,6 +1537,12 @@ public class CipherServiceTests
cipher.Edit = true;
}
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
@ -1740,71 +1592,14 @@ public class CipherServiceTests
.PushSyncCiphersAsync(deletingUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteManyAsync_WithOrgCipherAndEditPermission_DeletesCiphers(
Guid deletingUserId, List<CipherDetails> ciphers, Guid organizationId, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
cipher.Edit = true;
}
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
await sutProvider.Sut.DeleteManyAsync(cipherIds, deletingUserId, organizationId);
await sutProvider.GetDependency<ICipherRepository>()
.Received(1)
.DeleteAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == cipherIds.Count() && ids.All(id => cipherIds.Contains(id))), deletingUserId);
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogCipherEventsAsync(Arg.Any<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>());
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncCiphersAsync(deletingUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteManyAsync_WithOrgCipherLackingEditPermission_DoesNotDeleteCiphers(
Guid deletingUserId, List<Cipher> ciphers, Guid organizationId, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
var cipherDetailsList = ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organizationId,
Edit = false
}).ToList();
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(cipherDetailsList);
await sutProvider.Sut.DeleteManyAsync(cipherIds, deletingUserId, organizationId);
await sutProvider.GetDependency<ICipherRepository>()
.Received(1)
.DeleteAsync(Arg.Is<IEnumerable<Guid>>(ids => !ids.Any()), deletingUserId);
await sutProvider.GetDependency<IEventService>()
.DidNotReceiveWithAnyArgs()
.LogCipherEventsAsync(Arg.Any<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>());
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncCiphersAsync(deletingUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteManyAsync_WithLimitItemDeletionEnabled_WithoutManagePermission_DoesNotDeleteCiphers(
public async Task DeleteManyAsync_WithoutManagePermission_DoesNotDeleteCiphers(
Guid deletingUserId, List<CipherDetails> ciphers, User user, SutProvider<CipherService> sutProvider)
{
var organizationId = Guid.NewGuid();
@ -1817,9 +1612,6 @@ public class CipherServiceTests
cipher.Manage = false;
}
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
@ -1855,7 +1647,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task DeleteManyAsync_WithLimitItemDeletionEnabled_WithManagePermission_DeletesCiphers(
public async Task DeleteManyAsync_WithManagePermission_DeletesCiphers(
Guid deletingUserId, List<CipherDetails> ciphers, User user, SutProvider<CipherService> sutProvider)
{
var organizationId = Guid.NewGuid();
@ -1868,9 +1660,6 @@ public class CipherServiceTests
cipher.Manage = true;
}
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
@ -1913,9 +1702,12 @@ public class CipherServiceTests
cipherDetails.OrganizationId = null;
cipherDetails.DeletedDate = null;
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
await sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId);
@ -1926,26 +1718,7 @@ public class CipherServiceTests
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCipherUpdateAsync(cipherDetails, null);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteAsync_WithOrgCipherAndEditPermission_SoftDeletesCipher(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
cipherDetails.DeletedDate = null;
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(true);
await sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId);
Assert.NotNull(cipherDetails.DeletedDate);
Assert.Equal(cipherDetails.RevisionDate, cipherDetails.DeletedDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpsertAsync(cipherDetails);
await sutProvider.GetDependency<IEventService>().Received(1).LogCipherEventAsync(cipherDetails, EventType.Cipher_SoftDeleted);
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCipherUpdateAsync(cipherDetails, null);
}
[Theory]
[BitAutoData]
@ -1955,9 +1728,12 @@ public class CipherServiceTests
cipherDetails.UserId = Guid.NewGuid();
cipherDetails.OrganizationId = null;
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(false);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId));
@ -1965,48 +1741,23 @@ public class CipherServiceTests
Assert.Contains("do not have permissions", exception.Message);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteAsync_WithOrgCipherLackingEditPermission_ThrowsBadRequestException(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId));
Assert.Contains("do not have permissions", exception.Message);
}
[Theory]
[BitAutoData]
public async Task SoftDeleteAsync_WithEditPermission_SoftDeletesCipherDetails(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
cipherDetails.DeletedDate = null;
await sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId, true);
Assert.NotNull(cipherDetails.DeletedDate);
Assert.Equal(cipherDetails.RevisionDate, cipherDetails.DeletedDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpsertAsync(cipherDetails);
await sutProvider.GetDependency<IEventService>().Received(1).LogCipherEventAsync(cipherDetails, EventType.Cipher_SoftDeleted);
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCipherUpdateAsync(cipherDetails, null);
}
[Theory]
[BitAutoData]
public async Task SoftDeleteAsync_WithAlreadySoftDeletedCipher_SkipsOperation(
Guid deletingUserId, CipherDetails cipherDetails, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>()
.GetCanEditByIdAsync(deletingUserId, cipherDetails.Id)
.Returns(true);
// Set up as personal cipher owned by the deleting user
cipherDetails.UserId = deletingUserId;
cipherDetails.OrganizationId = null;
cipherDetails.DeletedDate = DateTime.UtcNow.AddDays(-1);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
await sutProvider.Sut.SoftDeleteAsync(cipherDetails, deletingUserId);
await sutProvider.GetDependency<ICipherRepository>().DidNotReceive().UpsertAsync(Arg.Any<Cipher>());
@ -2032,7 +1783,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteAsync_WithLimitItemDeletionEnabled_WithManagePermission_SoftDeletesCipher(
public async Task SoftDeleteAsync_WithManagePermission_SoftDeletesCipher(
Guid deletingUserId, CipherDetails cipherDetails, User user, SutProvider<CipherService> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
@ -2040,9 +1791,6 @@ public class CipherServiceTests
cipherDetails.Edit = false;
cipherDetails.Manage = true;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(user);
@ -2066,7 +1814,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteAsync_WithLimitItemDeletionEnabled_WithoutManagePermission_ThrowsBadRequestException(
public async Task SoftDeleteAsync_WithoutManagePermission_ThrowsBadRequestException(
Guid deletingUserId, CipherDetails cipherDetails, User user, SutProvider<CipherService> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
@ -2074,9 +1822,6 @@ public class CipherServiceTests
cipherDetails.Edit = true;
cipherDetails.Manage = false;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(user);
@ -2143,6 +1888,12 @@ public class CipherServiceTests
cipher.DeletedDate = null;
}
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(deletingUserId)
.Returns(new User
{
Id = deletingUserId,
});
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
@ -2192,72 +1943,14 @@ public class CipherServiceTests
.PushSyncCiphersAsync(deletingUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteManyAsync_WithOrgCipherAndEditPermission_SoftDeletesCiphers(
Guid deletingUserId, List<CipherDetails> ciphers, Guid organizationId, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
cipher.Edit = true;
cipher.DeletedDate = null;
}
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
await sutProvider.Sut.SoftDeleteManyAsync(cipherIds, deletingUserId, organizationId, false);
await sutProvider.GetDependency<ICipherRepository>()
.Received(1)
.SoftDeleteAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == cipherIds.Count() && ids.All(id => cipherIds.Contains(id))), deletingUserId);
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogCipherEventsAsync(Arg.Any<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>());
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncCiphersAsync(deletingUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteManyAsync_WithOrgCipherLackingEditPermission_DoesNotDeleteCiphers(
Guid deletingUserId, List<Cipher> ciphers, Guid organizationId, SutProvider<CipherService> sutProvider)
{
var cipherIds = ciphers.Select(c => c.Id).ToArray();
var cipherDetailsList = ciphers.Select(c => new CipherDetails
{
Id = c.Id,
OrganizationId = organizationId,
Edit = false
}).ToList();
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(cipherDetailsList);
await sutProvider.Sut.SoftDeleteManyAsync(cipherIds, deletingUserId, organizationId, false);
await sutProvider.GetDependency<ICipherRepository>()
.Received(1)
.SoftDeleteAsync(Arg.Is<IEnumerable<Guid>>(ids => !ids.Any()), deletingUserId);
await sutProvider.GetDependency<IEventService>()
.DidNotReceiveWithAnyArgs()
.LogCipherEventsAsync(Arg.Any<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>());
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncCiphersAsync(deletingUserId);
}
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteManyAsync_WithLimitItemDeletionEnabled_WithoutManagePermission_DoesNotDeleteCiphers(
public async Task SoftDeleteManyAsync_WithoutManagePermission_DoesNotDeleteCiphers(
Guid deletingUserId, List<CipherDetails> ciphers, User user, SutProvider<CipherService> sutProvider)
{
var organizationId = Guid.NewGuid();
@ -2270,9 +1963,6 @@ public class CipherServiceTests
cipher.Manage = false;
}
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);
@ -2308,7 +1998,7 @@ public class CipherServiceTests
[Theory]
[OrganizationCipherCustomize]
[BitAutoData]
public async Task SoftDeleteManyAsync_WithLimitItemDeletionEnabled_WithManagePermission_SoftDeletesCiphers(
public async Task SoftDeleteManyAsync_WithManagePermission_SoftDeletesCiphers(
Guid deletingUserId, List<CipherDetails> ciphers, User user, SutProvider<CipherService> sutProvider)
{
var organizationId = Guid.NewGuid();
@ -2322,9 +2012,6 @@ public class CipherServiceTests
cipher.DeletedDate = null;
}
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.LimitItemDeletion)
.Returns(true);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(deletingUserId)
.Returns(ciphers);