// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Threading.Tasks; using Horde.Build.Acls; using Horde.Build.Api; using Horde.Build.Collections; using Horde.Build.Models; using Horde.Build.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Horde.Build.Controllers { using UserId = ObjectId; /// /// Controller for the /api/v1/agents endpoint /// [ApiController] [Authorize] [Route("[controller]")] public class SubscriptionsController : ControllerBase { /// /// The ACL service singleton /// readonly AclService _aclService; /// /// Collection of subscription documents /// readonly ISubscriptionCollection _subscriptionCollection; /// /// Constructor /// /// The acl service singleton /// The collection of subscription documents public SubscriptionsController(AclService aclService, ISubscriptionCollection subscriptionCollection) { _aclService = aclService; _subscriptionCollection = subscriptionCollection; } /// /// Find subscriptions matching a criteria /// /// Name of the user /// Filter for properties to return /// List of subscriptions [HttpGet] [Route("/api/v1/subscriptions")] [ProducesResponseType(typeof(List), 200)] public async Task>> GetSubscriptionsAsync([FromQuery] string userId, [FromQuery] PropertyFilter? filter = null) { UserId userIdValue; if (!TryParseUserId(userId, out userIdValue)) { return BadRequest("Invalid user id"); } if (!await _aclService.AuthorizeAsUserAsync(User, userIdValue)) { return Forbid(); } List results = await _subscriptionCollection.FindSubscriptionsAsync(userIdValue); return results.ConvertAll(x => PropertyFilter.Apply(new GetSubscriptionResponse(x), filter)); } /// /// Find subscriptions matching a criteria /// /// The subscription id /// Filter for properties to return /// List of subscriptions [HttpGet] [Route("/api/v1/subscriptions/{subscriptionId}")] [ProducesResponseType(typeof(GetSubscriptionResponse), 200)] public async Task> GetSubscriptionAsync(string subscriptionId, [FromQuery] PropertyFilter? filter = null) { ISubscription? subscription = await _subscriptionCollection.GetAsync(subscriptionId); if (subscription == null) { return NotFound(); } if (!await _aclService.AuthorizeAsUserAsync(User, subscription.UserId)) { return Forbid(); } return PropertyFilter.Apply(new GetSubscriptionResponse(subscription), filter); } /// /// Remove a subscription /// /// The subscription id /// Async task [HttpDelete] [Route("/api/v1/subscriptions/{subscriptionId}")] [ProducesResponseType(typeof(List), 200)] public async Task DeleteSubscriptionAsync(string subscriptionId) { ISubscription? subscription = await _subscriptionCollection.GetAsync(subscriptionId); if (subscription == null) { return NotFound(); } if (!await _aclService.AuthorizeAsUserAsync(User, subscription.UserId)) { return Forbid(); } await _subscriptionCollection.RemoveAsync(new[] { subscription }); return Ok(); } /// /// Find subscriptions matching a criteria /// /// The new subscriptions to create /// List of subscriptions [HttpPost] [Route("/api/v1/subscriptions")] public async Task>> CreateSubscriptionsAsync(List subscriptions) { HashSet authorizedUsers = new HashSet(); UserId? currentUserId = User.GetUserId(); if(currentUserId != null) { authorizedUsers.Add(currentUserId.Value); } GlobalPermissionsCache cache = new GlobalPermissionsCache(); List newSubscriptions = new List(); foreach (CreateSubscriptionRequest subscription in subscriptions) { UserId newUserId; if (!TryParseUserId(subscription.UserId, out newUserId)) { return BadRequest($"Invalid user id: '{subscription.UserId}'."); } if (authorizedUsers.Add(newUserId) && !await _aclService.AuthorizeAsync(AclAction.Impersonate, User, cache)) { return Forbid(); } newSubscriptions.Add(new NewSubscription(subscription.Event, newUserId, subscription.NotificationType)); } List results = await _subscriptionCollection.AddAsync(newSubscriptions); return results.ConvertAll(x => new CreateSubscriptionResponse(x)); } /// /// Parse a user id from a string. Allows passing the user's name as well as their objectid value. /// /// /// /// bool TryParseUserId(string userName, out UserId objectId) { UserId newObjectId; if (UserId.TryParse(userName, out newObjectId)) { objectId = newObjectId; return true; } string? currentUserName = User.GetUserName(); if (currentUserName != null && String.Equals(userName, currentUserName, StringComparison.OrdinalIgnoreCase)) { UserId? currentUserId = User.GetUserId(); if (currentUserId != null) { objectId = currentUserId.Value; return true; } } objectId = default; return false; } } }