-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rework everything to be more model-centric
- Loading branch information
1 parent
0676885
commit ac451cb
Showing
11 changed files
with
755 additions
and
803 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace Bit.Core.AdminConsole.Extensions; | ||
|
||
public static class TaskExtensions | ||
{ | ||
public async static Task<TResult> Then<T, TResult>(this Task<T> source, Func<T, Task<TResult>> selector) | ||
{ | ||
T x = await source; | ||
return await selector(x); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/Core/AdminConsole/OrganizationAuth/Models/ApprovedAuthRequestIsMissingKeyException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Bit.Core.AdminConsole.OrganizationAuth.Models; | ||
|
||
public class ApprovedAuthRequestIsMissingKeyException : AuthRequestUpdateProcessingException | ||
{ | ||
public ApprovedAuthRequestIsMissingKeyException(Guid id) | ||
: base($"An auth request with id {id} was approved, but no key was provided. This auth request can not be approved.") | ||
{ | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...ore/AdminConsole/OrganizationAuth/Models/AuthRequestUpdateCouldNotBeProcessedException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Bit.Core.AdminConsole.OrganizationAuth.Models; | ||
|
||
public class AuthRequestUpdateCouldNotBeProcessedException : AuthRequestUpdateProcessingException | ||
{ | ||
public AuthRequestUpdateCouldNotBeProcessedException(Guid id) | ||
: base($"An auth request with id {id} could not be processed.") | ||
{ | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/Core/AdminConsole/OrganizationAuth/Models/AuthRequestUpdateProcessingException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Bit.Core.AdminConsole.OrganizationAuth.Models; | ||
|
||
public class AuthRequestUpdateProcessingException : Exception | ||
{ | ||
public AuthRequestUpdateProcessingException() { } | ||
|
||
public AuthRequestUpdateProcessingException(string message) | ||
: base(message) { } | ||
} |
112 changes: 112 additions & 0 deletions
112
src/Core/AdminConsole/OrganizationAuth/Models/AuthRequestUpdateProcessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Reflection; | ||
using Bit.Core.Auth.Entities; | ||
using Bit.Core.Enums; | ||
|
||
namespace Bit.Core.AdminConsole.OrganizationAuth.Models; | ||
|
||
public class AuthRequestUpdateProcessor<T> where T : AuthRequest | ||
{ | ||
public T ProcessedAuthRequest { get; private set; } | ||
|
||
private T _unprocessedAuthRequest { get; } | ||
private OrganizationAuthRequestUpdate _updates { get; } | ||
private AuthRequestUpdateProcessorConfiguration _configuration { get; } | ||
|
||
public AuthRequestUpdateProcessor( | ||
T authRequest, | ||
OrganizationAuthRequestUpdate updates, | ||
AuthRequestUpdateProcessorConfiguration configuration | ||
) | ||
{ | ||
_unprocessedAuthRequest = authRequest; | ||
_updates = updates; | ||
_configuration = configuration; | ||
} | ||
|
||
public AuthRequestUpdateProcessor<T> Process() | ||
{ | ||
var isExpired = DateTime.UtcNow > | ||
_unprocessedAuthRequest.CreationDate | ||
.Add(_configuration.AuthRequestExpiresAfter); | ||
var isSpent = _unprocessedAuthRequest == null || | ||
_unprocessedAuthRequest.Approved != null || | ||
_unprocessedAuthRequest.ResponseDate.HasValue || | ||
_unprocessedAuthRequest.AuthenticationDate.HasValue; | ||
var canBeProcessed = !isExpired && | ||
!isSpent && | ||
_unprocessedAuthRequest.Id == _updates.Id && | ||
_unprocessedAuthRequest.OrganizationId == _configuration.OrganizationId; | ||
if (!canBeProcessed) | ||
{ | ||
throw new AuthRequestUpdateCouldNotBeProcessedException(_unprocessedAuthRequest.Id); | ||
} | ||
return _updates.Approved ? | ||
Approve() : | ||
Deny(); | ||
} | ||
|
||
public async Task<AuthRequestUpdateProcessor<T>> SendPushNotification(Func<T, Task> callback) | ||
{ | ||
if (!ProcessedAuthRequest?.Approved ?? false || callback == null) | ||
{ | ||
return this; | ||
} | ||
await callback(ProcessedAuthRequest); | ||
return this; | ||
} | ||
|
||
public async Task<AuthRequestUpdateProcessor<T>> SendNewDeviceEmail(Func<T, string, Task> callback) | ||
{ | ||
if (!ProcessedAuthRequest?.Approved ?? false || callback == null) | ||
{ | ||
return this; | ||
} | ||
var deviceTypeDisplayName = _unprocessedAuthRequest.RequestDeviceType.GetType() | ||
.GetMember(_unprocessedAuthRequest.RequestDeviceType.ToString()) | ||
.FirstOrDefault()? | ||
// This unknown case can't be unit tested without adding an enum | ||
// with no display attribute. Faith and trust are required! | ||
.GetCustomAttribute<DisplayAttribute>()?.Name ?? "Unknown Device Type"; | ||
var deviceTypeAndIdentifierDisplayString = | ||
string.IsNullOrWhiteSpace(_unprocessedAuthRequest.RequestDeviceIdentifier) ? | ||
deviceTypeDisplayName : | ||
$"{deviceTypeDisplayName} - {_unprocessedAuthRequest.RequestDeviceIdentifier}"; | ||
await callback(ProcessedAuthRequest, deviceTypeAndIdentifierDisplayString); | ||
return this; | ||
} | ||
|
||
public async Task<AuthRequestUpdateProcessor<T>> SendEventLog(Func<T, EventType, Task> callback) | ||
{ | ||
if (!ProcessedAuthRequest?.Approved == null || callback == null) | ||
{ | ||
return this; | ||
} | ||
var eventType = _updates.Approved ? | ||
EventType.OrganizationUser_ApprovedAuthRequest : | ||
EventType.OrganizationUser_RejectedAuthRequest; | ||
await callback(ProcessedAuthRequest, eventType); | ||
return this; | ||
} | ||
|
||
private AuthRequestUpdateProcessor<T> Approve() | ||
{ | ||
if (string.IsNullOrWhiteSpace(_updates.Key)) | ||
{ | ||
throw new ApprovedAuthRequestIsMissingKeyException(_updates.Id); | ||
} | ||
ProcessedAuthRequest = _unprocessedAuthRequest; | ||
ProcessedAuthRequest.Key = _updates.Key; | ||
ProcessedAuthRequest.Approved = true; | ||
ProcessedAuthRequest.ResponseDate = DateTime.UtcNow; | ||
return this; | ||
} | ||
|
||
private AuthRequestUpdateProcessor<T> Deny() | ||
{ | ||
ProcessedAuthRequest = _unprocessedAuthRequest; | ||
ProcessedAuthRequest.Approved = false; | ||
ProcessedAuthRequest.ResponseDate = DateTime.UtcNow; | ||
return this; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/Core/AdminConsole/OrganizationAuth/Models/AuthRequestUpdateProcessorConfiguration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Bit.Core.AdminConsole.OrganizationAuth.Models; | ||
|
||
public class AuthRequestUpdateProcessorConfiguration | ||
{ | ||
public Guid OrganizationId { get; set; } | ||
public TimeSpan AuthRequestExpiresAfter { get; set; } | ||
} | ||
|
87 changes: 87 additions & 0 deletions
87
src/Core/AdminConsole/OrganizationAuth/Models/BatchAuthRequestUpdateProcessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using Bit.Core.Auth.Entities; | ||
using Bit.Core.Enums; | ||
|
||
namespace Bit.Core.AdminConsole.OrganizationAuth.Models; | ||
|
||
public class BatchAuthRequestUpdateProcessor<T> where T : AuthRequest | ||
{ | ||
public List<AuthRequestUpdateProcessor<T>> Processors { get; } = new List<AuthRequestUpdateProcessor<T>>(); | ||
private List<AuthRequestUpdateProcessor<T>> _processed => Processors | ||
.Where(p => p.ProcessedAuthRequest != null) | ||
.ToList(); | ||
|
||
public BatchAuthRequestUpdateProcessor( | ||
ICollection<T> authRequests, | ||
IEnumerable<OrganizationAuthRequestUpdate> updates, | ||
AuthRequestUpdateProcessorConfiguration configuration | ||
) | ||
{ | ||
Processors = authRequests?.Select(ar => | ||
{ | ||
return new AuthRequestUpdateProcessor<T>( | ||
ar, | ||
updates.FirstOrDefault(u => u.Id == ar.Id), | ||
configuration | ||
); | ||
}).ToList() ?? Processors; | ||
} | ||
|
||
public BatchAuthRequestUpdateProcessor<T> Process(Action<Exception> errorHandlerCallback) | ||
{ | ||
foreach (var processor in Processors) | ||
{ | ||
try | ||
{ | ||
processor.Process(); | ||
} | ||
catch (AuthRequestUpdateProcessingException e) | ||
{ | ||
errorHandlerCallback(e); | ||
} | ||
} | ||
return this; | ||
} | ||
|
||
public async Task<BatchAuthRequestUpdateProcessor<T>> Save(Func<IEnumerable<T>, Task> callback) | ||
{ | ||
if (_processed.Any()) | ||
{ | ||
await callback(_processed.Select(p => p.ProcessedAuthRequest)); | ||
} | ||
return this; | ||
} | ||
|
||
// Currently events like notifications, emails, and event logs are still | ||
// done per-request in a loop, which is different than saving updates to | ||
// the database. Saving can be done in bulk all the way through to the | ||
// repository. | ||
// | ||
// Perhaps these operations should be extended to be more batch-friendly | ||
// as well. | ||
public async Task<BatchAuthRequestUpdateProcessor<T>> SendPushNotifications(Func<T, Task> callback) | ||
{ | ||
foreach (var processor in _processed) | ||
{ | ||
await processor.SendPushNotification(callback); | ||
} | ||
return this; | ||
} | ||
|
||
public async Task<BatchAuthRequestUpdateProcessor<T>> SendNewDeviceEmails(Func<T, string, Task> callback) | ||
{ | ||
foreach (var processor in _processed) | ||
{ | ||
await processor.SendNewDeviceEmail(callback); | ||
} | ||
return this; | ||
} | ||
|
||
public async Task<BatchAuthRequestUpdateProcessor<T>> SendEventLogs(Func<T, EventType, Task> callback) | ||
{ | ||
foreach (var processor in _processed) | ||
{ | ||
await processor.SendEventLog(callback); | ||
} | ||
return this; | ||
} | ||
} |
Oops, something went wrong.