mirror of
https://github.com/bitwarden/server
synced 2026-01-28 15:23:38 +00:00
feat(emergency-access): [PM-29585] Prevent New EA Invitations or Acceptance - Initial implementation
This commit is contained in:
@@ -19,7 +19,25 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements
|
||||
/// <param name="policyDetails">Collection of policy details that apply to this user id</param>
|
||||
public class AutomaticUserConfirmationPolicyRequirement(IEnumerable<PolicyDetails> policyDetails) : IPolicyRequirement
|
||||
{
|
||||
public bool CannotBeGrantedEmergencyAccess() => policyDetails.Any();
|
||||
/// <summary>
|
||||
/// Returns true if the user cannot grant emergency access because they are in an
|
||||
/// auto-confirm organization with status Accepted, Confirmed, or Revoked.
|
||||
/// </summary>
|
||||
public bool CannotGrantEmergencyAccess() => policyDetails.Any(p =>
|
||||
p.OrganizationUserStatus is
|
||||
OrganizationUserStatusType.Accepted or
|
||||
OrganizationUserStatusType.Confirmed or
|
||||
OrganizationUserStatusType.Revoked);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the user cannot be granted emergency access because they are in an
|
||||
/// auto-confirm organization with status Accepted, Confirmed, or Revoked.
|
||||
/// </summary>
|
||||
public bool CannotBeGrantedEmergencyAccess() => policyDetails.Any(p =>
|
||||
p.OrganizationUserStatus is
|
||||
OrganizationUserStatusType.Accepted or
|
||||
OrganizationUserStatusType.Confirmed or
|
||||
OrganizationUserStatusType.Revoked);
|
||||
|
||||
public bool CannotJoinProvider() => policyDetails.Any();
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@@ -34,6 +36,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
|
||||
public EmergencyAccessService(
|
||||
IEmergencyAccessRepository emergencyAccessRepository,
|
||||
@@ -46,7 +50,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
IUserService userService,
|
||||
GlobalSettings globalSettings,
|
||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||
IFeatureService featureService,
|
||||
IPolicyRequirementQuery policyRequirementQuery)
|
||||
{
|
||||
_emergencyAccessRepository = emergencyAccessRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@@ -59,6 +65,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
_globalSettings = globalSettings;
|
||||
_dataProtectorTokenizer = dataProtectorTokenizer;
|
||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||
_featureService = featureService;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
}
|
||||
|
||||
public async Task<EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime)
|
||||
@@ -73,6 +81,17 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector.");
|
||||
}
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var requirement = await _policyRequirementQuery
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(grantorUser.Id);
|
||||
|
||||
if (requirement.CannotGrantEmergencyAccess())
|
||||
{
|
||||
throw new BadRequestException("You cannot invite emergency contacts because you are a member of an organization that uses Automatic User Confirmation.");
|
||||
}
|
||||
}
|
||||
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
GrantorId = grantorUser.Id,
|
||||
@@ -131,6 +150,17 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var requirement = await _policyRequirementQuery
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(granteeUser.Id);
|
||||
|
||||
if (requirement.CannotBeGrantedEmergencyAccess())
|
||||
{
|
||||
throw new BadRequestException("You cannot accept emergency access invitations because you are a member of an organization that uses Automatic User Confirmation.");
|
||||
}
|
||||
}
|
||||
|
||||
if (emergencyAccess.Status == EmergencyAccessStatusType.Accepted)
|
||||
{
|
||||
throw new BadRequestException("Invitation already accepted. You will receive an email when the grantor confirms you as an emergency access contact.");
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
|
||||
public class AutomaticUserConfirmationPolicyRequirementTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(OrganizationUserStatusType.Accepted)]
|
||||
[InlineData(OrganizationUserStatusType.Confirmed)]
|
||||
[InlineData(OrganizationUserStatusType.Revoked)]
|
||||
public void CannotGrantEmergencyAccess_WithActiveStatus_ReturnsTrue(OrganizationUserStatusType status)
|
||||
{
|
||||
var policyDetails = new[]
|
||||
{
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = status
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
|
||||
|
||||
Assert.True(sut.CannotGrantEmergencyAccess());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotGrantEmergencyAccess_WithInvitedStatus_ReturnsFalse()
|
||||
{
|
||||
var policyDetails = new[]
|
||||
{
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
|
||||
|
||||
Assert.False(sut.CannotGrantEmergencyAccess());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotGrantEmergencyAccess_WithNoPolicies_ReturnsFalse()
|
||||
{
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement([]);
|
||||
|
||||
Assert.False(sut.CannotGrantEmergencyAccess());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OrganizationUserStatusType.Accepted)]
|
||||
[InlineData(OrganizationUserStatusType.Confirmed)]
|
||||
[InlineData(OrganizationUserStatusType.Revoked)]
|
||||
public void CannotBeGrantedEmergencyAccess_WithActiveStatus_ReturnsTrue(OrganizationUserStatusType status)
|
||||
{
|
||||
var policyDetails = new[]
|
||||
{
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = status
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
|
||||
|
||||
Assert.True(sut.CannotBeGrantedEmergencyAccess());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotBeGrantedEmergencyAccess_WithInvitedStatus_ReturnsFalse()
|
||||
{
|
||||
var policyDetails = new[]
|
||||
{
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
|
||||
|
||||
Assert.False(sut.CannotBeGrantedEmergencyAccess());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotBeGrantedEmergencyAccess_WithNoPolicies_ReturnsFalse()
|
||||
{
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement([]);
|
||||
|
||||
Assert.False(sut.CannotBeGrantedEmergencyAccess());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotGrantEmergencyAccess_WithMultiplePolicies_OneActive_ReturnsTrue()
|
||||
{
|
||||
var policyDetails = new[]
|
||||
{
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||
},
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Confirmed
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
|
||||
|
||||
Assert.True(sut.CannotGrantEmergencyAccess());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotBeGrantedEmergencyAccess_WithMultiplePolicies_OneActive_ReturnsTrue()
|
||||
{
|
||||
var policyDetails = new[]
|
||||
{
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||
},
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Confirmed
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
|
||||
|
||||
Assert.True(sut.CannotBeGrantedEmergencyAccess());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@@ -1512,4 +1516,317 @@ public class EmergencyAccessServiceTests
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.GetAttachmentDownloadAsync(emergencyAccess.Id, default, default, granteeUser));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Accepted)]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked)]
|
||||
public async Task InviteAsync_FeatureFlagEnabled_UserInAutoConfirmOrg_ThrowsBadRequest(
|
||||
OrganizationUserStatusType status,
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User invitingUser,
|
||||
string email,
|
||||
int waitTime)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// Create a real requirement with an active status that blocks emergency access
|
||||
var requirement = new AutomaticUserConfirmationPolicyRequirement(
|
||||
[
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = status
|
||||
}
|
||||
]);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(invitingUser.Id)
|
||||
.Returns(requirement);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.View, waitTime));
|
||||
|
||||
Assert.Contains("You cannot invite emergency contacts because you are a member of an organization that uses Automatic User Confirmation.", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.DidNotReceiveWithAnyArgs().CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InviteAsync_FeatureFlagEnabled_UserNotInAutoConfirmOrg_Succeeds(
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User invitingUser,
|
||||
string email,
|
||||
int waitTime)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// Create a real requirement with no policies (user is not in auto-confirm org)
|
||||
var requirement = new AutomaticUserConfirmationPolicyRequirement([]);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(invitingUser.Id)
|
||||
.Returns(requirement);
|
||||
|
||||
var result = await sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.View, waitTime);
|
||||
|
||||
Assert.NotNull(result);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1).CreateAsync(Arg.Any<EmergencyAccess>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InviteAsync_FeatureFlagDisabled_UserInAutoConfirmOrg_Succeeds(
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User invitingUser,
|
||||
string email,
|
||||
int waitTime)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.View, waitTime);
|
||||
|
||||
Assert.NotNull(result);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1).CreateAsync(Arg.Any<EmergencyAccess>());
|
||||
await sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InviteAsync_FeatureFlagEnabled_UserInvitedToAutoConfirmOrg_Succeeds(
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User invitingUser,
|
||||
string email,
|
||||
int waitTime)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// Create a real requirement with Invited status (should not block emergency access)
|
||||
var requirement = new AutomaticUserConfirmationPolicyRequirement(
|
||||
[
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||
}
|
||||
]);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(invitingUser.Id)
|
||||
.Returns(requirement);
|
||||
|
||||
var result = await sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.View, waitTime);
|
||||
|
||||
Assert.NotNull(result);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1).CreateAsync(Arg.Any<EmergencyAccess>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Accepted)]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked)]
|
||||
public async Task AcceptUserAsync_FeatureFlagEnabled_UserInAutoConfirmOrg_ThrowsBadRequest(
|
||||
OrganizationUserStatusType status,
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User acceptingUser,
|
||||
EmergencyAccess emergencyAccess,
|
||||
string token)
|
||||
{
|
||||
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
||||
emergencyAccess.Email = acceptingUser.Email;
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(emergencyAccess);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(token, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// Create a real requirement with an active status that blocks emergency access
|
||||
var requirement = new AutomaticUserConfirmationPolicyRequirement(
|
||||
[
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = status
|
||||
}
|
||||
]);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(acceptingUser.Id)
|
||||
.Returns(requirement);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency<IUserService>()));
|
||||
|
||||
Assert.Contains("You cannot accept emergency access invitations because you are a member of an organization that uses Automatic User Confirmation.", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AcceptUserAsync_FeatureFlagEnabled_UserNotInAutoConfirmOrg_Succeeds(
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User acceptingUser,
|
||||
User invitingUser,
|
||||
EmergencyAccess emergencyAccess,
|
||||
string token)
|
||||
{
|
||||
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
||||
emergencyAccess.Email = acceptingUser.Email;
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(emergencyAccess);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(invitingUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(token, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// Create a real requirement with no policies (user is not in auto-confirm org)
|
||||
var requirement = new AutomaticUserConfirmationPolicyRequirement([]);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(acceptingUser.Id)
|
||||
.Returns(requirement);
|
||||
|
||||
await sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency<IUserService>());
|
||||
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Accepted));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AcceptUserAsync_FeatureFlagDisabled_UserInAutoConfirmOrg_Succeeds(
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User acceptingUser,
|
||||
User invitingUser,
|
||||
EmergencyAccess emergencyAccess,
|
||||
string token)
|
||||
{
|
||||
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
||||
emergencyAccess.Email = acceptingUser.Email;
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(emergencyAccess);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(invitingUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(token, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(false);
|
||||
|
||||
await sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency<IUserService>());
|
||||
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Accepted));
|
||||
await sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AcceptUserAsync_FeatureFlagEnabled_UserInvitedToAutoConfirmOrg_Succeeds(
|
||||
SutProvider<EmergencyAccessService> sutProvider,
|
||||
User acceptingUser,
|
||||
User invitingUser,
|
||||
EmergencyAccess emergencyAccess,
|
||||
string token)
|
||||
{
|
||||
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
||||
emergencyAccess.Email = acceptingUser.Email;
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(emergencyAccess);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(invitingUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(token, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
|
||||
.Returns(true);
|
||||
|
||||
// Create a real requirement with Invited status (should not block emergency access)
|
||||
var requirement = new AutomaticUserConfirmationPolicyRequirement(
|
||||
[
|
||||
new PolicyDetails
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
PolicyType = PolicyType.AutomaticUserConfirmation,
|
||||
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||
}
|
||||
]);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(acceptingUser.Id)
|
||||
.Returns(requirement);
|
||||
|
||||
await sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency<IUserService>());
|
||||
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Accepted));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user