Select Git revision
-
Marcel Nellesen authoredMarcel Nellesen authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TreeController.cs 13.34 KiB
using Coscine.WaterbutlerHelper;
using Coscine.WaterbutlerHelper.ReturnObjects;
using Coscine.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
{
[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;
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");
}
public Uri GenerateId(string resourceId, string path)
{
return new Uri($"https://hdl.handle.net/{_prefix}/{resourceId}@path={Uri.EscapeDataString(path)}");
}
[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 resourceTypeOptions = new Dictionary<string, string>();
if (resource.Type.DisplayName == "rds")
{
var data = new RDSResourceTypeModel().GetById((Guid)resource.ResourceTypeOptionId);
resourceTypeOptions.Add("bucketname", data.BucketName);
}
else if (resource.Type.DisplayName == "s3")
{
var data = new S3ResourceTypeModel().GetById((Guid)resource.ResourceTypeOptionId);
resourceTypeOptions.Add("accessKey", data.AccessKey);
resourceTypeOptions.Add("secretKey", data.SecretKey);
resourceTypeOptions.Add("bucketname", data.BucketName);
resourceTypeOptions.Add("resourceUrl", data.ResourceUrl);
}
else if (resource.Type.DisplayName == "gitlab")
{
var data = new GitlabResourceTypeModel().GetById((Guid)resource.ResourceTypeOptionId);
resourceTypeOptions.Add("token", data.Token);
resourceTypeOptions.Add("repositoryUrl", data.RepositoryUrl);
resourceTypeOptions.Add("repositoryNumber", data.RepositoryNumber.ToString());
}
var authHeader = _waterbutlerInterface.BuildAuthHeader(resource.Type.DisplayName, resourceTypeOptions);
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);
}
[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();
}
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;
}
private string GetResourceTypeName(Resource resource)
{
if (resource.Type.DisplayName.ToLower().Equals("s3"))
{
return "rds";
}
else
{
return resource.Type.DisplayName.ToLower();
}
}
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);
}
}
}
}