// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using EpicGames.Horde.Accounts; using HordeServer.Acls; using HordeServer.Users; namespace HordeServer.Accounts { /// /// An internal Horde account representing a user or service /// /// Service-to-service authentication always use this for authentication (for example, Robomerge accessing Horde) /// When external authentication is enabled (such as OpenID Connect) users cannot be authenticated through this. /// public interface IAccount { /// /// Unique internal ID for this Horde account /// AccountId Id { get; } /// /// Full name of the user /// string Name { get; } /// /// A login ID or username /// string Login { get; } /// /// Email associated with account /// string? Email { get; } /// /// Hashed password /// string? PasswordHash { get; } /// /// Salt for password hash (if PasswordHash is set) /// string? PasswordSalt { get; } /// /// If the account is active /// bool Enabled { get; } /// /// Description of the account (who is it for, is there an owner etc) /// string Description { get; } /// /// Get list of claims /// /// List of claims IReadOnlyList Claims { get; } /// /// Key for the current session. Refresh tokens are considered invalid when the session key no longer matches. /// string SessionKey { get; } /// /// Validate that a password is correct for this account /// bool ValidatePassword(string password); /// /// Get the latest version of this account /// /// Cancellation token for the operation /// New account object Task RefreshAsync(CancellationToken cancellationToken = default); /// /// Attempt to update settings for the account /// /// Options for the update /// Cancellation token for the operation /// On success, returns the updated account object Task TryUpdateAsync(UpdateAccountOptions options, CancellationToken cancellationToken = default); } /// /// Options for updating an account /// /// If set, name of account to update /// If set, login ID/username to update /// If set, claims to update /// If set, description to update /// If set, email to update /// If set, password hash to update /// If set, enabled flag to update /// If set, identifier for the current session public record class UpdateAccountOptions( string? Name = null, string? Login = null, IReadOnlyList? Claims = null, string? Description = null, string? Email = null, string? Password = null, bool? Enabled = null, string? SessionKey = null); /// /// Extension methods for accounts /// public static class AccountExtensions { /// /// Test whether a user has a particular claim /// /// Account to test /// Claim type to check for /// Claim value to check for /// True if the user has the claim public static bool HasClaim(this IAccount account, string type, string value) { foreach (IUserClaim claim in account.Claims) { if (claim.Type.Equals(type, StringComparison.OrdinalIgnoreCase) && claim.Value.Equals(value, StringComparison.Ordinal)) { return true; } } return false; } /// /// Test whether a user has a particular claim /// /// Account to test /// Claim to test for /// True if the user has the claim public static bool HasClaim(this IAccount account, AclClaimConfig claim) => HasClaim(account, claim.Type, claim.Value); /// /// Update settings for the account, retrying if the account object has changed /// /// The account to update /// Options for the update /// Cancellation token for the operation /// On success, returns the updated account object public static async Task UpdateAsync(this IAccount account, UpdateAccountOptions options, CancellationToken cancellationToken = default) { IAccount? updatedAccount = account; while (updatedAccount != null) { updatedAccount = await updatedAccount.TryUpdateAsync(options, cancellationToken); if (updatedAccount != null) { return updatedAccount; } updatedAccount = await account.RefreshAsync(cancellationToken); } return null; } } }