Files
UnrealEngine/Engine/Source/Programs/Horde/HordeServer/ServiceAccounts/ServiceAccountsController.cs
2025-05-18 13:04:45 +08:00

192 lines
6.6 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Horde.Accounts;
using EpicGames.Horde.ServiceAccounts;
using HordeServer.Server;
using HordeServer.Users;
using HordeServer.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace HordeServer.ServiceAccounts
{
/// <summary>
/// Controller for the /api/v1/serviceaccounts endpoint
/// </summary>
[ApiController]
[Authorize]
[Route("[controller]")]
public class ServiceAccountsController : HordeControllerBase
{
readonly IServiceAccountCollection _serviceAccountCollection;
readonly GlobalConfig _globalConfig;
/// <summary>
/// Constructor
/// </summary>
public ServiceAccountsController(IServiceAccountCollection accountCollection, IOptionsSnapshot<GlobalConfig> globalConfig)
{
_serviceAccountCollection = accountCollection;
_globalConfig = globalConfig.Value;
}
/// <summary>
/// Create a new service account
/// </summary>
[HttpPost]
[Route("/api/v1/serviceaccounts")]
[ProducesResponseType(typeof(CreateServiceAccountResponse), 200)]
public async Task<ActionResult<CreateServiceAccountResponse>> CreateServiceAccountAsync([FromBody] CreateServiceAccountRequest request, CancellationToken cancellationToken = default)
{
if (!_globalConfig.Authorize(ServiceAccountAclAction.CreateAccount, User))
{
return Forbid(ServiceAccountAclAction.CreateAccount);
}
List<IUserClaim> claims = request.Claims.ConvertAll<IUserClaim>(x => new UserClaim(x.Type, x.Value));
CreateServiceAccountOptions options = new (request.Name, request.Description, claims, request.Enabled);
(IServiceAccount account, string secretToken) = await _serviceAccountCollection.CreateAsync(options, cancellationToken);
return new CreateServiceAccountResponse(account.Id, secretToken);
}
/// <summary>
/// Gets a list of accounts
/// </summary>
[HttpGet]
[Route("/api/v1/serviceaccounts")]
[ProducesResponseType(typeof(List<GetServiceAccountResponse>), 200)]
public async Task<ActionResult<List<GetServiceAccountResponse>>> FindAccountsAsync([FromQuery] int? index = null, [FromQuery] int? count = null, CancellationToken cancellationToken = default)
{
if (!_globalConfig.Authorize(ServiceAccountAclAction.ViewAccount, User))
{
return Forbid(ServiceAccountAclAction.ViewAccount);
}
List<GetServiceAccountResponse> responses = new List<GetServiceAccountResponse>();
IReadOnlyList<IServiceAccount> accounts = await _serviceAccountCollection.FindAsync(index, count, cancellationToken);
foreach (IServiceAccount account in accounts)
{
responses.Add(CreateGetAccountResponse(account));
}
return responses;
}
/// <summary>
/// Gets information about an account by id
/// </summary>
[HttpGet]
[Route("/api/v1/serviceaccounts/{id}")]
[ProducesResponseType(typeof(GetServiceAccountResponse), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<object>> GetAccountAsync(ServiceAccountId id, [FromQuery] PropertyFilter? filter = null, CancellationToken cancellationToken = default)
{
if (!_globalConfig.Authorize(ServiceAccountAclAction.ViewAccount, User))
{
return Forbid(ServiceAccountAclAction.ViewAccount);
}
IServiceAccount? account = await _serviceAccountCollection.GetAsync(id, cancellationToken);
if (account == null)
{
return NotFound(id);
}
GetServiceAccountResponse response = CreateGetAccountResponse(account);
return PropertyFilter.Apply(response, filter);
}
/// <summary>
/// Gets information about the current account
/// </summary>
[HttpGet]
[Route("/api/v1/serviceaccounts/{id}/entitlements")]
[ProducesResponseType(typeof(GetAccountEntitlementsResponse), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<object>> GetAccountEntitlementsAsync(ServiceAccountId id, [FromQuery] PropertyFilter? filter = null, CancellationToken cancellationToken = default)
{
if (!_globalConfig.Authorize(ServiceAccountAclAction.ViewAccount, User))
{
return Forbid(ServiceAccountAclAction.ViewAccount);
}
IServiceAccount? account = await _serviceAccountCollection.GetAsync(id, cancellationToken);
if (account == null)
{
return NotFound(id);
}
GetAccountEntitlementsResponse response = AccountController.CreateGetAccountEntitlementsResponse(_globalConfig.Acl, claim => account.HasClaim(claim));
return PropertyFilter.Apply(response, filter);
}
/// <summary>
/// Updates an account by id
/// </summary>
[HttpPut]
[Route("/api/v1/serviceaccounts/{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UpdateServiceAccountResponse>> UpdateAccountAsync(ServiceAccountId id, UpdateServiceAccountRequest request, CancellationToken cancellationToken = default)
{
if (!_globalConfig.Authorize(ServiceAccountAclAction.UpdateAccount, User))
{
return Forbid(ServiceAccountAclAction.UpdateAccount);
}
IServiceAccount? account = await _serviceAccountCollection.GetAsync(id, cancellationToken);
if (account == null)
{
return NotFound(id);
}
IReadOnlyList<IUserClaim>? claims = null;
if (request.Claims != null)
{
claims = request.Claims.ConvertAll(x => new UserClaim(x.Type, x.Value));
}
UpdateServiceAccountOptions options = new (request.Name, request.Description, claims, request.ResetToken, request.Enabled);
(IServiceAccount? newAccount, string? newToken) = await account.UpdateAsync(options, cancellationToken);
if (newAccount == null)
{
return NotFound(id);
}
return Ok(new UpdateServiceAccountResponse { NewSecretToken = newToken });
}
/// <summary>
/// Deletes an account by id
/// </summary>
[HttpDelete]
[Route("/api/v1/serviceaccounts/{id}")]
[ProducesResponseType(200)]
public async Task<ActionResult> DeleteAccountAsync(ServiceAccountId id, CancellationToken cancellationToken = default)
{
if (!_globalConfig.Authorize(ServiceAccountAclAction.DeleteAccount, User))
{
return Forbid(ServiceAccountAclAction.DeleteAccount);
}
await _serviceAccountCollection.DeleteAsync(id, cancellationToken);
return Ok();
}
static GetServiceAccountResponse CreateGetAccountResponse(IServiceAccount account)
{
List<AccountClaimMessage> claims = new List<AccountClaimMessage>();
foreach (IUserClaim claim in account.Claims)
{
claims.Add(new AccountClaimMessage(claim.Type, claim.Value));
}
return new GetServiceAccountResponse(account.Id, claims, account.Description, account.Enabled);
}
}
}