mirror of
https://github.com/bitwarden/server.git
synced 2025-05-20 19:14:32 -05:00
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
This commit is contained in:
parent
250509c7ac
commit
397f3d6865
@ -11,11 +11,13 @@ public class DeleteProjectCommand : IDeleteProjectCommand
|
|||||||
{
|
{
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly ISecretRepository _secretRepository;
|
||||||
|
|
||||||
public DeleteProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext)
|
public DeleteProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext, ISecretRepository secretRepository)
|
||||||
{
|
{
|
||||||
_projectRepository = projectRepository;
|
_projectRepository = projectRepository;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
|
_secretRepository = secretRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Tuple<Project, string>>> DeleteProjects(List<Guid> ids, Guid userId)
|
public async Task<List<Tuple<Project, string>>> DeleteProjects(List<Guid> ids, Guid userId)
|
||||||
@ -25,7 +27,7 @@ public class DeleteProjectCommand : IDeleteProjectCommand
|
|||||||
throw new ArgumentNullException();
|
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)
|
if (projects?.Any() != true || projects.Count != ids.Count)
|
||||||
{
|
{
|
||||||
@ -72,9 +74,16 @@ public class DeleteProjectCommand : IDeleteProjectCommand
|
|||||||
|
|
||||||
if (deleteIds.Count > 0)
|
if (deleteIds.Count > 0)
|
||||||
{
|
{
|
||||||
|
var secretIds = results.SelectMany(projTuple => projTuple.Item1?.Secrets?.Select(s => s.Id) ?? Array.Empty<Guid>()).ToList();
|
||||||
|
|
||||||
|
if (secretIds.Count > 0)
|
||||||
|
{
|
||||||
|
await _secretRepository.UpdateRevisionDates(secretIds);
|
||||||
|
}
|
||||||
|
|
||||||
await _projectRepository.DeleteManyByIdAsync(deleteIds);
|
await _projectRepository.DeleteManyByIdAsync(deleteIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,12 +91,13 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Core.SecretsManager.Entities.Project>> GetManyByIds(IEnumerable<Guid> ids)
|
public async Task<IEnumerable<Core.SecretsManager.Entities.Project>> GetManyWithSecretsByIds(IEnumerable<Guid> ids)
|
||||||
{
|
{
|
||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
var projects = await dbContext.Project
|
var projects = await dbContext.Project
|
||||||
|
.Include(p => p.Secrets)
|
||||||
.Where(c => ids.Contains(c.Id) && c.DeletedDate == null)
|
.Where(c => ids.Contains(c.Id) && c.DeletedDate == null)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return Mapper.Map<List<Core.SecretsManager.Entities.Project>>(projects);
|
return Mapper.Map<List<Core.SecretsManager.Entities.Project>>(projects);
|
||||||
|
@ -212,11 +212,13 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
|
|||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
var secrets = dbContext.Secret.Where(c => ids.Contains(c.Id));
|
var secrets = dbContext.Secret.Where(c => ids.Contains(c.Id));
|
||||||
await secrets.ForEachAsync(secret =>
|
await secrets.ForEachAsync(secret =>
|
||||||
{
|
{
|
||||||
dbContext.Attach(secret);
|
dbContext.Attach(secret);
|
||||||
secret.DeletedDate = null;
|
secret.DeletedDate = null;
|
||||||
|
secret.RevisionDate = utcNow;
|
||||||
});
|
});
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
@ -252,4 +254,22 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
|
|||||||
}
|
}
|
||||||
return secrets;
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateRevisionDates(IEnumerable<Guid> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public class DeleteProjectCommandTests
|
|||||||
public async Task DeleteProjects_Throws_NotFoundException(List<Guid> data, Guid userId,
|
public async Task DeleteProjects_Throws_NotFoundException(List<Guid> data, Guid userId,
|
||||||
SutProvider<DeleteProjectCommand> sutProvider)
|
SutProvider<DeleteProjectCommand> sutProvider)
|
||||||
{
|
{
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(new List<Project>());
|
sutProvider.GetDependency<IProjectRepository>().GetManyWithSecretsByIds(data).Returns(new List<Project>());
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteProjects(data, userId));
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteProjects(data, userId));
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ public class DeleteProjectCommandTests
|
|||||||
{
|
{
|
||||||
Id = Guid.NewGuid()
|
Id = Guid.NewGuid()
|
||||||
};
|
};
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(new List<Project>() { project });
|
sutProvider.GetDependency<IProjectRepository>().GetManyWithSecretsByIds(data).Returns(new List<Project>() { project });
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteProjects(data, userId));
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteProjects(data, userId));
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ public class DeleteProjectCommandTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
||||||
sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User;
|
sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User;
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects);
|
sutProvider.GetDependency<IProjectRepository>().GetManyWithSecretsByIds(data).Returns(projects);
|
||||||
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(Arg.Any<Guid>(), userId).Returns(true);
|
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(Arg.Any<Guid>(), userId).Returns(true);
|
||||||
|
|
||||||
var results = await sutProvider.Sut.DeleteProjects(data, userId);
|
var results = await sutProvider.Sut.DeleteProjects(data, userId);
|
||||||
@ -73,7 +73,7 @@ public class DeleteProjectCommandTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
||||||
sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User;
|
sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User;
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects);
|
sutProvider.GetDependency<IProjectRepository>().GetManyWithSecretsByIds(data).Returns(projects);
|
||||||
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(userId, userId).Returns(false);
|
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(userId, userId).Returns(false);
|
||||||
|
|
||||||
var results = await sutProvider.Sut.DeleteProjects(data, userId);
|
var results = await sutProvider.Sut.DeleteProjects(data, userId);
|
||||||
@ -95,7 +95,7 @@ public class DeleteProjectCommandTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
||||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects);
|
sutProvider.GetDependency<IProjectRepository>().GetManyWithSecretsByIds(data).Returns(projects);
|
||||||
|
|
||||||
var results = await sutProvider.Sut.DeleteProjects(data, userId);
|
var results = await sutProvider.Sut.DeleteProjects(data, userId);
|
||||||
|
|
||||||
|
@ -111,7 +111,6 @@ public class ProjectsController : Controller
|
|||||||
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
|
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
|
||||||
var results = await _deleteProjectCommand.DeleteProjects(ids, userId);
|
var results = await _deleteProjectCommand.DeleteProjects(ids, userId);
|
||||||
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
|
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
|
||||||
return new ListResponseModel<BulkDeleteResponseModel>(responses);
|
return new ListResponseModel<BulkDeleteResponseModel>(responses);
|
||||||
|
@ -7,7 +7,7 @@ public interface IProjectRepository
|
|||||||
{
|
{
|
||||||
Task<IEnumerable<Project>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
Task<IEnumerable<Project>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||||
Task<IEnumerable<Project>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
Task<IEnumerable<Project>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||||
Task<IEnumerable<Project>> GetManyByIds(IEnumerable<Guid> ids);
|
Task<IEnumerable<Project>> GetManyWithSecretsByIds(IEnumerable<Guid> ids);
|
||||||
Task<Project> GetByIdAsync(Guid id);
|
Task<Project> GetByIdAsync(Guid id);
|
||||||
Task<Project> CreateAsync(Project project);
|
Task<Project> CreateAsync(Project project);
|
||||||
Task ReplaceAsync(Project project);
|
Task ReplaceAsync(Project project);
|
||||||
|
@ -17,4 +17,5 @@ public interface ISecretRepository
|
|||||||
Task HardDeleteManyByIdAsync(IEnumerable<Guid> ids);
|
Task HardDeleteManyByIdAsync(IEnumerable<Guid> ids);
|
||||||
Task RestoreManyByIdAsync(IEnumerable<Guid> ids);
|
Task RestoreManyByIdAsync(IEnumerable<Guid> ids);
|
||||||
Task<IEnumerable<Secret>> ImportAsync(IEnumerable<Secret> secrets);
|
Task<IEnumerable<Secret>> ImportAsync(IEnumerable<Secret> secrets);
|
||||||
|
Task UpdateRevisionDates(IEnumerable<Guid> ids);
|
||||||
}
|
}
|
||||||
|
@ -361,7 +361,7 @@ public class ProjectsControllerTest : IClassFixture<ApiApplicationFactory>, IAsy
|
|||||||
results!.Data.Select(x => x.Id).OrderBy(x => x));
|
results!.Data.Select(x => x.Id).OrderBy(x => x));
|
||||||
Assert.DoesNotContain(results.Data, x => x.Error != null);
|
Assert.DoesNotContain(results.Data, x => x.Error != null);
|
||||||
|
|
||||||
var projects = await _projectRepository.GetManyByIds(projectIds);
|
var projects = await _projectRepository.GetManyWithSecretsByIds(projectIds);
|
||||||
Assert.Empty(projects);
|
Assert.Empty(projects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user