// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Horde.ServiceAccounts;
using HordeServer.Acls;
using HordeServer.Users;
namespace HordeServer.ServiceAccounts
{
///
/// An internal Horde account representing a service
///
/// Service-to-service authentication always use this for authentication (for example, Robomerge accessing Horde).
///
public interface IServiceAccount
{
///
/// Unique internal ID for this Horde account
///
ServiceAccountId Id { get; }
///
/// Human-readable name for identifying this account
///
string Name { 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; }
///
/// If the account is active
///
bool Enabled { get; }
///
/// 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<(IServiceAccount?, string?)> TryUpdateAsync(UpdateServiceAccountOptions options, CancellationToken cancellationToken = default);
}
///
/// Options for updating an account
///
/// If set, name to update
/// If set, description to update
/// If set, claims to update
/// Whether to reset the secret token for this account
/// If set, enabled flag to update
public record UpdateServiceAccountOptions(
string? Name = null,
string? Description = null,
IReadOnlyList? Claims = null,
bool? ResetToken = null,
bool? Enabled = 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 IServiceAccount 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 IServiceAccount 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<(IServiceAccount?, string?)> UpdateAsync(this IServiceAccount account, UpdateServiceAccountOptions options, CancellationToken cancellationToken = default)
{
IServiceAccount? updatedAccount = account;
while (updatedAccount != null)
{
(IServiceAccount? newAccount, string? newToken) = await updatedAccount.TryUpdateAsync(options, cancellationToken);
if (newAccount != null)
{
return (newAccount, newToken);
}
updatedAccount = await account.RefreshAsync(cancellationToken);
}
return (null, null);
}
}
}