1
0
mirror of https://github.com/bitwarden/server synced 2026-01-06 18:43:36 +00:00

[SM-568] Delete service accounts (#2748)

This commit is contained in:
Oscar Hinton
2023-03-06 20:25:27 +01:00
committed by GitHub
parent de559e80f4
commit a0df350ea3
7 changed files with 224 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
public class DeleteServiceAccountsCommand : IDeleteServiceAccountsCommand
{
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly ICurrentContext _currentContext;
public DeleteServiceAccountsCommand(
IServiceAccountRepository serviceAccountRepository,
ICurrentContext currentContext)
{
_serviceAccountRepository = serviceAccountRepository;
_currentContext = currentContext;
}
public async Task<List<Tuple<ServiceAccount, string>>> DeleteServiceAccounts(List<Guid> ids, Guid userId)
{
if (ids.Any() != true || userId == new Guid())
{
throw new ArgumentNullException();
}
var serviceAccounts = (await _serviceAccountRepository.GetManyByIds(ids))?.ToList();
if (serviceAccounts?.Any() != true || serviceAccounts.Count != ids.Count)
{
throw new NotFoundException();
}
// Ensure all service accounts belongs to the same organization
var organizationId = serviceAccounts.First().OrganizationId;
if (serviceAccounts.Any(p => p.OrganizationId != organizationId))
{
throw new BadRequestException();
}
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var results = new List<Tuple<ServiceAccount, String>>(serviceAccounts.Count);
var deleteIds = new List<Guid>();
foreach (var sa in serviceAccounts)
{
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(sa.Id, userId),
_ => false,
};
if (!hasAccess)
{
results.Add(new Tuple<ServiceAccount, string>(sa, "access denied"));
}
else
{
results.Add(new Tuple<ServiceAccount, string>(sa, ""));
deleteIds.Add(sa.Id);
}
}
if (deleteIds.Count > 0)
{
await _serviceAccountRepository.DeleteManyByIdAsync(deleteIds);
}
return results;
}
}

View File

@@ -28,6 +28,7 @@ public static class SecretsManagerCollectionExtensions
services.AddScoped<IDeleteProjectCommand, DeleteProjectCommand>();
services.AddScoped<ICreateServiceAccountCommand, CreateServiceAccountCommand>();
services.AddScoped<IUpdateServiceAccountCommand, UpdateServiceAccountCommand>();
services.AddScoped<IDeleteServiceAccountsCommand, DeleteServiceAccountsCommand>();
services.AddScoped<IRevokeAccessTokensCommand, RevokeAccessTokensCommand>();
services.AddScoped<ICreateAccessTokenCommand, CreateAccessTokenCommand>();
services.AddScoped<ICreateAccessPoliciesCommand, CreateAccessPoliciesCommand>();

View File

@@ -32,6 +32,16 @@ public class ServiceAccountRepository : Repository<Core.SecretsManager.Entities.
return Mapper.Map<List<Core.SecretsManager.Entities.ServiceAccount>>(serviceAccounts);
}
public async Task<IEnumerable<Core.SecretsManager.Entities.ServiceAccount>> GetManyByIds(IEnumerable<Guid> ids)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var serviceAccounts = await dbContext.ServiceAccount
.Where(c => ids.Contains(c.Id))
.ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.ServiceAccount>>(serviceAccounts);
}
public async Task<bool> UserHasReadAccessToServiceAccount(Guid id, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
@@ -71,6 +81,26 @@ public class ServiceAccountRepository : Repository<Core.SecretsManager.Entities.
return Mapper.Map<List<Core.SecretsManager.Entities.ServiceAccount>>(serviceAccounts);
}
public async Task DeleteManyByIdAsync(IEnumerable<Guid> ids)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
// Policies can't have a cascade delete, so we need to delete them manually.
var policies = dbContext.AccessPolicies.Where(ap =>
((ServiceAccountProjectAccessPolicy)ap).ServiceAccountId.HasValue && ids.Contains(((ServiceAccountProjectAccessPolicy)ap).ServiceAccountId!.Value) ||
((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId.HasValue && ids.Contains(((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId!.Value) ||
((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId.HasValue && ids.Contains(((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId!.Value));
dbContext.RemoveRange(policies);
var apiKeys = dbContext.ApiKeys.Where(a => a.ServiceAccountId.HasValue && ids.Contains(a.ServiceAccountId!.Value));
dbContext.RemoveRange(apiKeys);
var serviceAccounts = dbContext.ServiceAccount.Where(c => ids.Contains(c.Id));
dbContext.RemoveRange(serviceAccounts);
await dbContext.SaveChangesAsync();
}
private static Expression<Func<ServiceAccount, bool>> UserHasReadAccessToServiceAccount(Guid userId) => sa =>
sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read));