1
0
mirror of https://github.com/bitwarden/server synced 2025-12-21 10:43:44 +00:00

[PM-27131] Auto confirm policy requirement (#6649)

* Added Auto confirm policy enforcement requirement. Includes strict single org enforcement along with blocking provider users from joining orgs with auto confirm enabled.
This commit is contained in:
Jared McCannon
2025-12-15 15:40:00 -06:00
committed by GitHub
parent bead4f1d5a
commit e646b91a50
20 changed files with 1488 additions and 238 deletions

View File

@@ -1,7 +1,9 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Models.Business.Tokenables;
@@ -24,6 +26,7 @@ using Bit.Test.Common.Fakes;
using Microsoft.AspNetCore.DataProtection;
using NSubstitute;
using Xunit;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationUsers;
@@ -673,6 +676,79 @@ public class AcceptOrgUserCommandTests
Assert.Equal("User not found within organization.", exception.Message);
}
// Auto-confirm policy validation tests --------------------------------------------------------------------------
[Theory]
[BitAutoData]
public async Task AcceptOrgUserAsync_WithAutoConfirmIsNotEnabled_DoesNotCheckCompliance(
SutProvider<AcceptOrgUserCommand> sutProvider,
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
{
// Arrange
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
// Act
var resultOrgUser = await sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService);
// Assert
AssertValidAcceptedOrgUser(resultOrgUser, orgUser, user);
await sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>().DidNotReceiveWithAnyArgs()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>());
}
[Theory]
[BitAutoData]
public async Task AcceptOrgUserAsync_WithUserThatIsCompliantWithAutoConfirm_AcceptsUser(
SutProvider<AcceptOrgUserCommand> sutProvider,
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
{
// Arrange
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
// Mock auto-confirm enforcement query to return valid (no auto-confirm restrictions)
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser], user)));
// Act
var resultOrgUser = await sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService);
// Assert
AssertValidAcceptedOrgUser(resultOrgUser, orgUser, user);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).ReplaceAsync(
Arg.Is<OrganizationUser>(ou => ou.Id == orgUser.Id && ou.Status == OrganizationUserStatusType.Accepted));
}
[Theory]
[BitAutoData]
public async Task AcceptOrgUserAsync_WithAutoConfirmIsEnabledAndFailsCompliance_ThrowsBadRequestException(
SutProvider<AcceptOrgUserCommand> sutProvider,
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails,
OrganizationUser otherOrgUser)
{
// Arrange
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser, otherOrgUser], user),
new UserCannotBelongToAnotherOrganization()));
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService));
// Should get auto-confirm error
Assert.Equal(new UserCannotBelongToAnotherOrganization().Message, exception.Message);
}
// Private helpers -------------------------------------------------------------------------------------------------
/// <summary>
@@ -716,7 +792,7 @@ public class AcceptOrgUserCommandTests
/// - Provides mock data for an admin to validate email functionality.
/// - Returns the corresponding organization for the given org ID.
/// </summary>
private void SetupCommonAcceptOrgUserMocks(SutProvider<AcceptOrgUserCommand> sutProvider, User user,
private static void SetupCommonAcceptOrgUserMocks(SutProvider<AcceptOrgUserCommand> sutProvider, User user,
Organization org,
OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
{
@@ -729,18 +805,12 @@ public class AcceptOrgUserCommandTests
// User is not part of any other orgs
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns(
Task.FromResult<ICollection<OrganizationUser>>(new List<OrganizationUser>())
);
.Returns([]);
// Org they are trying to join does not have single org policy
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg, OrganizationUserStatusType.Invited)
.Returns(
Task.FromResult<ICollection<OrganizationUserPolicyDetails>>(
new List<OrganizationUserPolicyDetails>()
)
);
.Returns([]);
// User is not part of any organization that applies the single org policy
sutProvider.GetDependency<IPolicyService>()
@@ -750,20 +820,24 @@ public class AcceptOrgUserCommandTests
// Org does not require 2FA
sutProvider.GetDependency<IPolicyService>().GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited)
.Returns(Task.FromResult<ICollection<OrganizationUserPolicyDetails>>(
new List<OrganizationUserPolicyDetails>()));
.Returns([]);
// Provide at least 1 admin to test email functionality
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByMinimumRoleAsync(orgUser.OrganizationId, OrganizationUserType.Admin)
.Returns(Task.FromResult<IEnumerable<OrganizationUserUserDetails>>(
new List<OrganizationUserUserDetails>() { adminUserDetails }
));
.Returns([adminUserDetails]);
// Return org
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(org.Id)
.Returns(Task.FromResult(org));
.Returns(org);
// Auto-confirm enforcement query returns valid by default (no restrictions)
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser], user);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(request)
.Returns(Valid(request));
}