// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using HordeServer.Api; using HordeServer.Collections; using HordeCommon; using HordeServer.Models; using HordeServer.Services; using HordeServer.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using MongoDB.Bson; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Claims; using System.Threading.Tasks; namespace HordeServer.Controllers { /// /// Object containing settings for the server /// public class AdminSettings { /// /// The default perforce server /// public string? DefaultServerAndPort { get; set; } /// /// The default perforce username /// public string? DefaultUserName { get; set; } /// /// The default perforce password /// public string? DefaultPassword { get; set; } } /// /// The conform limit value /// public class ConformSettings { /// /// Maximum number of conforms allowed at once /// public int MaxCount { get; set; } } /// /// Controller managing account status /// [ApiController] [Authorize] [Route("[controller]")] public class AdminController : ControllerBase { /// /// The database service singleton /// DatabaseService DatabaseService; /// /// The acl service singleton /// AclService AclService; /// /// The upgrade service singleton /// UpgradeService UpgradeService; /// /// Settings for the server /// IOptionsMonitor Settings; /// /// Constructor /// /// The database service singleton /// The ACL service singleton /// The upgrade service singelton /// Server settings public AdminController(DatabaseService DatabaseService, AclService AclService, UpgradeService UpgradeService, IOptionsMonitor Settings) { this.DatabaseService = DatabaseService; this.AclService = AclService; this.UpgradeService = UpgradeService; this.Settings = Settings; } /// /// Force a reset on the database /// [HttpPost] [Route("/api/v1/admin/reset")] public async Task ForceResetAsync([FromQuery] string? Instance = null) { if(!await AclService.AuthorizeAsync(AclAction.AdminWrite, User)) { return Forbid(); } for (; ; ) { Globals Globals = await DatabaseService.GetGlobalsAsync(); if (Instance == null) { return NotFound($"Missing code query parameter. Set to {Globals.InstanceId} to reset."); } if (Globals.InstanceId != Instance.ToObjectId()) { return NotFound($"Incorrect code query parameter. Should be {Globals.InstanceId}."); } Globals.ForceReset = true; if (await DatabaseService.TryUpdateSingletonAsync(Globals)) { return Ok("Database will be reinitialized on next restart"); } } } /// /// Upgrade the database to the latest schema /// /// The schema version to upgrade from. [HttpPost] [Route("/api/v1/admin/upgradeschema")] public async Task UpgradeSchemaAsync([FromQuery] int? FromVersion = null) { if (!await AclService.AuthorizeAsync(AclAction.AdminWrite, User)) { return Forbid(); } await UpgradeService.UpgradeSchemaAsync(FromVersion); return Ok(); } /// /// Issues a token for the given roles. Issues a token for the current user if not specified. /// /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/token")] public async Task> GetTokenAsync() { if (!await AclService.AuthorizeAsync(AclAction.IssueBearerToken, User)) { return Forbid(); } return AclService.IssueBearerToken(User.Claims, GetDefaultExpiryTime()); } /// /// Issues a token for the given roles. Issues a token for the current user if not specified. /// /// Roles for the new token /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/roletoken")] public async Task> GetRoleTokenAsync([FromQuery] string Roles) { if (!await AclService.AuthorizeAsync(AclAction.AdminWrite, User)) { return Forbid(); } List Claims = new List(); Claims.AddRange(Roles.Split('+').Select(x => new Claim(ClaimTypes.Role, x))); return AclService.IssueBearerToken(Claims, GetDefaultExpiryTime()); } /// /// Issues a token for the given roles. Issues a token for the current user if not specified. /// /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/registrationtoken")] public async Task> GetRegistrationTokenAsync() { if (!await AclService.AuthorizeAsync(AclAction.AdminWrite, User)) { return Forbid(); } List Claims = new List(); Claims.Add(new AclClaim(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); Claims.Add(AclService.AgentRegistrationClaim); return AclService.IssueBearerToken(Claims, null); } /// /// Issues a token valid to upload new versions of the agent software. /// /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/softwaretoken")] public async Task> GetSoftwareTokenAsync() { if (!await AclService.AuthorizeAsync(AclAction.AdminWrite, User)) { return Forbid(); } List Claims = new List(); Claims.Add(new AclClaim(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); Claims.Add(AclService.UploadSoftwareClaim); return AclService.IssueBearerToken(Claims, null); } /// /// Issues a token valid to download new versions of the agent software. /// /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/softwaredownloadtoken")] public async Task> GetSoftwareDownloadTokenAsync() { if (!await AclService.AuthorizeAsync(AclAction.AdminRead, User)) { return Forbid(); } List Claims = new List(); Claims.Add(new AclClaim(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); Claims.Add(AclService.DownloadSoftwareClaim); return AclService.IssueBearerToken(Claims, null); } /// /// Issues a token valid to configure streams and projects /// /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/configtoken")] public async Task> GetConfigToken() { if (!await AclService.AuthorizeAsync(AclAction.AdminRead, User)) { return Forbid(); } List Claims = new List(); Claims.Add(new AclClaim(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); Claims.Add(AclService.ConfigureProjectsClaim); return AclService.IssueBearerToken(Claims, null); } /// /// Issues a token valid to start chained jobs /// /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/chainedjobtoken")] public async Task> GetChainedJobToken() { if (!await AclService.AuthorizeAsync(AclAction.AdminRead, User)) { return Forbid(); } List Claims = new List(); //Claims.Add(new AclClaim(ClaimTypes.Name, User.Identity.Name ?? "Unknown")); Claims.Add(AclService.StartChainedJobClaim); return AclService.IssueBearerToken(Claims, null); } /// /// Gets the default expiry time for a token /// /// private TimeSpan? GetDefaultExpiryTime() { TimeSpan? ExpiryTime = null; if (Settings.CurrentValue.JwtExpiryTimeHours != -1) { ExpiryTime = TimeSpan.FromHours(Settings.CurrentValue.JwtExpiryTimeHours); } return ExpiryTime; } } }