1
0
mirror of https://github.com/bitwarden/server synced 2026-02-15 07:55:18 +00:00

Use interface instead of oneof

This commit is contained in:
Thomas Rittson
2026-01-03 15:44:30 +10:00
parent f96dba422c
commit 53e4ab8ab2
6 changed files with 100 additions and 15 deletions

View File

@@ -1,25 +1,38 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using OneOf;
using OneOf.Types;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
public class GetOrganizationUserQuery(IOrganizationUserRepository organizationUserRepository)
: IGetOrganizationUserQuery
{
public async Task<OneOf<InvitedOrganizationUser, AcceptedOrganizationUser, ConfirmedOrganizationUser, None>> GetOrganizationUserAsync(Guid organizationUserId)
public async Task<ITypedOrganizationUser?> GetOrganizationUserAsync(Guid organizationUserId)
{
var organizationUser = await organizationUserRepository.GetByIdAsync(organizationUserId);
if (organizationUser == null)
{
return new None();
return null;
}
return ConvertToStronglyTypedModel(organizationUser);
}
public async Task<IEnumerable<ITypedOrganizationUser>> GetManyOrganizationUsersAsync(IEnumerable<Guid> organizationUserIds)
{
var organizationUsers = await organizationUserRepository.GetManyAsync(organizationUserIds);
return organizationUsers
.Select(ConvertToStronglyTypedModel)
.ToList();
}
private static ITypedOrganizationUser ConvertToStronglyTypedModel(OrganizationUser organizationUser)
{
// Determine the appropriate model type based on the status
// For revoked users, use GetPriorActiveOrganizationUserStatusType to determine the underlying status
var effectiveStatus = organizationUser.Status == OrganizationUserStatusType.Revoked

View File

@@ -11,12 +11,12 @@ public interface IGetOrganizationUserQuery
/// based on their status (Invited, Accepted, Confirmed, or Revoked).
/// </summary>
/// <param name="organizationUserId">The ID of the organization user to retrieve.</param>
/// <returns>
/// A OneOf containing either:
/// - InvitedOrganizationUser (status: Invited or Revoked-Invited)
/// - AcceptedOrganizationUser (status: Accepted or Revoked-Accepted)
/// - ConfirmedOrganizationUser (status: Confirmed or Revoked-Confirmed)
/// - None if the user is not found
/// </returns>
Task<OneOf<InvitedOrganizationUser, AcceptedOrganizationUser, ConfirmedOrganizationUser, None>> GetOrganizationUserAsync(Guid organizationUserId);
Task<ITypedOrganizationUser?> GetOrganizationUserAsync(Guid organizationUserId);
/// <summary>
/// Retrieves multiple organization users by their IDs and returns the appropriate strongly-typed models
/// based on their status (Invited, Accepted, Confirmed, or Revoked).
/// </summary>
/// <param name="organizationUserIds">The IDs of the organization users to retrieve.</param>
Task<IEnumerable<ITypedOrganizationUser>> GetManyOrganizationUsersAsync(IEnumerable<Guid> organizationUserIds);
}

View File

@@ -0,0 +1,48 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
/// <summary>
/// Represents common properties shared by all typed organization user models.
/// </summary>
public interface ITypedOrganizationUser : IExternal, IOrganizationUserPermissions
{
/// <summary>
/// A unique identifier for the organization user.
/// </summary>
Guid Id { get; set; }
/// <summary>
/// The ID of the Organization.
/// </summary>
Guid OrganizationId { get; set; }
/// <summary>
/// The User's role in the Organization.
/// </summary>
OrganizationUserType Type { get; set; }
/// <summary>
/// The date the OrganizationUser was created.
/// </summary>
DateTime CreationDate { get; }
/// <summary>
/// The last date the OrganizationUser entry was updated.
/// </summary>
DateTime RevisionDate { get; }
/// <summary>
/// True if the User has access to Secrets Manager for this Organization, false otherwise.
/// </summary>
bool AccessSecretsManager { get; set; }
/// <summary>
/// True if the user's access has been revoked, false otherwise.
/// </summary>
bool Revoked { get; set; }
public OrganizationUser ToEntity();
}

View File

@@ -11,7 +11,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models;
/// by an organization administrator. At this stage, the user is linked to a User account but does not yet have
/// access to encrypted organization data.
/// </summary>
public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions
public class AcceptedOrganizationUser : ITypedOrganizationUser
{
/// <summary>
/// A unique identifier for the organization user.
@@ -160,4 +160,12 @@ public class AcceptedOrganizationUser : IExternal, IOrganizationUserPermissions
Revoked = isRevoked
};
}
/// <summary>
/// Implicitly converts an AcceptedOrganizationUser to an OrganizationUser entity.
/// </summary>
public static implicit operator OrganizationUser(AcceptedOrganizationUser accepted)
{
return accepted.ToEntity();
}
}

View File

@@ -11,7 +11,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models;
/// confirmed by an organization administrator. At this stage, the user has access to encrypted organization data
/// through the encrypted organization key.
/// </summary>
public class ConfirmedOrganizationUser : IExternal, IOrganizationUserPermissions
public class ConfirmedOrganizationUser : ITypedOrganizationUser
{
/// <summary>
/// A unique identifier for the organization user.
@@ -149,4 +149,12 @@ public class ConfirmedOrganizationUser : IExternal, IOrganizationUserPermissions
Revoked = isRevoked
};
}
/// <summary>
/// Implicitly converts a ConfirmedOrganizationUser to an OrganizationUser entity.
/// </summary>
public static implicit operator OrganizationUser(ConfirmedOrganizationUser confirmed)
{
return confirmed.ToEntity();
}
}

View File

@@ -11,7 +11,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Models;
/// Represents an invitation to join an organization.
/// At this stage, the invitation is sent to an email address but is not yet linked to a specific User account.
/// </summary>
public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions
public class InvitedOrganizationUser : ITypedOrganizationUser
{
/// <summary>
/// A unique identifier for the organization user.
@@ -68,6 +68,14 @@ public class InvitedOrganizationUser : IExternal, IOrganizationUserPermissions
Id = CoreHelpers.GenerateComb();
}
/// <summary>
/// Implicitly converts an InvitedOrganizationUser to an OrganizationUser entity.
/// </summary>
public static implicit operator OrganizationUser(InvitedOrganizationUser invited)
{
return invited.ToEntity();
}
/// <summary>
/// Transitions this invited user to an accepted state when the user accepts the invitation.
/// </summary>