Select Git revision
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TreeController.cs 14.09 KiB
using Coscine.Api.WaterbutlerHelper;
using Coscine.Api.WaterbutlerHelper.ReturnObjects;
using Coscine.Api.WaterbutlerHelper.Services;
using Coscine.ApiCommons;
using Coscine.ApiCommons.Factories;
using Coscine.Database.DataModel;
using Coscine.Database.Models;
using Coscine.Database.Util;
using Coscine.Logging;
using Coscine.Metadata;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using VDS.RDF;
using VDS.RDF.Parsing;
using VDS.RDF.Writing;
namespace Coscine.Api.Tree.Controllers
{
/// <summary>
/// This controller represents the actions which can be taken with a tree object.
/// </summary>
[Authorize]
public class TreeController : Controller
{
private readonly Authenticator _authenticator;
private readonly ResourceModel _resourceModel;
private readonly RdfStoreConnector _rdfStoreConnector;
private readonly ProjectRoleModel _projectRoleModel;
private readonly ProjectResourceModel _projectResourceModel;
private readonly CoscineLogger _coscineLogger;
private readonly AnalyticsLogObject _analyticsLogObject;
private readonly IDataSourceService _dataSourceService;
private readonly WaterbutlerInterface _waterbutlerInterface;
private readonly string _blobApiLink;
private readonly string _prefix;
/// <summary>
/// Tree controller constructor
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="dataSourceService">Source Service for data</param>
public TreeController(ILogger<TreeController> logger, IDataSourceService dataSourceService)
{
_authenticator = new Authenticator(this, Program.Configuration);
_resourceModel = new ResourceModel();
_rdfStoreConnector = new RdfStoreConnector(Program.Configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
_projectRoleModel = new ProjectRoleModel();
_projectResourceModel = new ProjectResourceModel();
_coscineLogger = new CoscineLogger(logger);
_analyticsLogObject = new AnalyticsLogObject();
_dataSourceService = dataSourceService;
_waterbutlerInterface = new WaterbutlerInterface(Program.Configuration, _dataSourceService);
var rule = Program.Configuration.GetStringAndWait("traefik/frontends/Coscine.Api.Blob/routes/Coscine.Api.Blob/rule");
var host = rule.Substring("Host:".Length, rule.IndexOf(";") - "Host:".Length);
var path = rule.Substring(rule.IndexOf("/"));
_blobApiLink = $"https://{host}{path}/blob/";
_prefix = Program.Configuration.GetStringAndWait("coscine/global/epic/prefix");
}
/// <summary>
/// Generates Id
/// </summary>
/// <param name="resourceId">Id of the resource</param>
/// <param name="path"> Path to file</param>
/// <returns> Uri </returns>
public Uri GenerateId(string resourceId, string path)
{
return new Uri($"https://hdl.handle.net/{_prefix}/{resourceId}@path={Uri.EscapeDataString(path)}");
}
/// <summary>
/// This method retrieves the metadata
/// </summary>
/// <param name="resourceId"> Id of a resource</param>
/// <param name="path">Path to the file</param>
/// <returns> JSON Object with the metadata if ok, otherwise Statuscode 400 or 401 or 404</returns>
[HttpGet("[controller]/{resourceId}/{*path}")]
public async Task<IActionResult> GetMetadata(string resourceId, string path)
{
path = $"/{path}";
if (path.Contains("%2F") || path.Contains("%2f"))
{
return BadRequest("Path can not contain the sequence %2F.");
}
var user = _authenticator.GetUser();
var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
if (user == null || !_resourceModel.HasAccess(user, resource, UserRoles.Owner, UserRoles.Member))
{
return Forbid("User has no Access to this resource.");
}
if (check != null)
{
return check;
}
var authHeader = _waterbutlerInterface.BuildAuthHeader(resource);
if (authHeader == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
var provider = GetResourceTypeName(resource);
var infos = await _waterbutlerInterface.GetObjectInfoAsync(path, provider, authHeader);
// Not found
if (infos == null)
{
return NotFound($"Found nothing in waterbutler under \"{path}\".");
}
var graphs = new List<JToken>();
foreach (var info in infos)
{
var id = GenerateId(resourceId, info.Path);
if (_rdfStoreConnector.HasGraph(id.AbsoluteUri))
{
var graph = _rdfStoreConnector.GetGraph(id);
graphs.Add(JToken.Parse(VDS.RDF.Writing.StringWriter.Write(graph, new RdfJsonWriter())));
}
}
var jObject = new JObject(
new JProperty("data", new JObject(
new JProperty("metadataStorage", JToken.FromObject(graphs)),
new JProperty("fileStorage", JToken.FromObject(infos.Select(x => new ObjectMetaInfoReturnObject(x, _blobApiLink, resource.Id.ToString()))))
))
);
LogAnalytics("View MD", resourceId, path, user);
return Json(jObject);
}
/// <summary>
/// This method stores the metadata of the file
/// </summary>
/// <param name="resourceId">Id of the resource</param>
/// <param name="path">Path to the file</param>
/// <returns>If ok Statuscode 204, otherwise Statuscode 400 or 401</returns>
[HttpPut("[controller]/{resourceId}/{*path}")]
public IActionResult StoreMetadataForFile(string resourceId, string path)
{
path = $"/{path}";
if (path.Contains("%2F") || path.Contains("%2f"))
{
return BadRequest("Path can not contain the sequence %2F.");
}
var innerBlock = ObjectFactory<JToken>.DeserializeFromStream(Request.Body);
var graphNameUri = GenerateId(resourceId, path);
var json = new JObject
{
[graphNameUri.AbsoluteUri] = innerBlock
};
var user = _authenticator.GetUser();
var resource = _resourceModel.GetById(Guid.Parse(resourceId));
if (user == null || !_resourceModel.HasAccess(user, resource, UserRoles.Owner, UserRoles.Member))
{
return Forbid("User is no project member!");
}
if (resource.ApplicationProfile[resource.ApplicationProfile.Length - 1] != '/')
{
resource.ApplicationProfile += '/';
}
json[graphNameUri.AbsoluteUri]["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] = new JArray
{
new JObject
{
["value"] = resource.ApplicationProfile,
["type"] = "uri"
}
};
// throw bad request if empty node value is detected
JToken root = json.First.First;
foreach (var node in root)
{
string nodeValue = node.First.First["value"].ToString().ToLower();
if (string.IsNullOrEmpty(nodeValue))
{
return BadRequest("Empty values in application profile are not accepted.");
}
}
var graph = new Graph();
graph.LoadFromString(json.ToString(), new RdfJsonParser());
var fixedValuesGraph = new Graph();
fixedValuesGraph.LoadFromString(resource.FixedValues, new RdfJsonParser());
var shapesGraph = (Graph)_rdfStoreConnector.GetGraph(resource.ApplicationProfile);
foreach (var triple in fixedValuesGraph.Triples.Where(x => x.Predicate.ToString() == "https://purl.org/coscine/fixedValue"))
{
var shapeTriples = shapesGraph.Triples.Where((shapeTriple) =>
shapeTriple.Subject.ToString() == triple.Subject.ToString()
&& shapeTriple.Predicate.ToString() == "http://www.w3.org/ns/shacl#path");
var entry = shapeTriples.First();
// Remove any existing triples
foreach (var triple2 in graph.GetTriplesWithSubjectPredicate(graph.CreateUriNode(graphNameUri), entry.Object).ToList())
{
graph.Retract(triple2);
}
var tripleObject = triple.Object;
var tripleObjectString = tripleObject.ToString();
if (tripleObjectString == "{ME}")
{
tripleObjectString = tripleObjectString.Replace("{ME}", user.DisplayName);
tripleObject = graph.CreateLiteralNode(tripleObjectString, new Uri(XmlSpecsHelper.XmlSchemaDataTypeString));
}
else if (tripleObjectString == "{TODAY}")
{
tripleObjectString = tripleObjectString.Replace("{TODAY}", DateTime.Today.ToString("yyyy-MM-dd"));
tripleObject = graph.CreateLiteralNode(tripleObjectString, new Uri(XmlSpecsHelper.XmlSchemaDataTypeDate));
}
graph.Assert(graph.CreateUriNode(graphNameUri), entry.Object, tripleObject);
}
// Default values is not checked or added
// validate the data
if (!_rdfStoreConnector.ValidateShacl(graph, graphNameUri))
{
return BadRequest("Data has the wrong format!");
}
// store the data
if (_rdfStoreConnector.HasGraph(graphNameUri))
{
_rdfStoreConnector.ClearGraph(graphNameUri);
LogAnalytics("Update MD", resourceId, path, user);
}
else
{
_rdfStoreConnector.CreateNamedGraph(graphNameUri);
LogAnalytics("Upload MD", resourceId, path, user);
}
// BaseUri must be set for the sparql query
graph.BaseUri = graphNameUri;
_rdfStoreConnector.AddGraph(graph);
return NoContent();
}
/// <summary>
/// Checks the resource Id and the path
/// </summary>
/// <param name="resourceId">Id of the resource</param>
/// <param name="path">Path to the file</param>
/// <param name="resource">Resource</param>
/// <returns>null, otherwise Statuscode 400 or 404 </returns>
private IActionResult CheckResourceIdAndPath(string resourceId, string path, out Resource resource)
{
resource = null;
if (string.IsNullOrWhiteSpace(path))
{
return BadRequest($"Your path \"{path}\" is empty.");
}
Regex rgx = new Regex(@"[\:?*<>|]+");
if (rgx.IsMatch(path))
{
return BadRequest($"Your path \"{path}\" contains bad characters. The following characters are not permissible: {@"\/:?*<>|"}.");
}
if (!Guid.TryParse(resourceId, out Guid resourceGuid))
{
return BadRequest($"{resourceId} is not a guid.");
}
try
{
resource = _resourceModel.GetById(resourceGuid);
if (resource == null)
{
return NotFound($"Could not find resource with id: {resourceId}");
}
}
catch (Exception)
{
return NotFound($"Could not find resource with id: {resourceId}");
}
if (resource.Type == null)
{
ResourceTypeModel resourceTypeModel = new ResourceTypeModel();
resource.Type = resourceTypeModel.GetById(resource.TypeId);
}
// All good
return null;
}
/// <summary>
/// Gets the name of the resource type
/// </summary>
/// <param name="resource">Resource</param>
/// <returns> Processed name of the resource </returns>
private string GetResourceTypeName(Resource resource)
{
if (resource.Type.DisplayName.ToLower().Equals("s3"))
{
return "rds";
}
else
{
return resource.Type.DisplayName.ToLower();
}
}
/// <summary>
/// Log analytics
/// </summary>
/// <param name="operation">Operation</param>
/// <param name="resourceId">Resource of the id</param>
/// <param name="filename">Name of the file</param>
/// <param name="user">User object</param>
private void LogAnalytics(string operation, string resourceId, string filename, User user)
{
if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
{
_analyticsLogObject.Type = "Action";
_analyticsLogObject.Operation = operation;
_analyticsLogObject.FileId = resourceId + "/" + HttpUtility.UrlDecode(filename).Substring(1);
_analyticsLogObject.ResourceId = resourceId;
_analyticsLogObject.ProjectId = _projectResourceModel.GetProjectForResource(new Guid(resourceId)).ToString();
_analyticsLogObject.RoleId = _projectRoleModel.GetGetUserRoleForProject(new Guid(_analyticsLogObject.ProjectId), user.Id).ToString();
_coscineLogger.AnalyticsLog(_analyticsLogObject);
}
}
}
}