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