-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-10319] - Revoke Non Complaint Users for 2FA and Single Org Policy…
… Enablement (#5037) - Revoking users when enabling single org and 2fa policies. - Updated emails sent when users are revoked via 2FA or Single Organization policy enablement Co-authored-by: Matt Bishop <[email protected]> Co-authored-by: Rui Tomé <[email protected]>
- Loading branch information
1 parent
8f703a2
commit 1b75e35
Showing
36 changed files
with
1,074 additions
and
73 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 |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
public enum EventSystemUser : byte | ||
{ | ||
Unknown = 0, | ||
SCIM = 1, | ||
DomainVerification = 2, | ||
PublicApi = 3, | ||
|
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 @@ | ||
using Bit.Core.Enums; | ||
|
||
namespace Bit.Core.AdminConsole.Models.Data; | ||
|
||
public interface IActingUser | ||
{ | ||
Guid? UserId { get; } | ||
bool IsOrganizationOwnerOrProvider { get; } | ||
EventSystemUser? SystemUserType { get; } | ||
} |
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,16 @@ | ||
using Bit.Core.Enums; | ||
|
||
namespace Bit.Core.AdminConsole.Models.Data; | ||
|
||
public class StandardUser : IActingUser | ||
{ | ||
public StandardUser(Guid userId, bool isOrganizationOwner) | ||
{ | ||
UserId = userId; | ||
IsOrganizationOwnerOrProvider = isOrganizationOwner; | ||
} | ||
|
||
public Guid? UserId { get; } | ||
public bool IsOrganizationOwnerOrProvider { get; } | ||
public EventSystemUser? SystemUserType => throw new Exception($"{nameof(StandardUser)} does not have a {nameof(SystemUserType)}"); | ||
} |
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,16 @@ | ||
using Bit.Core.Enums; | ||
|
||
namespace Bit.Core.AdminConsole.Models.Data; | ||
|
||
public class SystemUser : IActingUser | ||
{ | ||
public SystemUser(EventSystemUser systemUser) | ||
{ | ||
SystemUserType = systemUser; | ||
} | ||
|
||
public Guid? UserId => throw new Exception($"{nameof(SystemUserType)} does not have a {nameof(UserId)}."); | ||
|
||
public bool IsOrganizationOwnerOrProvider => false; | ||
public EventSystemUser? SystemUserType { get; } | ||
} |
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
9 changes: 9 additions & 0 deletions
9
...zationFeatures/OrganizationUsers/Interfaces/IRevokeNonCompliantOrganizationUserCommand.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 @@ | ||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; | ||
using Bit.Core.Models.Commands; | ||
|
||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; | ||
|
||
public interface IRevokeNonCompliantOrganizationUserCommand | ||
{ | ||
Task<CommandResult> RevokeNonCompliantOrganizationUsersAsync(RevokeOrganizationUsersRequest request); | ||
} |
13 changes: 13 additions & 0 deletions
13
...nConsole/OrganizationFeatures/OrganizationUsers/Requests/RevokeOrganizationUserRequest.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,13 @@ | ||
using Bit.Core.AdminConsole.Models.Data; | ||
using Bit.Core.Models.Data.Organizations.OrganizationUsers; | ||
|
||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; | ||
|
||
public record RevokeOrganizationUsersRequest( | ||
Guid OrganizationId, | ||
IEnumerable<OrganizationUserUserDetails> OrganizationUsers, | ||
IActingUser ActionPerformedBy) | ||
{ | ||
public RevokeOrganizationUsersRequest(Guid organizationId, OrganizationUserUserDetails organizationUser, IActingUser actionPerformedBy) | ||
: this(organizationId, [organizationUser], actionPerformedBy) { } | ||
} |
112 changes: 112 additions & 0 deletions
112
...nsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommand.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 Bit.Core.AdminConsole.Models.Data; | ||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; | ||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; | ||
using Bit.Core.Enums; | ||
using Bit.Core.Models.Commands; | ||
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<CommandResult> 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<CommandResult> 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; | ||
} |
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
Oops, something went wrong.