mirror of
https://github.com/bitwarden/server.git
synced 2025-04-13 09:08:17 -05:00
[PM-12485] Create OrganizationUpdateKeys command (#5600)
* Add OrganizationUpdateKeysCommand * Add unit tests for OrganizationUpdateKeysCommand to validate permission checks and key updates * Register OrganizationUpdateKeysCommand for dependency injection * Refactor OrganizationsController to use IOrganizationUpdateKeysCommand for updating organization keys * Remove outdated unit tests for UpdateOrganizationKeysAsync in OrganizationServiceTests * Remove UpdateOrganizationKeysAsync method from IOrganizationService and OrganizationService implementations * Add IOrganizationUpdateKeysCommand dependency mock to OrganizationsControllerTests
This commit is contained in:
parent
0a4f97b50e
commit
f1a4829e5e
@ -65,6 +65,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -88,7 +89,8 @@ public class OrganizationsController : Controller
|
||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
||||
IOrganizationDeleteCommand organizationDeleteCommand,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IPricingClient pricingClient)
|
||||
IPricingClient pricingClient,
|
||||
IOrganizationUpdateKeysCommand organizationUpdateKeysCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -112,6 +114,7 @@ public class OrganizationsController : Controller
|
||||
_organizationDeleteCommand = organizationDeleteCommand;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
_pricingClient = pricingClient;
|
||||
_organizationUpdateKeysCommand = organizationUpdateKeysCommand;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -490,7 +493,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("{id}/keys")]
|
||||
public async Task<OrganizationKeysResponseModel> PostKeys(string id, [FromBody] OrganizationKeysRequestModel model)
|
||||
public async Task<OrganizationKeysResponseModel> PostKeys(Guid id, [FromBody] OrganizationKeysRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
@ -498,7 +501,7 @@ public class OrganizationsController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey,
|
||||
var org = await _organizationUpdateKeysCommand.UpdateOrganizationKeysAsync(id, model.PublicKey,
|
||||
model.EncryptedPrivateKey);
|
||||
return new OrganizationKeysResponseModel(org);
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
public interface IOrganizationUpdateKeysCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Update the keys for an organization.
|
||||
/// </summary>
|
||||
/// <param name="orgId">The ID of the organization to update.</param>
|
||||
/// <param name="publicKey">The public key for the organization.</param>
|
||||
/// <param name="privateKey">The private key for the organization.</param>
|
||||
/// <returns>The updated organization.</returns>
|
||||
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
public class OrganizationUpdateKeysCommand : IOrganizationUpdateKeysCommand
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
|
||||
public const string OrganizationKeysAlreadyExistErrorMessage = "Organization Keys already exist.";
|
||||
|
||||
public OrganizationUpdateKeysCommand(
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationService organizationService)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationService = organizationService;
|
||||
}
|
||||
|
||||
public async Task<Organization> UpdateOrganizationKeysAsync(Guid organizationId, string publicKey, string privateKey)
|
||||
{
|
||||
if (!await _currentContext.ManageResetPassword(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
// If the keys already exist, error out
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
if (organization.PublicKey != null && organization.PrivateKey != null)
|
||||
{
|
||||
throw new BadRequestException(OrganizationKeysAlreadyExistErrorMessage);
|
||||
}
|
||||
|
||||
// Update org with generated public/private key
|
||||
organization.PublicKey = publicKey;
|
||||
organization.PrivateKey = privateKey;
|
||||
|
||||
await _organizationService.UpdateAsync(organization);
|
||||
|
||||
return organization;
|
||||
}
|
||||
}
|
@ -43,7 +43,6 @@ public interface IOrganizationService
|
||||
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
|
||||
bool overwriteExisting, EventSystemUser eventSystemUser);
|
||||
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
|
||||
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
|
||||
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
|
||||
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
|
||||
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
||||
|
@ -1418,28 +1418,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey)
|
||||
{
|
||||
if (!await _currentContext.ManageResetPassword(orgId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
// If the keys already exist, error out
|
||||
var org = await _organizationRepository.GetByIdAsync(orgId);
|
||||
if (org.PublicKey != null && org.PrivateKey != null)
|
||||
{
|
||||
throw new BadRequestException("Organization Keys already exist");
|
||||
}
|
||||
|
||||
// Update org with generated public/private key
|
||||
org.PublicKey = publicKey;
|
||||
org.PrivateKey = privateKey;
|
||||
await UpdateAsync(org);
|
||||
|
||||
return org;
|
||||
}
|
||||
|
||||
private async Task UpdateUsersAsync(Group group, HashSet<string> groupUsers,
|
||||
Dictionary<string, Guid> existingUsersIdDict, HashSet<Guid> existingUsers = null)
|
||||
{
|
||||
|
@ -60,6 +60,7 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddOrganizationDomainCommandsQueries();
|
||||
services.AddOrganizationSignUpCommands();
|
||||
services.AddOrganizationDeleteCommands();
|
||||
services.AddOrganizationUpdateCommands();
|
||||
services.AddOrganizationEnableCommands();
|
||||
services.AddOrganizationDisableCommands();
|
||||
services.AddOrganizationAuthCommands();
|
||||
@ -77,6 +78,11 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddScoped<IOrganizationInitiateDeleteCommand, OrganizationInitiateDeleteCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationUpdateCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IOrganizationUpdateKeysCommand, OrganizationUpdateKeysCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationEnableCommands(this IServiceCollection services) =>
|
||||
services.AddScoped<IOrganizationEnableCommand, OrganizationEnableCommand>();
|
||||
|
||||
|
@ -60,6 +60,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
public OrganizationsControllerTests()
|
||||
@ -86,6 +87,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_organizationDeleteCommand = Substitute.For<IOrganizationDeleteCommand>();
|
||||
_policyRequirementQuery = Substitute.For<IPolicyRequirementQuery>();
|
||||
_pricingClient = Substitute.For<IPricingClient>();
|
||||
_organizationUpdateKeysCommand = Substitute.For<IOrganizationUpdateKeysCommand>();
|
||||
|
||||
_sut = new OrganizationsController(
|
||||
_organizationRepository,
|
||||
@ -109,7 +111,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_cloudOrganizationSignUpCommand,
|
||||
_organizationDeleteCommand,
|
||||
_policyRequirementQuery,
|
||||
_pricingClient);
|
||||
_pricingClient,
|
||||
_organizationUpdateKeysCommand);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -0,0 +1,75 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationUpdateKeysCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPasswordPermission_ThrowsUnauthorizedException(
|
||||
Guid orgId, string publicKey, string privateKey, SutProvider<OrganizationUpdateKeysCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManageResetPassword(orgId)
|
||||
.Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WhenKeysAlreadyExist_ThrowsBadRequestException(
|
||||
Organization organization, string publicKey, string privateKey,
|
||||
SutProvider<OrganizationUpdateKeysCommand> sutProvider)
|
||||
{
|
||||
organization.PublicKey = "existingPublicKey";
|
||||
organization.PrivateKey = "existingPrivateKey";
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManageResetPassword(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(organization.Id, publicKey, privateKey));
|
||||
|
||||
Assert.Equal(OrganizationUpdateKeysCommand.OrganizationKeysAlreadyExistErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WhenKeysDoNotExist_UpdatesOrganization(
|
||||
Organization organization, string publicKey, string privateKey,
|
||||
SutProvider<OrganizationUpdateKeysCommand> sutProvider)
|
||||
{
|
||||
organization.PublicKey = null;
|
||||
organization.PrivateKey = null;
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManageResetPassword(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateOrganizationKeysAsync(organization.Id, publicKey, privateKey);
|
||||
|
||||
Assert.Equal(publicKey, result.PublicKey);
|
||||
Assert.Equal(privateKey, result.PrivateKey);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>()
|
||||
.Received(1)
|
||||
.UpdateAsync(organization);
|
||||
}
|
||||
}
|
@ -814,48 +814,6 @@ public class OrganizationServiceTests
|
||||
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPassword_Throws(Guid orgId, string publicKey,
|
||||
string privateKey, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var currentContext = Substitute.For<ICurrentContext>();
|
||||
currentContext.ManageResetPassword(orgId).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Throws(Organization org, string publicKey,
|
||||
string privateKey, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||
currentContext.ManageResetPassword(org.Id).Returns(true);
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey));
|
||||
Assert.Contains("Organization Keys already exist", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Success(Organization org, string publicKey,
|
||||
string privateKey, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
org.PublicKey = null;
|
||||
org.PrivateKey = null;
|
||||
|
||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||
currentContext.ManageResetPassword(org.Id).Returns(true);
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
await sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[PaidOrganizationCustomize(CheckedPlanType = PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData("Cannot set max seat autoscaling below seat count", 1, 0, 2, 2)]
|
||||
|
Loading…
x
Reference in New Issue
Block a user