1
0
mirror of https://github.com/bitwarden/server synced 2026-01-07 11:03:37 +00:00

Merge remote-tracking branch 'origin/main' into xunit-v3-full-upgrade

This commit is contained in:
Justin Baur
2025-11-19 10:10:16 -05:00
49 changed files with 13376 additions and 115 deletions

View File

@@ -1,6 +1,8 @@
using System.Text.Json;
using System.Reflection;
using System.Text.Json;
using AutoFixture;
using AutoFixture.Kernel;
using AutoFixture.Xunit2;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
@@ -20,6 +22,18 @@ public class OrganizationCustomization : ICustomization
{
public bool UseGroups { get; set; }
public PlanType PlanType { get; set; }
public bool UseAutomaticUserConfirmation { get; set; }
public OrganizationCustomization()
{
}
public OrganizationCustomization(bool useAutomaticUserConfirmation, PlanType planType)
{
UseAutomaticUserConfirmation = useAutomaticUserConfirmation;
PlanType = planType;
}
public void Customize(IFixture fixture)
{
@@ -37,7 +51,8 @@ public class OrganizationCustomization : ICustomization
.With(o => o.UseGroups, UseGroups)
.With(o => o.PlanType, PlanType)
.With(o => o.Seats, seats)
.With(o => o.SmSeats, smSeats));
.With(o => o.SmSeats, smSeats)
.With(o => o.UseAutomaticUserConfirmation, UseAutomaticUserConfirmation));
fixture.Customize<Collection>(composer =>
composer
@@ -277,3 +292,9 @@ internal class EphemeralDataProtectionAutoDataAttribute : CustomAutoDataAttribut
public EphemeralDataProtectionAutoDataAttribute() : base(new SutProviderCustomization(), new EphemeralDataProtectionCustomization())
{ }
}
internal class OrganizationAttribute(bool useAutomaticUserConfirmation = false, PlanType planType = PlanType.Free) : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter) =>
new OrganizationCustomization(useAutomaticUserConfirmation, planType);
}

View File

@@ -0,0 +1,696 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUsers;
[SutProviderCustomize]
public class AutomaticallyConfirmOrganizationUsersValidatorTests
{
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithNullOrganizationUser_ReturnsUserNotFoundError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
Organization organization)
{
// Arrange
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = null,
OrganizationUserId = Guid.NewGuid(),
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserNotFoundError>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithNullUserId_ReturnsUserNotFoundError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser)
{
// Arrange
organizationUser.UserId = null;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserNotFoundError>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithNullOrganization_ReturnsOrganizationNotFoundError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId)
{
// Arrange
organizationUser.UserId = userId;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = null,
OrganizationId = organizationUser.OrganizationId,
Key = "test-key"
};
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OrganizationNotFound>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithValidAcceptedUser_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true, planType: PlanType.EnterpriseAnnually)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsValid);
Assert.Equal(request, result.Request);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithMismatchedOrganizationId_ReturnsOrganizationUserIdIsInvalidError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = Guid.NewGuid(); // Different from organization.Id
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OrganizationUserIdIsInvalid>(result.AsError);
}
[Theory]
[BitAutoData(OrganizationUserStatusType.Invited)]
[BitAutoData(OrganizationUserStatusType.Revoked)]
[BitAutoData(OrganizationUserStatusType.Confirmed)]
public async Task ValidateAsync_WithNotAcceptedStatus_ReturnsUserIsNotAcceptedError(
OrganizationUserStatusType statusType,
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
Guid userId)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
organizationUser.Status = statusType;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserIsNotAccepted>(result.AsError);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Custom)]
[BitAutoData(OrganizationUserType.Admin)]
public async Task ValidateAsync_WithNonUserType_ReturnsUserIsNotUserTypeError(
OrganizationUserType userType,
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
organizationUser.Type = userType;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserIsNotUserType>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserWithout2FA_And2FARequired_ReturnsError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
var twoFactorPolicyDetails = new PolicyDetails
{
OrganizationId = organization.Id,
PolicyType = PolicyType.TwoFactorAuthentication
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, false)]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<RequireTwoFactorPolicyRequirement>(userId)
.Returns(new RequireTwoFactorPolicyRequirement([twoFactorPolicyDetails]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserDoesNotHaveTwoFactorEnabled>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserWith2FA_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserWithout2FA_And2FANotRequired_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, false)]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<RequireTwoFactorPolicyRequirement>(userId)
.Returns(new RequireTwoFactorPolicyRequirement([])); // No 2FA policy
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInMultipleOrgs_WithSingleOrgPolicyOnThisOrg_ReturnsError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
var singleOrgPolicyDetails = new PolicyDetails
{
OrganizationId = organization.Id,
PolicyType = PolicyType.SingleOrg
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser, otherOrgUser]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<SingleOrganizationPolicyRequirement>(userId)
.Returns(new SingleOrganizationPolicyRequirement([singleOrgPolicyDetails]));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInMultipleOrgs_WithSingleOrgPolicyOnOtherOrg_ReturnsError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
var otherOrgId = Guid.NewGuid(); // Different org
var singleOrgPolicyDetails = new PolicyDetails
{
OrganizationId = otherOrgId,
PolicyType = PolicyType.SingleOrg,
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser, otherOrgUser]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<SingleOrganizationPolicyRequirement>(userId)
.Returns(new SingleOrganizationPolicyRequirement([singleOrgPolicyDetails]));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OtherOrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInSingleOrg_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]); // Single org
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInMultipleOrgs_WithNoSingleOrgPolicy_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
Guid userId,
Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
autoConfirmPolicy.Type = PolicyType.AutomaticUserConfirmation;
autoConfirmPolicy.Enabled = true;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser, otherOrgUser]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<SingleOrganizationPolicyRequirement>(userId)
.Returns(new SingleOrganizationPolicyRequirement([]));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithAutoConfirmPolicyDisabled_ReturnsAutoConfirmPolicyNotEnabledError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns((Policy)null);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<AutomaticallyConfirmUsersPolicyIsNotEnabled>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithOrganizationUseAutomaticUserConfirmationDisabled_ReturnsAutoConfirmPolicyNotEnabledError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: false)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<AutomaticallyConfirmUsersPolicyIsNotEnabled>(result.AsError);
}
}

View File

@@ -0,0 +1,730 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.Models.Data.OrganizationUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Utilities.v2;
using Bit.Core.AdminConsole.Utilities.v2.Validation;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUsers;
[SutProviderCustomize]
public class AutomaticallyConfirmUsersCommandTests
{
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WithValidRequest_ConfirmsUserSuccessfully(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert
Assert.True(result.IsSuccess);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key));
await AssertSuccessfulOperationsAsync(sutProvider, organizationUser, organization, user, key);
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WithInvalidUserOrgId_ReturnsOrganizationUserIdIsInvalidError(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = Guid.NewGuid(); // User belongs to another organization
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, false, new OrganizationUserIdIsInvalid());
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OrganizationUserIdIsInvalid>(result.AsError);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.DidNotReceive()
.ConfirmOrganizationUserAsync(Arg.Any<AcceptedOrganizationUserToConfirm>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenAlreadyConfirmed_ReturnsNoneSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
// Return false to indicate the user is already confirmed
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(x =>
x.OrganizationUserId == organizationUser.Id && x.Key == request.Key))
.Returns(false);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert
Assert.True(result.IsSuccess);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(x =>
x.OrganizationUserId == organizationUser.Id && x.Key == request.Key));
// Verify no side effects occurred
await sutProvider.GetDependency<IEventService>()
.DidNotReceive()
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<DateTime?>());
await sutProvider.GetDependency<IPushNotificationService>()
.DidNotReceive()
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectionEnabled_CreatesDefaultCollection(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName, // Non-empty to trigger creation
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
SetupPolicyRequirementMock(sutProvider, user.Id, organization.Id, true); // Policy requires collection
sutProvider.GetDependency<IOrganizationUserRepository>().ConfirmOrganizationUserAsync(
Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert
Assert.True(result.IsSuccess);
await sutProvider.GetDependency<ICollectionRepository>()
.Received(1)
.CreateAsync(
Arg.Is<Collection>(c =>
c.OrganizationId == organization.Id &&
c.Name == defaultCollectionName &&
c.Type == CollectionType.DefaultUserCollection),
Arg.Is<IEnumerable<CollectionAccessSelection>>(groups => groups == null),
Arg.Is<IEnumerable<CollectionAccessSelection>>(access =>
access.FirstOrDefault(x => x.Id == organizationUser.Id && x.Manage) != null));
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectionDisabled_DoesNotCreateCollection(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = string.Empty, // Empty, so the collection won't be created
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
SetupPolicyRequirementMock(sutProvider, user.Id, organization.Id, false); // Policy doesn't require
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert
Assert.True(result.IsSuccess);
await sutProvider.GetDependency<ICollectionRepository>()
.DidNotReceive()
.CreateAsync(Arg.Any<Collection>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenCreateDefaultCollectionFails_LogsErrorButReturnsSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName, // Non-empty to trigger creation
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
SetupPolicyRequirementMock(sutProvider, user.Id, organization.Id, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)).Returns(true);
var collectionException = new Exception("Collection creation failed");
sutProvider.GetDependency<ICollectionRepository>()
.CreateAsync(Arg.Any<Collection>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
Arg.Any<IEnumerable<CollectionAccessSelection>>())
.ThrowsAsync(collectionException);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert - side effects are fire-and-forget, so command returns success even if collection creation fails
Assert.True(result.IsSuccess);
sutProvider.GetDependency<ILogger<AutomaticallyConfirmOrganizationUserCommand>>()
.Received(1)
.Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString()!.Contains("Failed to create default collection for user")),
collectionException,
Arg.Any<Func<object, Exception?, string>>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenEventLogFails_LogsErrorButReturnsSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
var eventException = new Exception("Event logging failed");
sutProvider.GetDependency<IEventService>()
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(),
EventType.OrganizationUser_AutomaticallyConfirmed,
Arg.Any<DateTime?>())
.ThrowsAsync(eventException);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert - side effects are fire-and-forget, so command returns success even if event log fails
Assert.True(result.IsSuccess);
sutProvider.GetDependency<ILogger<AutomaticallyConfirmOrganizationUserCommand>>()
.Received(1)
.Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString()!.Contains("Failed to log OrganizationUser_AutomaticallyConfirmed event")),
eventException,
Arg.Any<Func<object, Exception?, string>>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenSendEmailFails_LogsErrorButReturnsSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
var emailException = new Exception("Email sending failed");
sutProvider.GetDependency<IMailService>()
.SendOrganizationConfirmedEmailAsync(organization.Name, user.Email, organizationUser.AccessSecretsManager)
.ThrowsAsync(emailException);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert - side effects are fire-and-forget, so command returns success even if email fails
Assert.True(result.IsSuccess);
sutProvider.GetDependency<ILogger<AutomaticallyConfirmOrganizationUserCommand>>()
.Received(1)
.Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString()!.Contains("Failed to send OrganizationUserConfirmed")),
emailException,
Arg.Any<Func<object, Exception?, string>>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenUserNotFoundForEmail_LogsErrorButReturnsSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
// Return null when retrieving user for email
sutProvider.GetDependency<IUserRepository>()
.GetByIdAsync(user.Id)
.Returns((User)null!);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert - side effects are fire-and-forget, so command returns success even if user not found for email
Assert.True(result.IsSuccess);
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenDeleteDeviceRegistrationFails_LogsErrorButReturnsSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName,
Device device)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
device.UserId = user.Id;
device.PushToken = "test-push-token";
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(new List<Device> { device });
var deviceException = new Exception("Device registration deletion failed");
sutProvider.GetDependency<IPushRegistrationService>()
.DeleteUserRegistrationOrganizationAsync(Arg.Any<IEnumerable<string>>(), organization.Id.ToString())
.ThrowsAsync(deviceException);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert - side effects are fire-and-forget, so command returns success even if device registration deletion fails
Assert.True(result.IsSuccess);
sutProvider.GetDependency<ILogger<AutomaticallyConfirmOrganizationUserCommand>>()
.Received(1)
.Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString()!.Contains("Failed to delete device registration")),
deviceException,
Arg.Any<Func<object, Exception?, string>>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WhenPushSyncOrgKeysFails_LogsErrorButReturnsSuccess(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
var pushException = new Exception("Push sync failed");
sutProvider.GetDependency<IPushNotificationService>()
.PushSyncOrgKeysAsync(user.Id)
.ThrowsAsync(pushException);
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert - side effects are fire-and-forget, so command returns success even if push sync fails
Assert.True(result.IsSuccess);
sutProvider.GetDependency<ILogger<AutomaticallyConfirmOrganizationUserCommand>>()
.Received(1)
.Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString()!.Contains("Failed to push organization keys")),
pushException,
Arg.Any<Func<object, Exception?, string>>());
}
[Theory]
[BitAutoData]
public async Task AutomaticallyConfirmOrganizationUserAsync_WithDevicesWithoutPushToken_FiltersCorrectly(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
Guid performingUserId,
string key,
string defaultCollectionName,
Device deviceWithToken,
Device deviceWithoutToken)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
deviceWithToken.UserId = user.Id;
deviceWithToken.PushToken = "test-token";
deviceWithoutToken.UserId = user.Id;
deviceWithoutToken.PushToken = null;
var request = new AutomaticallyConfirmOrganizationUserRequest
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organization.Id,
Key = key,
DefaultUserCollectionName = defaultCollectionName,
PerformedBy = new StandardUser(performingUserId, true)
};
SetupRepositoryMocks(sutProvider, organizationUser, organization, user);
SetupValidatorMock(sutProvider, request, organizationUser, organization, true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.ConfirmOrganizationUserAsync(Arg.Is<AcceptedOrganizationUserToConfirm>(o =>
o.OrganizationUserId == organizationUser.Id && o.Key == request.Key))
.Returns(true);
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(new List<Device> { deviceWithToken, deviceWithoutToken });
// Act
var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request);
// Assert
Assert.True(result.IsSuccess);
await sutProvider.GetDependency<IPushRegistrationService>()
.Received(1)
.DeleteUserRegistrationOrganizationAsync(
Arg.Is<IEnumerable<string>>(devices =>
devices.Count(d => deviceWithToken.Id.ToString() == d) == 1),
organization.Id.ToString());
}
private static void SetupRepositoryMocks(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
OrganizationUser organizationUser,
Organization organization,
User user)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IUserRepository>()
.GetByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(new List<Device>());
}
private static void SetupValidatorMock(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
AutomaticallyConfirmOrganizationUserRequest originalRequest,
OrganizationUser organizationUser,
Organization organization,
bool isValid,
Error? error = null)
{
var validationRequest = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = originalRequest.PerformedBy,
DefaultUserCollectionName = originalRequest.DefaultUserCollectionName,
OrganizationUserId = originalRequest.OrganizationUserId,
OrganizationUser = organizationUser,
OrganizationId = originalRequest.OrganizationId,
Organization = organization,
Key = originalRequest.Key
};
var validationResult = isValid
? ValidationResultHelpers.Valid(validationRequest)
: ValidationResultHelpers.Invalid(validationRequest, error ?? new UserIsNotAccepted());
sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUsersValidator>()
.ValidateAsync(Arg.Any<AutomaticallyConfirmOrganizationUserValidationRequest>())
.Returns(validationResult);
}
private static void SetupPolicyRequirementMock(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
Guid userId,
Guid organizationId,
bool requiresDefaultCollection)
{
var policyDetails = requiresDefaultCollection
? new List<PolicyDetails> { new() { OrganizationId = organizationId } }
: new List<PolicyDetails>();
var policyRequirement = new OrganizationDataOwnershipPolicyRequirement(
requiresDefaultCollection ? OrganizationDataOwnershipState.Enabled : OrganizationDataOwnershipState.Disabled,
policyDetails);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(userId)
.Returns(policyRequirement);
}
private static async Task AssertSuccessfulOperationsAsync(
SutProvider<AutomaticallyConfirmOrganizationUserCommand> sutProvider,
OrganizationUser organizationUser,
Organization organization,
User user,
string key)
{
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogOrganizationUserEventAsync(
Arg.Is<OrganizationUser>(x => x.Id == organizationUser.Id),
EventType.OrganizationUser_AutomaticallyConfirmed,
Arg.Any<DateTime?>());
await sutProvider.GetDependency<IMailService>()
.Received(1)
.SendOrganizationConfirmedEmailAsync(
organization.Name,
user.Email,
organizationUser.AccessSecretsManager);
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncOrgKeysAsync(user.Id);
await sutProvider.GetDependency<IPushRegistrationService>()
.Received(1)
.DeleteUserRegistrationOrganizationAsync(
Arg.Any<IEnumerable<string>>(),
organization.Id.ToString());
}
}

View File

@@ -1,5 +1,7 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Utilities.v2;
using Bit.Core.AdminConsole.Utilities.v2.Validation;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;