mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
[AC-1191] TDE admin approval email (#3044)
* feat: add new command for updating request and emailing user, refs AC-1191 * feat: inject service with organization service collection extensions, refs AC-1191 * feat: add function to send admin approval email to mail services (interface/noop/handlebars), refs AC-1191 * feat: add html/text mail templates and add view model for email data, refs AC-1191 * feat: update org auth request controller to use new command during auth request update, refs AC-1191 * fix: dotnet format, refs AC-1191 * refactor: update user not found error, FirstOrDefault for enum type display name, refs AC-1191 * refactor: update user not found to log error instead of throws, refs AC-1191 * fix: remove whitespace lint errors, refs AC-1191 * refactor: update hardcoded UTC timezone string, refs AC-1191 * refactor: add unit test for new command, refs AC-1191 * refactor: improve enum name fallback and identifier string creation, refs AC-1191 * refactor: add addtional unit tests, refs AC-1191 * refactor: update success test to use more generated params, refs AC-1191 * fix: dotnet format...again, refs AC-1191 * refactor: make UTC display a constant for handlebars mail service, refs AC-1191 * refactor: update displayTypeIdentifer to displayTypeAndIdentifier for clarity, refs AC-1191
This commit is contained in:
parent
62beb7d1e8
commit
3b4c8afea0
@ -2,6 +2,7 @@
|
|||||||
using Bit.Api.Auth.Models.Response;
|
using Bit.Api.Auth.Models.Response;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationAuth.Interfaces;
|
||||||
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
|
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@ -21,13 +22,16 @@ public class OrganizationAuthRequestsController : Controller
|
|||||||
private readonly IAuthRequestRepository _authRequestRepository;
|
private readonly IAuthRequestRepository _authRequestRepository;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IAuthRequestService _authRequestService;
|
private readonly IAuthRequestService _authRequestService;
|
||||||
|
private readonly IUpdateOrganizationAuthRequestCommand _updateOrganizationAuthRequestCommand;
|
||||||
|
|
||||||
public OrganizationAuthRequestsController(IAuthRequestRepository authRequestRepository,
|
public OrganizationAuthRequestsController(IAuthRequestRepository authRequestRepository,
|
||||||
ICurrentContext currentContext, IAuthRequestService authRequestService)
|
ICurrentContext currentContext, IAuthRequestService authRequestService,
|
||||||
|
IUpdateOrganizationAuthRequestCommand updateOrganizationAuthRequestCommand)
|
||||||
{
|
{
|
||||||
_authRequestRepository = authRequestRepository;
|
_authRequestRepository = authRequestRepository;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_authRequestService = authRequestService;
|
_authRequestService = authRequestService;
|
||||||
|
_updateOrganizationAuthRequestCommand = updateOrganizationAuthRequestCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
@ -55,8 +59,7 @@ public class OrganizationAuthRequestsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _authRequestService.UpdateAuthRequestAsync(authRequest.Id, authRequest.UserId,
|
await _updateOrganizationAuthRequestCommand.UpdateAsync(authRequest.Id, authRequest.UserId, model.RequestApproved, model.EncryptedUserKey);
|
||||||
new AuthRequestUpdateRequestModel { RequestApproved = model.RequestApproved, Key = model.EncryptedUserKey });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("deny")]
|
[HttpPost("deny")]
|
||||||
@ -81,3 +84,4 @@ public class OrganizationAuthRequestsController : Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.Core.AdminConsole.OrganizationAuth.Interfaces;
|
||||||
|
|
||||||
|
public interface IUpdateOrganizationAuthRequestCommand
|
||||||
|
{
|
||||||
|
Task UpdateAsync(Guid requestId, Guid userId, bool requestApproved, string encryptedUserKey);
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Reflection;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationAuth.Interfaces;
|
||||||
|
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
|
||||||
|
using Bit.Core.Auth.Services;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationAuth;
|
||||||
|
|
||||||
|
public class UpdateOrganizationAuthRequestCommand : IUpdateOrganizationAuthRequestCommand
|
||||||
|
{
|
||||||
|
private readonly IAuthRequestService _authRequestService;
|
||||||
|
private readonly IMailService _mailService;
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly ILogger<UpdateOrganizationAuthRequestCommand> _logger;
|
||||||
|
|
||||||
|
public UpdateOrganizationAuthRequestCommand(
|
||||||
|
IAuthRequestService authRequestService,
|
||||||
|
IMailService mailService,
|
||||||
|
IUserRepository userRepository,
|
||||||
|
ILogger<UpdateOrganizationAuthRequestCommand> logger)
|
||||||
|
{
|
||||||
|
_authRequestService = authRequestService;
|
||||||
|
_mailService = mailService;
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(Guid requestId, Guid userId, bool requestApproved, string encryptedUserKey)
|
||||||
|
{
|
||||||
|
var updatedAuthRequest = await _authRequestService.UpdateAuthRequestAsync(requestId, userId,
|
||||||
|
new AuthRequestUpdateRequestModel { RequestApproved = requestApproved, Key = encryptedUserKey });
|
||||||
|
|
||||||
|
if (updatedAuthRequest.Approved is true)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("User ({id}) not found. Trusted device admin approval email not sent.", userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var approvalDateTime = updatedAuthRequest.ResponseDate ?? DateTime.UtcNow;
|
||||||
|
var deviceTypeDisplayName = updatedAuthRequest.RequestDeviceType.GetType()
|
||||||
|
.GetMember(updatedAuthRequest.RequestDeviceType.ToString())
|
||||||
|
.FirstOrDefault()?
|
||||||
|
.GetCustomAttribute<DisplayAttribute>()?.Name ?? "Unknown";
|
||||||
|
var deviceTypeAndIdentifier = $"{deviceTypeDisplayName} - {updatedAuthRequest.RequestDeviceIdentifier}";
|
||||||
|
await _mailService.SendTrustedDeviceAdminApprovalEmailAsync(user.Email, approvalDateTime,
|
||||||
|
updatedAuthRequest.RequestIpAddress, deviceTypeAndIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
{{#>FullHtmlLayout}}
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||||
|
You must log in on the device below within 12 hours or approval will expire.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||||
|
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Device:</b> {{DeviceType}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||||
|
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">IP Address:</b> {{IpAddress}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||||
|
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Date:</b> {{TheDate}} at {{TheTime}} {{TimeZone}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
|
||||||
|
If you do not recognize this device, contact your organization administrator.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{{/FullHtmlLayout}}
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
{{#>BasicTextLayout}}
|
||||||
|
You must log in on the device below within 12 hours or approval will expire.
|
||||||
|
|
||||||
|
Device Type: {{DeviceType}}
|
||||||
|
IP Address: {{IpAddress}}
|
||||||
|
Date: {{TheDate}} at {{TheTime}} {{TimeZone}}
|
||||||
|
|
||||||
|
If you do not recognize this device, contact your organization administrator.
|
||||||
|
{{/BasicTextLayout}}
|
@ -0,0 +1,3 @@
|
|||||||
|
namespace Bit.Core.Models.Mail;
|
||||||
|
|
||||||
|
public class TrustedDeviceAdminApprovalViewModel : NewDeviceLoggedInModel { }
|
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.OrganizationAuth;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationAuth.Interfaces;
|
||||||
|
using Bit.Core.Models.Business.Tokenables;
|
||||||
using Bit.Core.OrganizationFeatures.Groups;
|
using Bit.Core.OrganizationFeatures.Groups;
|
||||||
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationApiKeys;
|
using Bit.Core.OrganizationFeatures.OrganizationApiKeys;
|
||||||
@ -41,6 +43,7 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddOrganizationGroupCommands();
|
services.AddOrganizationGroupCommands();
|
||||||
services.AddOrganizationLicenseCommandsQueries();
|
services.AddOrganizationLicenseCommandsQueries();
|
||||||
services.AddOrganizationDomainCommandsQueries();
|
services.AddOrganizationDomainCommandsQueries();
|
||||||
|
services.AddOrganizationAuthCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||||
@ -110,6 +113,11 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddScoped<IDeleteOrganizationDomainCommand, DeleteOrganizationDomainCommand>();
|
services.AddScoped<IDeleteOrganizationDomainCommand, DeleteOrganizationDomainCommand>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddOrganizationAuthCommands(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IUpdateOrganizationAuthRequestCommand, UpdateOrganizationAuthRequestCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddTokenizers(this IServiceCollection services)
|
private static void AddTokenizers(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>
|
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>
|
||||||
@ -121,3 +129,4 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,4 +55,6 @@ public interface IMailService
|
|||||||
Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
||||||
Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
||||||
Task SendUnverifiedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName);
|
Task SendUnverifiedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName);
|
||||||
|
Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip, string deviceTypeAndIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ namespace Bit.Core.Services;
|
|||||||
public class HandlebarsMailService : IMailService
|
public class HandlebarsMailService : IMailService
|
||||||
{
|
{
|
||||||
private const string Namespace = "Bit.Core.MailTemplates.Handlebars";
|
private const string Namespace = "Bit.Core.MailTemplates.Handlebars";
|
||||||
|
private const string _utcTimeZoneDisplay = "UTC";
|
||||||
|
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IMailDeliveryService _mailDeliveryService;
|
private readonly IMailDeliveryService _mailDeliveryService;
|
||||||
@ -353,7 +354,7 @@ public class HandlebarsMailService : IMailService
|
|||||||
DeviceType = deviceType,
|
DeviceType = deviceType,
|
||||||
TheDate = timestamp.ToLongDateString(),
|
TheDate = timestamp.ToLongDateString(),
|
||||||
TheTime = timestamp.ToShortTimeString(),
|
TheTime = timestamp.ToShortTimeString(),
|
||||||
TimeZone = "UTC",
|
TimeZone = _utcTimeZoneDisplay,
|
||||||
IpAddress = ip
|
IpAddress = ip
|
||||||
};
|
};
|
||||||
await AddMessageContentAsync(message, "NewDeviceLoggedIn", model);
|
await AddMessageContentAsync(message, "NewDeviceLoggedIn", model);
|
||||||
@ -370,7 +371,7 @@ public class HandlebarsMailService : IMailService
|
|||||||
SiteName = _globalSettings.SiteName,
|
SiteName = _globalSettings.SiteName,
|
||||||
TheDate = timestamp.ToLongDateString(),
|
TheDate = timestamp.ToLongDateString(),
|
||||||
TheTime = timestamp.ToShortTimeString(),
|
TheTime = timestamp.ToShortTimeString(),
|
||||||
TimeZone = "UTC",
|
TimeZone = _utcTimeZoneDisplay,
|
||||||
IpAddress = ip
|
IpAddress = ip
|
||||||
};
|
};
|
||||||
await AddMessageContentAsync(message, "Auth.RecoverTwoFactor", model);
|
await AddMessageContentAsync(message, "Auth.RecoverTwoFactor", model);
|
||||||
@ -856,7 +857,7 @@ public class HandlebarsMailService : IMailService
|
|||||||
{
|
{
|
||||||
TheDate = utcNow.ToLongDateString(),
|
TheDate = utcNow.ToLongDateString(),
|
||||||
TheTime = utcNow.ToShortTimeString(),
|
TheTime = utcNow.ToShortTimeString(),
|
||||||
TimeZone = "UTC",
|
TimeZone = _utcTimeZoneDisplay,
|
||||||
IpAddress = ip,
|
IpAddress = ip,
|
||||||
AffectedEmail = email
|
AffectedEmail = email
|
||||||
|
|
||||||
@ -873,7 +874,7 @@ public class HandlebarsMailService : IMailService
|
|||||||
{
|
{
|
||||||
TheDate = utcNow.ToLongDateString(),
|
TheDate = utcNow.ToLongDateString(),
|
||||||
TheTime = utcNow.ToShortTimeString(),
|
TheTime = utcNow.ToShortTimeString(),
|
||||||
TimeZone = "UTC",
|
TimeZone = _utcTimeZoneDisplay,
|
||||||
IpAddress = ip,
|
IpAddress = ip,
|
||||||
AffectedEmail = email
|
AffectedEmail = email
|
||||||
|
|
||||||
@ -896,8 +897,26 @@ public class HandlebarsMailService : IMailService
|
|||||||
await _mailDeliveryService.SendEmailAsync(message);
|
await _mailDeliveryService.SendEmailAsync(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip,
|
||||||
|
string deviceTypeAndIdentifier)
|
||||||
|
{
|
||||||
|
var message = CreateDefaultMessage("Login request approved", email);
|
||||||
|
var model = new TrustedDeviceAdminApprovalViewModel
|
||||||
|
{
|
||||||
|
TheDate = utcNow.ToLongDateString(),
|
||||||
|
TheTime = utcNow.ToShortTimeString(),
|
||||||
|
TimeZone = _utcTimeZoneDisplay,
|
||||||
|
IpAddress = ip,
|
||||||
|
DeviceType = deviceTypeAndIdentifier,
|
||||||
|
};
|
||||||
|
await AddMessageContentAsync(message, "Auth.TrustedDeviceAdminApproval", model);
|
||||||
|
message.Category = "TrustedDeviceAdminApproval";
|
||||||
|
await _mailDeliveryService.SendEmailAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetUserIdentifier(string email, string userName)
|
private static string GetUserIdentifier(string email, string userName)
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(userName) ? email : CoreHelpers.SanitizeForEmail(userName, false);
|
return string.IsNullOrEmpty(userName) ? email : CoreHelpers.SanitizeForEmail(userName, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,4 +238,10 @@ public class NoopMailService : IMailService
|
|||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip, string deviceTypeAndIdentifier)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
using Bit.Core.AdminConsole.OrganizationAuth;
|
||||||
|
using Bit.Core.Auth.Entities;
|
||||||
|
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
|
||||||
|
using Bit.Core.Auth.Services;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using NSubstitute;
|
||||||
|
using NSubstitute.ReturnsExtensions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.AdminConsole.OrganizationAuth;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class UpdateOrganizationAuthRequestCommandTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task UpdateOrgAuthRequest_Approved_SendEmail_Success(
|
||||||
|
DateTime responseDate, string email, DeviceType deviceType, string deviceIdentifier,
|
||||||
|
string requestIpAddress, Guid requestId, Guid userId, bool requestApproved,
|
||||||
|
string encryptedUserKey, SutProvider<UpdateOrganizationAuthRequestCommand> sutProvider)
|
||||||
|
{
|
||||||
|
var expectedDeviceTypeAndIdentifier = $"{deviceType} - {deviceIdentifier}";
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IAuthRequestService>()
|
||||||
|
.UpdateAuthRequestAsync(requestId, userId,
|
||||||
|
Arg.Is<AuthRequestUpdateRequestModel>(x =>
|
||||||
|
x.RequestApproved == requestApproved && x.Key == encryptedUserKey))
|
||||||
|
.Returns(new AuthRequest()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Approved = true,
|
||||||
|
ResponseDate = responseDate,
|
||||||
|
RequestDeviceType = deviceType,
|
||||||
|
RequestDeviceIdentifier = deviceIdentifier,
|
||||||
|
RequestIpAddress = requestIpAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.GetByIdAsync(userId)
|
||||||
|
.Returns(new User()
|
||||||
|
{
|
||||||
|
Email = email
|
||||||
|
});
|
||||||
|
|
||||||
|
await sutProvider.Sut.UpdateAsync(requestId, userId, requestApproved, encryptedUserKey);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>().Received(1).GetByIdAsync(userId);
|
||||||
|
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||||
|
.SendTrustedDeviceAdminApprovalEmailAsync(email, responseDate, requestIpAddress, expectedDeviceTypeAndIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task UpdateOrgAuthRequest_Denied_NonExecutes(
|
||||||
|
SutProvider<UpdateOrganizationAuthRequestCommand> sutProvider, Guid requestId, Guid userId,
|
||||||
|
bool requestApproved, string encryptedUserKey)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IAuthRequestService>()
|
||||||
|
.UpdateAuthRequestAsync(requestId, userId,
|
||||||
|
Arg.Is<AuthRequestUpdateRequestModel>(x =>
|
||||||
|
x.RequestApproved == requestApproved && x.Key == encryptedUserKey))
|
||||||
|
.Returns(new AuthRequest() { Approved = false });
|
||||||
|
|
||||||
|
await sutProvider.Sut.UpdateAsync(requestId, userId, requestApproved, encryptedUserKey);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>().DidNotReceive().GetByIdAsync(userId);
|
||||||
|
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||||
|
.SendTrustedDeviceAdminApprovalEmailAsync(Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>(),
|
||||||
|
Arg.Any<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task UpdateOrgAuthRequest_Approved_UserNotFound(
|
||||||
|
SutProvider<UpdateOrganizationAuthRequestCommand> sutProvider, Guid requestId, Guid userId,
|
||||||
|
bool requestApproved, string encryptedUserKey)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IAuthRequestService>()
|
||||||
|
.UpdateAuthRequestAsync(requestId, userId,
|
||||||
|
Arg.Is<AuthRequestUpdateRequestModel>(x =>
|
||||||
|
x.RequestApproved == requestApproved && x.Key == encryptedUserKey))
|
||||||
|
.Returns(new AuthRequest() { Approved = true, });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.GetByIdAsync(userId)
|
||||||
|
.ReturnsNull();
|
||||||
|
|
||||||
|
await sutProvider.Sut.UpdateAsync(requestId, userId, requestApproved, encryptedUserKey);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>().Received(1).GetByIdAsync(userId);
|
||||||
|
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||||
|
.SendTrustedDeviceAdminApprovalEmailAsync(Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>(),
|
||||||
|
Arg.Any<string>());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user