1
0
mirror of https://github.com/bitwarden/server synced 2026-02-12 14:33:49 +00:00

[PM-27145] - Block Auto Confirm Enable Admin Portal (#6981)

* Extracted policy compliance checking for the organization out and added a check when attempting to enable auto user confirm via Admin Portal

* Moved injection order. Fixed error message.
This commit is contained in:
Jared McCannon
2026-02-11 09:59:18 -06:00
committed by GitHub
parent 946a03233b
commit 0566de90d6
10 changed files with 863 additions and 385 deletions

View File

@@ -10,8 +10,10 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.Providers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities.v2;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Organizations.Services;
@@ -59,6 +61,7 @@ public class OrganizationsController : Controller
private readonly IPricingClient _pricingClient;
private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand;
private readonly IOrganizationBillingService _organizationBillingService;
private readonly IAutomaticUserConfirmationOrganizationPolicyComplianceValidator _automaticUserConfirmationOrganizationPolicyComplianceValidator;
public OrganizationsController(
IOrganizationRepository organizationRepository,
@@ -84,7 +87,8 @@ public class OrganizationsController : Controller
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand,
IPricingClient pricingClient,
IResendOrganizationInviteCommand resendOrganizationInviteCommand,
IOrganizationBillingService organizationBillingService)
IOrganizationBillingService organizationBillingService,
IAutomaticUserConfirmationOrganizationPolicyComplianceValidator automaticUserConfirmationOrganizationPolicyComplianceValidator)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@@ -110,6 +114,7 @@ public class OrganizationsController : Controller
_pricingClient = pricingClient;
_resendOrganizationInviteCommand = resendOrganizationInviteCommand;
_organizationBillingService = organizationBillingService;
_automaticUserConfirmationOrganizationPolicyComplianceValidator = automaticUserConfirmationOrganizationPolicyComplianceValidator;
}
[RequirePermission(Permission.Org_List_View)]
@@ -250,7 +255,8 @@ public class OrganizationsController : Controller
BillingEmail = organization.BillingEmail,
Status = organization.Status,
PlanType = organization.PlanType,
Seats = organization.Seats
Seats = organization.Seats,
UseAutomaticUserConfirmation = organization.UseAutomaticUserConfirmation
};
if (model.PlanType.HasValue)
@@ -285,6 +291,13 @@ public class OrganizationsController : Controller
return RedirectToAction("Edit", new { id });
}
if (await CheckOrganizationPolicyComplianceAsync(existingOrganizationData, organization) is { } error)
{
TempData["Error"] = error.Message;
return RedirectToAction("Edit", new { id });
}
await HandlePotentialProviderSeatScalingAsync(
existingOrganizationData,
model);
@@ -312,6 +325,19 @@ public class OrganizationsController : Controller
return RedirectToAction("Edit", new { id });
}
private async Task<Error> CheckOrganizationPolicyComplianceAsync(Organization existingOrganizationData, Organization updatedOrganization)
{
if (!existingOrganizationData.UseAutomaticUserConfirmation && updatedOrganization.UseAutomaticUserConfirmation)
{
var validationResult = await _automaticUserConfirmationOrganizationPolicyComplianceValidator.IsOrganizationCompliantAsync(
new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(existingOrganizationData.Id));
return validationResult.Match(error => error, _ => null);
}
return null;
}
[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Org_Delete)]

View File

@@ -0,0 +1,58 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities.v2;
using Bit.Core.AdminConsole.Utilities.v2.Validation;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
public class AutomaticUserConfirmationOrganizationPolicyComplianceValidator(
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository)
: IAutomaticUserConfirmationOrganizationPolicyComplianceValidator
{
public async Task<ValidationResult<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>>
IsOrganizationCompliantAsync(AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest request)
{
var organizationUsers = await organizationUserRepository.GetManyDetailsByOrganizationAsync(request.OrganizationId);
if (await ValidateUserComplianceWithSingleOrgAsync(request, organizationUsers) is { } singleOrgNonCompliant)
{
return Invalid(request, singleOrgNonCompliant);
}
if (await ValidateNoProviderUsersAsync(organizationUsers) is { } orgHasProviderMember)
{
return Invalid(request, orgHasProviderMember);
}
return Valid(request);
}
private async Task<Error?> ValidateUserComplianceWithSingleOrgAsync(
AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest request,
ICollection<OrganizationUserUserDetails> organizationUsers)
{
var userIds = organizationUsers
.Where(u => u.UserId is not null && u.Status != OrganizationUserStatusType.Invited)
.Select(u => u.UserId!.Value);
var hasNonCompliantUser = (await organizationUserRepository.GetManyByManyUsersAsync(userIds))
.Any(uo => uo.OrganizationId != request.OrganizationId
&& uo.Status != OrganizationUserStatusType.Invited);
return hasNonCompliantUser ? new UserNotCompliantWithSingleOrganization() : null;
}
private async Task<Error?> ValidateNoProviderUsersAsync(ICollection<OrganizationUserUserDetails> organizationUsers)
{
var userIds = organizationUsers.Where(x => x.UserId is not null)
.Select(x => x.UserId!.Value);
return (await providerUserRepository.GetManyByManyUsersAsync(userIds)).Count != 0
? new ProviderExistsInOrganization()
: null;
}
}

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
public record AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(Guid OrganizationId);

View File

@@ -0,0 +1,7 @@
using Bit.Core.AdminConsole.Utilities.v2;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
public record UserNotCompliantWithSingleOrganization() : BadRequestError("All organization users must be compliant with the Single organization policy before enabling the Automatically confirm invited users policy. Please remove users who are members of multiple organizations.");
public record ProviderExistsInOrganization() : BadRequestError("The organization has users with the Provider user type. Please remove provider users before enabling the Automatically confirm invited users policy.");

View File

@@ -0,0 +1,28 @@
using Bit.Core.AdminConsole.Utilities.v2.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
/// <summary>
/// Validates that an organization meets the prerequisites for enabling the Automatic User Confirmation policy.
/// </summary>
/// <remarks>
/// The following conditions must be met:
/// <list type="bullet">
/// <item>All non-invited organization users belong only to this organization (Single Organization compliance)</item>
/// <item>No organization users are provider members</item>
/// </list>
/// </remarks>
public interface IAutomaticUserConfirmationOrganizationPolicyComplianceValidator
{
/// <summary>
/// Checks whether the organization is compliant with the Automatic User Confirmation policy prerequisites.
/// </summary>
/// <param name="request">The request containing the organization ID to validate.</param>
/// <returns>
/// A <see cref="ValidationResult{TRequest}"/> that is valid if the organization is compliant,
/// or contains a <see cref="UserNotCompliantWithSingleOrganization"/> or <see cref="ProviderExistsInOrganization"/>
/// error if validation fails.
/// </returns>
Task<ValidationResult<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>>
IsOrganizationCompliantAsync(AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest request);
}

View File

@@ -21,12 +21,14 @@ public static class PolicyServiceCollectionExtensions
services.AddScoped<IPolicyQuery, PolicyQuery>();
services.AddScoped<IPolicyEventHandlerFactory, PolicyEventHandlerHandlerFactory>();
services.AddScoped<IAutomaticUserConfirmationPolicyEnforcementValidator, AutomaticUserConfirmationPolicyEnforcementValidator>();
services.AddScoped<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator, AutomaticUserConfirmationOrganizationPolicyComplianceValidator>();
services.AddPolicyValidators();
services.AddPolicyRequirements();
services.AddPolicySideEffects();
services.AddPolicyUpdateEvents();
services.AddScoped<IAutomaticUserConfirmationPolicyEnforcementValidator, AutomaticUserConfirmationPolicyEnforcementValidator>();
}
[Obsolete("Use AddPolicyUpdateEvents instead.")]

View File

@@ -1,11 +1,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
@@ -19,19 +16,11 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
/// <li>No provider users exist</li>
/// </ul>
/// </summary>
public class AutomaticUserConfirmationPolicyEventHandler(
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository)
public class AutomaticUserConfirmationPolicyEventHandler(IAutomaticUserConfirmationOrganizationPolicyComplianceValidator validator)
: IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent
{
public PolicyType Type => PolicyType.AutomaticUserConfirmation;
private const string _usersNotCompliantWithSingleOrgErrorMessage =
"All organization users must be compliant with the Single organization policy before enabling the Automatically confirm invited users policy. Please remove users who are members of multiple organizations.";
private const string _providerUsersExistErrorMessage =
"The organization has users with the Provider user type. Please remove provider users before enabling the Automatically confirm invited users policy.";
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
@@ -43,7 +32,11 @@ public class AutomaticUserConfirmationPolicyEventHandler(
return string.Empty;
}
return await ValidateEnablingPolicyAsync(policyUpdate.OrganizationId);
return (await validator.IsOrganizationCompliantAsync(
new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(policyUpdate.OrganizationId)))
.Match(
error => error.Message,
_ => string.Empty);
}
public async Task<string> ValidateAsync(SavePolicyModel savePolicyModel, Policy? currentPolicy) =>
@@ -51,48 +44,4 @@ public class AutomaticUserConfirmationPolicyEventHandler(
public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) =>
Task.CompletedTask;
private async Task<string> ValidateEnablingPolicyAsync(Guid organizationId)
{
var organizationUsers = await organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId);
var singleOrgValidationError = await ValidateUserComplianceWithSingleOrgAsync(organizationId, organizationUsers);
if (!string.IsNullOrWhiteSpace(singleOrgValidationError))
{
return singleOrgValidationError;
}
var providerValidationError = await ValidateNoProviderUsersAsync(organizationUsers);
if (!string.IsNullOrWhiteSpace(providerValidationError))
{
return providerValidationError;
}
return string.Empty;
}
private async Task<string> ValidateUserComplianceWithSingleOrgAsync(Guid organizationId,
ICollection<OrganizationUserUserDetails> organizationUsers)
{
var userIds = organizationUsers.Where(
u => u.UserId is not null &&
u.Status != OrganizationUserStatusType.Invited)
.Select(u => u.UserId!.Value);
var hasNonCompliantUser = (await organizationUserRepository.GetManyByManyUsersAsync(userIds))
.Any(uo => uo.OrganizationId != organizationId
&& uo.Status != OrganizationUserStatusType.Invited);
return hasNonCompliantUser ? _usersNotCompliantWithSingleOrgErrorMessage : string.Empty;
}
private async Task<string> ValidateNoProviderUsersAsync(ICollection<OrganizationUserUserDetails> organizationUsers)
{
var userIds = organizationUsers.Where(x => x.UserId is not null)
.Select(x => x.UserId!.Value);
return (await providerUserRepository.GetManyByManyUsersAsync(userIds)).Count != 0
? _providerUsersExistErrorMessage
: string.Empty;
}
}

View File

@@ -5,6 +5,7 @@ using Bit.Admin.Services;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Providers.Services;
@@ -12,7 +13,11 @@ using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using NSubstitute;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Admin.Test.AdminConsole.Controllers;
@@ -299,18 +304,164 @@ public class OrganizationsControllerTests
.Returns(true);
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organization.Id);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Valid(request));
// Act
_ = await sutProvider.Sut.Edit(organization.Id, update);
// Assert
await organizationRepository.Received(1).ReplaceAsync(Arg.Is<Organization>(o => o.Id == organization.Id
&& o.UseAutomaticUserConfirmation == true));
}
// Annul
await organizationRepository.DeleteAsync(organization);
[BitAutoData]
[SutProviderCustomize]
[Theory]
public async Task Edit_EnableUseAutomaticUserConfirmation_ValidationFails_RedirectsWithError(
Organization organization,
SutProvider<OrganizationsController> sutProvider)
{
// Arrange
var update = new OrganizationEditModel
{
PlanType = PlanType.TeamsMonthly,
UseAutomaticUserConfirmation = true
};
organization.UseAutomaticUserConfirmation = false;
sutProvider.GetDependency<IAccessControlService>()
.UserHasPermission(Permission.Org_Plan_Edit)
.Returns(true);
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organization.Id);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Invalid(request, new UserNotCompliantWithSingleOrganization()));
sutProvider.Sut.TempData = new TempDataDictionary(new DefaultHttpContext(), Substitute.For<ITempDataProvider>());
// Act
var result = await sutProvider.Sut.Edit(organization.Id, update);
// Assert
var redirectResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Edit", redirectResult.ActionName);
Assert.Equal(organization.Id, redirectResult.RouteValues!["id"]);
await organizationRepository.DidNotReceive().ReplaceAsync(Arg.Any<Organization>());
}
[BitAutoData]
[SutProviderCustomize]
[Theory]
public async Task Edit_EnableUseAutomaticUserConfirmation_ProviderValidationFails_RedirectsWithError(
Organization organization,
SutProvider<OrganizationsController> sutProvider)
{
// Arrange
var update = new OrganizationEditModel
{
PlanType = PlanType.TeamsMonthly,
UseAutomaticUserConfirmation = true
};
organization.UseAutomaticUserConfirmation = false;
sutProvider.GetDependency<IAccessControlService>()
.UserHasPermission(Permission.Org_Plan_Edit)
.Returns(true);
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organization.Id);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Invalid(request, new ProviderExistsInOrganization()));
sutProvider.Sut.TempData = new TempDataDictionary(new DefaultHttpContext(), Substitute.For<ITempDataProvider>());
// Act
var result = await sutProvider.Sut.Edit(organization.Id, update);
// Assert
var redirectResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Edit", redirectResult.ActionName);
await organizationRepository.DidNotReceive().ReplaceAsync(Arg.Any<Organization>());
}
[BitAutoData]
[SutProviderCustomize]
[Theory]
public async Task Edit_UseAutomaticUserConfirmation_NotChanged_DoesNotCallValidator(
SutProvider<OrganizationsController> sutProvider)
{
// Arrange
var organizationId = new Guid();
var update = new OrganizationEditModel
{
UseSecretsManager = false,
UseAutomaticUserConfirmation = false
};
var organization = new Organization
{
Id = organizationId,
UseAutomaticUserConfirmation = false
};
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
.Returns(organization);
// Act
_ = await sutProvider.Sut.Edit(organizationId, update);
// Assert
await sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.DidNotReceive()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>());
}
[BitAutoData]
[SutProviderCustomize]
[Theory]
public async Task Edit_UseAutomaticUserConfirmation_AlreadyEnabled_DoesNotCallValidator(
SutProvider<OrganizationsController> sutProvider)
{
// Arrange
var organizationId = new Guid();
var update = new OrganizationEditModel
{
UseSecretsManager = false,
UseAutomaticUserConfirmation = true
};
var organization = new Organization
{
Id = organizationId,
UseAutomaticUserConfirmation = true
};
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
.Returns(organization);
// Act
_ = await sutProvider.Sut.Edit(organizationId, update);
// Assert
await sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.DidNotReceive()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>());
}
#endregion

View File

@@ -0,0 +1,544 @@
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
[SutProviderCustomize]
public class AutomaticUserConfirmationOrganizationPolicyComplianceValidatorTests
{
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_AllUsersCompliant_NoProviders_ReturnsValid(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_UserInAnotherOrg_ReturnsUserNotCompliantWithSingleOrganization(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(), // Different org
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserNotCompliantWithSingleOrganization>(result.AsError);
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_ProviderUsersExist_ReturnsProviderExistsInOrganization(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
var providerUser = new ProviderUser
{
Id = Guid.NewGuid(),
ProviderId = Guid.NewGuid(),
UserId = userId
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([providerUser]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<ProviderExistsInOrganization>(result.AsError);
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_InvitedUsersExcluded_FromSingleOrgCheck(
Guid organizationId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange - invited user has null UserId and Invited status
var invitedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = null,
Status = OrganizationUserStatusType.Invited,
Email = "invited@example.com"
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([invitedUser]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
// Invited users with null UserId should not trigger the single org query
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => !ids.Any()));
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_InvitedUserWithUserId_ExcludedFromSingleOrgCheck(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange - Invited status users are excluded regardless of UserId
var invitedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Invited
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([invitedUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
// Invited users should not be included in the single org compliance query
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => !ids.Any()));
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_UserInAnotherOrgWithInvitedStatus_ReturnsValid(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
// User has an Invited status in another org - should not count as non-compliant
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = userId,
Status = OrganizationUserStatusType.Invited
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_SingleOrgViolationTakesPrecedence_OverProviderCheck(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange - user is in another org AND is a provider user
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserNotCompliantWithSingleOrganization>(result.AsError);
// Provider check should not be called since single org check failed first
await sutProvider.GetDependency<IProviderUserRepository>()
.DidNotReceive()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>());
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_MixedUsers_OnlyNonInvitedChecked(
Guid organizationId,
Guid confirmedUserId,
Guid acceptedUserId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var invitedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = null,
Status = OrganizationUserStatusType.Invited,
Email = "invited@example.com"
};
var confirmedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = confirmedUserId,
Status = OrganizationUserStatusType.Confirmed
};
var acceptedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = acceptedUserId,
Status = OrganizationUserStatusType.Accepted
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([invitedUser, confirmedUser, acceptedUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
// Only confirmed and accepted users should be checked for single org compliance
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids =>
ids.Count() == 2 &&
ids.Contains(confirmedUserId) &&
ids.Contains(acceptedUserId)));
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_NoOrganizationUsers_ReturnsValid(
Guid organizationId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_UserInSameOrgOnly_ReturnsValid(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
// User exists in the same org only (the GetManyByManyUsersAsync returns same-org entry)
var sameOrgUser = new OrganizationUser
{
Id = orgUser.Id,
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([sameOrgUser]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_ProviderCheckIncludesAllUsersWithUserIds(
Guid organizationId,
Guid userId1,
Guid userId2,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange - provider check includes users regardless of Invited status (only excludes null UserId)
var confirmedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId1,
Status = OrganizationUserStatusType.Confirmed
};
var invitedUserWithNullId = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = null,
Status = OrganizationUserStatusType.Invited,
Email = "invited@example.com"
};
var acceptedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId2,
Status = OrganizationUserStatusType.Accepted
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([confirmedUser, invitedUserWithNullId, acceptedUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
// Provider check should include all users with non-null UserIds (confirmed + accepted)
await sutProvider.GetDependency<IProviderUserRepository>()
.Received(1)
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids =>
ids.Count() == 2 &&
ids.Contains(userId1) &&
ids.Contains(userId2)));
}
[Theory, BitAutoData]
public async Task IsOrganizationCompliantAsync_RevokedUserInAnotherOrg_ReturnsUserNotCompliant(
Guid organizationId,
Guid userId,
SutProvider<AutomaticUserConfirmationOrganizationPolicyComplianceValidator> sutProvider)
{
// Arrange
var revokedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
UserId = userId,
Status = OrganizationUserStatusType.Revoked
};
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = userId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns([revokedUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organizationId);
// Act
var result = await sutProvider.Sut.IsOrganizationCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserNotCompliantWithSingleOrganization>(result.AsError);
}
}

View File

@@ -1,19 +1,14 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
@@ -34,35 +29,14 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_UsersNotCompliantWithSingleOrg_ReturnsError(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
Guid nonCompliantUserId,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Confirmed,
UserId = nonCompliantUserId,
Email = "user@example.com"
};
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(policyUpdate.OrganizationId);
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = nonCompliantUserId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Invalid(request, new UserNotCompliantWithSingleOrganization()));
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
@@ -71,85 +45,17 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase);
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_UserWithInvitedStatusInOtherOrg_ValidationPasses(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
Guid userId,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Confirmed,
UserId = userId,
};
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = null, // invited users do not have a user id
Status = OrganizationUserStatusType.Invited,
Email = orgUser.Email
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
// Assert
Assert.True(string.IsNullOrEmpty(result));
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_ProviderUsersExist_ReturnsError(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
Guid userId,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Confirmed,
UserId = userId
};
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(policyUpdate.OrganizationId);
var providerUser = new ProviderUser
{
Id = Guid.NewGuid(),
ProviderId = Guid.NewGuid(),
UserId = userId,
Status = ProviderUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([providerUser]);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Invalid(request, new ProviderExistsInOrganization()));
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
@@ -158,33 +64,17 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
Assert.Contains("Provider user type", result, StringComparison.OrdinalIgnoreCase);
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_AllValidationsPassed_ReturnsEmptyString(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var orgUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Confirmed,
UserId = Guid.NewGuid()
};
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(policyUpdate.OrganizationId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Valid(request));
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
@@ -208,9 +98,9 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
// Assert
Assert.True(string.IsNullOrEmpty(result));
await sutProvider.GetDependency<IOrganizationUserRepository>()
await sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.DidNotReceive()
.GetManyDetailsByOrganizationAsync(Arg.Any<Guid>());
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>());
}
[Theory, BitAutoData]
@@ -227,212 +117,31 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
// Assert
Assert.True(string.IsNullOrEmpty(result));
await sutProvider.GetDependency<IOrganizationUserRepository>()
await sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.DidNotReceive()
.GetManyDetailsByOrganizationAsync(Arg.Any<Guid>());
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>());
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_IncludesOwnersAndAdmins_InComplianceCheck(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
Guid nonCompliantOwnerId,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var ownerUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Confirmed,
UserId = nonCompliantOwnerId,
};
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = nonCompliantOwnerId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([ownerUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
// Assert
Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase);
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_InvitedUsersExcluded_FromComplianceCheck(
public async Task ValidateAsync_EnablingPolicy_PassesCorrectOrganizationId(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var invitedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Invited,
UserId = null,
Email = "invited@example.com"
};
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(policyUpdate.OrganizationId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([invitedUser]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Valid(request));
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
await sutProvider.Sut.ValidateAsync(policyUpdate, null);
// Assert
Assert.True(string.IsNullOrEmpty(result));
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_MixedUsersWithNullUserId_HandlesCorrectly(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
Guid confirmedUserId,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var invitedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Invited,
UserId = null,
Email = "invited@example.com"
};
var confirmedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Confirmed,
UserId = confirmedUserId,
Email = "confirmed@example.com"
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([invitedUser, confirmedUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
// Assert
Assert.True(string.IsNullOrEmpty(result));
await sutProvider.GetDependency<IOrganizationUserRepository>()
await sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.Received(1)
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == 1 && ids.First() == confirmedUserId));
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_RevokedUsersIncluded_InComplianceCheck(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var revokedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Revoked,
UserId = Guid.NewGuid(),
};
var additionalOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Revoked,
UserId = revokedUser.UserId,
};
var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUserRepository
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([revokedUser]);
orgUserRepository.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([additionalOrgUser]);
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([]);
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
// Assert
Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase);
}
[Theory, BitAutoData]
public async Task ValidateAsync_EnablingPolicy_AcceptedUsersIncluded_InComplianceCheck(
[PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate,
Guid nonCompliantUserId,
SutProvider<AutomaticUserConfirmationPolicyEventHandler> sutProvider)
{
// Arrange
var acceptedUser = new OrganizationUserUserDetails
{
Id = Guid.NewGuid(),
OrganizationId = policyUpdate.OrganizationId,
Type = OrganizationUserType.User,
Status = OrganizationUserStatusType.Accepted,
UserId = nonCompliantUserId,
};
var otherOrgUser = new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
UserId = nonCompliantUserId,
Status = OrganizationUserStatusType.Confirmed
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([acceptedUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([otherOrgUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
// Assert
Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase);
.IsOrganizationCompliantAsync(Arg.Is<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>(
r => r.OrganizationId == policyUpdate.OrganizationId));
}
[Theory, BitAutoData]
@@ -442,10 +151,11 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests
{
// Arrange
var savePolicyModel = new SavePolicyModel(policyUpdate);
var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(policyUpdate.OrganizationId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
.Returns([]);
sutProvider.GetDependency<IAutomaticUserConfirmationOrganizationPolicyComplianceValidator>()
.IsOrganizationCompliantAsync(Arg.Any<AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest>())
.Returns(Valid(request));
// Act
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);