// Copyright Epic Games, Inc. All Rights Reserved. using HordeServer.Api; using HordeServer.Models; using HordeServer.Services; using HordeServer.Utilities; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using MongoDB.Bson; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; using System.Security.Claims; using System.Text.Json; using System.Threading.Tasks; using PoolId = HordeServer.Utilities.StringId; using AgentSoftwareChannelName = HordeServer.Utilities.StringId; namespace HordeServer.Controllers { /// /// Controller for the /api/v1/agents endpoint /// [ApiController] [Authorize] [Route("[controller]")] public class AgentsController : ControllerBase { /// /// Singleton instance of the ACL service /// AclService AclService; /// /// Singleton instance of the agent service /// AgentService AgentService; /// /// The pool service /// PoolService PoolService; /// /// Constructor /// /// The ACL service singleton /// The agent service /// The pool service public AgentsController(AclService AclService, AgentService AgentService, PoolService PoolService) { this.AclService = AclService; this.AgentService = AgentService; this.PoolService = PoolService; } /// /// Register an agent to perform remote work. /// /// Request parameters /// Information about the registered agent [HttpPost] [Route("/api/v1/agents")] public async Task> CreateAgentAsync([FromBody] CreateAgentRequest Request) { if(!await AclService.AuthorizeAsync(AclAction.CreateAgent, User)) { return Forbid(); } AgentSoftwareChannelName? Channel = String.IsNullOrEmpty(Request.Channel) ? (AgentSoftwareChannelName?)null : new AgentSoftwareChannelName(Request.Channel); IAgent Agent = await AgentService.CreateAgentAsync(Request.Name, Request.Enabled, Request.Ephemeral, Channel, Request.Pools?.ConvertAll(x => new PoolId(x))); return new CreateAgentResponse(Agent.Id.ToString()); } /// /// Finds the agents matching specified criteria. /// /// The pool containing the agent /// First result to return /// Number of results to return /// If set, only returns agents modified after this time /// Filter for the properties to return /// List of matching agents [HttpGet] [Route("/api/v1/agents")] [ProducesResponseType(typeof(List), 200)] public async Task>> FindAgentsAsync([FromQuery] string? Pool = null, [FromQuery] int? Index = null, [FromQuery] int? Count = null, [FromQuery] DateTimeOffset? ModifiedAfter = null, [FromQuery] PropertyFilter? Filter = null) { GlobalPermissionsCache PermissionsCache = new GlobalPermissionsCache(); List Agents = await AgentService.FindAgentsAsync(Pool?.ToObjectId(), ModifiedAfter?.UtcDateTime, Index, Count); List Pools = await PoolService.GetPoolsAsync(); List Responses = new List(); foreach (IAgent Agent in Agents) { bool bIncludeAcl = await AgentService.AuthorizeAsync(Agent, AclAction.ViewPermissions, User, PermissionsCache); List PoolIds = Agent.GetPools(Pools).Select(x => x.Id).ToList(); Responses.Add(new GetAgentResponse(Agent, PoolIds, bIncludeAcl).ApplyFilter(Filter)); } return Responses; } /// /// Retrieve information about a specific agent /// /// Id of the agent to get information about /// Filter for the properties to return /// Information about the requested agent [HttpGet] [Route("/api/v1/agents/{AgentId}")] [ProducesResponseType(typeof(GetAgentResponse), 200)] public async Task> GetAgentAsync(string AgentId, [FromQuery] PropertyFilter? Filter = null) { IAgent? Agent = await AgentService.GetAgentAsync(new AgentId(AgentId)); if (Agent == null) { return NotFound(); } List Pools = await PoolService.GetPoolsAsync(); GlobalPermissionsCache Cache = new GlobalPermissionsCache(); bool bIncludeAcl = await AgentService.AuthorizeAsync(Agent, AclAction.ViewPermissions, User, Cache); List PoolIds = Agent.GetPools(Pools).Select(x => x.Id).ToList(); return new GetAgentResponse(Agent, PoolIds, bIncludeAcl).ApplyFilter(Filter); } /// /// Update an agent's properties. /// /// Id of the agent to update. /// Properties on the agent to update. /// Http result code [HttpPut] [Route("/api/v1/agents/{AgentId}")] public async Task UpdateAgentAsync(string AgentId, [FromBody] UpdateAgentRequest Update) { IAgent? Agent = await AgentService.GetAgentAsync(new AgentId(AgentId)); if (Agent == null) { return NotFound(); } GlobalPermissionsCache Cache = new GlobalPermissionsCache(); if (!await AgentService.AuthorizeAsync(Agent, AclAction.UpdateAgent, User, Cache)) { return Forbid(); } if (Update.Acl != null && !await AgentService.AuthorizeAsync(Agent, AclAction.ChangePermissions, User, Cache)) { return Forbid(); } AgentSoftwareChannelName? Channel = String.IsNullOrEmpty(Update.Channel) ? (AgentSoftwareChannelName?)null : new AgentSoftwareChannelName(Update.Channel); await AgentService.UpdateAgentAsync(Agent, Update.Enabled, Update.RequestConform, Update.RequestRestart, Update.RequestShutdown, Channel, Update.Pools?.ConvertAll(x => new PoolId(x)), Acl.Merge(Agent.Acl, Update.Acl), Update.Comment); return Ok(); } /// /// Remove a registered agent. /// /// Id of the agent to delete. /// Http result code [HttpDelete] [Route("/api/v1/agents/{AgentId}")] public async Task DeleteAgentAsync(string AgentId) { IAgent? Agent = await AgentService.GetAgentAsync(new AgentId(AgentId)); if (Agent == null) { return NotFound(); } if (!await AgentService.AuthorizeAsync(Agent, AclAction.DeleteAgent, User, null)) { return Forbid(); } await AgentService.DeleteAgentAsync(Agent); return new OkResult(); } /// /// Find all the sessions of a particular agent /// /// Unique id of the agent to find /// Start time to include in the search /// Finish time to include in the search /// Index of the first result to return /// Number of results to return /// Sessions [HttpGet] [Route("/api/v1/agents/{AgentId}/sessions")] public async Task>> FindSessionsAsync(string AgentId, [FromQuery] DateTimeOffset? StartTime, [FromQuery] DateTimeOffset? FinishTime, [FromQuery] int Index = 0, [FromQuery] int Count = 50) { AgentId AgentIdValue = new AgentId(AgentId); IAgent? Agent = await AgentService.GetAgentAsync(AgentIdValue); if (Agent == null) { return NotFound(); } if (!await AgentService.AuthorizeAsync(Agent, AclAction.ViewSession, User, null)) { return Forbid(); } List Sessions = await AgentService.FindSessionsAsync(AgentIdValue, StartTime?.UtcDateTime, FinishTime?.UtcDateTime, Index, Count); return Sessions.ConvertAll(x => new GetAgentSessionResponse(x)); } /// /// Find all the sessions of a particular agent /// /// Unique id of the agent to find /// Unique id of the session /// Sessions [HttpGet] [Route("/api/v1/agents/{AgentId}/sessions/{SessionId}")] public async Task> GetSessionAsync(string AgentId, string SessionId) { AgentId AgentIdValue = new AgentId(AgentId); IAgent? Agent = await AgentService.GetAgentAsync(AgentIdValue); if (Agent == null) { return NotFound(); } if (!await AgentService.AuthorizeAsync(Agent, AclAction.ViewSession, User, null)) { return Forbid(); } ISession? Session = await AgentService.GetSessionAsync(SessionId.ToObjectId()); if(Session == null || Session.AgentId != AgentIdValue) { return NotFound(); } return new GetAgentSessionResponse(Session); } /// /// Find all the leases for a particular agent /// /// Unique id of the agent to find /// The session to query /// Start of the time window to consider /// End of the time window to consider /// Index of the first result to return /// Number of results to return /// Sessions [HttpGet] [Route("/api/v1/agents/{AgentId}/leases")] public async Task>> FindLeasesAsync(string AgentId, [FromQuery] string? SessionId, [FromQuery] DateTimeOffset? StartTime, [FromQuery] DateTimeOffset? FinishTime, [FromQuery] int Index = 0, [FromQuery] int Count = 1000) { AgentId AgentIdValue = new AgentId(AgentId); IAgent? Agent = await AgentService.GetAgentAsync(AgentIdValue); if (Agent == null) { return NotFound(); } if (!await AgentService.AuthorizeAsync(Agent, AclAction.ViewLeases, User, null)) { return Forbid(); } List Leases = await AgentService.FindLeasesAsync(AgentIdValue, SessionId?.ToObjectId(), StartTime?.UtcDateTime, FinishTime?.UtcDateTime, Index, Count); return Leases.ConvertAll(x => new GetAgentLeaseResponse(x)); } /// /// Get info about a particular lease /// /// Unique id of the agent to find /// Unique id of the particular lease /// Lease matching the given id [HttpGet] [Route("/api/v1/agents/{AgentId}/leases/{LeaseId}")] public async Task> GetLeaseAsync(string AgentId, string LeaseId) { AgentId AgentIdValue = new AgentId(AgentId); IAgent? Agent = await AgentService.GetAgentAsync(AgentIdValue); if(Agent == null) { return NotFound(); } if (!await AclService.AuthorizeAsync(AclAction.ViewLeases, User)) { return Forbid(); } ObjectId LeaseIdValue = LeaseId.ToObjectId(); ILease? Lease = await AgentService.GetLeaseAsync(LeaseIdValue); if (Lease == null || Lease.AgentId != AgentIdValue) { return NotFound(); } return new GetAgentLeaseResponse(Lease); } } }