// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using HordeServer.Acls; using HordeServer.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace HordeServer.Server { /// /// 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 : HordeControllerBase { readonly IAclService _aclService; readonly IOptionsSnapshot _globalConfig; readonly IOptions _serverSettings; /// /// Constructor /// public AdminController(IAclService aclService, IOptionsSnapshot globalConfig, IOptions serverSettings) { _aclService = aclService; _globalConfig = globalConfig; _serverSettings = serverSettings; } /// /// Issues a token for the given roles. Issues a token for the current user if not specified. /// /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/token")] public async Task> GetTokenAsync(CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(ServerAclAction.IssueBearerToken, User)) { return Forbid(ServerAclAction.IssueBearerToken); } return await _aclService.IssueBearerTokenAsync(User.Claims, GetDefaultExpiryTime(), cancellationToken); } /// /// Issues a token for the given roles. Issues a token for the current user if not specified. /// /// Roles for the new token /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/roletoken")] public async Task> GetRoleTokenAsync([FromQuery] string roles, CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(AdminAclAction.AdminWrite, User)) { return Forbid(AdminAclAction.AdminWrite); } List claims = new List(); claims.AddRange(roles.Split('+').Select(x => new Claim(ClaimTypes.Role, x))); return await _aclService.IssueBearerTokenAsync(claims, GetDefaultExpiryTime(), cancellationToken); } /// /// Issues a token for the given roles. Issues a token for the current user if not specified. /// /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/registrationtoken")] public async Task> GetRegistrationTokenAsync(CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(AdminAclAction.AdminWrite, User)) { return Forbid(AdminAclAction.AdminWrite); } List claims = new List(); claims.Add(new AclClaimConfig(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); claims.Add(HordeClaims.AgentRegistrationClaim); return await _aclService.IssueBearerTokenAsync(claims, null, cancellationToken); } /// /// Issues a token valid to upload new versions of the agent software. /// /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/softwaretoken")] public async Task> GetSoftwareTokenAsync(CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(AdminAclAction.AdminWrite, User)) { return Forbid(AdminAclAction.AdminWrite); } List claims = new List(); claims.Add(new AclClaimConfig(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); claims.Add(HordeClaims.UploadToolsClaim); return await _aclService.IssueBearerTokenAsync(claims, null, cancellationToken); } /// /// Issues a token valid to download new versions of the agent software. /// /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/softwaredownloadtoken")] public async Task> GetSoftwareDownloadTokenAsync(CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(AdminAclAction.AdminRead, User)) { return Forbid(AdminAclAction.AdminRead); } List claims = new List(); claims.Add(new AclClaimConfig(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); claims.Add(HordeClaims.DownloadSoftwareClaim); return await _aclService.IssueBearerTokenAsync(claims, null, cancellationToken); } /// /// Issues a token valid to configure streams and projects /// /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/configtoken")] public async Task> GetConfigTokenAsync(CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(AdminAclAction.AdminRead, User)) { return Forbid(AdminAclAction.AdminRead); } List claims = new List(); claims.Add(new AclClaimConfig(ClaimTypes.Name, User.Identity?.Name ?? "Unknown")); claims.Add(HordeClaims.ConfigureProjectsClaim); return await _aclService.IssueBearerTokenAsync(claims, null, cancellationToken); } /// /// Issues a token valid to start chained jobs /// /// Cancellation token for the operation /// Administrative settings for the server [HttpGet] [Route("/api/v1/admin/chainedjobtoken")] public async Task> GetChainedJobTokenAsync(CancellationToken cancellationToken) { if (!_globalConfig.Value.Authorize(AdminAclAction.AdminRead, User)) { return Forbid(AdminAclAction.AdminRead); } List claims = new List(); //Claims.Add(new AclClaim(ClaimTypes.Name, User.Identity.Name ?? "Unknown")); claims.Add(HordeClaims.StartChainedJobClaim); return await _aclService.IssueBearerTokenAsync(claims, null, cancellationToken); } /// /// Gets the default expiry time for a token /// /// private TimeSpan? GetDefaultExpiryTime() { ServerSettings serverSettings = _serverSettings.Value; TimeSpan? expiryTime = null; if (serverSettings.JwtExpiryTimeHours != -1) { expiryTime = TimeSpan.FromHours(serverSettings.JwtExpiryTimeHours); } return expiryTime; } } }