Select Git revision
AdminController.cs
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
AdminController.cs 10.41 KiB
using Coscine.Action;
using Coscine.Action.EventArgs;
using Coscine.Api.Admin.ParameterObjects;
using Coscine.Api.Admin.ReturnObjects;
using Coscine.ApiCommons;
using Coscine.Configuration;
using Coscine.Database.DataModel;
using Coscine.Database.Models;
using Coscine.Logging;
using Coscine.Metadata;
using Coscine.ResourceTypes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
namespace Coscine.Api.Admin.Controllers
{
/// <summary>
/// This controller represents the actions which can be taken with a user object.
/// </summary>
[Authorize]
public class AdminController : Controller
{
private readonly RdfStoreConnector _rdfStoreConnector = new(Program.Configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
private readonly Authenticator _authenticator;
private readonly string _graphUrl;
private readonly string _memberUrl;
private readonly string _userUrlPrefix;
private readonly string _roleUrl;
private readonly string _roleUrlPrefix;
private readonly string _adminRole;
private readonly ResourceModel _resourceModel;
private readonly ProjectModel _projectModel;
private readonly ProjectQuotaModel _projectQuotaModel;
private readonly ResourceTypeModel _resourceTypeModel;
private readonly Emitter _emitter;
private readonly float _oneGb = 1024 * 1024 * 1024;
private readonly CoscineLogger _coscineLogger;
/// <summary>
/// Default Constructor.
/// </summary>
public AdminController(ILogger<AdminController> logger)
{
_authenticator = new Authenticator(this, Program.Configuration);
_graphUrl = "https://ror.org/04xfq0f34/roles";
_memberUrl = "http://www.w3.org/ns/org#member";
_userUrlPrefix = "https://coscine.rwth-aachen.de/u/";
_roleUrl = "http://www.w3.org/ns/org#role";
_roleUrlPrefix = "https://purl.org/coscine/terms/role#";
_adminRole = "supportAdmin";
_resourceModel = new ResourceModel();
_projectModel = new ProjectModel();
_projectQuotaModel = new ProjectQuotaModel();
_resourceTypeModel = new ResourceTypeModel();
_emitter = new Emitter(Program.Configuration);
_coscineLogger = new CoscineLogger(logger);
}
/// <summary>
/// Check if the user has a specific role.
/// </summary>
/// <param name="userId">Id (GUID) if the user.</param>
/// <param name="role">The role of the user.</param>
/// <returns>True if user has the role and false if not.</returns>
private bool HasRole(string userId, string role)
{
var graph = _rdfStoreConnector.GetGraph(_graphUrl);
// Get the subject (blank node for the specific member)
var userTriples = graph.GetTriplesWithPredicateObject(graph.CreateUriNode(new Uri(_memberUrl)), graph.CreateUriNode(new Uri($"{_userUrlPrefix}{userId.ToUpper()}")));
// Extract the node
var userSubject = userTriples?.FirstOrDefault()?.Subject;
if (userSubject == null)
{
return false;
}
// Get the role based on the blank node and compare it to the requested role
var roleTriples = graph.GetTriplesWithSubjectPredicate(userSubject, graph.CreateUriNode(new Uri(_roleUrl)));
return (roleTriples?.FirstOrDefault()?.Object.ToString()) == _roleUrlPrefix + role;
}
/// <summary>
/// Sum up allocated quota for all resources of a given resource type within a project.
/// </summary>
/// <param name="resourceType">The used resource type.</param>
/// <param name="projectId">The used project.</param>
/// <returns>Allocated quota of the given resource type in the project.</returns>
private int CalculateAllocatedForAll(ResourceType resourceType, Guid projectId)
{
var resources = _resourceModel.GetAllWhere((resource) =>
(from projectResource in resource.ProjectResources
where projectResource.ProjectId == projectId
select projectResource).Any() &&
resource.TypeId == resourceType.Id);
var allocated = resources.Sum(resource => ResourceTypeFactory
.Instance
.GetResourceType(resource)
.GetResourceQuotaAvailable(resource.Id.ToString(), _resourceModel.GetResourceTypeOptions(resource.Id))
.Result);
return (int)allocated;
}
/// <summary>
/// Sum up used quota for all resources of a given resource type within a project.
/// </summary>
/// <param name="resourceType">The used resource type.</param>
/// <param name="projectId">The used project.</param>
/// <returns>Used quota of the given resource type in the project.</returns>
private int CalculateUsedForAll(ResourceType resourceType, Guid projectId)
{
var resources = _resourceModel.GetAllWhere((resource) =>
(from projectResource in resource.ProjectResources
where projectResource.ProjectId == projectId
select projectResource).Any() &&
resource.TypeId == resourceType.Id);
var used = Math.Ceiling(resources.Sum(resource => ResourceTypeFactory
.Instance
.GetResourceType(resource)
.GetResourceQuotaUsed(resource.Id.ToString(), _resourceModel.GetResourceTypeOptions(resource.Id))
.Result / _oneGb));
return (int)used;
}
/// <summary>
/// Find the project related to the projectString(GUID or slug)
/// </summary>
/// <param name="projectString">Either the id (GUID) of the project or the slug.</param>
/// <returns>JSON list of all quotas.</returns>
[HttpGet("[controller]/{projectString}")]
public ActionResult<ProjectObject> GetProject(string projectString)
{
var user = _authenticator.GetUserId();
if (!HasRole(user, _adminRole))
{
return Unauthorized($@"User has not the role: ""{_adminRole}"".");
}
Project project;
if (Guid.TryParse(projectString, out Guid guid))
{
project = _projectModel.GetById(guid);
}
else
{
project = _projectModel.GetBySlug(projectString);
}
if (project == null)
{
return NotFound("Project was not found.");
}
var quotas = _projectQuotaModel.GetAllWhere(x => x.ProjectId == project.Id);
return new ProjectObject
{
GUID = project.Id,
Name = project.ProjectName,
ShortName = project.DisplayName,
Quotas = quotas.Select(x => new ProjectQuotaObject
{
QuotaId = x.RelationId,
ResourceType = _resourceTypeModel.GetById(x.ResourceTypeId).DisplayName,
Quota = x.Quota,
MaxQuota = x.MaxQuota,
Used = CalculateUsedForAll(_resourceTypeModel.GetById(x.ResourceTypeId), project.Id),
Allocated = CalculateAllocatedForAll(_resourceTypeModel.GetById(x.ResourceTypeId), project.Id)
}).ToList(),
};
}
/// <summary>
/// Update the project quota
/// </summary>
/// <param name="updateQuotaParameter">JSON object for updating quota.</param>
/// <returns>NoContent (204) on success.</returns>
[HttpPost("[controller]/")]
public IActionResult UpdateQuota([FromBody] UpdateQuotaParameterObject updateQuotaParameter)
{
var user = _authenticator.GetUser();
if (!HasRole(user.Id.ToString(), _adminRole))
{
return Unauthorized($@"User has not the role: ""{_adminRole}"".");
}
var projectQuotaModel = new ProjectQuotaModel();
var projectQuota = projectQuotaModel.GetById(updateQuotaParameter.QuotaId);
if (projectQuota == null)
{
return NotFound("Quota was not found.");
}
projectQuota.MaxQuota = (int)updateQuotaParameter.Quota;
projectQuota.Quota = projectQuota.MaxQuota;
projectQuotaModel.Update(projectQuota);
_emitter.EmitQuotaChanged(new AdminEventArgs(Program.Configuration) { ProjectId = projectQuota.ProjectId });
if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
{
LogAnalyticsAdminProjectQuotaChange(projectQuota.ProjectId, user);
}
return NoContent();
}
private void LogAnalyticsAdminProjectQuotaChange(Guid projectId, User user)
{
try
{
var quotas = _projectQuotaModel.GetAllWhere(x => x.ProjectId == projectId);
var quotaObjects = quotas.Select(x => new ProjectQuotaObject
{
QuotaId = x.RelationId,
ResourceType = _resourceTypeModel.GetById(x.ResourceTypeId).DisplayName,
Quota = x.Quota,
MaxQuota = x.MaxQuota,
Used = x.Quota,
Allocated = CalculateAllocatedForAll(_resourceTypeModel.GetById(x.ResourceTypeId), projectId)
}).ToList();
_coscineLogger.AnalyticsLog(
new AnalyticsLogObject
{
Type = "Action",
Operation = "Admin Project Quota Change",
UserId = user.Id.ToString(),
ProjectId = projectId.ToString(),
QuotaSize = quotaObjects.ConvertAll(x => $"{x.ResourceType}: {x.Allocated}/{x.Used}")
});
}
#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
catch (Exception)
#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
{
}
}
}
}