Skip to content

Commit f1a4829

Browse files
authored
[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
1 parent 0a4f97b commit f1a4829

File tree

9 files changed

+151
-69
lines changed

9 files changed

+151
-69
lines changed

src/Api/AdminConsole/Controllers/OrganizationsController.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public class OrganizationsController : Controller
6565
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
6666
private readonly IPolicyRequirementQuery _policyRequirementQuery;
6767
private readonly IPricingClient _pricingClient;
68+
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
6869

6970
public OrganizationsController(
7071
IOrganizationRepository organizationRepository,
@@ -88,7 +89,8 @@ public OrganizationsController(
8889
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
8990
IOrganizationDeleteCommand organizationDeleteCommand,
9091
IPolicyRequirementQuery policyRequirementQuery,
91-
IPricingClient pricingClient)
92+
IPricingClient pricingClient,
93+
IOrganizationUpdateKeysCommand organizationUpdateKeysCommand)
9294
{
9395
_organizationRepository = organizationRepository;
9496
_organizationUserRepository = organizationUserRepository;
@@ -112,6 +114,7 @@ public OrganizationsController(
112114
_organizationDeleteCommand = organizationDeleteCommand;
113115
_policyRequirementQuery = policyRequirementQuery;
114116
_pricingClient = pricingClient;
117+
_organizationUpdateKeysCommand = organizationUpdateKeysCommand;
115118
}
116119

117120
[HttpGet("{id}")]
@@ -490,15 +493,15 @@ public async Task<OrganizationPublicKeyResponseModel> GetKeys(string id)
490493
}
491494

492495
[HttpPost("{id}/keys")]
493-
public async Task<OrganizationKeysResponseModel> PostKeys(string id, [FromBody] OrganizationKeysRequestModel model)
496+
public async Task<OrganizationKeysResponseModel> PostKeys(Guid id, [FromBody] OrganizationKeysRequestModel model)
494497
{
495498
var user = await _userService.GetUserByPrincipalAsync(User);
496499
if (user == null)
497500
{
498501
throw new UnauthorizedAccessException();
499502
}
500503

501-
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey,
504+
var org = await _organizationUpdateKeysCommand.UpdateOrganizationKeysAsync(id, model.PublicKey,
502505
model.EncryptedPrivateKey);
503506
return new OrganizationKeysResponseModel(org);
504507
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
3+
public interface IOrganizationUpdateKeysCommand
4+
{
5+
/// <summary>
6+
/// Update the keys for an organization.
7+
/// </summary>
8+
/// <param name="orgId">The ID of the organization to update.</param>
9+
/// <param name="publicKey">The public key for the organization.</param>
10+
/// <param name="privateKey">The private key for the organization.</param>
11+
/// <returns>The updated organization.</returns>
12+
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
using Bit.Core.Context;
3+
using Bit.Core.Exceptions;
4+
using Bit.Core.Repositories;
5+
using Bit.Core.Services;
6+
7+
public class OrganizationUpdateKeysCommand : IOrganizationUpdateKeysCommand
8+
{
9+
private readonly ICurrentContext _currentContext;
10+
private readonly IOrganizationRepository _organizationRepository;
11+
private readonly IOrganizationService _organizationService;
12+
13+
public const string OrganizationKeysAlreadyExistErrorMessage = "Organization Keys already exist.";
14+
15+
public OrganizationUpdateKeysCommand(
16+
ICurrentContext currentContext,
17+
IOrganizationRepository organizationRepository,
18+
IOrganizationService organizationService)
19+
{
20+
_currentContext = currentContext;
21+
_organizationRepository = organizationRepository;
22+
_organizationService = organizationService;
23+
}
24+
25+
public async Task<Organization> UpdateOrganizationKeysAsync(Guid organizationId, string publicKey, string privateKey)
26+
{
27+
if (!await _currentContext.ManageResetPassword(organizationId))
28+
{
29+
throw new UnauthorizedAccessException();
30+
}
31+
32+
// If the keys already exist, error out
33+
var organization = await _organizationRepository.GetByIdAsync(organizationId);
34+
if (organization.PublicKey != null && organization.PrivateKey != null)
35+
{
36+
throw new BadRequestException(OrganizationKeysAlreadyExistErrorMessage);
37+
}
38+
39+
// Update org with generated public/private key
40+
organization.PublicKey = publicKey;
41+
organization.PrivateKey = privateKey;
42+
43+
await _organizationService.UpdateAsync(organization);
44+
45+
return organization;
46+
}
47+
}

src/Core/AdminConsole/Services/IOrganizationService.cs

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ Task ImportAsync(Guid organizationId, IEnumerable<ImportedGroup> groups,
4343
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
4444
bool overwriteExisting, EventSystemUser eventSystemUser);
4545
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
46-
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
4746
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
4847
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
4948
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,

src/Core/AdminConsole/Services/Implementations/OrganizationService.cs

-22
Original file line numberDiff line numberDiff line change
@@ -1418,28 +1418,6 @@ public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId)
14181418
}
14191419
}
14201420

1421-
public async Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey)
1422-
{
1423-
if (!await _currentContext.ManageResetPassword(orgId))
1424-
{
1425-
throw new UnauthorizedAccessException();
1426-
}
1427-
1428-
// If the keys already exist, error out
1429-
var org = await _organizationRepository.GetByIdAsync(orgId);
1430-
if (org.PublicKey != null && org.PrivateKey != null)
1431-
{
1432-
throw new BadRequestException("Organization Keys already exist");
1433-
}
1434-
1435-
// Update org with generated public/private key
1436-
org.PublicKey = publicKey;
1437-
org.PrivateKey = privateKey;
1438-
await UpdateAsync(org);
1439-
1440-
return org;
1441-
}
1442-
14431421
private async Task UpdateUsersAsync(Group group, HashSet<string> groupUsers,
14441422
Dictionary<string, Guid> existingUsersIdDict, HashSet<Guid> existingUsers = null)
14451423
{

src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static void AddOrganizationServices(this IServiceCollection services, IGl
6060
services.AddOrganizationDomainCommandsQueries();
6161
services.AddOrganizationSignUpCommands();
6262
services.AddOrganizationDeleteCommands();
63+
services.AddOrganizationUpdateCommands();
6364
services.AddOrganizationEnableCommands();
6465
services.AddOrganizationDisableCommands();
6566
services.AddOrganizationAuthCommands();
@@ -77,6 +78,11 @@ private static void AddOrganizationDeleteCommands(this IServiceCollection servic
7778
services.AddScoped<IOrganizationInitiateDeleteCommand, OrganizationInitiateDeleteCommand>();
7879
}
7980

81+
private static void AddOrganizationUpdateCommands(this IServiceCollection services)
82+
{
83+
services.AddScoped<IOrganizationUpdateKeysCommand, OrganizationUpdateKeysCommand>();
84+
}
85+
8086
private static void AddOrganizationEnableCommands(this IServiceCollection services) =>
8187
services.AddScoped<IOrganizationEnableCommand, OrganizationEnableCommand>();
8288

test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class OrganizationsControllerTests : IDisposable
6060
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
6161
private readonly IPolicyRequirementQuery _policyRequirementQuery;
6262
private readonly IPricingClient _pricingClient;
63+
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
6364
private readonly OrganizationsController _sut;
6465

6566
public OrganizationsControllerTests()
@@ -86,6 +87,7 @@ public OrganizationsControllerTests()
8687
_organizationDeleteCommand = Substitute.For<IOrganizationDeleteCommand>();
8788
_policyRequirementQuery = Substitute.For<IPolicyRequirementQuery>();
8889
_pricingClient = Substitute.For<IPricingClient>();
90+
_organizationUpdateKeysCommand = Substitute.For<IOrganizationUpdateKeysCommand>();
8991

9092
_sut = new OrganizationsController(
9193
_organizationRepository,
@@ -109,7 +111,8 @@ public OrganizationsControllerTests()
109111
_cloudOrganizationSignUpCommand,
110112
_organizationDeleteCommand,
111113
_policyRequirementQuery,
112-
_pricingClient);
114+
_pricingClient,
115+
_organizationUpdateKeysCommand);
113116
}
114117

115118
public void Dispose()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
using Bit.Core.Context;
3+
using Bit.Core.Exceptions;
4+
using Bit.Core.Repositories;
5+
using Bit.Core.Services;
6+
using Bit.Test.Common.AutoFixture;
7+
using Bit.Test.Common.AutoFixture.Attributes;
8+
using NSubstitute;
9+
using Xunit;
10+
11+
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
12+
13+
[SutProviderCustomize]
14+
public class OrganizationUpdateKeysCommandTests
15+
{
16+
[Theory, BitAutoData]
17+
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPasswordPermission_ThrowsUnauthorizedException(
18+
Guid orgId, string publicKey, string privateKey, SutProvider<OrganizationUpdateKeysCommand> sutProvider)
19+
{
20+
sutProvider.GetDependency<ICurrentContext>()
21+
.ManageResetPassword(orgId)
22+
.Returns(false);
23+
24+
await Assert.ThrowsAsync<UnauthorizedAccessException>(
25+
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
26+
}
27+
28+
[Theory, BitAutoData]
29+
public async Task UpdateOrganizationKeysAsync_WhenKeysAlreadyExist_ThrowsBadRequestException(
30+
Organization organization, string publicKey, string privateKey,
31+
SutProvider<OrganizationUpdateKeysCommand> sutProvider)
32+
{
33+
organization.PublicKey = "existingPublicKey";
34+
organization.PrivateKey = "existingPrivateKey";
35+
36+
sutProvider.GetDependency<ICurrentContext>()
37+
.ManageResetPassword(organization.Id)
38+
.Returns(true);
39+
40+
sutProvider.GetDependency<IOrganizationRepository>()
41+
.GetByIdAsync(organization.Id)
42+
.Returns(organization);
43+
44+
var exception = await Assert.ThrowsAsync<BadRequestException>(
45+
() => sutProvider.Sut.UpdateOrganizationKeysAsync(organization.Id, publicKey, privateKey));
46+
47+
Assert.Equal(OrganizationUpdateKeysCommand.OrganizationKeysAlreadyExistErrorMessage, exception.Message);
48+
}
49+
50+
[Theory, BitAutoData]
51+
public async Task UpdateOrganizationKeysAsync_WhenKeysDoNotExist_UpdatesOrganization(
52+
Organization organization, string publicKey, string privateKey,
53+
SutProvider<OrganizationUpdateKeysCommand> sutProvider)
54+
{
55+
organization.PublicKey = null;
56+
organization.PrivateKey = null;
57+
58+
sutProvider.GetDependency<ICurrentContext>()
59+
.ManageResetPassword(organization.Id)
60+
.Returns(true);
61+
62+
sutProvider.GetDependency<IOrganizationRepository>()
63+
.GetByIdAsync(organization.Id)
64+
.Returns(organization);
65+
66+
var result = await sutProvider.Sut.UpdateOrganizationKeysAsync(organization.Id, publicKey, privateKey);
67+
68+
Assert.Equal(publicKey, result.PublicKey);
69+
Assert.Equal(privateKey, result.PrivateKey);
70+
71+
await sutProvider.GetDependency<IOrganizationService>()
72+
.Received(1)
73+
.UpdateAsync(organization);
74+
}
75+
}

test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs

-42
Original file line numberDiff line numberDiff line change
@@ -814,48 +814,6 @@ private void InviteUserHelper_ArrangeValidPermissions(Organization organization,
814814
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
815815
}
816816

817-
[Theory, BitAutoData]
818-
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPassword_Throws(Guid orgId, string publicKey,
819-
string privateKey, SutProvider<OrganizationService> sutProvider)
820-
{
821-
var currentContext = Substitute.For<ICurrentContext>();
822-
currentContext.ManageResetPassword(orgId).Returns(false);
823-
824-
await Assert.ThrowsAsync<UnauthorizedAccessException>(
825-
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
826-
}
827-
828-
[Theory, BitAutoData]
829-
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Throws(Organization org, string publicKey,
830-
string privateKey, SutProvider<OrganizationService> sutProvider)
831-
{
832-
var currentContext = sutProvider.GetDependency<ICurrentContext>();
833-
currentContext.ManageResetPassword(org.Id).Returns(true);
834-
835-
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
836-
organizationRepository.GetByIdAsync(org.Id).Returns(org);
837-
838-
var exception = await Assert.ThrowsAsync<BadRequestException>(
839-
() => sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey));
840-
Assert.Contains("Organization Keys already exist", exception.Message);
841-
}
842-
843-
[Theory, BitAutoData]
844-
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Success(Organization org, string publicKey,
845-
string privateKey, SutProvider<OrganizationService> sutProvider)
846-
{
847-
org.PublicKey = null;
848-
org.PrivateKey = null;
849-
850-
var currentContext = sutProvider.GetDependency<ICurrentContext>();
851-
currentContext.ManageResetPassword(org.Id).Returns(true);
852-
853-
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
854-
organizationRepository.GetByIdAsync(org.Id).Returns(org);
855-
856-
await sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey);
857-
}
858-
859817
[Theory]
860818
[PaidOrganizationCustomize(CheckedPlanType = PlanType.EnterpriseAnnually)]
861819
[BitAutoData("Cannot set max seat autoscaling below seat count", 1, 0, 2, 2)]

0 commit comments

Comments
 (0)