Skip to content

Commit

Permalink
Merge branch 'master' into ac/ac-1487/missing-admin-auth-request-email
Browse files Browse the repository at this point in the history
  • Loading branch information
shane-melton authored Jul 11, 2023
2 parents 7cefa15 + b629c31 commit b5f0bea
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 206 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<!--2022.6.2-->
<Version>2023.5.1</Version>
<Version>2023.7.0</Version>
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
case not null when requirement == ProjectOperations.Update:
await CanUpdateProjectAsync(context, requirement, resource);
break;
case not null when requirement == ProjectOperations.Delete:
await CanDeleteProjectAsync(context, requirement, resource);
break;
default:
throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement));
}
Expand Down Expand Up @@ -75,4 +78,18 @@ private async Task CanUpdateProjectAsync(AuthorizationHandlerContext context,
context.Succeed(requirement);
}
}

private async Task CanDeleteProjectAsync(AuthorizationHandlerContext context,
ProjectOperationRequirement requirement, Project resource)
{
var (accessClient, userId) =
await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);

var access = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient);

if (access.Write)
{
context.Succeed(requirement);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;

Expand All @@ -10,74 +7,14 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Projects;
public class DeleteProjectCommand : IDeleteProjectCommand
{
private readonly IProjectRepository _projectRepository;
private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository;

public DeleteProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext, ISecretRepository secretRepository)
public DeleteProjectCommand(IProjectRepository projectRepository)
{
_projectRepository = projectRepository;
_currentContext = currentContext;
_secretRepository = secretRepository;
}

public async Task<List<Tuple<Project, string>>> DeleteProjects(List<Guid> ids, Guid userId)
public async Task DeleteProjects(IEnumerable<Project> projects)
{
if (ids.Any() != true || userId == new Guid())
{
throw new ArgumentNullException();
}

var projects = (await _projectRepository.GetManyWithSecretsByIds(ids))?.ToList();

if (projects?.Any() != true || projects.Count != ids.Count)
{
throw new NotFoundException();
}

// Ensure all projects belongs to the same organization
var organizationId = projects.First().OrganizationId;
if (projects.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<Project, string>>(projects.Count);
var deleteIds = new List<Guid>();

foreach (var project in projects)
{
var access = await _projectRepository.AccessToProjectAsync(project.Id, userId, accessClient);
if (!access.Write)
{
results.Add(new Tuple<Project, string>(project, "access denied"));
}
else
{
results.Add(new Tuple<Project, string>(project, ""));
deleteIds.Add(project.Id);
}
}

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);
}

return results;
await _projectRepository.DeleteManyByIdAsync(projects.Select(p => p.Id));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,23 @@ public async Task<IEnumerable<ProjectPermissionDetails>> GetManyByOrganizationId

public async Task DeleteManyByIdAsync(IEnumerable<Guid> ids)
{
using (var scope = ServiceScopeFactory.CreateScope())
using var scope = ServiceScopeFactory.CreateScope();
var utcNow = DateTime.UtcNow;
var dbContext = GetDatabaseContext(scope);
var projects = dbContext.Project
.Where(c => ids.Contains(c.Id))
.Include(p => p.Secrets);
await projects.ForEachAsync(project =>
{
var dbContext = GetDatabaseContext(scope);
var projects = dbContext.Project.Where(c => ids.Contains(c.Id));
await projects.ForEachAsync(project =>
foreach (var projectSecret in project.Secrets)
{
dbContext.Remove(project);
});
await dbContext.SaveChangesAsync();
}
projectSecret.RevisionDate = utcNow;
}
dbContext.Remove(project);
});

await dbContext.SaveChangesAsync();
}

public async Task<IEnumerable<Core.SecretsManager.Entities.Project>> GetManyWithSecretsByIds(IEnumerable<Guid> ids)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public async Task CanUpdateProject_NotSupportedClientType_DoesNotSucceed(
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(false);
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, project.OrganizationId)
.ReturnsForAnyArgs(
(AccessClientType.ServiceAccount, new Guid()));
(AccessClientType.Organization, new Guid()));
var requirement = ProjectOperations.Update;
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, project);
Expand All @@ -202,19 +202,43 @@ public async Task CanUpdateProject_NotSupportedClientType_DoesNotSucceed(
}

[Theory]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false)]
public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionType, bool read, bool write,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
[BitAutoData(PermissionType.RunAsAdmin, true, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)]
public async Task CanUpdateProject_AccessCheck(PermissionType permissionType, bool read, bool write,
bool expected,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = ProjectOperations.Update;
SetupPermission(sutProvider, permissionType, project.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
.Returns((read, write));
var requirement = ProjectOperations.Update;
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, project);

await sutProvider.Sut.HandleAsync(authzContext);

Assert.Equal(expected, authzContext.HasSucceeded);
}

[Theory]
[BitAutoData]
public async Task CanDeleteProject_AccessToSecretsManagerFalse_DoesNotSucceed(
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project,
ClaimsPrincipal claimsPrincipal)
{
var requirement = ProjectOperations.Delete;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
.Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, project);

Expand All @@ -224,26 +248,68 @@ public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionTyp
}

[Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true)]
[BitAutoData(PermissionType.RunAsAdmin, false, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true)]
public async Task CanUpdateProject_Success(PermissionType permissionType, bool read, bool write,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
[BitAutoData]
public async Task CanDeleteProject_NullResource_DoesNotSucceed(
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = ProjectOperations.Delete;
SetupPermission(sutProvider, PermissionType.RunAsAdmin, project.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, null);

await sutProvider.Sut.HandleAsync(authzContext);

Assert.False(authzContext.HasSucceeded);
}

[Theory]
[BitAutoData]
public async Task CanDeleteProject_NotSupportedClientType_DoesNotSucceed(
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
{
var requirement = ProjectOperations.Delete;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(false);
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, project.OrganizationId)
.ReturnsForAnyArgs(
(AccessClientType.Organization, new Guid()));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, project);

await sutProvider.Sut.HandleAsync(authzContext);

Assert.False(authzContext.HasSucceeded);
}

[Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)]
public async Task CanDeleteProject_AccessCheck(PermissionType permissionType, bool read, bool write,
bool expected,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = ProjectOperations.Delete;
SetupPermission(sutProvider, permissionType, project.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
.Returns((read, write));
var requirement = ProjectOperations.Update;
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, project);

await sutProvider.Sut.HandleAsync(authzContext);

Assert.True(authzContext.HasSucceeded);
Assert.Equal(expected, authzContext.HasSucceeded);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ public async Task CanDeleteSecret_NullResource_DoesNotSucceed(
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)]
public async Task CanDeleteProject_AccessCheck(PermissionType permissionType, bool read, bool write,
public async Task CanDeleteSecret_AccessCheck(PermissionType permissionType, bool read, bool write,
bool expected,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal,
Expand Down
Loading

0 comments on commit b5f0bea

Please sign in to comment.