mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[SM-381] New secrets access (#2629)
* [SM-66] Create Secret Database Table (#2144) Objective The purpose of this PR is to create a database table, entity, and repository for the new Secret database table. The new Secret table will use entity framework for all database providers. * [SM-67] Get all secrets by org ID (#2163) Add a controller to fetch secrets associated with an organization ID. To note, the [SecretsManager] attribute makes this controller only available for local development. * [SM-68] Add API endpoints for getting, creating, and editing secrets (#2201) The purpose of this PR is to add API endpoints for getting, creating, and editing secrets for the Secrets Manager project. * Move interfaces to core (#2211) * [SM-63] Read UTC DateTimes from databases via EF and order by revision date (#2206) * Read UTC DateTimes from db and order by revision * Move orderby to repo layer * [SM-185] Add EE_Testing_env to server (#2222) * Sm 104 project Database (#2192) * Project DB addition and sprocs * Adding spaces to the end of each file, fixing minor issues * removing useless comments * Adding soft delete proc to migration * Project EF Scaffold * Additional changes to use EF instead of procedures * Adding dependency injection * Fixing lint errors * Bug fixes * Adding migration scripts, removing sproc files, and setting up Entity framework code * Adding back accidentally deleted sproc * Removing files that shouldn't have been created * Lint * Small changes based on Oscar's rec (#2215) * Migrations for making CreateDate not null * adding space to end of file * Making Revision date not null * dotnet format * Adding nonclustered indexes to SQL * SM-104: Update PR with changes Thomas proposed Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Thomas Avery <tavery@bitwarden.com> Co-authored-by: Colton Hurst <colton@coltonhurst.com> * Removing org ID from create request body (#2243) * SM-114: Add create & update project endpoints (#2251) * SM-114: Initial commit with create project endpoint (for SM) * SM-114: Add Update Project route (for SM) * SM-114: Fix file encodings * Fix DI issue for SM Project Create/Update commands * Fix import ordering for linter * SM-114: Remove unneeded lines setting DeletedDate, as it should already be null * SM-114: Only have OrgId in route for CreateProject * Remove unneeded using * SM-114: Initial commit with create project endpoint (for SM) * SM-114: Add Update Project route (for SM) * SM-114: Fix file encodings * Fix DI issue for SM Project Create/Update commands * Fix import ordering for linter * SM-114: Remove unneeded lines setting DeletedDate, as it should already be null * SM-114: Only have OrgId in route for CreateProject * Remove unneeded using * Fully remove OrgId from ProjectCreateRequestModel * [SM-64] Soft Delete Secrets (#2253) * Bulk delete secrets with command unit tests * Controller unit tests * Optimize conditionals * SM-64 bulk delete integration test * fix test * SM-64 code review updated * [SM-65] Fix return empty secrets list (#2281) * Secrets return empty list * [SM-246] Use repository in integration test (#2285) * [SM-190] Add integration tests to Secrets (#2292) * Adding integration tests for the SecretsController Co-authored-by: Hinton <hinton@users.noreply.github.com> * Sm 95 - Adding GetProjects endpoint (#2295) * SM-114: Initial commit with create project endpoint (for SM) * SM-114: Add Update Project route (for SM) * SM-114: Fix file encodings * Fix DI issue for SM Project Create/Update commands * Adding GetProjectsByOrg * fixing merge conflicts * fix * Updating to return empty list * removing null check Co-authored-by: Colton Hurst <colton@coltonhurst.com> Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> * [SM-191] Create ServiceAccount Table (#2301) * SM-191 Create ServiceAccount Table * [SM-207] API for listing service accounts by organization (#2307) * SM-207 list service accounts by org * SM-96: Add ability to get project by id (#2314) * SM-96: Small change to allow getting project by id * Fix whitespace issue * Add first integration test and fix date bug * Ensure tests are consistent * Add more project controller integration tests * Remove commented delete for now * [SM-187] Create ServiceAccounts (#2323) * SM-187 Create & Update ServiceAccounts * Remove extra new line src/Api/Controllers/ServiceAccountsController.cs Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [SM-218] [SM-219] SM Auth flow (#2297) * SM-282 Delete Projects (#2335) * SM-282 delete & bulk delete projects * Have delete commands return tuple with object * Fix admin project not working after secrets manager changes (#2339) * [SM-150] proj and secrets mapping (#2286) * Beggining of changes for Project Secrets mapping * Beggining of changes for project and secrets mapping * Inital changes to add Mapping table for Project Secrets * Resolve migration not working properly * Indent sql * Changes to try and return projects in the GetManyByOrganizaationIDAsync on SecretRepository. * Changes made with Oscar * Add reversemap * running lint and removing comments * Lint fixes * fixing merge issues * Trying to fix the DB issue * DB fixes * fixes * removing unused space * fixing lint issue * final lint fix I hope * removing manually added sql.sqlproj * Lint changes and fixing the sql proj issues * adding ServiceAccount to sql proj * Removing ON DELETE CASCADE * remove On delete cascade * changes for deleting project and secret inside of the Organization_DeleteById procedure. * changes for deleting project and secret inside of the Organization_DeleteById procedure. * migration changes * Updating constraints * removing void * remove spaces * updating cipherRepo tests to be task instead of void * fixing * fixing * test * fix * fix * changes to remove circular dependency * fixes * sending guid and string name of the project over * Update src/Sql/dbo/Tables/Secret.sql Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * Update src/Sql/dbo/Tables/Project.sql Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * removing unused code * Potential refactor (#2340) * migrations * Postgres migraiton * Update src/Api/SecretManagerFeatures/Models/Response/SecretResponseModel.cs Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * rename file * Update util/Migrator/DbScripts/2022-09-19_00_ProjectSecret.sql Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * Lint fixes * removing extra semi colon * removing circular references with projects and secrets * adding back projects * Add ProjectFixture * Update util/Migrator/DbScripts/2022-09-19_00_ProjectSecret.sql Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * Update util/Migrator/DbScripts/2022-09-19_00_ProjectSecret.sql Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Hinton <hinton@users.noreply.github.com> * [SM-300] Access token endpoint (#2377) * [SM-324] Add Organization to JWT claim (#2379) * [SM-259] Add create access token endpoint for service accounts (#2411) * Add create access token for service accounts * [SM-259] Fix create access token scope initialization (#2418) * Fix namespace for ServiceAccount command tests * Remove "this" from SecretsManager requests * Fix have scope be assigned a JSON list * SM-99: Individual Project / Secrets Tab (#2399) Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [SM-361] Add Support for never expiring ApiKeys (#2450) * Update database to support never expiring ApiKey * Update Api to support never expiring ApiKeys * Fix unit test variable naming * Remove required from model * Fix spacing * Add EF migrations * Run dotnet format * Update util/Migrator/DbScripts/2022-11-29_00_ApiKey_Never_Expire.sql Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [SM-359] Fix project secrets migration (#2443) * [SM-299] Add UseSecretsManager flag (#2413) * [SM-193] Access Policy (#2359) * [SM-371] Fix and re-enable parallel integration tests (#2460) * Fix and re-enable parallel integration tests * Fix package lock files * Move fix to ApiApplicationFactory * Run dotnet restore --force * Run dotnet format * Reset packages.lock.json files * Add project access checks for listing * SM-99: Add CreateSecretWithProject Integration Test (#2452) * Add GetSecretsByProjectAsync endpoint * Add GetManyByProjectIdAsync endpoint * Update response model for GetSecretsByProjectAsync * Include projects when returning secrets by project id * SM-99: Add ability to specify projectId when creating a secret * SM-99: Update tests to accomodate for new create secret parameter * Fix failing test * SM-99: Handle optional projectId for new secret in ToSecret() * SM-99: Filter out deleted secrets on GetManyByProjectIdAsync() and small refactorings * SM-99: make CreateAsync for secret more clear * Add CreateSecretWithProject integration test * Fix CreateSecretWithProject integration test for SM-99 * Run dotnet format * Undo added space * Refactor test * Refactor CreateSecretWithProject API Integration test again * Change to boolean flag * [SM-379] Add SDK device type (#2486) * Add support for service accounts * Improve logic for project repository * Add remaining client types * Experiment with separate enum for access control * Add access checks to update project * Rework AccessClientType * Add access checks to fetching project * Add checks to delete project command (untested) * Remove some service account stuff * Add ServiceAccount to AccessClientType * Change CS8509 to error and 8424 to ignore * Remove unused utcNow * Fix delete tests * SM-73 changes (#2422) * testing * test2 * testing * trying to save the projects associated with the secret * changes * more changes * Fix EF error * Second attempt * Replace AddIfNotExists with Add. * changes * fixing await issue * lint * lint fixes * suggested changes * suggested changes * updating tests * fixing tests 2 * fixing tests * fixing test * fixing test * fixing tests * test * testing * fixing tests for the millionth time * fixing tests * allowing nulls for projectIds, fixing lint * fixing tests Co-authored-by: Hinton <hinton@users.noreply.github.com> * fixing tests * fixing tests * [SM-222] [SM-357] Squash Secrets Manager migrations (#2540) * Fix tables not being cleaned up * Fix migration * Squash secrets manager migrations * Reset EF to pre SM state * Add EF migrations * Fix unified docker * Add missed copy * Fix all unit tests * draft changes to add access checks to secrets * updating code * more changes * fixing issues * updating logic for access checks * updating secrets controller * changes * changes * merging more * changes * updateS * removing unused comment * changes requested by Thomas * more changes suggested by Thomas * making thomas's suggested changes * final changes * Run dotnet format * fixes * run dotnet format * Updating tests * Suggested changes * lint fixes * Test updates * Changes * Fixes for tests, and dotnet format * Fixes * test fixes * changes * fix * fix * test fix * removing duplicate * Removing dupe --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Thomas Avery <tavery@bitwarden.com> Co-authored-by: Colton Hurst <colton@coltonhurst.com>
This commit is contained in:
parent
bcaba6652b
commit
ec8476912d
@ -1,4 +1,7 @@
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
@ -7,14 +10,34 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
public class CreateSecretCommand : ICreateSecretCommand
|
||||
{
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public CreateSecretCommand(ISecretRepository secretRepository)
|
||||
public CreateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
|
||||
{
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task<Secret> CreateAsync(Secret secret)
|
||||
public async Task<Secret> CreateAsync(Secret secret, Guid userId)
|
||||
{
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
var project = secret.Projects?.FirstOrDefault();
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => project != null && await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return await _secretRepository.CreateAsync(secret);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
@ -10,25 +11,27 @@ public class DeleteSecretCommand : IDeleteSecretCommand
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
|
||||
public DeleteSecretCommand(ICurrentContext currentContext, ISecretRepository secretRepository)
|
||||
public DeleteSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids)
|
||||
public async Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids, Guid userId)
|
||||
{
|
||||
var secrets = await _secretRepository.GetManyByIds(ids);
|
||||
var secrets = (await _secretRepository.GetManyByIds(ids)).ToList();
|
||||
|
||||
if (secrets?.Any() != true)
|
||||
if (secrets.Any() != true)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Ensure all secrets belongs to the same organization
|
||||
var organizationId = secrets.First().OrganizationId;
|
||||
if (secrets.Any(p => p.OrganizationId != organizationId))
|
||||
if (secrets.Any(secret => secret.OrganizationId != organizationId))
|
||||
{
|
||||
throw new BadRequestException();
|
||||
}
|
||||
@ -38,21 +41,46 @@ public class DeleteSecretCommand : IDeleteSecretCommand
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var results = ids.Select(id =>
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var results = new List<Tuple<Secret, string>>();
|
||||
var deleteIds = new List<Guid>();
|
||||
|
||||
foreach (var secret in secrets)
|
||||
{
|
||||
var secret = secrets.FirstOrDefault(secret => secret.Id == id);
|
||||
if (secret == null)
|
||||
var hasAccess = orgAdmin;
|
||||
|
||||
if (secret.Projects != null && secret.Projects?.Count > 0)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
var projectId = secret.Projects.First().Id;
|
||||
|
||||
hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(projectId, userId),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
results.Add(new Tuple<Secret, string>(secret, "access denied"));
|
||||
}
|
||||
// TODO Once permissions are implemented add check for each secret here.
|
||||
else
|
||||
{
|
||||
return new Tuple<Secret, string>(secret, "");
|
||||
deleteIds.Add(secret.Id);
|
||||
results.Add(new Tuple<Secret, string>(secret, ""));
|
||||
}
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (deleteIds.Count > 0)
|
||||
{
|
||||
await _secretRepository.SoftDeleteManyByIdAsync(deleteIds);
|
||||
}
|
||||
|
||||
await _secretRepository.SoftDeleteManyByIdAsync(ids);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
@ -8,23 +10,45 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
public class UpdateSecretCommand : IUpdateSecretCommand
|
||||
{
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public UpdateSecretCommand(ISecretRepository secretRepository)
|
||||
public UpdateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
|
||||
{
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task<Secret> UpdateAsync(Secret secret)
|
||||
public async Task<Secret> UpdateAsync(Secret updatedSecret, Guid userId)
|
||||
{
|
||||
var existingSecret = await _secretRepository.GetByIdAsync(secret.Id);
|
||||
if (existingSecret == null)
|
||||
var secret = await _secretRepository.GetByIdAsync(updatedSecret.Id);
|
||||
if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
secret.OrganizationId = existingSecret.OrganizationId;
|
||||
secret.CreationDate = existingSecret.CreationDate;
|
||||
secret.DeletedDate = existingSecret.DeletedDate;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var project = updatedSecret.Projects?.FirstOrDefault();
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => project != null && await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
secret.Key = updatedSecret.Key;
|
||||
secret.Value = updatedSecret.Value;
|
||||
secret.Note = updatedSecret.Note;
|
||||
secret.Projects = updatedSecret.Projects;
|
||||
secret.RevisionDate = DateTime.UtcNow;
|
||||
|
||||
await _secretRepository.UpdateAsync(secret);
|
||||
|
@ -74,6 +74,9 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
||||
private static Expression<Func<Project, bool>> ServiceAccountHasReadAccessToProject(Guid serviceAccountId) => p =>
|
||||
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Read);
|
||||
|
||||
private static Expression<Func<Project, bool>> ServiceAccountHasWriteAccessToProject(Guid serviceAccountId) => p =>
|
||||
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Write);
|
||||
|
||||
public async Task DeleteManyByIdAsync(IEnumerable<Guid> ids)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
@ -100,6 +103,28 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ServiceAccountHasReadAccessToProject(Guid id, Guid userId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var query = dbContext.Project
|
||||
.Where(p => p.Id == id)
|
||||
.Where(ServiceAccountHasReadAccessToProject(userId));
|
||||
|
||||
return await query.AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ServiceAccountHasWriteAccessToProject(Guid id, Guid userId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var query = dbContext.Project
|
||||
.Where(p => p.Id == id)
|
||||
.Where(ServiceAccountHasWriteAccessToProject(userId));
|
||||
|
||||
return await query.AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> UserHasReadAccessToProject(Guid id, Guid userId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
@ -1,4 +1,6 @@
|
||||
using AutoMapper;
|
||||
using System.Linq.Expressions;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
@ -6,6 +8,7 @@ using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
||||
namespace Bit.Commercial.Infrastructure.EntityFramework.SecretsManager.Repositories;
|
||||
|
||||
public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret, Secret, Guid>, ISecretRepository
|
||||
@ -34,35 +37,58 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var secrets = await dbContext.Secret
|
||||
.Where(c => ids.Contains(c.Id) && c.DeletedDate == null)
|
||||
.Include(c => c.Projects)
|
||||
.ToListAsync();
|
||||
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByOrganizationIdAsync(Guid organizationId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var secrets = await dbContext.Secret
|
||||
.Where(c => c.OrganizationId == organizationId && c.DeletedDate == null)
|
||||
.Include("Projects")
|
||||
.OrderBy(c => c.RevisionDate)
|
||||
.ToListAsync();
|
||||
private static Expression<Func<Secret, bool>> ServiceAccountHasReadAccessToSecret(Guid serviceAccountId) => s =>
|
||||
s.Projects.Any(p =>
|
||||
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Read));
|
||||
|
||||
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
|
||||
}
|
||||
private static Expression<Func<Secret, bool>> UserHasReadAccessToSecret(Guid userId) => s =>
|
||||
s.Projects.Any(p =>
|
||||
p.UserAccessPolicies.Any(ap => ap.OrganizationUser.UserId == userId && ap.Read) ||
|
||||
p.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.UserId == userId && ap.Read)));
|
||||
|
||||
|
||||
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var query = dbContext.Secret.Include(c => c.Projects).Where(c => c.OrganizationId == organizationId && c.DeletedDate == null);
|
||||
|
||||
query = accessType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => query,
|
||||
AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)),
|
||||
AccessClientType.ServiceAccount => query.Where(ServiceAccountHasReadAccessToSecret(userId)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
|
||||
};
|
||||
|
||||
var secrets = await query.OrderBy(c => c.RevisionDate).ToListAsync();
|
||||
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByProjectIdAsync(Guid projectId)
|
||||
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var secrets = await dbContext.Secret
|
||||
.Where(s => s.Projects.Any(p => p.Id == projectId) && s.DeletedDate == null).Include("Projects")
|
||||
.OrderBy(s => s.RevisionDate).ToListAsync();
|
||||
var query = dbContext.Secret.Include(s => s.Projects)
|
||||
.Where(s => s.Projects.Any(p => p.Id == projectId) && s.DeletedDate == null);
|
||||
|
||||
query = accessType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => query,
|
||||
AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)),
|
||||
AccessClientType.ServiceAccount => query.Where(ServiceAccountHasReadAccessToSecret(userId)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
|
||||
};
|
||||
|
||||
var secrets = await query.OrderBy(s => s.RevisionDate).ToListAsync();
|
||||
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
|
||||
}
|
||||
}
|
||||
@ -96,6 +122,7 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var mappedEntity = Mapper.Map<Secret>(secret);
|
||||
|
||||
var entity = await dbContext.Secret
|
||||
.Include("Projects")
|
||||
.FirstAsync(s => s.Id == secret.Id);
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
using Bit.Commercial.Core.Test.SecretsManager.Enums;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
|
||||
@ -14,14 +17,54 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
||||
public class CreateSecretCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_CallsCreate(Secret data,
|
||||
SutProvider<CreateSecretCommand> sutProvider)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateAsync_Success(PermissionType permissionType, Secret data,
|
||||
SutProvider<CreateSecretCommand> sutProvider, Guid userId, Project mockProject)
|
||||
{
|
||||
await sutProvider.Sut.CreateAsync(data);
|
||||
data.Projects = new List<Project>() { mockProject };
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(true);
|
||||
}
|
||||
|
||||
await sutProvider.Sut.CreateAsync(data, userId);
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1)
|
||||
.CreateAsync(data);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_UserWithoutPermission_ThrowsNotFound(Secret data,
|
||||
SutProvider<CreateSecretCommand> sutProvider, Guid userId, Project mockProject)
|
||||
{
|
||||
data.Projects = new List<Project>() { mockProject };
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.CreateAsync(data, userId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_NoProjects_User_ThrowsNotFound(Secret data,
|
||||
SutProvider<CreateSecretCommand> sutProvider, Guid userId)
|
||||
{
|
||||
data.Projects = null;
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.CreateAsync(data, userId));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,20 @@
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
using Bit.Commercial.Core.Test.SecretsManager.Enums;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
||||
public class DeleteSecretCommandTests
|
||||
{
|
||||
[Theory]
|
||||
@ -20,7 +24,7 @@ public class DeleteSecretCommandTests
|
||||
{
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(new List<Secret>());
|
||||
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data));
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data, default));
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().SoftDeleteManyByIdAsync(default);
|
||||
}
|
||||
@ -36,22 +40,39 @@ public class DeleteSecretCommandTests
|
||||
};
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(new List<Secret>() { secret });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data));
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data, default));
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().SoftDeleteManyByIdAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteSecrets_Success(List<Guid> data,
|
||||
SutProvider<DeleteSecretCommand> sutProvider)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task DeleteSecrets_Success(PermissionType permissionType, List<Guid> data,
|
||||
SutProvider<DeleteSecretCommand> sutProvider, Guid userId, Guid organizationId, Project mockProject)
|
||||
{
|
||||
List<Project> projects = null;
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(mockProject.Id, userId).Returns(true);
|
||||
projects = new List<Project>() { mockProject };
|
||||
}
|
||||
|
||||
|
||||
var secrets = new List<Secret>();
|
||||
foreach (Guid id in data)
|
||||
{
|
||||
var secret = new Secret()
|
||||
{
|
||||
Id = id
|
||||
Id = id,
|
||||
OrganizationId = organizationId,
|
||||
Projects = projects
|
||||
};
|
||||
secrets.Add(secret);
|
||||
}
|
||||
@ -59,9 +80,9 @@ public class DeleteSecretCommandTests
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(secrets);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
|
||||
var results = await sutProvider.Sut.DeleteSecrets(data);
|
||||
var results = await sutProvider.Sut.DeleteSecrets(data, userId);
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1).SoftDeleteManyByIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1).SoftDeleteManyByIdAsync(Arg.Is(data));
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.Equal("", result.Item2);
|
||||
|
@ -1,7 +1,10 @@
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
using Bit.Commercial.Core.Test.SecretsManager.Enums;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -13,23 +16,38 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[SecretCustomize]
|
||||
[ProjectCustomize]
|
||||
public class UpdateSecretCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_SecretDoesNotExist_ThrowsNotFound(Secret data, SutProvider<UpdateSecretCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, default));
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().UpdateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_CallsReplaceAsync(Secret data, SutProvider<UpdateSecretCommand> sutProvider)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task UpdateAsync_Success(PermissionType permissionType, Secret data, SutProvider<UpdateSecretCommand> sutProvider, Guid userId, Project mockProject)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Projects = new List<Project>() { mockProject };
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(true);
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
await sutProvider.Sut.UpdateAsync(data);
|
||||
await sutProvider.Sut.UpdateAsync(data, userId);
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1)
|
||||
.UpdateAsync(data);
|
||||
@ -37,11 +55,14 @@ public class UpdateSecretCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
|
||||
public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
|
||||
{
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
|
||||
|
||||
var updatedOrgId = Guid.NewGuid();
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(updatedOrgId).Returns(true);
|
||||
|
||||
var secretUpdate = new Secret()
|
||||
{
|
||||
OrganizationId = updatedOrgId,
|
||||
@ -49,7 +70,7 @@ public class UpdateSecretCommandTests
|
||||
Key = existingSecret.Key,
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
|
||||
|
||||
Assert.Equal(existingSecret.OrganizationId, result.OrganizationId);
|
||||
Assert.NotEqual(existingSecret.OrganizationId, updatedOrgId);
|
||||
@ -57,9 +78,11 @@ public class UpdateSecretCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
|
||||
public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
|
||||
|
||||
var updatedCreationDate = DateTime.UtcNow;
|
||||
var secretUpdate = new Secret()
|
||||
@ -67,9 +90,10 @@ public class UpdateSecretCommandTests
|
||||
CreationDate = updatedCreationDate,
|
||||
Id = existingSecret.Id,
|
||||
Key = existingSecret.Key,
|
||||
OrganizationId = existingSecret.OrganizationId
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
|
||||
|
||||
Assert.Equal(existingSecret.CreationDate, result.CreationDate);
|
||||
Assert.NotEqual(existingSecret.CreationDate, updatedCreationDate);
|
||||
@ -77,9 +101,11 @@ public class UpdateSecretCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
|
||||
public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
|
||||
|
||||
var updatedDeletionDate = DateTime.UtcNow;
|
||||
var secretUpdate = new Secret()
|
||||
@ -87,9 +113,10 @@ public class UpdateSecretCommandTests
|
||||
DeletedDate = updatedDeletionDate,
|
||||
Id = existingSecret.Id,
|
||||
Key = existingSecret.Key,
|
||||
OrganizationId = existingSecret.OrganizationId
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
|
||||
|
||||
Assert.Equal(existingSecret.DeletedDate, result.DeletedDate);
|
||||
Assert.NotEqual(existingSecret.DeletedDate, updatedDeletionDate);
|
||||
@ -98,9 +125,12 @@ public class UpdateSecretCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
|
||||
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
|
||||
|
||||
var updatedRevisionDate = DateTime.UtcNow.AddDays(10);
|
||||
var secretUpdate = new Secret()
|
||||
@ -108,11 +138,12 @@ public class UpdateSecretCommandTests
|
||||
RevisionDate = updatedRevisionDate,
|
||||
Id = existingSecret.Id,
|
||||
Key = existingSecret.Key,
|
||||
OrganizationId = existingSecret.OrganizationId
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
|
||||
|
||||
Assert.NotEqual(existingSecret.RevisionDate, result.RevisionDate);
|
||||
Assert.NotEqual(secretUpdate.RevisionDate, result.RevisionDate);
|
||||
AssertHelper.AssertRecent(result.RevisionDate);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,12 @@
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@ -16,22 +19,21 @@ public class SecretsController : Controller
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ICreateSecretCommand _createSecretCommand;
|
||||
private readonly IUpdateSecretCommand _updateSecretCommand;
|
||||
private readonly IDeleteSecretCommand _deleteSecretCommand;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public SecretsController(
|
||||
ICurrentContext currentContext,
|
||||
ISecretRepository secretRepository,
|
||||
ICreateSecretCommand createSecretCommand,
|
||||
IUpdateSecretCommand updateSecretCommand,
|
||||
IDeleteSecretCommand deleteSecretCommand)
|
||||
public SecretsController(ISecretRepository secretRepository, IProjectRepository projectRepository, ICreateSecretCommand createSecretCommand, IUpdateSecretCommand updateSecretCommand, IDeleteSecretCommand deleteSecretCommand, IUserService userService, ICurrentContext currentContext)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_secretRepository = secretRepository;
|
||||
_createSecretCommand = createSecretCommand;
|
||||
_updateSecretCommand = updateSecretCommand;
|
||||
_deleteSecretCommand = deleteSecretCommand;
|
||||
_projectRepository = projectRepository;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[HttpGet("organizations/{organizationId}/secrets")]
|
||||
@ -42,7 +44,12 @@ public class SecretsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, accessClient);
|
||||
|
||||
return new SecretWithProjectsListResponseModel(secrets);
|
||||
}
|
||||
|
||||
@ -54,7 +61,8 @@ public class SecretsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId));
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId), userId);
|
||||
return new SecretResponseModel(result);
|
||||
}
|
||||
|
||||
@ -62,34 +70,74 @@ public class SecretsController : Controller
|
||||
public async Task<SecretResponseModel> GetAsync([FromRoute] Guid id)
|
||||
{
|
||||
var secret = await _secretRepository.GetByIdAsync(id);
|
||||
if (secret == null)
|
||||
if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await UserHasReadAccessToSecret(secret))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new SecretResponseModel(secret);
|
||||
}
|
||||
|
||||
[HttpGet("projects/{projectId}/secrets")]
|
||||
public async Task<SecretWithProjectsListResponseModel> GetSecretsByProjectAsync([FromRoute] Guid projectId)
|
||||
{
|
||||
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId);
|
||||
var responses = secrets.Select(s => new SecretResponseModel(s));
|
||||
var project = await _projectRepository.GetByIdAsync(projectId);
|
||||
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId, userId, accessClient);
|
||||
|
||||
return new SecretWithProjectsListResponseModel(secrets);
|
||||
}
|
||||
|
||||
[HttpPut("secrets/{id}")]
|
||||
public async Task<SecretResponseModel> UpdateAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
|
||||
public async Task<SecretResponseModel> UpdateSecretAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
|
||||
{
|
||||
var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id));
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var secret = updateRequest.ToSecret(id);
|
||||
var result = await _updateSecretCommand.UpdateAsync(secret, userId);
|
||||
return new SecretResponseModel(result);
|
||||
}
|
||||
|
||||
// TODO Once permissions are setup for Secrets Manager need to enforce them on delete.
|
||||
[HttpPost("secrets/delete")]
|
||||
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
|
||||
{
|
||||
var results = await _deleteSecretCommand.DeleteSecrets(ids);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var results = await _deleteSecretCommand.DeleteSecrets(ids, userId);
|
||||
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
|
||||
return new ListResponseModel<BulkDeleteResponseModel>(responses);
|
||||
}
|
||||
|
||||
public async Task<bool> UserHasReadAccessToSecret(Secret secret)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
var hasAccess = orgAdmin;
|
||||
|
||||
if (secret.Projects?.Count > 0)
|
||||
{
|
||||
Guid projectId = secret.Projects.FirstOrDefault().Id;
|
||||
hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _projectRepository.UserHasReadAccessToProject(projectId, userId),
|
||||
AccessClientType.ServiceAccount => await _projectRepository.ServiceAccountHasReadAccessToProject(projectId, userId),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
return hasAccess;
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public class SecretsManagerPortingController : Controller
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId);
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
|
||||
|
||||
if (projects == null && secrets == null)
|
||||
{
|
||||
|
@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
|
||||
public interface ICreateSecretCommand
|
||||
{
|
||||
Task<Secret> CreateAsync(Secret secret);
|
||||
Task<Secret> CreateAsync(Secret secret, Guid userId);
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
|
||||
public interface IDeleteSecretCommand
|
||||
{
|
||||
Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids);
|
||||
Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids, Guid userId);
|
||||
}
|
||||
|
||||
|
@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
|
||||
public interface IUpdateSecretCommand
|
||||
{
|
||||
Task<Secret> UpdateAsync(Secret secret);
|
||||
Task<Secret> UpdateAsync(Secret secret, Guid userId);
|
||||
}
|
||||
|
@ -15,4 +15,6 @@ public interface IProjectRepository
|
||||
Task<IEnumerable<Project>> ImportAsync(IEnumerable<Project> projects);
|
||||
Task<bool> UserHasReadAccessToProject(Guid id, Guid userId);
|
||||
Task<bool> UserHasWriteAccessToProject(Guid id, Guid userId);
|
||||
Task<bool> ServiceAccountHasWriteAccessToProject(Guid id, Guid userId);
|
||||
Task<bool> ServiceAccountHasReadAccessToProject(Guid id, Guid userId);
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
public interface ISecretRepository
|
||||
{
|
||||
Task<IEnumerable<Secret>> GetManyByOrganizationIdAsync(Guid organizationId);
|
||||
Task<IEnumerable<Secret>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||
Task<IEnumerable<Secret>> GetManyByIds(IEnumerable<Guid> ids);
|
||||
Task<IEnumerable<Secret>> GetManyByProjectIdAsync(Guid projectId);
|
||||
Task<IEnumerable<Secret>> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType);
|
||||
Task<Secret> GetByIdAsync(Guid id);
|
||||
Task<Secret> CreateAsync(Secret secret);
|
||||
Task<Secret> UpdateAsync(Secret secret);
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.SecretsManager.Enums;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Test.Common.Helpers;
|
||||
@ -20,6 +22,7 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IAccessPolicyRepository _accessPolicyRepository;
|
||||
|
||||
private string _email = null!;
|
||||
private SecretsManagerOrganizationHelper _organizationHelper = null!;
|
||||
@ -30,6 +33,7 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
_client = _factory.CreateClient();
|
||||
_secretRepository = _factory.GetService<ISecretRepository>();
|
||||
_projectRepository = _factory.GetService<IProjectRepository>();
|
||||
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
@ -64,12 +68,36 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListByOrganization_Owner_Success()
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task ListByOrganization_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
var (org, orgUserOwner) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
Id = new Guid(),
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
var secretIds = new List<Guid>();
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@ -78,7 +106,9 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
Note = _mockEncryptedString,
|
||||
Projects = new List<Project> { project }
|
||||
|
||||
});
|
||||
secretIds.Add(secret.Id);
|
||||
}
|
||||
@ -113,7 +143,7 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_Owner_Success()
|
||||
public async Task CreateWithoutProject_RunAsAdmin_Success()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
@ -147,11 +177,33 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateWithProject_Owner_Success()
|
||||
public async Task CreateWithoutProject_RunAsUser_NotFound()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var request = new SecretCreateRequestModel
|
||||
{
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
};
|
||||
|
||||
var response = await _client.PostAsJsonAsync($"/organizations/{org.Id}/secrets", request);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateWithProject_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, orgAdminUser) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
AccessClientType accessType = AccessClientType.NoAccessCheck;
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project()
|
||||
{
|
||||
Id = new Guid(),
|
||||
@ -159,6 +211,25 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
var orgUserId = (Guid)orgAdminUser.UserId;
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
accessType = AccessClientType.User;
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new Core.SecretsManager.Entities.UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id , Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
orgUserId = (Guid)orgUser.UserId;
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
var secretRequest = new SecretCreateRequestModel()
|
||||
{
|
||||
Key = _mockEncryptedString,
|
||||
@ -170,7 +241,7 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
secretResponse.EnsureSuccessStatusCode();
|
||||
var secretResult = await secretResponse.Content.ReadFromJsonAsync<SecretResponseModel>();
|
||||
|
||||
var secret = (await _secretRepository.GetManyByProjectIdAsync(project.Id)).First();
|
||||
var secret = (await _secretRepository.GetManyByProjectIdAsync(project.Id, orgUserId, accessType)).First();
|
||||
|
||||
Assert.NotNull(secretResult);
|
||||
Assert.Equal(secret.Id.ToString(), secretResult!.Id);
|
||||
@ -203,18 +274,48 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_Owner_Success()
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task Get_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project()
|
||||
{
|
||||
Id = new Guid(),
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.Admin, true);
|
||||
await LoginAsync(email);
|
||||
}
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
Note = _mockEncryptedString,
|
||||
Projects = new List<Project> { project }
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/secrets/{secret.Id}");
|
||||
@ -255,25 +356,51 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_Owner_Success()
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task Update_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project()
|
||||
{
|
||||
Id = new Guid(),
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
Note = _mockEncryptedString,
|
||||
Projects = permissionType == PermissionType.RunAsUserWithPermission ? new List<Project>() { project } : null
|
||||
});
|
||||
|
||||
var request = new SecretUpdateRequestModel()
|
||||
{
|
||||
Key = _mockEncryptedString,
|
||||
Value = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=",
|
||||
Note = _mockEncryptedString
|
||||
Note = _mockEncryptedString,
|
||||
ProjectIds = permissionType == PermissionType.RunAsUserWithPermission ? new Guid[] { project.Id } : null
|
||||
};
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/secrets/{secret.Id}", request);
|
||||
@ -316,16 +443,41 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
});
|
||||
var secretIds = new[] { secret.Id };
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secrets/delete", secretIds);
|
||||
var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/delete", secretIds);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_Owner_Success()
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task Delete_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project()
|
||||
{
|
||||
Id = new Guid(),
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
|
||||
var secretIds = new List<Guid>();
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@ -334,12 +486,13 @@ public class SecretsControllerTest : IClassFixture<ApiApplicationFactory>, IAsyn
|
||||
OrganizationId = org.Id,
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
Note = _mockEncryptedString,
|
||||
Projects = new List<Project>() { project }
|
||||
});
|
||||
secretIds.Add(secret.Id);
|
||||
}
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secrets/delete", secretIds);
|
||||
var response = await _client.PostAsJsonAsync($"/secrets/delete", secretIds);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var results = await response.Content.ReadFromJsonAsync<ListResponseModel<BulkDeleteResponseModel>>();
|
||||
|
@ -1,10 +1,13 @@
|
||||
using Bit.Api.SecretsManager.Controllers;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.Test.SecretsManager.Enums;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -22,33 +25,50 @@ public class SecretsControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void GetSecretsByOrganization_ReturnsEmptyList(SutProvider<SecretsController> sutProvider, Guid id)
|
||||
public async void GetSecretsByOrganization_ReturnsEmptyList(SutProvider<SecretsController> sutProvider, Guid id, Guid organizationId, Guid userId, AccessClientType accessType)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(id).Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
var result = await sutProvider.Sut.ListByOrganizationAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1)
|
||||
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
|
||||
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), userId, accessType);
|
||||
|
||||
Assert.Empty(result.Secrets);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void GetSecretsByOrganization_Success(SutProvider<SecretsController> sutProvider, Secret resultSecret)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void GetSecretsByOrganization_Success(PermissionType permissionType, SutProvider<SecretsController> sutProvider, Core.SecretsManager.Entities.Secret resultSecret, Guid organizationId, Guid userId, Core.SecretsManager.Entities.Project mockProject, AccessClientType accessType)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByOrganizationIdAsync(default).ReturnsForAnyArgs(new List<Secret> { resultSecret });
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByOrganizationIdAsync(default, default, default).ReturnsForAnyArgs(new List<Core.SecretsManager.Entities.Secret> { resultSecret });
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultSecret.Projects = new List<Core.SecretsManager.Entities.Project>() { mockProject };
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true);
|
||||
}
|
||||
|
||||
|
||||
var result = await sutProvider.Sut.ListByOrganizationAsync(resultSecret.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1)
|
||||
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(resultSecret.OrganizationId)));
|
||||
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(resultSecret.OrganizationId)), userId, accessType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void GetSecretsByOrganization_AccessDenied_Throws(SutProvider<SecretsController> sutProvider, Secret resultSecret)
|
||||
public async void GetSecretsByOrganization_AccessDenied_Throws(SutProvider<SecretsController> sutProvider, Core.SecretsManager.Entities.Secret resultSecret)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(false);
|
||||
|
||||
@ -64,11 +84,29 @@ public class SecretsControllerTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void GetSecret_Success(SutProvider<SecretsController> sutProvider, Secret resultSecret)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void GetSecret_Success(PermissionType permissionType, SutProvider<SecretsController> sutProvider, Secret resultSecret, Guid userId, Guid organizationId, Project mockProject)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
mockProject.OrganizationId = organizationId;
|
||||
resultSecret.Projects = new List<Project>() { mockProject };
|
||||
resultSecret.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(default).ReturnsForAnyArgs(resultSecret);
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
resultSecret.OrganizationId = organizationId;
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true);
|
||||
}
|
||||
|
||||
var result = await sutProvider.Sut.GetAsync(resultSecret.Id);
|
||||
|
||||
await sutProvider.GetDependency<ISecretRepository>().Received(1)
|
||||
@ -76,46 +114,89 @@ public class SecretsControllerTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void CreateSecret_Success(SutProvider<SecretsController> sutProvider, SecretCreateRequestModel data, Guid organizationId)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void CreateSecret_Success(PermissionType permissionType, SutProvider<SecretsController> sutProvider, SecretCreateRequestModel data, Guid organizationId, Project mockProject, Guid userId)
|
||||
{
|
||||
var resultSecret = data.ToSecret(organizationId);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultSecret.Projects = new List<Core.SecretsManager.Entities.Project>() { mockProject };
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true);
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<ICreateSecretCommand>().CreateAsync(default).ReturnsForAnyArgs(resultSecret);
|
||||
sutProvider.GetDependency<ICreateSecretCommand>().CreateAsync(default, userId).ReturnsForAnyArgs(resultSecret);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, data);
|
||||
await sutProvider.GetDependency<ICreateSecretCommand>().Received(1)
|
||||
.CreateAsync(Arg.Any<Secret>());
|
||||
.CreateAsync(Arg.Any<Secret>(), userId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void UpdateSecret_Success(SutProvider<SecretsController> sutProvider, SecretUpdateRequestModel data, Guid secretId)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void UpdateSecret_Success(PermissionType permissionType, SutProvider<SecretsController> sutProvider, SecretUpdateRequestModel data, Guid secretId, Guid organizationId, Guid userId, Project mockProject)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.ProjectIds = new Guid[] { mockProject.Id };
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true);
|
||||
}
|
||||
|
||||
var resultSecret = data.ToSecret(secretId);
|
||||
sutProvider.GetDependency<IUpdateSecretCommand>().UpdateAsync(default).ReturnsForAnyArgs(resultSecret);
|
||||
sutProvider.GetDependency<IUpdateSecretCommand>().UpdateAsync(default, userId).ReturnsForAnyArgs(resultSecret);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(secretId, data);
|
||||
var result = await sutProvider.Sut.UpdateSecretAsync(secretId, data);
|
||||
await sutProvider.GetDependency<IUpdateSecretCommand>().Received(1)
|
||||
.UpdateAsync(Arg.Any<Secret>());
|
||||
.UpdateAsync(Arg.Any<Secret>(), userId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void BulkDeleteSecret_Success(SutProvider<SecretsController> sutProvider, List<Secret> data)
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void BulkDeleteSecret_Success(PermissionType permissionType, SutProvider<SecretsController> sutProvider, List<Secret> data, Guid organizationId, Guid userId, Project mockProject)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.FirstOrDefault().Projects = new List<Project>() { mockProject };
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IProjectRepository>().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true);
|
||||
}
|
||||
|
||||
|
||||
var ids = data.Select(secret => secret.Id).ToList();
|
||||
var mockResult = new List<Tuple<Secret, string>>();
|
||||
|
||||
foreach (var secret in data)
|
||||
{
|
||||
mockResult.Add(new Tuple<Secret, string>(secret, ""));
|
||||
}
|
||||
sutProvider.GetDependency<IDeleteSecretCommand>().DeleteSecrets(ids).ReturnsForAnyArgs(mockResult);
|
||||
sutProvider.GetDependency<IDeleteSecretCommand>().DeleteSecrets(ids, userId).ReturnsForAnyArgs(mockResult);
|
||||
|
||||
var results = await sutProvider.Sut.BulkDeleteAsync(ids);
|
||||
await sutProvider.GetDependency<IDeleteSecretCommand>().Received(1)
|
||||
.DeleteSecrets(Arg.Is(ids));
|
||||
.DeleteSecrets(Arg.Is(ids), userId);
|
||||
Assert.Equal(data.Count, results.Data.Count());
|
||||
}
|
||||
|
||||
@ -123,6 +204,7 @@ public class SecretsControllerTests
|
||||
[BitAutoData]
|
||||
public async void BulkDeleteSecret_NoGuids_ThrowsArgumentNullException(SutProvider<SecretsController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(new Guid());
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => sutProvider.Sut.BulkDeleteAsync(new List<Guid>()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user