1
0
mirror of https://github.com/bitwarden/server synced 2025-12-23 11:43:23 +00:00

Fixed impelmentation in enforcement query. also cleaned up format. added tests.

This commit is contained in:
jrmccannon
2025-11-26 09:22:56 -06:00
parent 18cba7861c
commit ba7fa768f3
3 changed files with 320 additions and 24 deletions

View File

@@ -14,34 +14,32 @@ public class AutomaticUserConfirmationPolicyEnforcementQuery(
public async Task<ValidationResult<AutomaticUserConfirmationPolicyEnforcementRequest>> IsCompliantAsync( public async Task<ValidationResult<AutomaticUserConfirmationPolicyEnforcementRequest>> IsCompliantAsync(
AutomaticUserConfirmationPolicyEnforcementRequest request) AutomaticUserConfirmationPolicyEnforcementRequest request)
{ {
var (organizationUser, otherOrganizationsOrganizationUsers, user) = request;
var automaticUserConfirmationPolicyRequirement = await policyRequirementQuery var automaticUserConfirmationPolicyRequirement = await policyRequirementQuery
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(request.User.Id); .GetAsync<AutomaticUserConfirmationPolicyRequirement>(request.User.Id);
if (automaticUserConfirmationPolicyRequirement.IsEnabled(organizationUser.OrganizationId)) if (automaticUserConfirmationPolicyRequirement.IsEnabled(request.OrganizationUser.OrganizationId)
{ && await OrganizationUserBelongsToAnotherOrganizationAsync(request))
return Invalid(request, new AutoConfirmDoesNotAllowMembershipToOtherOrganizations());
}
if (automaticUserConfirmationPolicyRequirement.IsEnabledAndUserIsAProvider(organizationUser.OrganizationId))
{
return Invalid(request, new ProviderUsersCannotJoin());
}
if (automaticUserConfirmationPolicyRequirement.IsEnabledForOrganizationsOtherThan(organizationUser
.OrganizationId))
{ {
return Invalid(request, new OrganizationEnforcesSingleOrgPolicy()); return Invalid(request, new OrganizationEnforcesSingleOrgPolicy());
} }
if (otherOrganizationsOrganizationUsers is { Count: > 0 } if (automaticUserConfirmationPolicyRequirement.IsEnabledAndUserIsAProvider(request.OrganizationUser.OrganizationId))
|| (await organizationUserRepository.GetManyByUserAsync(user.Id)) {
.Any(x => x.OrganizationId != organizationUser.OrganizationId)) return Invalid(request, new ProviderUsersCannotJoin());
}
if (automaticUserConfirmationPolicyRequirement.IsEnabledForOrganizationsOtherThan(request.OrganizationUser
.OrganizationId))
{ {
return Invalid(request, new OtherOrganizationEnforcesSingleOrgPolicy()); return Invalid(request, new OtherOrganizationEnforcesSingleOrgPolicy());
} }
return Valid(request); return Valid(request);
} }
private async Task<bool> OrganizationUserBelongsToAnotherOrganizationAsync(
AutomaticUserConfirmationPolicyEnforcementRequest request) =>
request.OtherOrganizationsOrganizationUsers?.ToArray() is { Length: > 0 }
|| (await organizationUserRepository.GetManyByUserAsync(request.User.Id))
.Any(x => x.OrganizationId != request.OrganizationUser.OrganizationId);
} }

View File

@@ -47,12 +47,5 @@ public record AutomaticUserConfirmationPolicyEnforcementRequest
OtherOrganizationsOrganizationUsers = null; OtherOrganizationsOrganizationUsers = null;
User = user; User = user;
} }
public void Deconstruct(out OrganizationUser organizationUser, out ICollection<OrganizationUser>? otherOrganizationsOrganizationUsers, out User user)
{
organizationUser = OrganizationUser;
otherOrganizationsOrganizationUsers = OtherOrganizationsOrganizationUsers?.ToArray();
user = User;
}
} }

View File

@@ -0,0 +1,305 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
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.Policies.Enforcement.AutoConfirm;
[SutProviderCustomize]
public class AutomaticUserConfirmationPolicyEnforcementQueryTests
{
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithNoOtherOrganizations_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user)
{
// Arrange
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledOnSameOrganizationButNoOtherOrgs_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user)
{
// Arrange
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation,
IsProvider = false
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]); // Only belongs to this one org
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledOnSameOrgAndUserHasOtherOrgs_ReturnsOrganizationEnforcesSingleOrgPolicyError(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
User user)
{
// Arrange
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation,
IsProvider = false
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[otherOrgUser], // User has other org memberships
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithUserIsProvider_ReturnsProviderUsersCannotJoinError(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user)
{
// Arrange
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation,
IsProvider = true
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]); // Only in this org, so first check passes
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<ProviderUsersCannotJoin>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledOnOtherOrganization_ReturnsOtherOrganizationEnforcesSingleOrgPolicyError(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user)
{
// Arrange
var otherOrgId = Guid.NewGuid();
var policyDetails = new PolicyDetails
{
OrganizationId = otherOrgId, // Different from organizationUser.OrganizationId
PolicyType = PolicyType.AutomaticUserConfirmation,
IsProvider = false
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OtherOrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithOtherOrganizationsButNoPolicyEnabled_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
User user)
{
// Arrange
// No policy enabled, so even with other org memberships, it should be valid
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[otherOrgUser], // User has other organization memberships
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithEmptyOtherOrganizationsAndSingleOrg_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user)
{
// Arrange
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]); // Only one org
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
Assert.Equal(request, result.Request);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_ChecksConditionsInCorrectOrder_ReturnsFirstFailure(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
User user)
{
// Arrange - Set up conditions that would fail multiple checks
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId, // Would trigger first check
PolicyType = PolicyType.AutomaticUserConfirmation,
IsProvider = true // Would also trigger second check if first passes
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
[otherOrgUser], // Would also fail the last check
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert - Should fail on the FIRST check (IsEnabled on same org AND has other orgs)
Assert.True(result.IsError);
Assert.IsType<OrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithNullOtherOrganizations_ReturnsValidWhenNoOtherOrgs(
SutProvider<AutomaticUserConfirmationPolicyEnforcementQuery> sutProvider,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user)
{
// Arrange
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser,
null, // Null other organizations
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]); // Only one org
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
}