From a080802ebcce87035f0e790535677d6ca2eecbf3 Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Thu, 12 Feb 2026 15:38:46 +0000 Subject: [PATCH] Fix SingleOrg policy not revoking non-compliant users; add integration test --- .../OrganizationUserRepository.cs | 2 +- .../Controllers/PoliciesControllerTests.cs | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs index c15bd72c5bb5..b086b8c487d1 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -407,7 +407,7 @@ where Ids.Contains(ou.Id) { var dbContext = GetDatabaseContext(scope); var query = from ou in dbContext.OrganizationUsers - where userIds.Contains(ou.Id) + where ou.UserId.HasValue && userIds.Contains(ou.UserId.Value) select ou; return Mapper.Map>(await query.ToListAsync()); } diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs index d58538ae1cb2..2f749d0c85b0 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs @@ -441,4 +441,47 @@ public async Task Put_MasterPasswordPolicy_ExcessiveMinComplexity_ReturnsBadRequ // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } + + [Fact] + public async Task Put_SingleOrgPolicy_RevokesNonCompliantUser() + { + // Arrange + // Create a second organization (Org B) with its own owner + var orgBOwnerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(orgBOwnerEmail); + var (orgB, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually, + ownerEmail: orgBOwnerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card); + + // Create a user that belongs to both Org A and Org B + var multiOrgUserEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(multiOrgUserEmail); + + var orgUserInOrgA = await OrganizationTestHelpers.CreateUserAsync(_factory, _organization.Id, + multiOrgUserEmail, OrganizationUserType.User); + await OrganizationTestHelpers.CreateUserAsync(_factory, orgB.Id, + multiOrgUserEmail, OrganizationUserType.User); + + // Re-authenticate as the owner of Org A + await _loginHelper.LoginAsync(_ownerEmail); + + var request = new PolicyRequestModel + { + Enabled = true, + Data = null + }; + + // Act - Enable Single Org policy on Org A + var response = await _client.PutAsync( + $"/organizations/{_organization.Id}/policies/{PolicyType.SingleOrg}", + JsonContent.Create(request)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Verify the multi-org user was revoked in Org A + var organizationUserRepository = _factory.GetService(); + var updatedOrgUser = await organizationUserRepository.GetByIdAsync(orgUserInOrgA.Id); + Assert.NotNull(updatedOrgUser); + Assert.Equal(OrganizationUserStatusType.Revoked, updatedOrgUser.Status); + } }