1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[SM-771] Add new endpoint for bulk enabling users for Secrets Manager (#3020)

* Add new endpoint for bulk enabling users for sm

* Review updates
This commit is contained in:
Thomas Avery 2023-06-29 11:42:44 -05:00 committed by GitHub
parent 481004394f
commit 74ab7e8672
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 0 deletions

View File

@ -9,6 +9,7 @@ using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -19,6 +20,7 @@ namespace Bit.Api.Controllers;
[Authorize("Application")]
public class OrganizationUsersController : Controller
{
private readonly IEnableAccessSecretsManagerCommand _enableAccessSecretsManagerCommand;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
@ -29,6 +31,7 @@ public class OrganizationUsersController : Controller
private readonly ICurrentContext _currentContext;
public OrganizationUsersController(
IEnableAccessSecretsManagerCommand enableAccessSecretsManagerCommand,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
@ -38,6 +41,7 @@ public class OrganizationUsersController : Controller
IPolicyRepository policyRepository,
ICurrentContext currentContext)
{
_enableAccessSecretsManagerCommand = enableAccessSecretsManagerCommand;
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
@ -420,6 +424,29 @@ public class OrganizationUsersController : Controller
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
}
[HttpPatch("enable-secrets-manager")]
[HttpPut("enable-secrets-manager")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkEnableSecretsManagerAsync(Guid orgId,
[FromBody] OrganizationUserBulkRequestModel model)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var orgUsers = (await _organizationUserRepository.GetManyAsync(model.Ids))
.Where(ou => ou.OrganizationId == orgId).ToList();
if (orgUsers.Count == 0)
{
throw new BadRequestException("Users invalid.");
}
var results = await _enableAccessSecretsManagerCommand.EnableUsersAsync(orgUsers);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.organizationUser.Id, r.error)));
}
private async Task RestoreOrRevokeUserAsync(
Guid orgId,
Guid id,

View File

@ -15,6 +15,8 @@ using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterpri
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Tokens;
@ -29,6 +31,7 @@ public static class OrganizationServiceCollectionExtensions
public static void AddOrganizationServices(this IServiceCollection services, IGlobalSettings globalSettings)
{
services.AddScoped<IOrganizationService, OrganizationService>();
services.AddScoped<IEnableAccessSecretsManagerCommand, EnableAccessSecretsManagerCommand>();
services.AddTokenizers();
services.AddOrganizationGroupCommands();
services.AddOrganizationConnectionCommands();

View File

@ -0,0 +1,43 @@
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
namespace Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
public class EnableAccessSecretsManagerCommand : IEnableAccessSecretsManagerCommand
{
private readonly IOrganizationUserRepository _organizationUserRepository;
public EnableAccessSecretsManagerCommand(IOrganizationUserRepository organizationUserRepository)
{
_organizationUserRepository = organizationUserRepository;
}
public async Task<List<(OrganizationUser organizationUser, string error)>> EnableUsersAsync(
IEnumerable<OrganizationUser> organizationUsers)
{
var results = new List<(OrganizationUser organizationUser, string error)>();
var usersToEnable = new List<OrganizationUser>();
foreach (var orgUser in organizationUsers)
{
if (orgUser.AccessSecretsManager)
{
results.Add((orgUser, "User already has access to Secrets Manager"));
}
else
{
orgUser.AccessSecretsManager = true;
usersToEnable.Add(orgUser);
results.Add((orgUser, ""));
}
}
if (usersToEnable.Any())
{
await _organizationUserRepository.ReplaceManyAsync(usersToEnable);
}
return results;
}
}

View File

@ -0,0 +1,9 @@
using Bit.Core.Entities;
namespace Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
public interface IEnableAccessSecretsManagerCommand
{
Task<List<(OrganizationUser organizationUser, string error)>> EnableUsersAsync(
IEnumerable<OrganizationUser> organizationUsers);
}

View File

@ -0,0 +1,81 @@
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.SecretsManager.Commands.EnableAccessSecretsManager;
[SutProviderCustomize]
public class EnableAccessSecretsManagerCommandTests
{
[Theory]
[BitAutoData]
public async Task EnableUsers_UsersAlreadyEnabled_DoesNotCallRepository(
SutProvider<EnableAccessSecretsManagerCommand> sutProvider, ICollection<OrganizationUser> data)
{
foreach (var item in data)
{
item.AccessSecretsManager = true;
}
var result = await sutProvider.Sut.EnableUsersAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.ReplaceManyAsync(default);
Assert.Equal(data.Count, result.Count);
Assert.Equal(data.Count,
result.Where(x => x.error == "User already has access to Secrets Manager").ToList().Count);
}
[Theory]
[BitAutoData]
public async Task EnableUsers_OneUserNotEnabled_CallsRepositoryForOne(
SutProvider<EnableAccessSecretsManagerCommand> sutProvider, ICollection<OrganizationUser> data)
{
var firstUser = new List<OrganizationUser>();
foreach (var item in data)
{
if (item == data.First())
{
item.AccessSecretsManager = false;
firstUser.Add(item);
}
else
{
item.AccessSecretsManager = true;
}
}
var result = await sutProvider.Sut.EnableUsersAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.ReplaceManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(firstUser)));
Assert.Equal(data.Count, result.Count);
Assert.Equal(data.Count - 1,
result.Where(x => x.error == "User already has access to Secrets Manager").ToList().Count);
}
[Theory]
[BitAutoData]
public async Task EnableUsers_Success(
SutProvider<EnableAccessSecretsManagerCommand> sutProvider, ICollection<OrganizationUser> data)
{
foreach (var item in data)
{
item.AccessSecretsManager = false;
}
var result = await sutProvider.Sut.EnableUsersAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.ReplaceManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
Assert.Equal(data.Count, result.Count);
}
}