diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/AcceptedOrganizationUser.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/AcceptedOrganizationUser.cs index a223c13990..4f596d596a 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/AcceptedOrganizationUser.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/AcceptedOrganizationUser.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models; @@ -57,13 +57,24 @@ public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions /// public bool AccessSecretsManager { get; set; } + /// + /// True if the user's access has been revoked, false otherwise. + /// + public bool Revoked { get; set; } + /// /// Transitions this accepted user to a confirmed state when an organization admin confirms them. /// /// The Organization symmetric key encrypted with the User's public key. /// A new instance. + /// Thrown if the user is revoked. public ConfirmedOrganizationUser ToConfirmed(string key) { + if (Revoked) + { + throw new InvalidOperationException("Cannot transition a revoked user to confirmed status"); + } + return new ConfirmedOrganizationUser { Id = Id, @@ -76,14 +87,15 @@ public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions CreationDate = CreationDate, RevisionDate = DateTime.UtcNow, Permissions = Permissions, - AccessSecretsManager = AccessSecretsManager + AccessSecretsManager = AccessSecretsManager, + Revoked = false }; } /// /// Converts this model to an entity. /// - /// An entity with Status set to Accepted. + /// An entity with Status set to Accepted or Revoked based on the Revoked flag. public OrganizationUser ToEntity() { return new OrganizationUser @@ -94,7 +106,7 @@ public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions Email = null, Key = null, ResetPasswordKey = null, - Status = OrganizationUserStatusType.Accepted, + Status = Revoked ? OrganizationUserStatusType.Revoked : OrganizationUserStatusType.Accepted, Type = Type, ExternalId = ExternalId, CreationDate = CreationDate, @@ -107,16 +119,28 @@ public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions /// /// Creates an from an entity. /// - /// The entity to convert from. Must have Status = Accepted and UserId must not be null. + /// The entity to convert from. Must have Status = Accepted or Revoked (with pre-revoked status of Accepted), and UserId must not be null. /// A new instance. - /// Thrown if the entity is not in Accepted status or UserId is null. + /// Thrown if the entity status is invalid or UserId is null. public static AcceptedOrganizationUser FromEntity(OrganizationUser entity) { - if (entity.Status != OrganizationUserStatusType.Accepted) + var isRevoked = entity.Status == OrganizationUserStatusType.Revoked; + + if (!isRevoked && entity.Status != OrganizationUserStatusType.Accepted) { throw new InvalidOperationException($"Cannot create AcceptedOrganizationUser from entity with status {entity.Status}"); } + if (isRevoked) + { + // Validate that the revoked user's pre-revoked status is Accepted + var preRevokedStatus = OrganizationService.GetPriorActiveOrganizationUserStatusType(entity); + if (preRevokedStatus != OrganizationUserStatusType.Accepted) + { + throw new InvalidOperationException($"Cannot create AcceptedOrganizationUser from revoked entity with pre-revoked status {preRevokedStatus}"); + } + } + if (!entity.UserId.HasValue) { throw new InvalidOperationException("Cannot create AcceptedOrganizationUser from entity with null UserId"); @@ -132,7 +156,8 @@ public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions CreationDate = entity.CreationDate, RevisionDate = entity.RevisionDate, Permissions = entity.Permissions, - AccessSecretsManager = entity.AccessSecretsManager + AccessSecretsManager = entity.AccessSecretsManager, + Revoked = isRevoked }; } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/ConfirmedOrganizationUser.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/ConfirmedOrganizationUser.cs index b6e1bda29c..4637a2c63f 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/ConfirmedOrganizationUser.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/ConfirmedOrganizationUser.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models; @@ -69,10 +69,15 @@ public class ConfirmedOrganizationUser : IExternal, IOrganizationUserPermissions /// public bool AccessSecretsManager { get; set; } + /// + /// True if the user's access has been revoked, false otherwise. + /// + public bool Revoked { get; set; } + /// /// Converts this model to an entity. /// - /// An entity with Status set to Confirmed. + /// An entity with Status set to Confirmed or Revoked based on the Revoked flag. public OrganizationUser ToEntity() { return new OrganizationUser @@ -83,7 +88,7 @@ public class ConfirmedOrganizationUser : IExternal, IOrganizationUserPermissions Email = null, Key = Key, ResetPasswordKey = ResetPasswordKey, - Status = OrganizationUserStatusType.Confirmed, + Status = Revoked ? OrganizationUserStatusType.Revoked : OrganizationUserStatusType.Confirmed, Type = Type, ExternalId = ExternalId, CreationDate = CreationDate, @@ -96,16 +101,28 @@ public class ConfirmedOrganizationUser : IExternal, IOrganizationUserPermissions /// /// Creates a from an entity. /// - /// The entity to convert from. Must have Status = Confirmed, UserId and Key must not be null. + /// The entity to convert from. Must have Status = Confirmed or Revoked (with pre-revoked status of Confirmed), UserId and Key must not be null. /// A new instance. - /// Thrown if the entity is not in Confirmed status, or UserId or Key is null. + /// Thrown if the entity status is invalid, or UserId or Key is null. public static ConfirmedOrganizationUser FromEntity(OrganizationUser entity) { - if (entity.Status != OrganizationUserStatusType.Confirmed) + var isRevoked = entity.Status == OrganizationUserStatusType.Revoked; + + if (!isRevoked && entity.Status != OrganizationUserStatusType.Confirmed) { throw new InvalidOperationException($"Cannot create ConfirmedOrganizationUser from entity with status {entity.Status}"); } + if (isRevoked) + { + // Validate that the revoked user's pre-revoked status is Confirmed + var preRevokedStatus = OrganizationService.GetPriorActiveOrganizationUserStatusType(entity); + if (preRevokedStatus != OrganizationUserStatusType.Confirmed) + { + throw new InvalidOperationException($"Cannot create ConfirmedOrganizationUser from revoked entity with pre-revoked status {preRevokedStatus}"); + } + } + if (!entity.UserId.HasValue) { throw new InvalidOperationException("Cannot create ConfirmedOrganizationUser from entity with null UserId"); @@ -128,7 +145,8 @@ public class ConfirmedOrganizationUser : IExternal, IOrganizationUserPermissions CreationDate = entity.CreationDate, RevisionDate = entity.RevisionDate, Permissions = entity.Permissions, - AccessSecretsManager = entity.AccessSecretsManager + AccessSecretsManager = entity.AccessSecretsManager, + Revoked = isRevoked }; } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/InvitedOrganizationUser.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/InvitedOrganizationUser.cs index 2383dd9a86..e39bf562ae 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/InvitedOrganizationUser.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Models/InvitedOrganizationUser.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.Services; using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models; @@ -57,6 +58,11 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions /// public bool AccessSecretsManager { get; set; } + /// + /// True if the user's access has been revoked, false otherwise. + /// + public bool Revoked { get; set; } + public void SetNewId() { Id = CoreHelpers.GenerateComb(); @@ -67,8 +73,14 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions /// /// The ID of the User who accepted the invitation. /// A new instance. + /// Thrown if the user is revoked. public AcceptedOrganizationUser ToAccepted(Guid userId) { + if (Revoked) + { + throw new InvalidOperationException("Cannot transition a revoked user to accepted status"); + } + return new AcceptedOrganizationUser { Id = Id, @@ -79,14 +91,15 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions CreationDate = CreationDate, RevisionDate = DateTime.UtcNow, Permissions = Permissions, - AccessSecretsManager = AccessSecretsManager + AccessSecretsManager = AccessSecretsManager, + Revoked = false }; } /// /// Converts this model to an entity. /// - /// An entity with Status set to Invited. + /// An entity with Status set to Invited or Revoked based on the Revoked flag. public OrganizationUser ToEntity() { return new OrganizationUser @@ -97,7 +110,7 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions Email = Email, Key = null, ResetPasswordKey = null, - Status = OrganizationUserStatusType.Invited, + Status = Revoked ? OrganizationUserStatusType.Revoked : OrganizationUserStatusType.Invited, Type = Type, ExternalId = ExternalId, CreationDate = CreationDate, @@ -110,16 +123,28 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions /// /// Creates an from an entity. /// - /// The entity to convert from. Must have Status = Invited and Email must not be null. + /// The entity to convert from. Must have Status = Invited or Revoked (with pre-revoked status of Invited), and Email must not be null. /// A new instance. - /// Thrown if the entity is not in Invited status or Email is null. + /// Thrown if the entity status is invalid or Email is null. public static InvitedOrganizationUser FromEntity(OrganizationUser entity) { - if (entity.Status != OrganizationUserStatusType.Invited) + var isRevoked = entity.Status == OrganizationUserStatusType.Revoked; + + if (!isRevoked && entity.Status != OrganizationUserStatusType.Invited) { throw new InvalidOperationException($"Cannot create InvitedOrganizationUser from entity with status {entity.Status}"); } + if (isRevoked) + { + // Validate that the revoked user's pre-revoked status is Invited + var preRevokedStatus = OrganizationService.GetPriorActiveOrganizationUserStatusType(entity); + if (preRevokedStatus != OrganizationUserStatusType.Invited) + { + throw new InvalidOperationException($"Cannot create InvitedOrganizationUser from revoked entity with pre-revoked status {preRevokedStatus}"); + } + } + if (string.IsNullOrEmpty(entity.Email)) { throw new InvalidOperationException("Cannot create InvitedOrganizationUser from entity with null Email"); @@ -135,7 +160,8 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions CreationDate = entity.CreationDate, RevisionDate = entity.RevisionDate, Permissions = entity.Permissions, - AccessSecretsManager = entity.AccessSecretsManager + AccessSecretsManager = entity.AccessSecretsManager, + Revoked = isRevoked }; } }