// 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;
}
}
}