diff --git a/src/Core/Auth/Enums/EmergencyAccessStatusType.cs b/src/Core/Auth/Enums/EmergencyAccessStatusType.cs
index 7faaa11752..d817d6a950 100644
--- a/src/Core/Auth/Enums/EmergencyAccessStatusType.cs
+++ b/src/Core/Auth/Enums/EmergencyAccessStatusType.cs
@@ -2,9 +2,24 @@
public enum EmergencyAccessStatusType : byte
{
+ ///
+ /// The user has been invited to be an emergency contact.
+ ///
Invited = 0,
+ ///
+ /// The invited user, "grantee", has accepted the request to be an emergency contact.
+ ///
Accepted = 1,
+ ///
+ /// The inviting user, "grantor", has approved the grantee's acceptance.
+ ///
Confirmed = 2,
+ ///
+ /// The grantee has initiated the recovery process.
+ ///
RecoveryInitiated = 3,
+ ///
+ /// The grantee has excercised their emergency access.
+ ///
RecoveryApproved = 4,
}
diff --git a/src/Core/Auth/Services/IEmergencyAccessService.cs b/src/Core/Auth/Services/IEmergencyAccessService.cs
index 2c94632510..6dd17151e6 100644
--- a/src/Core/Auth/Services/IEmergencyAccessService.cs
+++ b/src/Core/Auth/Services/IEmergencyAccessService.cs
@@ -3,6 +3,7 @@ using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
+using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Vault.Models.Data;
@@ -20,6 +21,15 @@ public interface IEmergencyAccessService
Task InitiateAsync(Guid id, User initiatingUser);
Task ApproveAsync(Guid id, User approvingUser);
Task RejectAsync(Guid id, User rejectingUser);
+ ///
+ /// This request is made by the Grantee user to fetch the policies for the Grantor User.
+ /// The Grantor User has to be the owner of the organization.
+ /// If the Grantor user has OrganizationUserType.Owner then the policies for the _Grantor_ user
+ /// are returned.
+ ///
+ /// EmergencyAccess.Id being acted on
+ /// User making the request, this is the Grantee
+ /// null if the GrantorUser is not an organization owner; A list of policies otherwise.
Task> GetPoliciesAsync(Guid id, User requestingUser);
Task<(EmergencyAccess, User)> TakeoverAsync(Guid id, User initiatingUser);
Task PasswordAsync(Guid id, User user, string newMasterPasswordHash, string key);
diff --git a/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs b/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs
index dda16e29fe..2418830ea7 100644
--- a/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs
+++ b/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs
@@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
-using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
@@ -16,7 +15,6 @@ using Bit.Core.Tokens;
using Bit.Core.Vault.Models.Data;
using Bit.Core.Vault.Repositories;
using Bit.Core.Vault.Services;
-using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Auth.Services;
@@ -31,8 +29,6 @@ public class EmergencyAccessService : IEmergencyAccessService
private readonly IMailService _mailService;
private readonly IUserService _userService;
private readonly GlobalSettings _globalSettings;
- private readonly IPasswordHasher _passwordHasher;
- private readonly IOrganizationService _organizationService;
private readonly IDataProtectorTokenFactory _dataProtectorTokenizer;
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
@@ -45,9 +41,7 @@ public class EmergencyAccessService : IEmergencyAccessService
ICipherService cipherService,
IMailService mailService,
IUserService userService,
- IPasswordHasher passwordHasher,
GlobalSettings globalSettings,
- IOrganizationService organizationService,
IDataProtectorTokenFactory dataProtectorTokenizer,
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
{
@@ -59,9 +53,7 @@ public class EmergencyAccessService : IEmergencyAccessService
_cipherService = cipherService;
_mailService = mailService;
_userService = userService;
- _passwordHasher = passwordHasher;
_globalSettings = globalSettings;
- _organizationService = organizationService;
_dataProtectorTokenizer = dataProtectorTokenizer;
_removeOrganizationUserCommand = removeOrganizationUserCommand;
}
@@ -126,7 +118,12 @@ public class EmergencyAccessService : IEmergencyAccessService
throw new BadRequestException("Emergency Access not valid.");
}
- if (!_dataProtectorTokenizer.TryUnprotect(token, out var data) && data.IsValid(emergencyAccessId, user.Email))
+ if (!_dataProtectorTokenizer.TryUnprotect(token, out var data))
+ {
+ throw new BadRequestException("Invalid token.");
+ }
+
+ if (!data.IsValid(emergencyAccessId, user.Email))
{
throw new BadRequestException("Invalid token.");
}
@@ -140,6 +137,8 @@ public class EmergencyAccessService : IEmergencyAccessService
throw new BadRequestException("Invitation already accepted.");
}
+ // TODO PM-21687
+ // Might not be reachable since the Tokenable.IsValid() does an email comparison
if (string.IsNullOrWhiteSpace(emergencyAccess.Email) ||
!emergencyAccess.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
{
@@ -163,6 +162,8 @@ public class EmergencyAccessService : IEmergencyAccessService
public async Task DeleteAsync(Guid emergencyAccessId, Guid grantorId)
{
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
+ // TODO PM-19438/PM-21687
+ // Not sure why the GrantorId and the GranteeId are supposed to be the same?
if (emergencyAccess == null || (emergencyAccess.GrantorId != grantorId && emergencyAccess.GranteeId != grantorId))
{
throw new BadRequestException("Emergency Access not valid.");
@@ -171,9 +172,9 @@ public class EmergencyAccessService : IEmergencyAccessService
await _emergencyAccessRepository.DeleteAsync(emergencyAccess);
}
- public async Task ConfirmUserAsync(Guid emergencyAcccessId, string key, Guid confirmingUserId)
+ public async Task ConfirmUserAsync(Guid emergencyAccessId, string key, Guid confirmingUserId)
{
- var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAcccessId);
+ var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
if (emergencyAccess == null || emergencyAccess.Status != EmergencyAccessStatusType.Accepted ||
emergencyAccess.GrantorId != confirmingUserId)
{
@@ -224,7 +225,6 @@ public class EmergencyAccessService : IEmergencyAccessService
public async Task InitiateAsync(Guid id, User initiatingUser)
{
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
-
if (emergencyAccess == null || emergencyAccess.GranteeId != initiatingUser.Id ||
emergencyAccess.Status != EmergencyAccessStatusType.Confirmed)
{
@@ -285,6 +285,9 @@ public class EmergencyAccessService : IEmergencyAccessService
public async Task> GetPoliciesAsync(Guid id, User requestingUser)
{
+ // TODO PM-21687
+ // Should we look up policies here or just verify the EmergencyAccess is correct
+ // and handle policy logic else where? Should this be a query/Command?
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
if (!IsValidRequest(emergencyAccess, requestingUser, EmergencyAccessType.Takeover))
@@ -295,7 +298,9 @@ public class EmergencyAccessService : IEmergencyAccessService
var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId);
var grantorOrganizations = await _organizationUserRepository.GetManyByUserAsync(grantor.Id);
- var isOrganizationOwner = grantorOrganizations.Any(organization => organization.Type == OrganizationUserType.Owner);
+ var isOrganizationOwner = grantorOrganizations
+ .Any(organization => organization.Type == OrganizationUserType.Owner);
+
var policies = isOrganizationOwner ? await _policyRepository.GetManyByUserIdAsync(grantor.Id) : null;
return policies;
@@ -311,7 +316,8 @@ public class EmergencyAccessService : IEmergencyAccessService
}
var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId);
-
+ // TODO PM-21687
+ // Redundant check of the EmergencyAccessType -> checked in IsValidRequest() ln 308
if (emergencyAccess.Type == EmergencyAccessType.Takeover && grantor.UsesKeyConnector)
{
throw new BadRequestException("You cannot takeover an account that is using Key Connector.");
@@ -336,7 +342,9 @@ public class EmergencyAccessService : IEmergencyAccessService
grantor.LastPasswordChangeDate = grantor.RevisionDate;
grantor.Key = key;
// Disable TwoFactor providers since they will otherwise block logins
- grantor.SetTwoFactorProviders(new Dictionary());
+ grantor.SetTwoFactorProviders([]);
+ // Disable New Device Verification since it will otherwise block logins
+ grantor.VerifyDevices = false;
await _userRepository.ReplaceAsync(grantor);
// Remove grantor from all organizations unless Owner
@@ -421,12 +429,22 @@ public class EmergencyAccessService : IEmergencyAccessService
await _mailService.SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUsersName, token);
}
- private string NameOrEmail(User user)
+ private static string NameOrEmail(User user)
{
return string.IsNullOrWhiteSpace(user.Name) ? user.Email : user.Name;
}
- private bool IsValidRequest(EmergencyAccess availableAccess, User requestingUser, EmergencyAccessType requestedAccessType)
+
+ /*
+ * Checks if EmergencyAccess Object is null
+ * Checks the requesting user is the same as the granteeUser (So we are checking for proper grantee action)
+ * Status _must_ equal RecoveryApproved (This means the grantor has invited, the grantee has accepted, and the grantor has approved so the shared key exists but hasn't been exercised yet)
+ * request type must equal the type of access requested (View or Takeover)
+ */
+ private static bool IsValidRequest(
+ EmergencyAccess availableAccess,
+ User requestingUser,
+ EmergencyAccessType requestedAccessType)
{
return availableAccess != null &&
availableAccess.GranteeId == requestingUser.Id &&
diff --git a/test/Core.Test/Auth/Services/EmergencyAccessServiceTests.cs b/test/Core.Test/Auth/Services/EmergencyAccessServiceTests.cs
index 6c2352ca00..006515aafd 100644
--- a/test/Core.Test/Auth/Services/EmergencyAccessServiceTests.cs
+++ b/test/Core.Test/Auth/Services/EmergencyAccessServiceTests.cs
@@ -1,11 +1,17 @@
-using Bit.Core.Auth.Entities;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
+using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
+using Bit.Core.Auth.Models.Business.Tokenables;
+using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Services;
using Bit.Core.Entities;
+using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
+using Bit.Core.Tokens;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
@@ -17,27 +23,21 @@ namespace Bit.Core.Test.Auth.Services;
public class EmergencyAccessServiceTests
{
[Theory, BitAutoData]
- public async Task SaveAsync_PremiumCannotUpdate(
- SutProvider sutProvider, User savingUser)
+ public async Task InviteAsync_UserWithOutPremium_ThrowsBadRequest(
+ SutProvider sutProvider, User invitingUser, string email, int waitTime)
{
- savingUser.Premium = false;
- var emergencyAccess = new EmergencyAccess
- {
- Type = EmergencyAccessType.Takeover,
- GrantorId = savingUser.Id,
- };
-
- sutProvider.GetDependency().GetUserByIdAsync(savingUser.Id).Returns(savingUser);
+ sutProvider.GetDependency().CanAccessPremium(invitingUser).Returns(false);
var exception = await Assert.ThrowsAsync(
- () => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
+ () => sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.Takeover, waitTime));
Assert.Contains("Not a premium user.", exception.Message);
- await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs().CreateAsync(default);
}
[Theory, BitAutoData]
- public async Task InviteAsync_UserWithKeyConnectorCannotUseTakeover(
+ public async Task InviteAsync_UserWithKeyConnector_ThrowsBadRequest(
SutProvider sutProvider, User invitingUser, string email, int waitTime)
{
invitingUser.UsesKeyConnector = true;
@@ -47,11 +47,461 @@ public class EmergencyAccessServiceTests
() => sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.Takeover, waitTime));
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
- await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs().CreateAsync(default);
+ }
+
+ [Theory]
+ [BitAutoData(EmergencyAccessType.Takeover)]
+ [BitAutoData(EmergencyAccessType.View)]
+ public async Task InviteAsync_ReturnsEmergencyAccessObject(
+ EmergencyAccessType accessType, SutProvider sutProvider, User invitingUser, string email, int waitTime)
+ {
+ sutProvider.GetDependency().CanAccessPremium(invitingUser).Returns(true);
+
+ var result = await sutProvider.Sut.InviteAsync(invitingUser, email, accessType, waitTime);
+
+ Assert.NotNull(result);
+ Assert.Equal(accessType, result.Type);
+ Assert.Equal(invitingUser.Id, result.GrantorId);
+ Assert.Equal(email, result.Email);
+ Assert.Equal(EmergencyAccessStatusType.Invited, result.Status);
+ await sutProvider.GetDependency()
+ .Received(1)
+ .CreateAsync(Arg.Any());
+ sutProvider.GetDependency>()
+ .Received(1)
+ .Protect(Arg.Any());
+ await sutProvider.GetDependency()
+ .Received(1)
+ .SendEmergencyAccessInviteEmailAsync(Arg.Any(), Arg.Any(), Arg.Any());
}
[Theory, BitAutoData]
- public async Task ConfirmUserAsync_UserWithKeyConnectorCannotUseTakeover(
+ public async Task GetAsync_EmergencyAccessNull_ThrowsBadRequest(
+ SutProvider sutProvider, User user)
+ {
+ EmergencyAccessDetails emergencyAccess = null;
+ sutProvider.GetDependency()
+ .GetDetailsByIdGrantorIdAsync(Arg.Any(), Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.GetAsync(new Guid(), user.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ResendInviteAsync_EmergencyAccessNull_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User invitingUser,
+ Guid emergencyAccessId)
+ {
+ EmergencyAccess emergencyAccess = null;
+
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ResendInviteAsync(invitingUser, emergencyAccessId));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .SendEmergencyAccessInviteEmailAsync(default, default, default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ResendInviteAsync_InvitingUserIdNotGrantorUserId_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User invitingUser,
+ Guid emergencyAccessId)
+ {
+ var emergencyAccess = new EmergencyAccess
+ {
+ Status = EmergencyAccessStatusType.Invited,
+ GrantorId = Guid.NewGuid(),
+ Type = EmergencyAccessType.Takeover,
+ }; ;
+
+ sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ResendInviteAsync(invitingUser, emergencyAccessId));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .SendEmergencyAccessInviteEmailAsync(default, default, default);
+ }
+
+ [Theory]
+ [BitAutoData(EmergencyAccessStatusType.Accepted)]
+ [BitAutoData(EmergencyAccessStatusType.Confirmed)]
+ [BitAutoData(EmergencyAccessStatusType.RecoveryInitiated)]
+ [BitAutoData(EmergencyAccessStatusType.RecoveryApproved)]
+ public async Task ResendInviteAsync_EmergencyAccessStatusInvalid_ThrowsBadRequest(
+ EmergencyAccessStatusType statusType,
+ SutProvider sutProvider,
+ User invitingUser,
+ Guid emergencyAccessId)
+ {
+ var emergencyAccess = new EmergencyAccess
+ {
+ Status = statusType,
+ GrantorId = invitingUser.Id,
+ Type = EmergencyAccessType.Takeover,
+ };
+
+ sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ResendInviteAsync(invitingUser, emergencyAccessId));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .SendEmergencyAccessInviteEmailAsync(default, default, default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ResendInviteAsync_SendsInviteAsync(
+ SutProvider sutProvider,
+ User invitingUser,
+ Guid emergencyAccessId)
+ {
+ var emergencyAccess = new EmergencyAccess
+ {
+ Status = EmergencyAccessStatusType.Invited,
+ GrantorId = invitingUser.Id,
+ Type = EmergencyAccessType.Takeover,
+ }; ;
+
+ sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess);
+
+ await sutProvider.Sut.ResendInviteAsync(invitingUser, emergencyAccessId);
+ sutProvider.GetDependency>()
+ .Received(1)
+ .Protect(Arg.Any());
+ await sutProvider.GetDependency()
+ .Received(1)
+ .SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUser.Name, Arg.Any());
+ }
+
+ [Theory, BitAutoData]
+ public async Task AcceptUserAsync_EmergencyAccessNull_ThrowsBadRequest(
+ SutProvider sutProvider, User acceptingUser, string token)
+ {
+ EmergencyAccess emergencyAccess = null;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.AcceptUserAsync(new Guid(), acceptingUser, token, sutProvider.GetDependency()));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task AcceptUserAsync_CannotUnprotectToken_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User acceptingUser,
+ EmergencyAccess emergencyAccess,
+ string token)
+ {
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency>()
+ .TryUnprotect(token, out Arg.Any())
+ .Returns(false);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency()));
+
+ Assert.Contains("Invalid token.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task AcceptUserAsync_TokenDataInvalid_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User acceptingUser,
+ EmergencyAccess emergencyAccess,
+ EmergencyAccess wrongEmergencyAccess,
+ string token)
+ {
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency>()
+ .TryUnprotect(token, out Arg.Any())
+ .Returns(callInfo =>
+ {
+ callInfo[1] = new EmergencyAccessInviteTokenable(wrongEmergencyAccess, 1);
+ return true;
+ });
+
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency()));
+
+ Assert.Contains("Invalid token.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task AcceptUserAsync_AcceptedStatus_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User acceptingUser,
+ EmergencyAccess emergencyAccess,
+ string token)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
+ emergencyAccess.Email = acceptingUser.Email;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency>()
+ .TryUnprotect(token, out Arg.Any())
+ .Returns(callInfo =>
+ {
+ callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
+ return true;
+ });
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency()));
+
+ Assert.Contains("Invitation already accepted. You will receive an email when the grantor confirms you as an emergency access contact.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task AcceptUserAsync_NotInvitedStatus_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User acceptingUser,
+ EmergencyAccess emergencyAccess,
+ string token)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.Confirmed;
+ emergencyAccess.Email = acceptingUser.Email;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency>()
+ .TryUnprotect(token, out Arg.Any())
+ .Returns(callInfo =>
+ {
+ callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
+ return true;
+ });
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency()));
+
+ Assert.Contains("Invitation already accepted.", exception.Message);
+ }
+
+ [Theory(Skip = "Code not reachable, Tokenable checks email match in IsValid()"), BitAutoData]
+ public async Task AcceptUserAsync_EmergencyAccessEmailDoesNotMatch_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User acceptingUser,
+ EmergencyAccess emergencyAccess,
+ string token)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.Invited;
+ emergencyAccess.Email = acceptingUser.Email;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency>()
+ .TryUnprotect(token, out Arg.Any())
+ .Returns(callInfo =>
+ {
+ callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
+ return true;
+ });
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency()));
+
+ Assert.Contains("User email does not match invite.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task AcceptUserAsync_ReplaceEmergencyAccess_SendsEmail_Success(
+ SutProvider sutProvider,
+ User acceptingUser,
+ User invitingUser,
+ EmergencyAccess emergencyAccess,
+ string token)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.Invited;
+ emergencyAccess.Email = acceptingUser.Email;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency()
+ .GetUserByIdAsync(Arg.Any())
+ .Returns(invitingUser);
+
+ sutProvider.GetDependency>()
+ .TryUnprotect(token, out Arg.Any())
+ .Returns(callInfo =>
+ {
+ callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 1);
+ return true;
+ });
+
+ await sutProvider.Sut.AcceptUserAsync(emergencyAccess.Id, acceptingUser, token, sutProvider.GetDependency());
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(Arg.Is(x => x.Status == EmergencyAccessStatusType.Accepted));
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .SendEmergencyAccessAcceptedEmailAsync(acceptingUser.Email, invitingUser.Email);
+ }
+
+ [Theory, BitAutoData]
+ public async Task DeleteAsync_EmergencyAccessNull_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User invitingUser,
+ EmergencyAccess emergencyAccess)
+ {
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns((EmergencyAccess)null);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.DeleteAsync(emergencyAccess.Id, invitingUser.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task DeleteAsync_EmergencyAccessGrantorIdNotEqual_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User invitingUser,
+ EmergencyAccess emergencyAccess)
+ {
+ emergencyAccess.GrantorId = Guid.NewGuid();
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.DeleteAsync(emergencyAccess.Id, invitingUser.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task DeleteAsync_EmergencyAccessGranteeIdNotEqual_ThrowsBadRequest(
+ SutProvider sutProvider,
+ User invitingUser,
+ EmergencyAccess emergencyAccess)
+ {
+ emergencyAccess.GranteeId = Guid.NewGuid();
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.DeleteAsync(emergencyAccess.Id, invitingUser.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task DeleteAsync_EmergencyAccessIsDeleted_Success(
+ SutProvider sutProvider,
+ User user,
+ EmergencyAccess emergencyAccess)
+ {
+ emergencyAccess.GranteeId = user.Id;
+ emergencyAccess.GrantorId = user.Id;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ await sutProvider.Sut.DeleteAsync(emergencyAccess.Id, user.Id);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .DeleteAsync(emergencyAccess);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ConfirmUserAsync_EmergencyAccessNull_ThrowsBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ string key,
+ User grantorUser)
+ {
+ emergencyAccess.GrantorId = grantorUser.Id;
+ emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns((EmergencyAccess)null);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ConfirmUserAsync(emergencyAccess.Id, key, grantorUser.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ConfirmUserAsync_EmergencyAccessStatusIsNotAccepted_ThrowsBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ string key,
+ User grantorUser)
+ {
+ emergencyAccess.GrantorId = grantorUser.Id;
+ emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
+ sutProvider.GetDependency()
+ .GetByIdAsync(emergencyAccess.Id)
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ConfirmUserAsync(emergencyAccess.Id, key, grantorUser.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+
+ }
+
+ [Theory, BitAutoData]
+ public async Task ConfirmUserAsync_EmergencyAccessGrantorIdNotEqualToConfirmingUserId_ThrowsBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ string key,
+ User grantorUser)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ConfirmUserAsync(emergencyAccess.Id, key, grantorUser.Id));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ConfirmUserAsync_UserWithKeyConnectorCannotUseTakeover_ThrowsBadRequest(
SutProvider sutProvider, User confirmingUser, string key)
{
confirmingUser.UsesKeyConnector = true;
@@ -62,8 +512,13 @@ public class EmergencyAccessServiceTests
Type = EmergencyAccessType.Takeover,
};
- sutProvider.GetDependency().GetByIdAsync(confirmingUser.Id).Returns(confirmingUser);
- sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess);
+ sutProvider.GetDependency()
+ .GetByIdAsync(confirmingUser.Id)
+ .Returns(confirmingUser);
+
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
var exception = await Assert.ThrowsAsync(
() => sutProvider.Sut.ConfirmUserAsync(new Guid(), key, confirmingUser.Id));
@@ -73,29 +528,210 @@ public class EmergencyAccessServiceTests
}
[Theory, BitAutoData]
- public async Task SaveAsync_UserWithKeyConnectorCannotUseTakeover(
+ public async Task ConfirmUserAsync_ConfirmsAndReplacesEmergencyAccess_Success(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ string key,
+ User grantorUser,
+ User granteeUser)
+ {
+ emergencyAccess.GrantorId = grantorUser.Id;
+ emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ sutProvider.GetDependency()
+ .GetByIdAsync(grantorUser.Id)
+ .Returns(grantorUser);
+
+ sutProvider.GetDependency()
+ .GetByIdAsync(emergencyAccess.GranteeId.Value)
+ .Returns(granteeUser);
+
+ await sutProvider.Sut.ConfirmUserAsync(emergencyAccess.Id, key, grantorUser.Id);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(Arg.Is(x => x.Status == EmergencyAccessStatusType.Confirmed));
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .SendEmergencyAccessConfirmedEmailAsync(grantorUser.Name, granteeUser.Email);
+ }
+
+ [Theory, BitAutoData]
+ public async Task SaveAsync_PremiumCannotUpdate_ThrowsBadRequest(
SutProvider sutProvider, User savingUser)
{
- savingUser.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Type = EmergencyAccessType.Takeover,
GrantorId = savingUser.Id,
};
- var userService = sutProvider.GetDependency();
- userService.GetUserByIdAsync(savingUser.Id).Returns(savingUser);
- userService.CanAccessPremium(savingUser).Returns(true);
+ sutProvider.GetDependency()
+ .CanAccessPremium(savingUser)
+ .Returns(false);
var exception = await Assert.ThrowsAsync(
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
+ Assert.Contains("Not a premium user.", exception.Message);
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task SaveAsync_EmergencyAccessGrantorIdNotEqualToSavingUserId_ThrowsBadRequest(
+ SutProvider sutProvider, User savingUser)
+ {
+ savingUser.Premium = true;
+ var emergencyAccess = new EmergencyAccess
+ {
+ Type = EmergencyAccessType.Takeover,
+ GrantorId = new Guid(),
+ };
+
+ sutProvider.GetDependency()
+ .GetUserByIdAsync(savingUser.Id)
+ .Returns(savingUser);
+ sutProvider.GetDependency()
+ .CanAccessPremium(savingUser)
+ .Returns(true);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task SaveAsync_GrantorUserWithKeyConnectorCannotTakeover_ThrowsBadRequest(
+ SutProvider sutProvider, User grantorUser)
+ {
+ grantorUser.UsesKeyConnector = true;
+ var emergencyAccess = new EmergencyAccess
+ {
+ Type = EmergencyAccessType.Takeover,
+ GrantorId = grantorUser.Id,
+ };
+
+ var userService = sutProvider.GetDependency();
+ userService.GetUserByIdAsync(grantorUser.Id).Returns(grantorUser);
+ userService.CanAccessPremium(grantorUser).Returns(true);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.SaveAsync(emergencyAccess, grantorUser));
+
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, BitAutoData]
- public async Task InitiateAsync_UserWithKeyConnectorCannotUseTakeover(
+ public async Task SaveAsync_GrantorUserWithKeyConnectorCanView_SavesEmergencyAccess(
+ SutProvider sutProvider, User grantorUser)
+ {
+ grantorUser.UsesKeyConnector = true;
+ var emergencyAccess = new EmergencyAccess
+ {
+ Type = EmergencyAccessType.View,
+ GrantorId = grantorUser.Id,
+ };
+
+ var userService = sutProvider.GetDependency();
+ userService.GetUserByIdAsync(grantorUser.Id).Returns(grantorUser);
+ userService.CanAccessPremium(grantorUser).Returns(true);
+
+ await sutProvider.Sut.SaveAsync(emergencyAccess, grantorUser);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(emergencyAccess);
+ }
+
+ [Theory, BitAutoData]
+ public async Task SaveAsync_ValidRequest_SavesEmergencyAccess(
+ SutProvider sutProvider, User grantorUser)
+ {
+ grantorUser.UsesKeyConnector = false;
+ var emergencyAccess = new EmergencyAccess
+ {
+ Type = EmergencyAccessType.Takeover,
+ GrantorId = grantorUser.Id,
+ };
+
+ var userService = sutProvider.GetDependency();
+ userService.GetUserByIdAsync(grantorUser.Id).Returns(grantorUser);
+ userService.CanAccessPremium(grantorUser).Returns(true);
+
+ await sutProvider.Sut.SaveAsync(emergencyAccess, grantorUser);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(emergencyAccess);
+ }
+
+ [Theory, BitAutoData]
+ public async Task InitiateAsync_EmergencyAccessNull_ThrowBadRequest(
+ SutProvider sutProvider, User initiatingUser)
+ {
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns((EmergencyAccess)null);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task InitiateAsync_EmergencyAccessGranteeIdNotEqual_ThrowBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User initiatingUser)
+ {
+ emergencyAccess.GranteeId = new Guid();
+ sutProvider.GetDependency()
+ .GetByIdAsync(emergencyAccess.Id)
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task InitiateAsync_EmergencyAccessStatusIsNotConfirmed_ThrowBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User initiatingUser)
+ {
+ emergencyAccess.GranteeId = initiatingUser.Id;
+ emergencyAccess.Status = EmergencyAccessStatusType.Invited;
+ sutProvider.GetDependency()
+ .GetByIdAsync(emergencyAccess.Id)
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .ReplaceAsync(default);
+ }
+
+ [Theory, BitAutoData]
+ public async Task InitiateAsync_UserWithKeyConnectorCannotUseTakeover_ThrowsBadRequest(
SutProvider sutProvider, User initiatingUser, User grantor)
{
grantor.UsesKeyConnector = true;
@@ -107,40 +743,711 @@ public class EmergencyAccessServiceTests
Type = EmergencyAccessType.Takeover,
};
- sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess);
- sutProvider.GetDependency().GetByIdAsync(grantor.Id).Returns(grantor);
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+ sutProvider.GetDependency()
+ .GetByIdAsync(grantor.Id)
+ .Returns(grantor);
var exception = await Assert.ThrowsAsync(
() => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
- await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
+ await sutProvider.GetDependency()
+ .DidNotReceiveWithAnyArgs()
+ .ReplaceAsync(default);
}
[Theory, BitAutoData]
- public async Task TakeoverAsync_UserWithKeyConnectorCannotUseTakeover(
- SutProvider sutProvider, User requestingUser, User grantor)
+ public async Task InitiateAsync_UserWithKeyConnectorCanView_Success(
+ SutProvider sutProvider, User initiatingUser, User grantor)
+ {
+ grantor.UsesKeyConnector = true;
+ var emergencyAccess = new EmergencyAccess
+ {
+ Status = EmergencyAccessStatusType.Confirmed,
+ GranteeId = initiatingUser.Id,
+ GrantorId = grantor.Id,
+ Type = EmergencyAccessType.View,
+ };
+
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+ sutProvider.GetDependency()
+ .GetByIdAsync(grantor.Id)
+ .Returns(grantor);
+
+ await sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(Arg.Is(x => x.Status == EmergencyAccessStatusType.RecoveryInitiated));
+ }
+
+ [Theory, BitAutoData]
+ public async Task InitiateAsync_RequestIsCorrect_Success(
+ SutProvider sutProvider, User initiatingUser, User grantor)
+ {
+ var emergencyAccess = new EmergencyAccess
+ {
+ Status = EmergencyAccessStatusType.Confirmed,
+ GranteeId = initiatingUser.Id,
+ GrantorId = grantor.Id,
+ Type = EmergencyAccessType.Takeover,
+ };
+
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+ sutProvider.GetDependency()
+ .GetByIdAsync(grantor.Id)
+ .Returns(grantor);
+
+ await sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(Arg.Is(x => x.Status == EmergencyAccessStatusType.RecoveryInitiated));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ApproveAsync_EmergencyAccessNull_ThrowsBadrequest(
+ SutProvider sutProvider)
+ {
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns((EmergencyAccess)null);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ApproveAsync(new Guid(), null));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ApproveAsync_EmergencyAccessGrantorIdNotEquatToApproving_ThrowsBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User grantorUser)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ApproveAsync(emergencyAccess.Id, grantorUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData(EmergencyAccessStatusType.Invited)]
+ [BitAutoData(EmergencyAccessStatusType.Accepted)]
+ [BitAutoData(EmergencyAccessStatusType.Confirmed)]
+ [BitAutoData(EmergencyAccessStatusType.RecoveryApproved)]
+ public async Task ApproveAsync_EmergencyAccessStatusNotRecoveryInitiated_ThrowsBadRequest(
+ EmergencyAccessStatusType statusType,
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User grantorUser)
+ {
+ emergencyAccess.GrantorId = grantorUser.Id;
+ emergencyAccess.Status = statusType;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ApproveAsync(emergencyAccess.Id, grantorUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ApproveAsync_Success(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User grantorUser,
+ User granteeUser)
+ {
+ emergencyAccess.GrantorId = grantorUser.Id;
+ emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(granteeUser);
+
+ await sutProvider.Sut.ApproveAsync(emergencyAccess.Id, grantorUser);
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(Arg.Is(x => x.Status == EmergencyAccessStatusType.RecoveryApproved));
+ }
+
+ [Theory, BitAutoData]
+ public async Task RejectAsync_EmergencyAccessIdNull_ThrowsBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User GrantorUser)
+ {
+ emergencyAccess.GrantorId = GrantorUser.Id;
+ emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns((EmergencyAccess)null);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.RejectAsync(emergencyAccess.Id, GrantorUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory, BitAutoData]
+ public async Task RejectAsync_EmergencyAccessGrantorIdNotEqualToRequestUser_ThrowsBadRequest(
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User GrantorUser)
+ {
+ emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.RejectAsync(emergencyAccess.Id, GrantorUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData(EmergencyAccessStatusType.Invited)]
+ [BitAutoData(EmergencyAccessStatusType.Accepted)]
+ [BitAutoData(EmergencyAccessStatusType.Confirmed)]
+ public async Task RejectAsync_EmergencyAccessStatusNotValid_ThrowsBadRequest(
+ EmergencyAccessStatusType statusType,
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User GrantorUser)
+ {
+ emergencyAccess.GrantorId = GrantorUser.Id;
+ emergencyAccess.Status = statusType;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.RejectAsync(emergencyAccess.Id, GrantorUser));
+
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData(EmergencyAccessStatusType.RecoveryInitiated)]
+ [BitAutoData(EmergencyAccessStatusType.RecoveryApproved)]
+ public async Task RejectAsync_Success(
+ EmergencyAccessStatusType statusType,
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User GrantorUser,
+ User GranteeUser)
+ {
+ emergencyAccess.GrantorId = GrantorUser.Id;
+ emergencyAccess.Status = statusType;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(emergencyAccess);
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns(GranteeUser);
+
+ await sutProvider.Sut.RejectAsync(emergencyAccess.Id, GrantorUser);
+
+ await sutProvider.GetDependency()
+ .Received(1)
+ .ReplaceAsync(Arg.Is(x => x.Status == EmergencyAccessStatusType.Confirmed));
+ }
+
+ [Theory, BitAutoData]
+ public async Task GetPoliciesAsync_RequestNotValidEmergencyAccessNull_ThrowsBadRequest(
+ SutProvider sutProvider)
+ {
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any())
+ .Returns((EmergencyAccess)null);
+
+ var exception = await Assert.ThrowsAsync(
+ () => sutProvider.Sut.GetPoliciesAsync(default, default));
+ Assert.Contains("Emergency Access not valid.", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData(EmergencyAccessStatusType.Invited)]
+ [BitAutoData(EmergencyAccessStatusType.Accepted)]
+ [BitAutoData(EmergencyAccessStatusType.Confirmed)]
+ [BitAutoData(EmergencyAccessStatusType.RecoveryInitiated)]
+ public async Task GetPoliciesAsync_RequestNotValidStatusType_ThrowsBadRequest(
+ EmergencyAccessStatusType statusType,
+ SutProvider sutProvider,
+ EmergencyAccess emergencyAccess,
+ User granteeUser)
+ {
+ emergencyAccess.GranteeId = granteeUser.Id;
+ emergencyAccess.Status = statusType;
+ emergencyAccess.Type = EmergencyAccessType.Takeover;
+ sutProvider.GetDependency()
+ .GetByIdAsync(Arg.Any