From 5b572e2009bc73310201d51202ee04f5978aac18 Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Mon, 12 Jan 2026 14:14:35 +0000 Subject: [PATCH] Refactor AcceptInit method to support feature flag for organization initialization Updated the AcceptInit method in OrganizationUsersController to return an IResult type and handle organization initialization based on a feature flag. If the feature is enabled, it utilizes the new InitPendingOrganizationVNextAsync method for atomic organization setup and user confirmation. Integration tests were added to verify the behavior under both feature flag states, ensuring proper initialization and error handling. --- .../OrganizationUsersController.cs | 14 +- ...anizationUsersControllerAcceptInitTests.cs | 121 +++++++++++++++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 5cdd857f3f..e8915d3b13 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -309,7 +309,7 @@ public class OrganizationUsersController : BaseAdminConsoleController } [HttpPost("{organizationUserId}/accept-init")] - public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model) + public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -317,9 +317,21 @@ public class OrganizationUsersController : BaseAdminConsoleController throw new UnauthorizedAccessException(); } + if (_featureService.IsEnabled(FeatureFlagKeys.RefactorOrgAcceptInit)) + { + var result = await _initPendingOrganizationCommand.InitPendingOrganizationVNextAsync( + user, orgId, organizationUserId, + model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, + model.CollectionName, model.Token, model.Key); + + return Handle(result); + } + await _initPendingOrganizationCommand.InitPendingOrganizationAsync(user, orgId, organizationUserId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName, model.Token); await _acceptOrgUserCommand.AcceptOrgUserByEmailTokenAsync(organizationUserId, user, model.Token, _userService); await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id); + + return TypedResults.Ok(); } [HttpPost("{organizationUserId}/accept")] diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs index 4b079370c8..d106fd03b0 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs @@ -2,13 +2,16 @@ using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Helpers; +using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Core.Tokens; +using NSubstitute; using Xunit; namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; @@ -27,6 +30,7 @@ public class OrganizationUsersControllerAcceptInitTests : IClassFixture(_ => { }); _client = _factory.CreateClient(); _loginHelper = new LoginHelper(_factory, _client); + _featureService = _factory.GetService(); } public async Task InitializeAsync() @@ -92,6 +98,8 @@ public class OrganizationUsersControllerAcceptInitTests : IClassFixture(); _pendingOrganization.Enabled = true; @@ -275,7 +289,10 @@ public class OrganizationUsersControllerAcceptInitTests : IClassFixture(); + var updatedOrganization = await organizationRepository.GetByIdAsync(_pendingOrganization.Id); + + Assert.NotNull(updatedOrganization); + Assert.True(updatedOrganization.Enabled); + Assert.Equal(OrganizationStatusType.Created, updatedOrganization.Status); + Assert.Equal(_mockPublicKey, updatedOrganization.PublicKey); + Assert.Equal(_mockEncryptedPrivateKey, updatedOrganization.PrivateKey); + + // Verify user was confirmed (not just accepted) + var organizationUserRepository = _factory.GetService(); + var confirmedOrgUser = await organizationUserRepository.GetByIdAsync(_invitedOrgUser.Id); + + Assert.NotNull(confirmedOrgUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, confirmedOrgUser.Status); + Assert.Equal("test-user-key", confirmedOrgUser.Key); + Assert.Equal(_invitedUser.Id, confirmedOrgUser.UserId); + Assert.Null(confirmedOrgUser.Email); + + // Verify default collection was created + var collectionRepository = _factory.GetService(); + var collections = await collectionRepository.GetManyByOrganizationIdAsync(_pendingOrganization.Id); + + Assert.Single(collections); + Assert.Equal(_mockEncryptedString, collections.First().Name); + } + + [Fact] + public async Task AcceptInit_WithFeatureFlagEnabled_InvalidToken_ReturnsBadRequest() + { + // Arrange + _featureService.IsEnabled(FeatureFlagKeys.RefactorOrgAcceptInit).Returns(true); + + await _loginHelper.LoginAsync(_invitedUserEmail); + + var acceptInitRequest = new OrganizationUserAcceptInitRequestModel + { + Token = "invalid-token", + Key = "test-user-key", + Keys = new OrganizationKeysRequestModel + { + PublicKey = _mockPublicKey, + EncryptedPrivateKey = _mockEncryptedPrivateKey + }, + CollectionName = _mockEncryptedString + }; + + // Act + var response = await _client.PostAsJsonAsync( + $"organizations/{_pendingOrganization.Id}/users/{_invitedOrgUser.Id}/accept-init", + acceptInitRequest); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + + // Verify NO state changes occurred (atomic behavior) + var organizationRepository = _factory.GetService(); + var organization = await organizationRepository.GetByIdAsync(_pendingOrganization.Id); + + Assert.False(organization.Enabled); + Assert.Equal(OrganizationStatusType.Pending, organization.Status); + Assert.Null(organization.PublicKey); + Assert.Null(organization.PrivateKey); + + var organizationUserRepository = _factory.GetService(); + var orgUser = await organizationUserRepository.GetByIdAsync(_invitedOrgUser.Id); + + Assert.Equal(OrganizationUserStatusType.Invited, orgUser.Status); + Assert.Null(orgUser.UserId); + } }