From 397f3d686594394438bb9ce5e0ce5c43d625e137 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Fri, 10 Mar 2023 11:54:19 -0500 Subject: [PATCH] SM-561: Update Secret Revision Dates (#2770) * SM-561: Update secret revision date on restore * SM-561: Update secret revision dates when a project is deleted * SM-561: Fix bug when updating revision dates for secrets when their parent project is deleted * SM-561: Handle case when there are no secrets in the projects that are being deleted * SM-561: Rename func to GetManyWithSecretsByIds and move UpdateRevisionDates call from ProjectsController to projects delete command * SM-561: update secret ids before project deletion * SM-561: Refactor out command in command call to follow CQRS pattern * SM-561: Remove null check --- .../Commands/Projects/DeleteProjectCommand.cs | 15 +++++++++++--- .../Repositories/ProjectRepository.cs | 3 ++- .../Repositories/SecretRepository.cs | 20 +++++++++++++++++++ .../Projects/DeleteProjectCommandTests.cs | 10 +++++----- .../Controllers/ProjectsController.cs | 1 - .../Repositories/IProjectRepository.cs | 2 +- .../Repositories/ISecretRepository.cs | 1 + .../Controllers/ProjectsControllerTest.cs | 2 +- 8 files changed, 42 insertions(+), 12 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/DeleteProjectCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/DeleteProjectCommand.cs index e975df93f7..06e282c28e 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/DeleteProjectCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/DeleteProjectCommand.cs @@ -11,11 +11,13 @@ public class DeleteProjectCommand : IDeleteProjectCommand { private readonly IProjectRepository _projectRepository; private readonly ICurrentContext _currentContext; + private readonly ISecretRepository _secretRepository; - public DeleteProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext) + public DeleteProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext, ISecretRepository secretRepository) { _projectRepository = projectRepository; _currentContext = currentContext; + _secretRepository = secretRepository; } public async Task>> DeleteProjects(List ids, Guid userId) @@ -25,7 +27,7 @@ public class DeleteProjectCommand : IDeleteProjectCommand throw new ArgumentNullException(); } - var projects = (await _projectRepository.GetManyByIds(ids))?.ToList(); + var projects = (await _projectRepository.GetManyWithSecretsByIds(ids))?.ToList(); if (projects?.Any() != true || projects.Count != ids.Count) { @@ -72,9 +74,16 @@ public class DeleteProjectCommand : IDeleteProjectCommand if (deleteIds.Count > 0) { + var secretIds = results.SelectMany(projTuple => projTuple.Item1?.Secrets?.Select(s => s.Id) ?? Array.Empty()).ToList(); + + if (secretIds.Count > 0) + { + await _secretRepository.UpdateRevisionDates(secretIds); + } + await _projectRepository.DeleteManyByIdAsync(deleteIds); } + return results; } } - diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index ca17e8b385..fe5936cf16 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -91,12 +91,13 @@ public class ProjectRepository : Repository> GetManyByIds(IEnumerable ids) + public async Task> GetManyWithSecretsByIds(IEnumerable ids) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); var projects = await dbContext.Project + .Include(p => p.Secrets) .Where(c => ids.Contains(c.Id) && c.DeletedDate == null) .ToListAsync(); return Mapper.Map>(projects); diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index e08f89efbe..63316dd71a 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -212,11 +212,13 @@ public class SecretRepository : Repository ids.Contains(c.Id)); await secrets.ForEachAsync(secret => { dbContext.Attach(secret); secret.DeletedDate = null; + secret.RevisionDate = utcNow; }); await dbContext.SaveChangesAsync(); } @@ -252,4 +254,22 @@ public class SecretRepository : Repository ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var utcNow = DateTime.UtcNow; + var secrets = dbContext.Secret.Where(s => ids.Contains(s.Id)); + + await secrets.ForEachAsync(secret => + { + dbContext.Attach(secret); + secret.RevisionDate = utcNow; + }); + + await dbContext.SaveChangesAsync(); + } + } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs index e48c2cf0af..f85125b191 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs @@ -19,7 +19,7 @@ public class DeleteProjectCommandTests public async Task DeleteProjects_Throws_NotFoundException(List data, Guid userId, SutProvider sutProvider) { - sutProvider.GetDependency().GetManyByIds(data).Returns(new List()); + sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(new List()); await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteProjects(data, userId)); @@ -35,7 +35,7 @@ public class DeleteProjectCommandTests { Id = Guid.NewGuid() }; - sutProvider.GetDependency().GetManyByIds(data).Returns(new List() { project }); + sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(new List() { project }); await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteProjects(data, userId)); @@ -51,7 +51,7 @@ public class DeleteProjectCommandTests sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); sutProvider.GetDependency().ClientType = ClientType.User; - sutProvider.GetDependency().GetManyByIds(data).Returns(projects); + sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(projects); sutProvider.GetDependency().UserHasWriteAccessToProject(Arg.Any(), userId).Returns(true); var results = await sutProvider.Sut.DeleteProjects(data, userId); @@ -73,7 +73,7 @@ public class DeleteProjectCommandTests sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); sutProvider.GetDependency().ClientType = ClientType.User; - sutProvider.GetDependency().GetManyByIds(data).Returns(projects); + sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(projects); sutProvider.GetDependency().UserHasWriteAccessToProject(userId, userId).Returns(false); var results = await sutProvider.Sut.DeleteProjects(data, userId); @@ -95,7 +95,7 @@ public class DeleteProjectCommandTests sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); - sutProvider.GetDependency().GetManyByIds(data).Returns(projects); + sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(projects); var results = await sutProvider.Sut.DeleteProjects(data, userId); diff --git a/src/Api/SecretsManager/Controllers/ProjectsController.cs b/src/Api/SecretsManager/Controllers/ProjectsController.cs index 2af5e77572..b3b52cd074 100644 --- a/src/Api/SecretsManager/Controllers/ProjectsController.cs +++ b/src/Api/SecretsManager/Controllers/ProjectsController.cs @@ -111,7 +111,6 @@ public class ProjectsController : Controller public async Task> BulkDeleteAsync([FromBody] List ids) { var userId = _userService.GetProperUserId(User).Value; - var results = await _deleteProjectCommand.DeleteProjects(ids, userId); var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2)); return new ListResponseModel(responses); diff --git a/src/Core/SecretsManager/Repositories/IProjectRepository.cs b/src/Core/SecretsManager/Repositories/IProjectRepository.cs index 52409b0257..9bb234cd5d 100644 --- a/src/Core/SecretsManager/Repositories/IProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/IProjectRepository.cs @@ -7,7 +7,7 @@ public interface IProjectRepository { Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); Task> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType); - Task> GetManyByIds(IEnumerable ids); + Task> GetManyWithSecretsByIds(IEnumerable ids); Task GetByIdAsync(Guid id); Task CreateAsync(Project project); Task ReplaceAsync(Project project); diff --git a/src/Core/SecretsManager/Repositories/ISecretRepository.cs b/src/Core/SecretsManager/Repositories/ISecretRepository.cs index bfc59fed6a..44b7041771 100644 --- a/src/Core/SecretsManager/Repositories/ISecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/ISecretRepository.cs @@ -17,4 +17,5 @@ public interface ISecretRepository Task HardDeleteManyByIdAsync(IEnumerable ids); Task RestoreManyByIdAsync(IEnumerable ids); Task> ImportAsync(IEnumerable secrets); + Task UpdateRevisionDates(IEnumerable ids); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs index d855465e6e..a2b3258f99 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs @@ -361,7 +361,7 @@ public class ProjectsControllerTest : IClassFixture, IAsy results!.Data.Select(x => x.Id).OrderBy(x => x)); Assert.DoesNotContain(results.Data, x => x.Error != null); - var projects = await _projectRepository.GetManyByIds(projectIds); + var projects = await _projectRepository.GetManyWithSecretsByIds(projectIds); Assert.Empty(projects); }