mirror of
https://github.com/bitwarden/server.git
synced 2026-02-10 19:03:18 +08:00
Enhance InitPendingOrganizationCommand with policy validation and feature flag support
Updated the ValidatePoliciesAsync method to enforce the Automatic User Confirmation Policy when the feature flag is enabled. Added new unit tests to cover scenarios for automatic user confirmation and single organization policy violations, ensuring comprehensive validation during organization initialization. This improves error handling and maintains compliance with organizational policies.
This commit is contained in:
@@ -268,8 +268,19 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
|
||||
|
||||
private async Task<Error> ValidatePoliciesAsync(User user, Guid organizationId, Organization org, OrganizationUser orgUser)
|
||||
{
|
||||
var autoConfirmReq = await _policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id);
|
||||
if (autoConfirmReq.CannotCreateNewOrganization() || autoConfirmReq.IsEnabledForOrganizationsOtherThan(organizationId))
|
||||
// Enforce Automatic User Confirmation Policy (when feature flag is enabled)
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var autoConfirmReq = await _policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id);
|
||||
if (autoConfirmReq.CannotCreateNewOrganization())
|
||||
{
|
||||
return new SingleOrgPolicyViolationError();
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
return new SingleOrgPolicyViolationError();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Enums;
|
||||
@@ -198,9 +200,11 @@ public class InitPendingOrganizationCommandTests
|
||||
var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List<PolicyDetails>());
|
||||
var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List<PolicyDetails>());
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id).Returns(autoConfirmReq);
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
|
||||
.Returns(autoConfirmReq);
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id).Returns(twoFactorReq);
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||
.Returns(twoFactorReq);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -215,19 +219,46 @@ public class InitPendingOrganizationCommandTests
|
||||
Assert.Equal(user.Id, orgUser.UserId);
|
||||
Assert.Equal(userKey, orgUser.Key);
|
||||
Assert.Null(orgUser.Email);
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received().UpdateAsync(org);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received().ReplaceAsync(orgUser);
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>()
|
||||
.Received(1)
|
||||
.UpdateAsync(org);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(orgUser);
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithNullOrgUser_ReturnsOrganizationUserNotFoundError(
|
||||
User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey,
|
||||
SutProvider<InitPendingOrganizationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns((OrganizationUser)null);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
user, orgId, orgUserId, publicKey, privateKey, "", "token", userKey);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsError);
|
||||
Assert.IsType<OrganizationUserNotFoundError>(result.AsError);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithInvalidToken_ReturnsInvalidTokenError(
|
||||
User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey,
|
||||
SutProvider<InitPendingOrganizationCommand> sutProvider, OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(orgUserId).Returns(orgUser);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -249,7 +280,9 @@ public class InitPendingOrganizationCommandTests
|
||||
var token = CreateToken(orgUser, orgUserId, sutProvider);
|
||||
org.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -272,7 +305,9 @@ public class InitPendingOrganizationCommandTests
|
||||
org.Enabled = false;
|
||||
org.Status = OrganizationStatusType.Created;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -296,7 +331,9 @@ public class InitPendingOrganizationCommandTests
|
||||
org.Status = OrganizationStatusType.Pending;
|
||||
org.PublicKey = "existing-key";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -307,6 +344,29 @@ public class InitPendingOrganizationCommandTests
|
||||
Assert.IsType<OrganizationHasKeysError>(result.AsError);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithNullOrganization_ReturnsOrganizationNotFoundError(
|
||||
User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey,
|
||||
SutProvider<InitPendingOrganizationCommand> sutProvider, OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.OrganizationId = orgId;
|
||||
var token = CreateToken(orgUser, orgUserId, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns((Organization)null);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
user, orgId, orgUserId, publicKey, privateKey, "", token, userKey);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsError);
|
||||
Assert.IsType<OrganizationNotFoundError>(result.AsError);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithEmailMismatch_ReturnsEmailMismatchError(
|
||||
User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey,
|
||||
@@ -317,7 +377,9 @@ public class InitPendingOrganizationCommandTests
|
||||
orgUser.OrganizationId = orgId;
|
||||
var token = CreateToken(orgUser, orgUserId, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -344,15 +406,21 @@ public class InitPendingOrganizationCommandTests
|
||||
org.PrivateKey = null;
|
||||
org.PublicKey = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>().TwoFactorIsEnabledAsync(user).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(user)
|
||||
.Returns(true);
|
||||
|
||||
var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List<PolicyDetails>());
|
||||
var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List<PolicyDetails>());
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id).Returns(autoConfirmReq);
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
|
||||
.Returns(autoConfirmReq);
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id).Returns(twoFactorReq);
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||
.Returns(twoFactorReq);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -380,17 +448,23 @@ public class InitPendingOrganizationCommandTests
|
||||
org.PrivateKey = null;
|
||||
org.PublicKey = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>().TwoFactorIsEnabledAsync(user).Returns(false);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(user)
|
||||
.Returns(false);
|
||||
|
||||
var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List<PolicyDetails>());
|
||||
var twoFactorReq = new RequireTwoFactorPolicyRequirement(
|
||||
new List<PolicyDetails> { new PolicyDetails { OrganizationId = orgId } });
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id).Returns(autoConfirmReq);
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
|
||||
.Returns(autoConfirmReq);
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id).Returns(twoFactorReq);
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||
.Returns(twoFactorReq);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -417,16 +491,21 @@ public class InitPendingOrganizationCommandTests
|
||||
org.PublicKey = null;
|
||||
org.PlanType = PlanType.Free;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetCountByFreeOrganizationAdminUserAsync(user.Id).Returns(1);
|
||||
.GetCountByFreeOrganizationAdminUserAsync(user.Id)
|
||||
.Returns(1);
|
||||
|
||||
var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List<PolicyDetails>());
|
||||
var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List<PolicyDetails>());
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id).Returns(autoConfirmReq);
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
|
||||
.Returns(autoConfirmReq);
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id).Returns(twoFactorReq);
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||
.Returns(twoFactorReq);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
@@ -437,6 +516,92 @@ public class InitPendingOrganizationCommandTests
|
||||
Assert.IsType<FreeOrgAdminLimitError>(result.AsError);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithAutomaticUserConfirmationPolicy_ReturnsSingleOrgPolicyViolationError(
|
||||
User user, Guid orgId, Guid orgUserId, Guid otherOrgId, string publicKey, string privateKey, string userKey,
|
||||
SutProvider<InitPendingOrganizationCommand> sutProvider, Organization org, OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.OrganizationId = orgId;
|
||||
var token = CreateToken(orgUser, orgUserId, sutProvider);
|
||||
org.Enabled = false;
|
||||
org.Status = OrganizationStatusType.Pending;
|
||||
org.PrivateKey = null;
|
||||
org.PublicKey = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
|
||||
// Enable AutomaticConfirmUsers feature flag
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// User is subject to AutomaticUserConfirmation policy from another organization
|
||||
var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(
|
||||
new List<PolicyDetails> { new PolicyDetails { OrganizationId = otherOrgId } });
|
||||
var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List<PolicyDetails>());
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
|
||||
.Returns(autoConfirmReq);
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||
.Returns(twoFactorReq);
|
||||
|
||||
// No legacy SingleOrg policy
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
user, orgId, orgUserId, publicKey, privateKey, "", token, userKey);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsError);
|
||||
Assert.IsType<SingleOrgPolicyViolationError>(result.AsError);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithSingleOrgPolicy_ReturnsSingleOrgPolicyViolationError(
|
||||
User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey,
|
||||
SutProvider<InitPendingOrganizationCommand> sutProvider, Organization org, OrganizationUser orgUser)
|
||||
{
|
||||
// Arrange
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.OrganizationId = orgId;
|
||||
var token = CreateToken(orgUser, orgUserId, sutProvider);
|
||||
org.Enabled = false;
|
||||
org.Status = OrganizationStatusType.Pending;
|
||||
org.PrivateKey = null;
|
||||
org.PublicKey = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
|
||||
var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List<PolicyDetails>());
|
||||
var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List<PolicyDetails>());
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
|
||||
.Returns(autoConfirmReq);
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||
.Returns(twoFactorReq);
|
||||
|
||||
// User is subject to SingleOrg policy from another organization
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
user, orgId, orgUserId, publicKey, privateKey, "", token, userKey);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsError);
|
||||
Assert.IsType<SingleOrgPolicyViolationError>(result.AsError);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitPendingOrganizationVNextAsync_WithMismatchedOrganizationId_ReturnsOrganizationMismatchError(
|
||||
User user, Guid orgId, Guid differentOrgId, Guid orgUserId, string publicKey, string privateKey, string userKey,
|
||||
@@ -453,7 +618,9 @@ public class InitPendingOrganizationCommandTests
|
||||
org.PrivateKey = null;
|
||||
org.PublicKey = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(orgId)
|
||||
.Returns(org);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync(
|
||||
|
||||
Reference in New Issue
Block a user