Browse Source
## Type of change <!-- (mark with an `X`) --> ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective <!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding--> Previous PR: #3434 Adds ciphers and folders to the new key rotation. ## Code changes <!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes--> <!--Also refer to any related changes or PRs in other repositories--> * **file.ext:** Description of what was changed and why ## Before you submit - Please check for formatting errors (`dotnet format --verify-no-changes`) (required) - If making database changes - make sure you also update Entity Framework queries and/or migrations - Please add **unit tests** where it makes sense to do so (encouraged but not required) - If this change requires a **documentation update** - notify the documentation team - If this change has particular **deployment requirements** - notify the DevOps teampull/3529/head
17 changed files with 485 additions and 8 deletions
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
using Bit.Api.Auth.Validators; |
||||
using Bit.Api.Vault.Models.Request; |
||||
using Bit.Core; |
||||
using Bit.Core.Context; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Services; |
||||
using Bit.Core.Vault.Entities; |
||||
using Bit.Core.Vault.Repositories; |
||||
|
||||
namespace Bit.Api.Vault.Validators; |
||||
|
||||
public class CipherRotationValidator : IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> |
||||
{ |
||||
private readonly ICipherRepository _cipherRepository; |
||||
private readonly ICurrentContext _currentContext; |
||||
private readonly IFeatureService _featureService; |
||||
|
||||
private bool UseFlexibleCollections => |
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); |
||||
|
||||
public CipherRotationValidator(ICipherRepository cipherRepository, ICurrentContext currentContext, |
||||
IFeatureService featureService) |
||||
{ |
||||
_cipherRepository = cipherRepository; |
||||
_currentContext = currentContext; |
||||
_featureService = featureService; |
||||
|
||||
} |
||||
|
||||
public async Task<IEnumerable<Cipher>> ValidateAsync(User user, IEnumerable<CipherWithIdRequestModel> ciphers) |
||||
{ |
||||
var result = new List<Cipher>(); |
||||
if (ciphers == null || !ciphers.Any()) |
||||
{ |
||||
return result; |
||||
} |
||||
|
||||
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, UseFlexibleCollections); |
||||
if (existingCiphers == null || !existingCiphers.Any()) |
||||
{ |
||||
return result; |
||||
} |
||||
|
||||
foreach (var existing in existingCiphers) |
||||
{ |
||||
var cipher = ciphers.FirstOrDefault(c => c.Id == existing.Id); |
||||
if (cipher == null) |
||||
{ |
||||
throw new BadRequestException("All existing ciphers must be included in the rotation."); |
||||
} |
||||
result.Add(cipher.ToCipher(existing)); |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
using Bit.Api.Auth.Validators; |
||||
using Bit.Api.Vault.Models.Request; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Vault.Entities; |
||||
using Bit.Core.Vault.Repositories; |
||||
|
||||
namespace Bit.Api.Vault.Validators; |
||||
|
||||
public class FolderRotationValidator : IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> |
||||
{ |
||||
private readonly IFolderRepository _folderRepository; |
||||
|
||||
public FolderRotationValidator(IFolderRepository folderRepository) |
||||
{ |
||||
_folderRepository = folderRepository; |
||||
} |
||||
|
||||
public async Task<IEnumerable<Folder>> ValidateAsync(User user, IEnumerable<FolderWithIdRequestModel> folders) |
||||
{ |
||||
var result = new List<Folder>(); |
||||
if (folders == null || !folders.Any()) |
||||
{ |
||||
return result; |
||||
} |
||||
|
||||
var existingFolders = await _folderRepository.GetManyByUserIdAsync(user.Id); |
||||
if (existingFolders == null || !existingFolders.Any()) |
||||
{ |
||||
return result; |
||||
} |
||||
|
||||
foreach (var existing in existingFolders) |
||||
{ |
||||
var folder = folders.FirstOrDefault(c => c.Id == existing.Id); |
||||
if (folder == null) |
||||
{ |
||||
throw new BadRequestException("All existing folders must be included in the rotation."); |
||||
} |
||||
result.Add(folder.ToFolder(existing)); |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
using System.Data; |
||||
using Bit.Core.Vault.Entities; |
||||
using Dapper; |
||||
|
||||
namespace Bit.Infrastructure.Dapper.Vault.Helpers; |
||||
|
||||
public static class CipherHelpers |
||||
{ |
||||
public static DataTable ToDataTable(this IEnumerable<Cipher> ciphers) |
||||
{ |
||||
var ciphersTable = new DataTable(); |
||||
ciphersTable.SetTypeName("[dbo].[Cipher]"); |
||||
|
||||
var columnData = new List<(string name, Type type, Func<Cipher, object> getter)> |
||||
{ |
||||
(nameof(Cipher.Id), typeof(Guid), c => c.Id), |
||||
(nameof(Cipher.UserId), typeof(Guid), c => c.UserId), |
||||
(nameof(Cipher.OrganizationId), typeof(Guid), c => c.OrganizationId), |
||||
(nameof(Cipher.Type), typeof(short), c => c.Type), |
||||
(nameof(Cipher.Data), typeof(string), c => c.Data), |
||||
(nameof(Cipher.Favorites), typeof(string), c => c.Favorites), |
||||
(nameof(Cipher.Folders), typeof(string), c => c.Folders), |
||||
(nameof(Cipher.Attachments), typeof(string), c => c.Attachments), |
||||
(nameof(Cipher.CreationDate), typeof(DateTime), c => c.CreationDate), |
||||
(nameof(Cipher.RevisionDate), typeof(DateTime), c => c.RevisionDate), |
||||
(nameof(Cipher.DeletedDate), typeof(DateTime), c => c.DeletedDate), |
||||
(nameof(Cipher.Reprompt), typeof(short), c => c.Reprompt), |
||||
(nameof(Cipher.Key), typeof(string), c => c.Key), |
||||
}; |
||||
|
||||
return ciphers.BuildTable(ciphersTable, columnData); |
||||
} |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
using System.Data; |
||||
using Bit.Core.Vault.Entities; |
||||
using Dapper; |
||||
|
||||
namespace Bit.Infrastructure.Dapper.Vault.Helpers; |
||||
|
||||
public static class FolderHelpers |
||||
{ |
||||
public static DataTable ToDataTable(this IEnumerable<Folder> folders) |
||||
{ |
||||
var foldersTable = new DataTable(); |
||||
foldersTable.SetTypeName("[dbo].[Folder]"); |
||||
|
||||
var columnData = new List<(string name, Type type, Func<Folder, object> getter)> |
||||
{ |
||||
(nameof(Folder.Id), typeof(Guid), c => c.Id), |
||||
(nameof(Folder.UserId), typeof(Guid), c => c.UserId), |
||||
(nameof(Folder.Name), typeof(string), c => c.Name), |
||||
(nameof(Folder.CreationDate), typeof(DateTime), c => c.CreationDate), |
||||
(nameof(Folder.RevisionDate), typeof(DateTime), c => c.RevisionDate), |
||||
}; |
||||
|
||||
return folders.BuildTable(foldersTable, columnData); |
||||
} |
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
using Bit.Api.Vault.Models.Request; |
||||
using Bit.Api.Vault.Validators; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Vault.Models.Data; |
||||
using Bit.Core.Vault.Repositories; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Api.Test.Vault.Validators; |
||||
|
||||
[SutProviderCustomize] |
||||
public class CipherRotationValidatorTests |
||||
{ |
||||
[Theory, BitAutoData] |
||||
public async Task ValidateAsync_MissingCipher_Throws(SutProvider<CipherRotationValidator> sutProvider, User user, |
||||
IEnumerable<CipherWithIdRequestModel> ciphers) |
||||
{ |
||||
var userCiphers = ciphers.Select(c => new CipherDetails { Id = c.Id.GetValueOrDefault(), Type = c.Type }) |
||||
.ToList(); |
||||
userCiphers.Add(new CipherDetails { Id = Guid.NewGuid(), Type = Core.Vault.Enums.CipherType.Login }); |
||||
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(user.Id, Arg.Any<bool>()) |
||||
.Returns(userCiphers); |
||||
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.ValidateAsync(user, ciphers)); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ValidateAsync_CipherDoesNotBelongToUser_NotIncluded( |
||||
SutProvider<CipherRotationValidator> sutProvider, User user, IEnumerable<CipherWithIdRequestModel> ciphers) |
||||
{ |
||||
var userCiphers = ciphers.Select(c => new CipherDetails { Id = c.Id.GetValueOrDefault(), Type = c.Type }) |
||||
.ToList(); |
||||
userCiphers.RemoveAt(0); |
||||
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(user.Id, Arg.Any<bool>()) |
||||
.Returns(userCiphers); |
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(user, ciphers); |
||||
|
||||
Assert.DoesNotContain(result, c => c.Id == ciphers.First().Id); |
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
using Bit.Api.Vault.Models.Request; |
||||
using Bit.Api.Vault.Validators; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Vault.Entities; |
||||
using Bit.Core.Vault.Repositories; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Api.Test.Vault.Validators; |
||||
|
||||
[SutProviderCustomize] |
||||
public class FolderRotationValidatorTests |
||||
{ |
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task ValidateAsync_MissingFolder_Throws(SutProvider<FolderRotationValidator> sutProvider, User user, |
||||
IEnumerable<FolderWithIdRequestModel> folders) |
||||
{ |
||||
var userFolders = folders.Select(f => f.ToFolder(new Folder())).ToList(); |
||||
userFolders.Add(new Folder { Id = Guid.NewGuid(), Name = "Missing Folder" }); |
||||
sutProvider.GetDependency<IFolderRepository>().GetManyByUserIdAsync(user.Id).Returns(userFolders); |
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.ValidateAsync(user, folders)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task ValidateAsync_FolderDoesNotBelongToUser_NotReturned( |
||||
SutProvider<FolderRotationValidator> sutProvider, User user, IEnumerable<FolderWithIdRequestModel> folders) |
||||
{ |
||||
var userFolders = folders.Select(f => f.ToFolder(new Folder())).ToList(); |
||||
userFolders.RemoveAt(0); |
||||
sutProvider.GetDependency<IFolderRepository>().GetManyByUserIdAsync(user.Id).Returns(userFolders); |
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(user, folders); |
||||
|
||||
Assert.DoesNotContain(result, c => c.Id == folders.First().Id); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue