mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 14:13:18 +08:00
feat(emergency-access) [PM-29584] Create Email for Emergency Access Removal (#6793)
* feat(emergency-access) [PM-29584]: Add email template. * refactor(emergency-access) [PM-29584]: Move Emergency Access to Auth/UserFeatures. * refactor(emergency-access) [PM-29584]: Move EmergencyAccess tests to UserFeatures space. * feat(emergency-access) [PM-29584]: Add compiled EmergencyAccess templates. * test(emergency-access) [PM-29584]: Add mailer-specific tests. * refactor(emergency-access) [PM-29584]: Move mail to UserFeatures area. * feat(emergency-access) [PM-29584]: Update link for help pages, not web vault. * test(emergency-access) [PM-29584]: Update mail tests for new URL and single responsibility. * refactor(emergency-access) [PM-29584]: Add comments for added test.
This commit is contained in:
@@ -7,7 +7,7 @@ using Bit.Api.Auth.Models.Request;
|
|||||||
using Bit.Api.Auth.Models.Response;
|
using Bit.Api.Auth.Models.Response;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Api.Vault.Models.Response;
|
using Bit.Api.Vault.Models.Response;
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// FIXME: Update this file to be null safe and then delete the line below
|
// FIXME: Update this file to be null safe and then delete the line below
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
using Bit.Core.Jobs;
|
using Bit.Core.Jobs;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// FIXME: Update this file to be null safe and then delete the line below
|
// FIXME: Update this file to be null safe and then delete the line below
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
using Bit.Core.Jobs;
|
using Bit.Core.Jobs;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
@@ -19,7 +18,7 @@ using Bit.Core.Vault.Models.Data;
|
|||||||
using Bit.Core.Vault.Repositories;
|
using Bit.Core.Vault.Repositories;
|
||||||
using Bit.Core.Vault.Services;
|
using Bit.Core.Vault.Services;
|
||||||
|
|
||||||
namespace Bit.Core.Auth.Services;
|
namespace Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
|
|
||||||
public class EmergencyAccessService : IEmergencyAccessService
|
public class EmergencyAccessService : IEmergencyAccessService
|
||||||
{
|
{
|
||||||
@@ -61,7 +60,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime)
|
public async Task<Entities.EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime)
|
||||||
{
|
{
|
||||||
if (!await _userService.CanAccessPremium(grantorUser))
|
if (!await _userService.CanAccessPremium(grantorUser))
|
||||||
{
|
{
|
||||||
@@ -73,7 +72,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector.");
|
throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
GrantorId = grantorUser.Id,
|
GrantorId = grantorUser.Id,
|
||||||
Email = emergencyContactEmail.ToLowerInvariant(),
|
Email = emergencyContactEmail.ToLowerInvariant(),
|
||||||
@@ -113,7 +112,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
await SendInviteAsync(emergencyAccess, NameOrEmail(grantorUser));
|
await SendInviteAsync(emergencyAccess, NameOrEmail(grantorUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService)
|
public async Task<Entities.EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService)
|
||||||
{
|
{
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||||
if (emergencyAccess == null)
|
if (emergencyAccess == null)
|
||||||
@@ -175,7 +174,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
await _emergencyAccessRepository.DeleteAsync(emergencyAccess);
|
await _emergencyAccessRepository.DeleteAsync(emergencyAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId)
|
public async Task<Entities.EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId)
|
||||||
{
|
{
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||||
if (emergencyAccess == null || emergencyAccess.Status != EmergencyAccessStatusType.Accepted ||
|
if (emergencyAccess == null || emergencyAccess.Status != EmergencyAccessStatusType.Accepted ||
|
||||||
@@ -201,7 +200,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
return emergencyAccess;
|
return emergencyAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync(EmergencyAccess emergencyAccess, User grantorUser)
|
public async Task SaveAsync(Entities.EmergencyAccess emergencyAccess, User grantorUser)
|
||||||
{
|
{
|
||||||
if (!await _userService.CanAccessPremium(grantorUser))
|
if (!await _userService.CanAccessPremium(grantorUser))
|
||||||
{
|
{
|
||||||
@@ -311,7 +310,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO PM-21687: rename this to something like InitiateRecoveryTakeoverAsync
|
// TODO PM-21687: rename this to something like InitiateRecoveryTakeoverAsync
|
||||||
public async Task<(EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser)
|
public async Task<(Entities.EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser)
|
||||||
{
|
{
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||||
|
|
||||||
@@ -429,7 +428,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
return await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
return await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendInviteAsync(EmergencyAccess emergencyAccess, string invitingUsersName)
|
private async Task SendInviteAsync(Entities.EmergencyAccess emergencyAccess, string invitingUsersName)
|
||||||
{
|
{
|
||||||
var token = _dataProtectorTokenizer.Protect(new EmergencyAccessInviteTokenable(emergencyAccess, _globalSettings.OrganizationInviteExpirationHours));
|
var token = _dataProtectorTokenizer.Protect(new EmergencyAccessInviteTokenable(emergencyAccess, _globalSettings.OrganizationInviteExpirationHours));
|
||||||
await _mailService.SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUsersName, token);
|
await _mailService.SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUsersName, token);
|
||||||
@@ -449,7 +448,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
*/
|
*/
|
||||||
//TODO PM-21687: this IsValidRequest() checks the validity based on the granteeUser. There should be a complementary method for the grantorUser
|
//TODO PM-21687: this IsValidRequest() checks the validity based on the granteeUser. There should be a complementary method for the grantorUser
|
||||||
private static bool IsValidRequest(
|
private static bool IsValidRequest(
|
||||||
EmergencyAccess availableAccess,
|
Entities.EmergencyAccess availableAccess,
|
||||||
User requestingUser,
|
User requestingUser,
|
||||||
EmergencyAccessType requestedAccessType)
|
EmergencyAccessType requestedAccessType)
|
||||||
{
|
{
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Auth.Entities;
|
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
@@ -7,7 +6,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Vault.Models.Data;
|
using Bit.Core.Vault.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Auth.Services;
|
namespace Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
|
|
||||||
public interface IEmergencyAccessService
|
public interface IEmergencyAccessService
|
||||||
{
|
{
|
||||||
@@ -20,7 +19,7 @@ public interface IEmergencyAccessService
|
|||||||
/// <param name="accessType">Type of emergency access allowed to the emergency contact</param>
|
/// <param name="accessType">Type of emergency access allowed to the emergency contact</param>
|
||||||
/// <param name="waitTime">The amount of time to pass before the invite is auto confirmed</param>
|
/// <param name="waitTime">The amount of time to pass before the invite is auto confirmed</param>
|
||||||
/// <returns>a new Emergency Access object</returns>
|
/// <returns>a new Emergency Access object</returns>
|
||||||
Task<EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime);
|
Task<Entities.EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends an invite to the emergency contact associated with the emergency access id.
|
/// Sends an invite to the emergency contact associated with the emergency access id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -37,7 +36,7 @@ public interface IEmergencyAccessService
|
|||||||
/// <param name="token">the tokenable that was sent via email</param>
|
/// <param name="token">the tokenable that was sent via email</param>
|
||||||
/// <param name="userService">service dependency</param>
|
/// <param name="userService">service dependency</param>
|
||||||
/// <returns>void</returns>
|
/// <returns>void</returns>
|
||||||
Task<EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService);
|
Task<Entities.EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The creator of the emergency access request can delete the request.
|
/// The creator of the emergency access request can delete the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -53,7 +52,7 @@ public interface IEmergencyAccessService
|
|||||||
/// <param name="key">The grantor user key encrypted by the grantee public key; grantee.PubicKey(grantor.User.Key)</param>
|
/// <param name="key">The grantor user key encrypted by the grantee public key; grantee.PubicKey(grantor.User.Key)</param>
|
||||||
/// <param name="grantorId">Id of grantor user</param>
|
/// <param name="grantorId">Id of grantor user</param>
|
||||||
/// <returns>emergency access object associated with the Id passed in</returns>
|
/// <returns>emergency access object associated with the Id passed in</returns>
|
||||||
Task<EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId);
|
Task<Entities.EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches an emergency access object. The grantor user must own the object being fetched.
|
/// Fetches an emergency access object. The grantor user must own the object being fetched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -67,7 +66,7 @@ public interface IEmergencyAccessService
|
|||||||
/// <param name="emergencyAccess">emergency access entity being updated</param>
|
/// <param name="emergencyAccess">emergency access entity being updated</param>
|
||||||
/// <param name="grantorUser">grantor user</param>
|
/// <param name="grantorUser">grantor user</param>
|
||||||
/// <returns>void</returns>
|
/// <returns>void</returns>
|
||||||
Task SaveAsync(EmergencyAccess emergencyAccess, User grantorUser);
|
Task SaveAsync(Entities.EmergencyAccess emergencyAccess, User grantorUser);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initiates the recovery process. For either Takeover or view. Will send an email to the Grantor User notifying of the initiation.
|
/// Initiates the recovery process. For either Takeover or view. Will send an email to the Grantor User notifying of the initiation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -107,7 +106,7 @@ public interface IEmergencyAccessService
|
|||||||
/// <param name="emergencyAccessId">Id of entity being accessed</param>
|
/// <param name="emergencyAccessId">Id of entity being accessed</param>
|
||||||
/// <param name="granteeUser">grantee user of the emergency access entity</param>
|
/// <param name="granteeUser">grantee user of the emergency access entity</param>
|
||||||
/// <returns>emergency access entity and the grantorUser</returns>
|
/// <returns>emergency access entity and the grantorUser</returns>
|
||||||
Task<(EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser);
|
Task<(Entities.EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the grantor's password hash and updates the key for the EmergencyAccess entity.
|
/// Updates the grantor's password hash and updates the key for the EmergencyAccess entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.Core.Platform.Mail.Mailer;
|
||||||
|
|
||||||
|
namespace Bit.Core.Auth.UserFeatures.EmergencyAccess.Mail;
|
||||||
|
|
||||||
|
public class EmergencyAccessRemoveGranteesMailView : BaseMailView
|
||||||
|
{
|
||||||
|
public required IEnumerable<string> RemovedGranteeNames { get; set; }
|
||||||
|
public string EmergencyAccessHelpPageUrl => "https://bitwarden.com/help/emergency-access/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EmergencyAccessRemoveGranteesMail : BaseMail<EmergencyAccessRemoveGranteesMailView>
|
||||||
|
{
|
||||||
|
public override string Subject { get; set; } = "Emergency contacts removed";
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<!--<![endif]-->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style type="text/css">
|
||||||
|
#outlook a { padding:0; }
|
||||||
|
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
|
||||||
|
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
|
||||||
|
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
|
||||||
|
p { display:block;margin:13px 0; }
|
||||||
|
</style>
|
||||||
|
<!--[if mso]>
|
||||||
|
<noscript>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
</noscript>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.mj-outlook-group-fix { width:100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (min-width:480px) {
|
||||||
|
.mj-column-per-70 { width:70% !important; max-width: 70%; }
|
||||||
|
.mj-column-per-30 { width:30% !important; max-width: 30%; }
|
||||||
|
.mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style media="screen and (min-width:480px)">
|
||||||
|
.moz-text-html .mj-column-per-70 { width:70% !important; max-width: 70%; }
|
||||||
|
.moz-text-html .mj-column-per-30 { width:30% !important; max-width: 30%; }
|
||||||
|
.moz-text-html .mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
@media only screen and (max-width:480px) {
|
||||||
|
.mj-bw-hero-responsive-img {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (max-width:479px) {
|
||||||
|
table.mj-full-width-mobile { width: 100% !important; }
|
||||||
|
td.mj-full-width-mobile { width: auto !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.border-fix > table {
|
||||||
|
border-collapse: separate !important;
|
||||||
|
}
|
||||||
|
.border-fix > table > tbody > tr > td {
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body style="word-spacing:normal;background-color:#e6e9ef;">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="border-fix" style="background-color:#e6e9ef;" lang="und" dir="auto">
|
||||||
|
<!-- Blue Header Section -->
|
||||||
|
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="border-fix-outlook" role="presentation" style="width:660px;" width="660" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div class="border-fix" style="margin:0px auto;max-width:660px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 20px 0px 20px;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="660px" ><![endif]-->
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#175ddc;background-color:#175ddc;width:100%;border-radius:4px 4px 0px 0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:620px;" width="620" bgcolor="#175ddc" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div style="margin:0px auto;border-radius:4px 4px 0px 0px;max-width:620px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;border-radius:4px 4px 0px 0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:434px;" ><![endif]-->
|
||||||
|
|
||||||
|
<div class="mj-column-per-70 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:150px;">
|
||||||
|
|
||||||
|
<img alt src="https://bitwarden.com/images/logo-horizontal-white.png" style="border:0;display:block;outline:none;text-decoration:none;height:30px;width:100%;font-size:16px;" width="150" height="30">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0;padding-bottom:0;word-break:break-word;">
|
||||||
|
|
||||||
|
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:left;color:#ffffff;"><h1 style="font-weight: normal; font-size: 24px; line-height: 32px">
|
||||||
|
|
||||||
|
</h1></div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td><td class="" style="vertical-align:bottom;width:186px;" ><![endif]-->
|
||||||
|
|
||||||
|
<div class="mj-column-per-30 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:bottom;width:100%;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:bottom;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" class="mj-bw-hero-responsive-img" style="font-size:0px;padding:0px;word-break:break-word;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:155px;">
|
||||||
|
|
||||||
|
<img alt src="undefined" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:16px;" width="155" height="auto">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:660px;" width="660" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div style="margin:0px auto;max-width:660px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:0px 20px 0px 20px;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="660px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:620px;" width="620" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:620px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:0px 10px 0px 10px;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
|
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 15px;word-break:break-word;">
|
||||||
|
|
||||||
|
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:16px;line-height:24px;text-align:left;color:#1B2029;">The following emergency contacts have been removed from your account:
|
||||||
|
<ul>
|
||||||
|
{{#each RemovedGranteeNames}}
|
||||||
|
<li>{{this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
Learn more about <a href="{{EmergencyAccessHelpPageUrl}}">emergency access</a>.</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:660px;" width="660" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div style="margin:0px auto;max-width:660px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:5px 20px 10px 20px;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:620px;" ><![endif]-->
|
||||||
|
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:0;word-break:break-word;">
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://x.com/bitwarden" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-x-twitter.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://www.reddit.com/r/Bitwarden/" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-reddit.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://community.bitwarden.com/" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-discourse.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://github.com/bitwarden" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-github.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://www.youtube.com/channel/UCId9a_jQqvJre0_dE2lE_Rw" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-youtube.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://www.linkedin.com/company/bitwarden1/" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-linkedin.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td><td><![endif]-->
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px;vertical-align:middle;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||||
|
<a href="https://www.facebook.com/bitwarden/" target="_blank">
|
||||||
|
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-facebook.png" style="border-radius:3px;display:block;" width="24">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:12px;line-height:16px;text-align:center;color:#5A6D91;"><p style="margin-bottom: 5px; margin-top: 5px">
|
||||||
|
© 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa
|
||||||
|
Barbara, CA, USA
|
||||||
|
</p>
|
||||||
|
<p style="margin-top: 5px">
|
||||||
|
Always confirm you are on a trusted Bitwarden domain before logging
|
||||||
|
in:<br>
|
||||||
|
<a href="https://bitwarden.com/" style="text-decoration:none;color:#175ddc; font-weight:400">bitwarden.com</a> |
|
||||||
|
<a href="https://bitwarden.com/help/emails-from-bitwarden/" style="text-decoration:none; color:#175ddc; font-weight:400">Learn why we include this</a>
|
||||||
|
</p></div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
The following emergency contacts have been removed from your account:
|
||||||
|
|
||||||
|
{{#each RemovedGranteeNames}}
|
||||||
|
{{this}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
Learn more about emergency access at {{EmergencyAccessHelpPageUrl}}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<mjml>
|
||||||
|
<mj-head>
|
||||||
|
<mj-include path="../../../../components/head.mjml" />
|
||||||
|
</mj-head>
|
||||||
|
<mj-body css-class="border-fix">
|
||||||
|
<!-- Blue Header Section -->
|
||||||
|
<mj-wrapper css-class="border-fix" padding="20px 20px 0px 20px">
|
||||||
|
<mj-bw-hero title=""/>
|
||||||
|
</mj-wrapper>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<mj-wrapper padding="0px 20px 0px 20px">
|
||||||
|
<mj-section background-color="#fff" padding="0px 10px 0px 10px">
|
||||||
|
<mj-column>
|
||||||
|
<mj-text font-size="16px" line-height="24px" padding="10px 15px">
|
||||||
|
The following emergency contacts have been removed from your account:
|
||||||
|
<ul>
|
||||||
|
{{#each RemovedGranteeNames}}
|
||||||
|
<li>{{this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
Learn more about <a href="{{EmergencyAccessHelpPageUrl}}">emergency access</a>.
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</mj-wrapper>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<mj-include path="../../../../components/footer.mjml" />
|
||||||
|
</mj-body>
|
||||||
|
</mjml>
|
||||||
@@ -22,6 +22,7 @@ using Bit.Core.Auth.Repositories;
|
|||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.Services;
|
||||||
using Bit.Core.Auth.Services.Implementations;
|
using Bit.Core.Auth.Services.Implementations;
|
||||||
using Bit.Core.Auth.UserFeatures;
|
using Bit.Core.Auth.UserFeatures;
|
||||||
|
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
using Bit.Core.Auth.UserFeatures.PasswordValidation;
|
using Bit.Core.Auth.UserFeatures.PasswordValidation;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Billing.Services.Implementations;
|
using Bit.Core.Billing.Services.Implementations;
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
using Bit.Core.Auth.UserFeatures.EmergencyAccess.Mail;
|
||||||
|
using Bit.Core.Models.Mail;
|
||||||
|
using Bit.Core.Platform.Mail.Delivery;
|
||||||
|
using Bit.Core.Platform.Mail.Mailer;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.Auth.UserFeatures.EmergencyAccess;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class EmergencyAccessMailTests
|
||||||
|
{
|
||||||
|
// Constant values for all Emergency Access emails
|
||||||
|
private const string _emergencyAccessHelpUrl = "https://bitwarden.com/help/emergency-access/";
|
||||||
|
private const string _emergencyAccessMailSubject = "Emergency contacts removed";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Documents how to construct and send the emergency access removal email.
|
||||||
|
/// 1. Inject IMailer into their command/service
|
||||||
|
/// 2. Construct EmergencyAccessRemoveGranteesMail as shown below
|
||||||
|
/// 3. Call mailer.SendEmail(mail)
|
||||||
|
/// </summary>
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SendEmergencyAccessRemoveGranteesEmail_SingleGrantee_Success(
|
||||||
|
string grantorEmail,
|
||||||
|
string granteeName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var logger = Substitute.For<ILogger<HandlebarMailRenderer>>();
|
||||||
|
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||||
|
var deliveryService = Substitute.For<IMailDeliveryService>();
|
||||||
|
var mailer = new Mailer(
|
||||||
|
new HandlebarMailRenderer(logger, globalSettings),
|
||||||
|
deliveryService);
|
||||||
|
|
||||||
|
var mail = new EmergencyAccessRemoveGranteesMail
|
||||||
|
{
|
||||||
|
ToEmails = [grantorEmail],
|
||||||
|
View = new EmergencyAccessRemoveGranteesMailView
|
||||||
|
{
|
||||||
|
RemovedGranteeNames = [granteeName]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MailMessage sentMessage = null;
|
||||||
|
await deliveryService.SendEmailAsync(Arg.Do<MailMessage>(message =>
|
||||||
|
sentMessage = message
|
||||||
|
));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await mailer.SendEmail(mail);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(sentMessage);
|
||||||
|
Assert.Contains(grantorEmail, sentMessage.ToEmails);
|
||||||
|
|
||||||
|
// Verify the content contains the grantee name
|
||||||
|
Assert.Contains(granteeName, sentMessage.TextContent);
|
||||||
|
Assert.Contains(granteeName, sentMessage.HtmlContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Documents handling multiple removed grantees in a single email.
|
||||||
|
/// </summary>
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SendEmergencyAccessRemoveGranteesEmail_MultipleGrantees_RendersAllNames(
|
||||||
|
string grantorEmail)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var logger = Substitute.For<ILogger<HandlebarMailRenderer>>();
|
||||||
|
var globalSettings = new GlobalSettings { SelfHosted = false };
|
||||||
|
var deliveryService = Substitute.For<IMailDeliveryService>();
|
||||||
|
var mailer = new Mailer(
|
||||||
|
new HandlebarMailRenderer(logger, globalSettings),
|
||||||
|
deliveryService);
|
||||||
|
|
||||||
|
var granteeNames = new[] { "Alice", "Bob", "Carol" };
|
||||||
|
|
||||||
|
var mail = new EmergencyAccessRemoveGranteesMail
|
||||||
|
{
|
||||||
|
ToEmails = [grantorEmail],
|
||||||
|
View = new EmergencyAccessRemoveGranteesMailView
|
||||||
|
{
|
||||||
|
RemovedGranteeNames = granteeNames
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MailMessage sentMessage = null;
|
||||||
|
await deliveryService.SendEmailAsync(Arg.Do<MailMessage>(message =>
|
||||||
|
sentMessage = message
|
||||||
|
));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await mailer.SendEmail(mail);
|
||||||
|
|
||||||
|
// Assert - All grantee names should appear in the email
|
||||||
|
Assert.NotNull(sentMessage);
|
||||||
|
foreach (var granteeName in granteeNames)
|
||||||
|
{
|
||||||
|
Assert.Contains(granteeName, sentMessage.TextContent);
|
||||||
|
Assert.Contains(granteeName, sentMessage.HtmlContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the required GranteeNames for the email view model.
|
||||||
|
/// </summary>
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public void EmergencyAccessRemoveGranteesMailView_GranteeNames_AreRequired(
|
||||||
|
string grantorEmail)
|
||||||
|
{
|
||||||
|
// Arrange - Shows the minimum required to construct the email
|
||||||
|
var mail = new EmergencyAccessRemoveGranteesMail
|
||||||
|
{
|
||||||
|
ToEmails = [grantorEmail], // Required: who to send to
|
||||||
|
View = new EmergencyAccessRemoveGranteesMailView
|
||||||
|
{
|
||||||
|
// Required: at least one removed grantee name
|
||||||
|
RemovedGranteeNames = ["Example Grantee"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(mail);
|
||||||
|
Assert.NotNull(mail.View);
|
||||||
|
Assert.NotEmpty(mail.View.RemovedGranteeNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure consistency with help pages link and email subject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grantorEmail"></param>
|
||||||
|
/// <param name="granteeName"></param>
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public void EmergencyAccessRemoveGranteesMailView_SubjectAndHelpLink_MatchesExpectedValues(string grantorEmail, string granteeName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mail = new EmergencyAccessRemoveGranteesMail
|
||||||
|
{
|
||||||
|
ToEmails = [grantorEmail],
|
||||||
|
View = new EmergencyAccessRemoveGranteesMailView { RemovedGranteeNames = [granteeName] }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(mail);
|
||||||
|
Assert.NotNull(mail.View);
|
||||||
|
Assert.Equal(_emergencyAccessMailSubject, mail.Subject);
|
||||||
|
Assert.Equal(_emergencyAccessHelpUrl, mail.View.EmergencyAccessHelpPageUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@@ -17,7 +16,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Core.Test.Auth.Services;
|
namespace Bit.Core.Test.Auth.UserFeatures.EmergencyAccess;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class EmergencyAccessServiceTests
|
public class EmergencyAccessServiceTests
|
||||||
@@ -68,13 +67,13 @@ public class EmergencyAccessServiceTests
|
|||||||
Assert.Equal(EmergencyAccessStatusType.Invited, result.Status);
|
Assert.Equal(EmergencyAccessStatusType.Invited, result.Status);
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.CreateAsync(Arg.Any<EmergencyAccess>());
|
.CreateAsync(Arg.Any<Core.Auth.Entities.EmergencyAccess>());
|
||||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.Protect(Arg.Any<EmergencyAccessInviteTokenable>());
|
.Protect(Arg.Any<EmergencyAccessInviteTokenable>());
|
||||||
await sutProvider.GetDependency<IMailService>()
|
await sutProvider.GetDependency<IMailService>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.SendEmergencyAccessInviteEmailAsync(Arg.Any<EmergencyAccess>(), Arg.Any<string>(), Arg.Any<string>());
|
.SendEmergencyAccessInviteEmailAsync(Arg.Any<Core.Auth.Entities.EmergencyAccess>(), Arg.Any<string>(), Arg.Any<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@@ -98,7 +97,7 @@ public class EmergencyAccessServiceTests
|
|||||||
User invitingUser,
|
User invitingUser,
|
||||||
Guid emergencyAccessId)
|
Guid emergencyAccessId)
|
||||||
{
|
{
|
||||||
EmergencyAccess emergencyAccess = null;
|
Core.Auth.Entities.EmergencyAccess emergencyAccess = null;
|
||||||
|
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
@@ -119,7 +118,7 @@ public class EmergencyAccessServiceTests
|
|||||||
User invitingUser,
|
User invitingUser,
|
||||||
Guid emergencyAccessId)
|
Guid emergencyAccessId)
|
||||||
{
|
{
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = EmergencyAccessStatusType.Invited,
|
Status = EmergencyAccessStatusType.Invited,
|
||||||
GrantorId = Guid.NewGuid(),
|
GrantorId = Guid.NewGuid(),
|
||||||
@@ -148,7 +147,7 @@ public class EmergencyAccessServiceTests
|
|||||||
User invitingUser,
|
User invitingUser,
|
||||||
Guid emergencyAccessId)
|
Guid emergencyAccessId)
|
||||||
{
|
{
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = statusType,
|
Status = statusType,
|
||||||
GrantorId = invitingUser.Id,
|
GrantorId = invitingUser.Id,
|
||||||
@@ -172,7 +171,7 @@ public class EmergencyAccessServiceTests
|
|||||||
User invitingUser,
|
User invitingUser,
|
||||||
Guid emergencyAccessId)
|
Guid emergencyAccessId)
|
||||||
{
|
{
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = EmergencyAccessStatusType.Invited,
|
Status = EmergencyAccessStatusType.Invited,
|
||||||
GrantorId = invitingUser.Id,
|
GrantorId = invitingUser.Id,
|
||||||
@@ -194,7 +193,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task AcceptUserAsync_EmergencyAccessNull_ThrowsBadRequest(
|
public async Task AcceptUserAsync_EmergencyAccessNull_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider, User acceptingUser, string token)
|
SutProvider<EmergencyAccessService> sutProvider, User acceptingUser, string token)
|
||||||
{
|
{
|
||||||
EmergencyAccess emergencyAccess = null;
|
Core.Auth.Entities.EmergencyAccess emergencyAccess = null;
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns(emergencyAccess);
|
.Returns(emergencyAccess);
|
||||||
@@ -209,7 +208,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task AcceptUserAsync_CannotUnprotectToken_ThrowsBadRequest(
|
public async Task AcceptUserAsync_CannotUnprotectToken_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User acceptingUser,
|
User acceptingUser,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
@@ -230,8 +229,8 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task AcceptUserAsync_TokenDataInvalid_ThrowsBadRequest(
|
public async Task AcceptUserAsync_TokenDataInvalid_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User acceptingUser,
|
User acceptingUser,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
EmergencyAccess wrongEmergencyAccess,
|
Core.Auth.Entities.EmergencyAccess wrongEmergencyAccess,
|
||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
@@ -257,7 +256,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task AcceptUserAsync_AcceptedStatus_ThrowsBadRequest(
|
public async Task AcceptUserAsync_AcceptedStatus_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User acceptingUser,
|
User acceptingUser,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
|
emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
|
||||||
@@ -284,7 +283,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task AcceptUserAsync_NotInvitedStatus_ThrowsBadRequest(
|
public async Task AcceptUserAsync_NotInvitedStatus_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User acceptingUser,
|
User acceptingUser,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.Confirmed;
|
emergencyAccess.Status = EmergencyAccessStatusType.Confirmed;
|
||||||
@@ -311,7 +310,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task AcceptUserAsync_EmergencyAccessEmailDoesNotMatch_ThrowsBadRequest(
|
public async Task AcceptUserAsync_EmergencyAccessEmailDoesNotMatch_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User acceptingUser,
|
User acceptingUser,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
||||||
@@ -339,7 +338,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User acceptingUser,
|
User acceptingUser,
|
||||||
User invitingUser,
|
User invitingUser,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
emergencyAccess.Status = EmergencyAccessStatusType.Invited;
|
||||||
@@ -364,7 +363,7 @@ public class EmergencyAccessServiceTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Accepted));
|
.ReplaceAsync(Arg.Is<Core.Auth.Entities.EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Accepted));
|
||||||
|
|
||||||
await sutProvider.GetDependency<IMailService>()
|
await sutProvider.GetDependency<IMailService>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
@@ -375,11 +374,11 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task DeleteAsync_EmergencyAccessNull_ThrowsBadRequest(
|
public async Task DeleteAsync_EmergencyAccessNull_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User invitingUser,
|
User invitingUser,
|
||||||
EmergencyAccess emergencyAccess)
|
Core.Auth.Entities.EmergencyAccess emergencyAccess)
|
||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.DeleteAsync(emergencyAccess.Id, invitingUser.Id));
|
() => sutProvider.Sut.DeleteAsync(emergencyAccess.Id, invitingUser.Id));
|
||||||
@@ -391,7 +390,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task DeleteAsync_EmergencyAccessGrantorIdNotEqual_ThrowsBadRequest(
|
public async Task DeleteAsync_EmergencyAccessGrantorIdNotEqual_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User invitingUser,
|
User invitingUser,
|
||||||
EmergencyAccess emergencyAccess)
|
Core.Auth.Entities.EmergencyAccess emergencyAccess)
|
||||||
{
|
{
|
||||||
emergencyAccess.GrantorId = Guid.NewGuid();
|
emergencyAccess.GrantorId = Guid.NewGuid();
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
@@ -408,7 +407,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task DeleteAsync_EmergencyAccessGranteeIdNotEqual_ThrowsBadRequest(
|
public async Task DeleteAsync_EmergencyAccessGranteeIdNotEqual_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User invitingUser,
|
User invitingUser,
|
||||||
EmergencyAccess emergencyAccess)
|
Core.Auth.Entities.EmergencyAccess emergencyAccess)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = Guid.NewGuid();
|
emergencyAccess.GranteeId = Guid.NewGuid();
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
@@ -425,7 +424,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task DeleteAsync_EmergencyAccessIsDeleted_Success(
|
public async Task DeleteAsync_EmergencyAccessIsDeleted_Success(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
User user,
|
User user,
|
||||||
EmergencyAccess emergencyAccess)
|
Core.Auth.Entities.EmergencyAccess emergencyAccess)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = user.Id;
|
emergencyAccess.GranteeId = user.Id;
|
||||||
emergencyAccess.GrantorId = user.Id;
|
emergencyAccess.GrantorId = user.Id;
|
||||||
@@ -443,7 +442,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ConfirmUserAsync_EmergencyAccessNull_ThrowsBadRequest(
|
public async Task ConfirmUserAsync_EmergencyAccessNull_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string key,
|
string key,
|
||||||
User grantorUser)
|
User grantorUser)
|
||||||
{
|
{
|
||||||
@@ -451,7 +450,7 @@ public class EmergencyAccessServiceTests
|
|||||||
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
|
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.ConfirmUserAsync(emergencyAccess.Id, key, grantorUser.Id));
|
() => sutProvider.Sut.ConfirmUserAsync(emergencyAccess.Id, key, grantorUser.Id));
|
||||||
@@ -463,7 +462,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ConfirmUserAsync_EmergencyAccessStatusIsNotAccepted_ThrowsBadRequest(
|
public async Task ConfirmUserAsync_EmergencyAccessStatusIsNotAccepted_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string key,
|
string key,
|
||||||
User grantorUser)
|
User grantorUser)
|
||||||
{
|
{
|
||||||
@@ -484,7 +483,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ConfirmUserAsync_EmergencyAccessGrantorIdNotEqualToConfirmingUserId_ThrowsBadRequest(
|
public async Task ConfirmUserAsync_EmergencyAccessGrantorIdNotEqualToConfirmingUserId_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string key,
|
string key,
|
||||||
User grantorUser)
|
User grantorUser)
|
||||||
{
|
{
|
||||||
@@ -505,7 +504,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User confirmingUser, string key)
|
SutProvider<EmergencyAccessService> sutProvider, User confirmingUser, string key)
|
||||||
{
|
{
|
||||||
confirmingUser.UsesKeyConnector = true;
|
confirmingUser.UsesKeyConnector = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = EmergencyAccessStatusType.Accepted,
|
Status = EmergencyAccessStatusType.Accepted,
|
||||||
GrantorId = confirmingUser.Id,
|
GrantorId = confirmingUser.Id,
|
||||||
@@ -530,7 +529,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ConfirmUserAsync_ConfirmsAndReplacesEmergencyAccess_Success(
|
public async Task ConfirmUserAsync_ConfirmsAndReplacesEmergencyAccess_Success(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
string key,
|
string key,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
@@ -553,7 +552,7 @@ public class EmergencyAccessServiceTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Confirmed));
|
.ReplaceAsync(Arg.Is<Core.Auth.Entities.EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Confirmed));
|
||||||
|
|
||||||
await sutProvider.GetDependency<IMailService>()
|
await sutProvider.GetDependency<IMailService>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
@@ -564,7 +563,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task SaveAsync_PremiumCannotUpdate_ThrowsBadRequest(
|
public async Task SaveAsync_PremiumCannotUpdate_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
|
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
|
||||||
{
|
{
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Type = EmergencyAccessType.Takeover,
|
Type = EmergencyAccessType.Takeover,
|
||||||
GrantorId = savingUser.Id,
|
GrantorId = savingUser.Id,
|
||||||
@@ -586,7 +585,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
|
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
|
||||||
{
|
{
|
||||||
savingUser.Premium = true;
|
savingUser.Premium = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Type = EmergencyAccessType.Takeover,
|
Type = EmergencyAccessType.Takeover,
|
||||||
GrantorId = new Guid(),
|
GrantorId = new Guid(),
|
||||||
@@ -611,7 +610,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User grantorUser)
|
SutProvider<EmergencyAccessService> sutProvider, User grantorUser)
|
||||||
{
|
{
|
||||||
grantorUser.UsesKeyConnector = true;
|
grantorUser.UsesKeyConnector = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Type = EmergencyAccessType.Takeover,
|
Type = EmergencyAccessType.Takeover,
|
||||||
GrantorId = grantorUser.Id,
|
GrantorId = grantorUser.Id,
|
||||||
@@ -633,7 +632,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User grantorUser)
|
SutProvider<EmergencyAccessService> sutProvider, User grantorUser)
|
||||||
{
|
{
|
||||||
grantorUser.UsesKeyConnector = true;
|
grantorUser.UsesKeyConnector = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Type = EmergencyAccessType.View,
|
Type = EmergencyAccessType.View,
|
||||||
GrantorId = grantorUser.Id,
|
GrantorId = grantorUser.Id,
|
||||||
@@ -655,7 +654,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User grantorUser)
|
SutProvider<EmergencyAccessService> sutProvider, User grantorUser)
|
||||||
{
|
{
|
||||||
grantorUser.UsesKeyConnector = false;
|
grantorUser.UsesKeyConnector = false;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Type = EmergencyAccessType.Takeover,
|
Type = EmergencyAccessType.Takeover,
|
||||||
GrantorId = grantorUser.Id,
|
GrantorId = grantorUser.Id,
|
||||||
@@ -678,7 +677,7 @@ public class EmergencyAccessServiceTests
|
|||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
|
() => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
|
||||||
@@ -692,7 +691,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task InitiateAsync_EmergencyAccessGranteeIdNotEqual_ThrowBadRequest(
|
public async Task InitiateAsync_EmergencyAccessGranteeIdNotEqual_ThrowBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User initiatingUser)
|
User initiatingUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = new Guid();
|
emergencyAccess.GranteeId = new Guid();
|
||||||
@@ -712,7 +711,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task InitiateAsync_EmergencyAccessStatusIsNotConfirmed_ThrowBadRequest(
|
public async Task InitiateAsync_EmergencyAccessStatusIsNotConfirmed_ThrowBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User initiatingUser)
|
User initiatingUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = initiatingUser.Id;
|
emergencyAccess.GranteeId = initiatingUser.Id;
|
||||||
@@ -735,7 +734,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
||||||
{
|
{
|
||||||
grantor.UsesKeyConnector = true;
|
grantor.UsesKeyConnector = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = EmergencyAccessStatusType.Confirmed,
|
Status = EmergencyAccessStatusType.Confirmed,
|
||||||
GranteeId = initiatingUser.Id,
|
GranteeId = initiatingUser.Id,
|
||||||
@@ -764,7 +763,7 @@ public class EmergencyAccessServiceTests
|
|||||||
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
||||||
{
|
{
|
||||||
grantor.UsesKeyConnector = true;
|
grantor.UsesKeyConnector = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = EmergencyAccessStatusType.Confirmed,
|
Status = EmergencyAccessStatusType.Confirmed,
|
||||||
GranteeId = initiatingUser.Id,
|
GranteeId = initiatingUser.Id,
|
||||||
@@ -783,14 +782,14 @@ public class EmergencyAccessServiceTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.RecoveryInitiated));
|
.ReplaceAsync(Arg.Is<Core.Auth.Entities.EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.RecoveryInitiated));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task InitiateAsync_RequestIsCorrect_Success(
|
public async Task InitiateAsync_RequestIsCorrect_Success(
|
||||||
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
||||||
{
|
{
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
Status = EmergencyAccessStatusType.Confirmed,
|
Status = EmergencyAccessStatusType.Confirmed,
|
||||||
GranteeId = initiatingUser.Id,
|
GranteeId = initiatingUser.Id,
|
||||||
@@ -809,7 +808,7 @@ public class EmergencyAccessServiceTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.RecoveryInitiated));
|
.ReplaceAsync(Arg.Is<Core.Auth.Entities.EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.RecoveryInitiated));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@@ -818,7 +817,7 @@ public class EmergencyAccessServiceTests
|
|||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.ApproveAsync(new Guid(), null));
|
() => sutProvider.Sut.ApproveAsync(new Guid(), null));
|
||||||
@@ -829,7 +828,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ApproveAsync_EmergencyAccessGrantorIdNotEquatToApproving_ThrowsBadRequest(
|
public async Task ApproveAsync_EmergencyAccessGrantorIdNotEquatToApproving_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User grantorUser)
|
User grantorUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
|
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated;
|
||||||
@@ -851,7 +850,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task ApproveAsync_EmergencyAccessStatusNotRecoveryInitiated_ThrowsBadRequest(
|
public async Task ApproveAsync_EmergencyAccessStatusNotRecoveryInitiated_ThrowsBadRequest(
|
||||||
EmergencyAccessStatusType statusType,
|
EmergencyAccessStatusType statusType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User grantorUser)
|
User grantorUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GrantorId = grantorUser.Id;
|
emergencyAccess.GrantorId = grantorUser.Id;
|
||||||
@@ -869,7 +868,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ApproveAsync_Success(
|
public async Task ApproveAsync_Success(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
@@ -885,20 +884,20 @@ public class EmergencyAccessServiceTests
|
|||||||
await sutProvider.Sut.ApproveAsync(emergencyAccess.Id, grantorUser);
|
await sutProvider.Sut.ApproveAsync(emergencyAccess.Id, grantorUser);
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.RecoveryApproved));
|
.ReplaceAsync(Arg.Is<Core.Auth.Entities.EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.RecoveryApproved));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RejectAsync_EmergencyAccessIdNull_ThrowsBadRequest(
|
public async Task RejectAsync_EmergencyAccessIdNull_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User GrantorUser)
|
User GrantorUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GrantorId = GrantorUser.Id;
|
emergencyAccess.GrantorId = GrantorUser.Id;
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
|
emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.RejectAsync(emergencyAccess.Id, GrantorUser));
|
() => sutProvider.Sut.RejectAsync(emergencyAccess.Id, GrantorUser));
|
||||||
@@ -909,7 +908,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RejectAsync_EmergencyAccessGrantorIdNotEqualToRequestUser_ThrowsBadRequest(
|
public async Task RejectAsync_EmergencyAccessGrantorIdNotEqualToRequestUser_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User GrantorUser)
|
User GrantorUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
|
emergencyAccess.Status = EmergencyAccessStatusType.Accepted;
|
||||||
@@ -930,7 +929,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task RejectAsync_EmergencyAccessStatusNotValid_ThrowsBadRequest(
|
public async Task RejectAsync_EmergencyAccessStatusNotValid_ThrowsBadRequest(
|
||||||
EmergencyAccessStatusType statusType,
|
EmergencyAccessStatusType statusType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User GrantorUser)
|
User GrantorUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GrantorId = GrantorUser.Id;
|
emergencyAccess.GrantorId = GrantorUser.Id;
|
||||||
@@ -951,7 +950,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task RejectAsync_Success(
|
public async Task RejectAsync_Success(
|
||||||
EmergencyAccessStatusType statusType,
|
EmergencyAccessStatusType statusType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User GrantorUser,
|
User GrantorUser,
|
||||||
User GranteeUser)
|
User GranteeUser)
|
||||||
{
|
{
|
||||||
@@ -968,7 +967,7 @@ public class EmergencyAccessServiceTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
await sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.ReplaceAsync(Arg.Is<EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Confirmed));
|
.ReplaceAsync(Arg.Is<Core.Auth.Entities.EmergencyAccess>(x => x.Status == EmergencyAccessStatusType.Confirmed));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@@ -977,7 +976,7 @@ public class EmergencyAccessServiceTests
|
|||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.GetPoliciesAsync(default, default));
|
() => sutProvider.Sut.GetPoliciesAsync(default, default));
|
||||||
@@ -992,7 +991,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task GetPoliciesAsync_RequestNotValidStatusType_ThrowsBadRequest(
|
public async Task GetPoliciesAsync_RequestNotValidStatusType_ThrowsBadRequest(
|
||||||
EmergencyAccessStatusType statusType,
|
EmergencyAccessStatusType statusType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1010,7 +1009,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetPoliciesAsync_RequestNotValidType_ThrowsBadRequest(
|
public async Task GetPoliciesAsync_RequestNotValidType_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1032,7 +1031,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task GetPoliciesAsync_OrganizationUserTypeNotOwner_ReturnsNull(
|
public async Task GetPoliciesAsync_OrganizationUserTypeNotOwner_ReturnsNull(
|
||||||
OrganizationUserType userType,
|
OrganizationUserType userType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser,
|
User granteeUser,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
OrganizationUser grantorOrganizationUser)
|
OrganizationUser grantorOrganizationUser)
|
||||||
@@ -1062,7 +1061,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetPoliciesAsync_OrganizationUserEmpty_ReturnsNull(
|
public async Task GetPoliciesAsync_OrganizationUserEmpty_ReturnsNull(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser,
|
User granteeUser,
|
||||||
User grantorUser)
|
User grantorUser)
|
||||||
{
|
{
|
||||||
@@ -1090,7 +1089,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetPoliciesAsync_ReturnsNotNull(
|
public async Task GetPoliciesAsync_ReturnsNotNull(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser,
|
User granteeUser,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
OrganizationUser grantorOrganizationUser)
|
OrganizationUser grantorOrganizationUser)
|
||||||
@@ -1127,7 +1126,7 @@ public class EmergencyAccessServiceTests
|
|||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.TakeoverAsync(default, default));
|
() => sutProvider.Sut.TakeoverAsync(default, default));
|
||||||
@@ -1138,7 +1137,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task TakeoverAsync_RequestNotValid_GranteeNotEqualToRequestingUser_ThrowsBadRequest(
|
public async Task TakeoverAsync_RequestNotValid_GranteeNotEqualToRequestingUser_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryApproved;
|
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryApproved;
|
||||||
@@ -1161,7 +1160,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task TakeoverAsync_RequestNotValid_StatusType_ThrowsBadRequest(
|
public async Task TakeoverAsync_RequestNotValid_StatusType_ThrowsBadRequest(
|
||||||
EmergencyAccessStatusType statusType,
|
EmergencyAccessStatusType statusType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1180,7 +1179,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task TakeoverAsync_RequestNotValid_TypeIsView_ThrowsBadRequest(
|
public async Task TakeoverAsync_RequestNotValid_TypeIsView_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1203,7 +1202,7 @@ public class EmergencyAccessServiceTests
|
|||||||
User grantor)
|
User grantor)
|
||||||
{
|
{
|
||||||
grantor.UsesKeyConnector = true;
|
grantor.UsesKeyConnector = true;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
GrantorId = grantor.Id,
|
GrantorId = grantor.Id,
|
||||||
GranteeId = granteeUser.Id,
|
GranteeId = granteeUser.Id,
|
||||||
@@ -1232,7 +1231,7 @@ public class EmergencyAccessServiceTests
|
|||||||
User grantor)
|
User grantor)
|
||||||
{
|
{
|
||||||
grantor.UsesKeyConnector = false;
|
grantor.UsesKeyConnector = false;
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
GrantorId = grantor.Id,
|
GrantorId = grantor.Id,
|
||||||
GranteeId = granteeUser.Id,
|
GranteeId = granteeUser.Id,
|
||||||
@@ -1260,7 +1259,7 @@ public class EmergencyAccessServiceTests
|
|||||||
{
|
{
|
||||||
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
sutProvider.GetDependency<IEmergencyAccessRepository>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
.Returns((EmergencyAccess)null);
|
.Returns((Core.Auth.Entities.EmergencyAccess)null);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.PasswordAsync(default, default, default, default));
|
() => sutProvider.Sut.PasswordAsync(default, default, default, default));
|
||||||
@@ -1271,7 +1270,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PasswordAsync_RequestNotValid_GranteeNotEqualToRequestingUser_ThrowsBadRequest(
|
public async Task PasswordAsync_RequestNotValid_GranteeNotEqualToRequestingUser_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryApproved;
|
emergencyAccess.Status = EmergencyAccessStatusType.RecoveryApproved;
|
||||||
@@ -1294,7 +1293,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task PasswordAsync_RequestNotValid_StatusType_ThrowsBadRequest(
|
public async Task PasswordAsync_RequestNotValid_StatusType_ThrowsBadRequest(
|
||||||
EmergencyAccessStatusType statusType,
|
EmergencyAccessStatusType statusType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1313,7 +1312,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PasswordAsync_RequestNotValid_TypeIsView_ThrowsBadRequest(
|
public async Task PasswordAsync_RequestNotValid_TypeIsView_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1332,7 +1331,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PasswordAsync_NonOrgUser_Success(
|
public async Task PasswordAsync_NonOrgUser_Success(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser,
|
User granteeUser,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
string key,
|
string key,
|
||||||
@@ -1367,7 +1366,7 @@ public class EmergencyAccessServiceTests
|
|||||||
public async Task PasswordAsync_OrgUser_NotOrganizationOwner_RemovedFromOrganization_Success(
|
public async Task PasswordAsync_OrgUser_NotOrganizationOwner_RemovedFromOrganization_Success(
|
||||||
OrganizationUserType userType,
|
OrganizationUserType userType,
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser,
|
User granteeUser,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
OrganizationUser organizationUser,
|
OrganizationUser organizationUser,
|
||||||
@@ -1408,7 +1407,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PasswordAsync_OrgUser_IsOrganizationOwner_NotRemovedFromOrganization_Success(
|
public async Task PasswordAsync_OrgUser_IsOrganizationOwner_NotRemovedFromOrganization_Success(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser,
|
User granteeUser,
|
||||||
User grantorUser,
|
User grantorUser,
|
||||||
OrganizationUser organizationUser,
|
OrganizationUser organizationUser,
|
||||||
@@ -1459,7 +1458,7 @@ public class EmergencyAccessServiceTests
|
|||||||
Enabled = true
|
Enabled = true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var emergencyAccess = new EmergencyAccess
|
var emergencyAccess = new Core.Auth.Entities.EmergencyAccess
|
||||||
{
|
{
|
||||||
GrantorId = grantor.Id,
|
GrantorId = grantor.Id,
|
||||||
GranteeId = requestingUser.Id,
|
GranteeId = requestingUser.Id,
|
||||||
@@ -1484,7 +1483,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ViewAsync_EmergencyAccessTypeNotView_ThrowsBadRequest(
|
public async Task ViewAsync_EmergencyAccessTypeNotView_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -1500,7 +1499,7 @@ public class EmergencyAccessServiceTests
|
|||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetAttachmentDownloadAsync_EmergencyAccessTypeNotView_ThrowsBadRequest(
|
public async Task GetAttachmentDownloadAsync_EmergencyAccessTypeNotView_ThrowsBadRequest(
|
||||||
SutProvider<EmergencyAccessService> sutProvider,
|
SutProvider<EmergencyAccessService> sutProvider,
|
||||||
EmergencyAccess emergencyAccess,
|
Core.Auth.Entities.EmergencyAccess emergencyAccess,
|
||||||
User granteeUser)
|
User granteeUser)
|
||||||
{
|
{
|
||||||
emergencyAccess.GranteeId = granteeUser.Id;
|
emergencyAccess.GranteeId = granteeUser.Id;
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
@@ -23,6 +22,7 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using EmergencyAccessEntity = Bit.Core.Auth.Entities.EmergencyAccess;
|
||||||
|
|
||||||
namespace Bit.Core.Test.Auth.UserFeatures.Registration;
|
namespace Bit.Core.Test.Auth.UserFeatures.Registration;
|
||||||
|
|
||||||
@@ -726,7 +726,7 @@ public class RegisterUserCommandTests
|
|||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_Succeeds(
|
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_Succeeds(
|
||||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||||
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
EmergencyAccessEntity emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||||
@@ -767,7 +767,7 @@ public class RegisterUserCommandTests
|
|||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_InvalidToken_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user,
|
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_InvalidToken_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user,
|
||||||
string masterPasswordHash, EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
string masterPasswordHash, EmergencyAccessEntity emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||||
@@ -1112,7 +1112,7 @@ public class RegisterUserCommandTests
|
|||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_BlockedDomain_ThrowsBadRequestException(
|
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||||
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
EmergencyAccessEntity emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
user.Email = "user@blocked-domain.com";
|
user.Email = "user@blocked-domain.com";
|
||||||
|
|||||||
Reference in New Issue
Block a user