// 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 { /// /// Controller for the /api/v1/serviceaccounts endpoint /// [ApiController] [Authorize] [Route("[controller]")] public class ServiceAccountsController : HordeControllerBase { readonly IServiceAccountCollection _serviceAccountCollection; readonly GlobalConfig _globalConfig; /// /// Constructor /// public ServiceAccountsController(IServiceAccountCollection accountCollection, IOptionsSnapshot globalConfig) { _serviceAccountCollection = accountCollection; _globalConfig = globalConfig.Value; } /// /// Create a new service account /// [HttpPost] [Route("/api/v1/serviceaccounts")] [ProducesResponseType(typeof(CreateServiceAccountResponse), 200)] public async Task> CreateServiceAccountAsync([FromBody] CreateServiceAccountRequest request, CancellationToken cancellationToken = default) { if (!_globalConfig.Authorize(ServiceAccountAclAction.CreateAccount, User)) { return Forbid(ServiceAccountAclAction.CreateAccount); } List claims = request.Claims.ConvertAll(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); } /// /// Gets a list of accounts /// [HttpGet] [Route("/api/v1/serviceaccounts")] [ProducesResponseType(typeof(List), 200)] public async Task>> FindAccountsAsync([FromQuery] int? index = null, [FromQuery] int? count = null, CancellationToken cancellationToken = default) { if (!_globalConfig.Authorize(ServiceAccountAclAction.ViewAccount, User)) { return Forbid(ServiceAccountAclAction.ViewAccount); } List responses = new List(); IReadOnlyList accounts = await _serviceAccountCollection.FindAsync(index, count, cancellationToken); foreach (IServiceAccount account in accounts) { responses.Add(CreateGetAccountResponse(account)); } return responses; } /// /// Gets information about an account by id /// [HttpGet] [Route("/api/v1/serviceaccounts/{id}")] [ProducesResponseType(typeof(GetServiceAccountResponse), 200)] [ProducesResponseType(404)] public async Task> 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); } /// /// Gets information about the current account /// [HttpGet] [Route("/api/v1/serviceaccounts/{id}/entitlements")] [ProducesResponseType(typeof(GetAccountEntitlementsResponse), 200)] [ProducesResponseType(404)] public async Task> 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); } /// /// Updates an account by id /// [HttpPut] [Route("/api/v1/serviceaccounts/{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] public async Task> 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? 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 }); } /// /// Deletes an account by id /// [HttpDelete] [Route("/api/v1/serviceaccounts/{id}")] [ProducesResponseType(200)] public async Task 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 claims = new List(); foreach (IUserClaim claim in account.Claims) { claims.Add(new AccountClaimMessage(claim.Type, claim.Value)); } return new GetServiceAccountResponse(account.Id, claims, account.Description, account.Enabled); } } }