1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -05:00

[PM-15621] WIP – this is a broken build.

This commit is contained in:
Jimmy Vo 2025-04-03 17:41:04 -04:00
parent 67df34e784
commit c08b3d99ab
No known key found for this signature in database
GPG Key ID: 7CB834D6F4FFCA11
10 changed files with 323 additions and 168 deletions

View File

@ -594,7 +594,7 @@ public class OrganizationUsersController : Controller
var deletionResult = await _deleteManagedOrganizationUserAccountCommand.DeleteUserAsync(orgId, id, currentUser.Id); var deletionResult = await _deleteManagedOrganizationUserAccountCommand.DeleteUserAsync(orgId, id, currentUser.Id);
return deletionResult.result.MapToActionResult(); return deletionResult.MapToActionResult();
} }
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)] [RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]

View File

@ -0,0 +1,7 @@
namespace Bit.Core.AdminConsole.Errors;
public record BadRequestError<T> : Error<T>
{
public BadRequestError(string code, T invalidRequest)
: base(code, invalidRequest) { }
}

View File

@ -1,3 +1,8 @@
namespace Bit.Core.AdminConsole.Errors; namespace Bit.Core.AdminConsole.Errors;
public record Error<T>(string Message, T ErroredValue); public record Error<T>(string Message, T ErroredValue);
public static class ErrorMappers
{
public static Error<B> ToError<A, B>(this Error<A> errorA, B erroredValue) => new(errorA.Message, erroredValue);
}

View File

@ -1,5 +1,5 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -11,103 +11,108 @@ using Bit.Core.Services;
using Bit.Core.Tools.Enums; using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business; using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services; using Bit.Core.Tools.Services;
using Bit.Core.Validators;
#nullable enable
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
#nullable enable
public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganizationUserAccountCommand public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganizationUserAccountCommand
{ {
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IDeleteManagedOrganizationUserAccountValidator _deleteManagedOrganizationUserAccountValidator;
private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery; private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly IPushNotificationService _pushService; private readonly IPushNotificationService _pushService;
private readonly IProviderUserRepository _providerUserRepository;
public DeleteManagedOrganizationUserAccountCommand( public DeleteManagedOrganizationUserAccountCommand(
IUserService userService, IUserService userService,
IEventService eventService, IEventService eventService,
IDeleteManagedOrganizationUserAccountValidator deleteManagedOrganizationUserAccountValidator,
IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery, IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IUserRepository userRepository, IUserRepository userRepository,
ICurrentContext currentContext, ICurrentContext currentContext,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
IPushNotificationService pushService, IPushNotificationService pushService)
IProviderUserRepository providerUserRepository
)
{ {
_userService = userService; _userService = userService;
_eventService = eventService; _eventService = eventService;
_deleteManagedOrganizationUserAccountValidator = deleteManagedOrganizationUserAccountValidator;
_getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery; _getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_userRepository = userRepository; _userRepository = userRepository;
_currentContext = currentContext; _currentContext = currentContext;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_pushService = pushService; _pushService = pushService;
_providerUserRepository = providerUserRepository;
} }
public async Task<(Guid OrganizationUserId, CommandResult result)> DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId) public async Task<CommandResult> DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
{ {
var result = await InternalDeleteManyUsersAsync(organizationId, new[] { organizationUserId }, deletingUserId); var result = await InternalDeleteManyUsersAsync(organizationId, new[] { organizationUserId }, deletingUserId);
return result.FirstOrDefault();
if (result.InvalidResults.Count > 0)
{
var error = result.InvalidResults.FirstOrDefault()?.Errors.FirstOrDefault();
return new Failure();
}
return new Success();
} }
public async Task<IEnumerable<(Guid OrganizationUserId, CommandResult result)>> DeleteManyUsersAsync(Guid organizationId, IEnumerable<Guid> orgUserIds, Guid? deletingUserId) public async Task<IEnumerable<(Guid OrganizationUserId, CommandResult result)>> DeleteManyUsersAsync(Guid organizationId, IEnumerable<Guid> orgUserIds, Guid? deletingUserId)
{ {
return await InternalDeleteManyUsersAsync(organizationId, orgUserIds, deletingUserId); var results = await InternalDeleteManyUsersAsync(organizationId, orgUserIds, deletingUserId);
} }
private async Task<IEnumerable<(Guid OrganizationUserId, CommandResult result)>> InternalDeleteManyUsersAsync(Guid organizationId, IEnumerable<Guid> orgUserIds, Guid? deletingUserId) private async Task<PartialValidationResult<DeleteUserValidationRequest>> InternalDeleteManyUsersAsync(Guid organizationId, IEnumerable<Guid> orgUserIds, Guid? deletingUserId)
{ {
var orgUsers = await _organizationUserRepository.GetManyAsync(orgUserIds); var orgUsers = await _organizationUserRepository.GetManyAsync(orgUserIds);
var users = await GetUsersAsync(orgUsers); var users = await GetUsersAsync(orgUsers);
var managementStatus = await _getOrganizationUsersManagementStatusQuery.GetUsersOrganizationManagementStatusAsync(organizationId, orgUserIds); var managementStatuses = await _getOrganizationUsersManagementStatusQuery.GetUsersOrganizationManagementStatusAsync(organizationId, orgUserIds);
var userDeletionRequests = new List<(Guid OrganizationUserId, CommandResult result, OrganizationUser? orgUser, User? user)>(); var requests = CreateRequests(organizationId, deletingUserId, orgUserIds, orgUsers, users, managementStatuses);
var validationResults = await _deleteManagedOrganizationUserAccountValidator.ValidateAsync(requests);
await CancelPremiumsAsync(validationResults.ValidResults);
await HandleUserDeletionsAsync(validationResults.ValidResults);
await LogDeletedOrganizationUsersAsync(validationResults.ValidResults);
return validationResults;
}
private List<DeleteUserValidationRequest> CreateRequests(
Guid organizationId,
Guid? deletingUserId,
IEnumerable<Guid> orgUserIds,
ICollection<OrganizationUser> orgUsers,
IEnumerable<User> users,
IDictionary<Guid, bool> managementStatuses)
{
var requests = new List<DeleteUserValidationRequest>();
foreach (var orgUserId in orgUserIds) foreach (var orgUserId in orgUserIds)
{ {
var orgUser = orgUsers.FirstOrDefault(ou => ou.Id == orgUserId); var orgUser = orgUsers.FirstOrDefault(orgUser => orgUser.Id == orgUserId);
if (orgUser == null || orgUser.OrganizationId != organizationId) var user = users.FirstOrDefault(user => user.Id == orgUser?.UserId);
managementStatuses.TryGetValue(orgUserId, out var isManaged);
requests.Add(new DeleteUserValidationRequest
{ {
userDeletionRequests.Add((orgUserId, new BadRequestFailure("Member not found."), null, null)); User = user,
continue; OrganizationUser = orgUser,
} IsManaged = isManaged,
OrganizationId = organizationId,
var user = users.FirstOrDefault(u => u.Id == orgUser.UserId); DeletingUserId = deletingUserId,
});
if (user == null)
{
userDeletionRequests.Add((orgUserId, new BadRequestFailure("Member not found."), orgUser, null));
continue;
}
var result = await ValidateAsync(organizationId, orgUser, user, deletingUserId, managementStatus);
if (result is not Success)
{
userDeletionRequests.Add((orgUserId, result, orgUser, user));
continue;
}
await CancelPremiumAsync(user);
userDeletionRequests.Add((orgUserId, new Success(), orgUser, user));
} }
await HandleUserDeletionsAsync(userDeletionRequests); return requests;
await LogDeletedOrganizationUsersAsync(userDeletionRequests);
return userDeletionRequests
.Select(request => (request.OrganizationUserId, request.result))
.ToList();
} }
private async Task<IEnumerable<User>> GetUsersAsync(ICollection<OrganizationUser> orgUsers) private async Task<IEnumerable<User>> GetUsersAsync(ICollection<OrganizationUser> orgUsers)
@ -120,112 +125,12 @@ public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganiz
return await _userRepository.GetManyAsync(userIds); return await _userRepository.GetManyAsync(userIds);
} }
private async Task<CommandResult> ValidateAsync(Guid organizationId, OrganizationUser orgUser, User user, Guid? deletingUserId, IDictionary<Guid, bool> managementStatus) private async Task LogDeletedOrganizationUsersAsync(List<Valid<DeleteUserValidationRequest>> requests)
{
var validators = new[]
{
() => EnsureUserStatusIsNotInvited(orgUser),
() => PreventSelfDeletion(orgUser, deletingUserId),
() => EnsureUserIsManagedByOrganization(orgUser, managementStatus),
};
var result = CommandResultValidator.ExecuteValidators(validators);
if (result is not Success)
{
return result;
}
var asyncValidators = new[]
{
async () => await EnsureOnlyOwnersCanDeleteOwnersAsync(organizationId, orgUser, deletingUserId),
async () => await EnsureUserIsNotSoleOrganizationOwnerAsync(user),
async () => await EnsureUserIsNotSoleProviderOwnerAsync(user)
};
var asyncResult = await CommandResultValidator.ExecuteValidatorAsync(asyncValidators);
if (asyncResult is not Success)
{
return asyncResult;
}
return new Success();
}
private static CommandResult EnsureUserStatusIsNotInvited(OrganizationUser orgUser)
{
if (!orgUser.UserId.HasValue || orgUser.Status == OrganizationUserStatusType.Invited)
{
return new BadRequestFailure("You cannot delete a member with Invited status.");
}
return new Success();
}
private static CommandResult PreventSelfDeletion(OrganizationUser orgUser, Guid? deletingUserId)
{
if (!orgUser.UserId.HasValue || !deletingUserId.HasValue)
{
return new Success();
}
if (orgUser.UserId.Value == deletingUserId.Value)
{
return new BadRequestFailure("You cannot delete yourself.");
}
return new Success();
}
private async Task<CommandResult> EnsureOnlyOwnersCanDeleteOwnersAsync(Guid organizationId, OrganizationUser orgUser, Guid? deletingUserId)
{
if (orgUser.Type != OrganizationUserType.Owner)
{
return new Success();
}
if (deletingUserId.HasValue && !await _currentContext.OrganizationOwner(organizationId))
{
return new BadRequestFailure("Only owners can delete other owners.");
}
return new Success();
}
private static CommandResult EnsureUserIsManagedByOrganization(OrganizationUser orgUser, IDictionary<Guid, bool> managementStatus)
{
if (!managementStatus.TryGetValue(orgUser.Id, out var isManaged) || !isManaged)
{
return new BadRequestFailure("Member is not managed by the organization.");
}
return new Success();
}
private async Task<CommandResult> EnsureUserIsNotSoleOrganizationOwnerAsync(User user)
{
var onlyOwnerCount = await _organizationUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerCount > 0)
{
return new BadRequestFailure("Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user.");
}
return new Success();
}
private async Task<CommandResult> EnsureUserIsNotSoleProviderOwnerAsync(User user)
{
var onlyOwnerProviderCount = await _providerUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerProviderCount > 0)
{
return new BadRequestFailure("Cannot delete this user because it is the sole owner of at least one provider. Please delete these providers or upgrade another user.");
}
return new Success();
}
private async Task LogDeletedOrganizationUsersAsync(
List<(Guid OrganizationUserId, CommandResult result, OrganizationUser? orgUser, User? user)> userDeletionRequests)
{ {
var eventDate = DateTime.UtcNow; var eventDate = DateTime.UtcNow;
var events = userDeletionRequests var events = requests
.Where(request => .Select(request => (request.Value.OrganizationUser!, (EventType)EventType.OrganizationUser_Deleted, (DateTime?)eventDate))
request.result is Success)
.Select(request => (request.orgUser!, (EventType)EventType.OrganizationUser_Deleted, (DateTime?)eventDate))
.ToList(); .ToList();
if (events.Any()) if (events.Any())
@ -234,16 +139,15 @@ public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganiz
} }
} }
private async Task HandleUserDeletionsAsync(List<(Guid OrganizationUserId, CommandResult result, OrganizationUser? orgUser, User? user)> userDeletionRequests)
{
var usersToDelete = userDeletionRequests
.Where(request =>
request.result is Success)
.Select(request => request.user!);
if (usersToDelete.Any()) private async Task HandleUserDeletionsAsync(List<Valid<DeleteUserValidationRequest>> requests)
{
var users = requests
.Select(request => request.Value.User!);
if (users.Any())
{ {
await DeleteManyAsync(usersToDelete); await DeleteManyAsync(users);
} }
} }
@ -259,16 +163,23 @@ public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganiz
} }
private async Task CancelPremiumAsync(User user) private async Task CancelPremiumsAsync(List<Valid<DeleteUserValidationRequest>> requests)
{ {
if (string.IsNullOrWhiteSpace(user.GatewaySubscriptionId)) var users = requests
.Select(request => request.Value.User!);
foreach (var user in users)
{ {
return; try
{
await _userService.CancelPremiumAsync(user);
}
catch (GatewayException)
{
}
} }
try
{
await _userService.CancelPremiumAsync(user);
}
catch (GatewayException) { }
} }
} }

View File

@ -0,0 +1,15 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
#nullable enable
public class DeleteUserValidationRequest
{
public Guid OrganizationId { get; init; }
public OrganizationUser? OrganizationUser { get; init; }
public User? User { get; init; }
public Guid? DeletingUserId { get; init; }
public IDictionary<Guid, bool>? ManagementStatus { get; init; }
public bool? IsManaged { get; init; }
}

View File

@ -9,7 +9,8 @@ public interface IDeleteManagedOrganizationUserAccountCommand
/// <summary> /// <summary>
/// Removes a user from an organization and deletes all of their associated user data. /// Removes a user from an organization and deletes all of their associated user data.
/// </summary> /// </summary>
Task<(Guid OrganizationUserId, CommandResult result)> DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId); /// Jimmy temporary comment: consider removing the nullable from deletingUserId.
Task<CommandResult> DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
/// <summary> /// <summary>
/// Removes multiple users from an organization and deletes all of their associated user data. /// Removes multiple users from an organization and deletes all of their associated user data.
@ -17,5 +18,6 @@ public interface IDeleteManagedOrganizationUserAccountCommand
/// <returns> /// <returns>
/// An error message for each user that could not be removed, otherwise null. /// An error message for each user that could not be removed, otherwise null.
/// </returns> /// </returns>
/// Jimmy temporary comment: consider removing the nullable from deletingUserId.
Task<IEnumerable<(Guid OrganizationUserId, CommandResult result)>> DeleteManyUsersAsync(Guid organizationId, IEnumerable<Guid> orgUserIds, Guid? deletingUserId); Task<IEnumerable<(Guid OrganizationUserId, CommandResult result)>> DeleteManyUsersAsync(Guid organizationId, IEnumerable<Guid> orgUserIds, Guid? deletingUserId);
} }

View File

@ -0,0 +1,8 @@
using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IDeleteManagedOrganizationUserAccountValidator
{
Task<PartialValidationResult<DeleteUserValidationRequest>> ValidateAsync(List<DeleteUserValidationRequest> requests);
}

View File

@ -0,0 +1,160 @@
using Bit.Core.AdminConsole.Errors;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser;
public class DeleteManagedOrganizationUserAccountValidator(
ICurrentContext currentContext,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository) : IDeleteManagedOrganizationUserAccountValidator
{
public async Task<PartialValidationResult<DeleteUserValidationRequest>> ValidateAsync(List<DeleteUserValidationRequest> requests)
{
var invalidResults = new List<Invalid<DeleteUserValidationRequest>>();
var validResults = new List<Valid<DeleteUserValidationRequest>>();
foreach (var request in requests)
{
// The order of the validators matters.
// Earlier validators assert nullable properties so that later validators dont have to.
var validators = new[]
{
EnsureUserBelongsToOrganization,
EnsureUserStatusIsNotInvited,
PreventSelfDeletion,
EnsureUserIsManagedByOrganization
};
var asyncValidators = new[]
{
EnsureOnlyOwnersCanDeleteOwnersAsync,
EnsureUserIsNotSoleOrganizationOwnerAsync,
EnsureUserIsNotSoleProviderOwnerAsync
};
var result = await ExecuteValidatorsAsync(validators, asyncValidators, request);
if (result is Valid<DeleteUserValidationRequest> valid)
{
validResults.Add(valid);
}
else
{
invalidResults.Add((Invalid<DeleteUserValidationRequest>)result);
}
}
return new PartialValidationResult<DeleteUserValidationRequest>()
{
InvalidResults = invalidResults,
ValidResults = validResults
};
}
private static async Task<ValidationResult<DeleteUserValidationRequest>> ExecuteValidatorsAsync(
Func<DeleteUserValidationRequest, ValidationResult<DeleteUserValidationRequest>>[] validators,
Func<DeleteUserValidationRequest, Task<ValidationResult<DeleteUserValidationRequest>>>[] asyncValidators,
DeleteUserValidationRequest request)
{
foreach (var validator in validators)
{
var result = validator(request);
if (result is Invalid<DeleteUserValidationRequest>)
{
return result;
}
}
foreach (var asyncValidator in asyncValidators)
{
var result = await asyncValidator(request);
if (result is Invalid<DeleteUserValidationRequest>)
{
return result;
}
}
return new Valid<DeleteUserValidationRequest>(request);
}
private static ValidationResult<DeleteUserValidationRequest> EnsureUserBelongsToOrganization(DeleteUserValidationRequest request)
{
if (request.User == null || request.OrganizationUser == null)
{
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("You cannot delete a member with Invited status.", request));
}
return new Valid<DeleteUserValidationRequest>();
}
private static ValidationResult<DeleteUserValidationRequest> EnsureUserIsManagedByOrganization(DeleteUserValidationRequest request)
{
if (request.IsManaged == true)
{
return new Valid<DeleteUserValidationRequest>();
}
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("Member is not managed by the organization.", request));
}
private static ValidationResult<DeleteUserValidationRequest> EnsureUserStatusIsNotInvited(DeleteUserValidationRequest request)
{
if (request.OrganizationUser!.Status == OrganizationUserStatusType.Invited)
{
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("You cannot delete a member with Invited status.", request));
}
return new Valid<DeleteUserValidationRequest>();
}
private static ValidationResult<DeleteUserValidationRequest> PreventSelfDeletion(DeleteUserValidationRequest request)
{
if (request.OrganizationUser?.UserId == request.DeletingUserId)
{
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("You cannot delete yourself.", request));
}
return new Valid<DeleteUserValidationRequest>(request);
}
private async Task<ValidationResult<DeleteUserValidationRequest>> EnsureOnlyOwnersCanDeleteOwnersAsync(DeleteUserValidationRequest request)
{
if (request.OrganizationUser?.Type != OrganizationUserType.Owner)
{
return new Valid<DeleteUserValidationRequest>(request);
}
if (request.DeletingUserId.HasValue && !await currentContext.OrganizationOwner(request.OrganizationId))
{
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("Only owners can delete other owners.", request));
}
return new Valid<DeleteUserValidationRequest>(request);
}
private async Task<ValidationResult<DeleteUserValidationRequest>> EnsureUserIsNotSoleOrganizationOwnerAsync(DeleteUserValidationRequest request)
{
var onlyOwnerCount = await organizationUserRepository.GetCountByOnlyOwnerAsync(request.User!.Id);
if (onlyOwnerCount > 0)
{
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user.", request));
}
return new Valid<DeleteUserValidationRequest>(request);
}
private async Task<ValidationResult<DeleteUserValidationRequest>> EnsureUserIsNotSoleProviderOwnerAsync(DeleteUserValidationRequest request)
{
var onlyOwnerProviderCount = await providerUserRepository.GetCountByOnlyOwnerAsync(request.User!.Id);
if (onlyOwnerProviderCount > 0)
{
return new Invalid<DeleteUserValidationRequest>(new BadRequestError<DeleteUserValidationRequest>("Cannot delete this user because it is the sole owner of at least one provider. Please delete these providers or upgrade another user.", request));
}
return new Valid<DeleteUserValidationRequest>(request);
}
}

View File

@ -6,10 +6,35 @@ public abstract record ValidationResult<T>;
public record Valid<T> : ValidationResult<T> public record Valid<T> : ValidationResult<T>
{ {
public Valid() { }
public Valid(T Value)
{
this.Value = Value;
}
public T Value { get; init; } public T Value { get; init; }
} }
public record PartialValidationResult<T>
{
public List<Invalid<T>> InvalidResults { get; init; }
public List<Valid<T>> ValidResults { get; init; }
}
public record Invalid<T> : ValidationResult<T> public record Invalid<T> : ValidationResult<T>
{ {
public IEnumerable<Error<T>> Errors { get; init; } public IEnumerable<Error<T>> Errors { get; init; } = [];
public string ErrorMessageString => string.Join(" ", Errors.Select(e => e.Message));
public Invalid() { }
public Invalid(Error<T> error) : this([error]) { }
public Invalid(IEnumerable<Error<T>> errors)
{
Errors = errors;
}
} }

View File

@ -0,0 +1,22 @@
namespace Bit.Core.AdminConsole.Shared.Validation;
public static class ValidationResultMappers
{
// public static Failure MapToFailure<T>(this Error<T> error)
// {
//
// return error switch
// {
// BadRequestError<T> badRequestError => new Failure(badRequestError.Message),
// _ => throw new InvalidOperationException($"Unhandled commandResult type: {error.GetType().Name}")
// }
// // return commandResult switch
// // {
// // NoRecordFoundFailure<T> failure => new ObjectResult(failure.ErrorMessages) { StatusCode = StatusCodes.Status404NotFound },
// // BadRequestFailure<T> failure => new ObjectResult(failure.ErrorMessages) { StatusCode = StatusCodes.Status400BadRequest },
// // Failure<T> failure => new ObjectResult(failure.ErrorMessages) { StatusCode = StatusCodes.Status400BadRequest },
// // Success<T> success => new ObjectResult(success.Value) { StatusCode = StatusCodes.Status200OK },
// // _ => throw new InvalidOperationException($"Unhandled commandResult type: {commandResult.GetType().Name}")
// // };
// }
}