using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.Utilities.Commands; using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; public class RevokeNonCompliantOrganizationUserCommand(IOrganizationUserRepository organizationUserRepository, IEventService eventService, IHasConfirmedOwnersExceptQuery confirmedOwnersExceptQuery, TimeProvider timeProvider) : IRevokeNonCompliantOrganizationUserCommand { public const string ErrorCannotRevokeSelf = "You cannot revoke yourself."; public const string ErrorOnlyOwnersCanRevokeOtherOwners = "Only owners can revoke other owners."; public const string ErrorUserAlreadyRevoked = "User is already revoked."; public const string ErrorOrgMustHaveAtLeastOneOwner = "Organization must have at least one confirmed owner."; public const string ErrorInvalidUsers = "Invalid users."; public const string ErrorRequestedByWasNotValid = "Action was performed by an unexpected type."; public async Task RevokeNonCompliantOrganizationUsersAsync(RevokeOrganizationUsersRequest request) { var validationResult = await ValidateAsync(request); if (validationResult.HasErrors) { return validationResult; } await organizationUserRepository.RevokeManyByIdAsync(request.OrganizationUsers.Select(x => x.Id)); var now = timeProvider.GetUtcNow(); switch (request.ActionPerformedBy) { case StandardUser: await eventService.LogOrganizationUserEventsAsync( request.OrganizationUsers.Select(x => GetRevokedUserEventTuple(x, now))); break; case SystemUser { SystemUserType: not null } loggableSystem: await eventService.LogOrganizationUserEventsAsync( request.OrganizationUsers.Select(x => GetRevokedUserEventBySystemUserTuple(x, loggableSystem.SystemUserType.Value, now))); break; } return validationResult; } private static (OrganizationUserUserDetails organizationUser, EventType eventType, DateTime? time) GetRevokedUserEventTuple( OrganizationUserUserDetails organizationUser, DateTimeOffset dateTimeOffset) => new(organizationUser, EventType.OrganizationUser_Revoked, dateTimeOffset.UtcDateTime); private static (OrganizationUserUserDetails organizationUser, EventType eventType, EventSystemUser eventSystemUser, DateTime? time) GetRevokedUserEventBySystemUserTuple( OrganizationUserUserDetails organizationUser, EventSystemUser systemUser, DateTimeOffset dateTimeOffset) => new(organizationUser, EventType.OrganizationUser_Revoked, systemUser, dateTimeOffset.UtcDateTime); private async Task ValidateAsync(RevokeOrganizationUsersRequest request) { if (!PerformedByIsAnExpectedType(request.ActionPerformedBy)) { return new CommandResult(ErrorRequestedByWasNotValid); } if (request.ActionPerformedBy is StandardUser user && request.OrganizationUsers.Any(x => x.UserId == user.UserId)) { return new CommandResult(ErrorCannotRevokeSelf); } if (request.OrganizationUsers.Any(x => x.OrganizationId != request.OrganizationId)) { return new CommandResult(ErrorInvalidUsers); } if (!await confirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync( request.OrganizationId, request.OrganizationUsers.Select(x => x.Id))) { return new CommandResult(ErrorOrgMustHaveAtLeastOneOwner); } return request.OrganizationUsers.Aggregate(new CommandResult(), (result, userToRevoke) => { if (IsAlreadyRevoked(userToRevoke)) { result.ErrorMessages.Add($"{ErrorUserAlreadyRevoked} Id: {userToRevoke.Id}"); return result; } if (NonOwnersCannotRevokeOwners(userToRevoke, request.ActionPerformedBy)) { result.ErrorMessages.Add($"{ErrorOnlyOwnersCanRevokeOtherOwners}"); return result; } return result; }); } private static bool PerformedByIsAnExpectedType(IActingUser entity) => entity is SystemUser or StandardUser; private static bool IsAlreadyRevoked(OrganizationUserUserDetails organizationUser) => organizationUser is { Status: OrganizationUserStatusType.Revoked }; private static bool NonOwnersCannotRevokeOwners(OrganizationUserUserDetails organizationUser, IActingUser actingUser) => actingUser is StandardUser { IsOrganizationOwnerOrProvider: false } && organizationUser.Type == OrganizationUserType.Owner; }