Project 'andrew.cornell/nfa-pruning-analysis' was moved to 'katherine.cornell/nfa-pruning-analysis'. Please update any links and bookmarks that may still have the old path.
Select Git revision
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
BlobController.cs 16.72 KiB
using Coscine.ApiCommons;
using Coscine.Configuration;
using Coscine.Database.DataModel;
using Coscine.Database.Models;
using Coscine.Database.Util;
using Coscine.Logging;
using Coscine.Metadata;
using Coscine.ResourceTypes;
using Coscine.ResourceTypes.Base.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
namespace Coscine.Api.Blob.Controllers
{
/// <summary>
/// This controller represents the actions which can be taken with a Blob object.
/// </summary>
[Authorize]
public class BlobController : Controller
{
private readonly IConfiguration _configuration;
private readonly Authenticator _authenticator;
private readonly ResourceModel _resourceModel;
private readonly ProjectResourceModel _projectResourceModel;
private readonly ProjectRoleModel _projectRoleModel;
private readonly CoscineLogger _coscineLogger;
private readonly AnalyticsLogObject _analyticsLogObject;
private readonly RdfStoreConnector _rdfStoreConnector;
private readonly CoscineLDPHelper _coscineLDPHelper;
private readonly string _prefix;
/// <summary>
/// Blob controller constructor
/// </summary>
/// <param name="logger">Logger</param>
public BlobController(ILogger<BlobController> logger)
{
_configuration = Program.Configuration;
_authenticator = new Authenticator(this, _configuration);
_resourceModel = new ResourceModel();
_projectResourceModel = new ProjectResourceModel();
_projectRoleModel = new ProjectRoleModel();
_rdfStoreConnector = new RdfStoreConnector(_configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
_coscineLogger = new CoscineLogger(logger);
_analyticsLogObject = new AnalyticsLogObject();
_prefix = _configuration.GetStringAndWait("coscine/global/epic/prefix");
_coscineLDPHelper = new CoscineLDPHelper(_rdfStoreConnector, _prefix);
}
/// <summary>
/// This method checks if the given file exists and returns it
/// </summary>
/// <param name="resourceId">Id of the resource</param>
/// <param name="path"> Path to the file </param>
/// <returns>File if file exists otherwise status code 204, 400, 401 or 404 </returns>
[HttpGet("[controller]/{resourceId}/{*path}")]
[DisableRequestSizeLimit]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> GetFileWithPath(string resourceId, string path)
{
return await GetFile(resourceId, path);
}
/// <summary>
/// This method checks if the given file exists and returns it
/// </summary>
/// <param name="resourceId">Id of the resource</param>
/// <param name="path"> Path to the file </param>
/// <returns>File if file exists otherwise status code 204, 400, 401 or 404 </returns>
[HttpGet("[controller]/{resourceId}/")]
[DisableRequestSizeLimit]
public async Task<IActionResult> GetFileWithParameter(string resourceId, [FromQuery] string path)
{
// Strip the first slash, to reuse the previous implementation.
if (path.StartsWith("/"))
{
path = path[1..];
}
return await GetFile(resourceId, path);
}
/// <summary>
/// This method checks if the given file exists and returns it
/// </summary>
/// <param name="resourceId">Id of the resource</param>
/// <param name="path"> Path to the file </param>
/// <returns>File if file exists otherwise status code 204, 400, 401 or 404 </returns>
public async Task<IActionResult> GetFile(string resourceId, string path)
{
var user = _authenticator.GetUser();
//path = $"/{path}";
var checkPath = CheckPath(path);
if (checkPath != null)
{
return checkPath;
}
var checkResourceId = CheckResource(resourceId, out Resource resource);
if (checkResourceId != null)
{
return checkResourceId;
}
// Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
// - Resource: View Resource (RCV, Metadatamanager)
if (user is null || !_resourceModel.HasAccess(user, resource, UserRoles.Owner, UserRoles.Member, UserRoles.Guest))
{
return Forbid("User does not have permission to download files from the resource.");
}
try
{
var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource);
if (resourceTypeDefinition == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
var infos = await resourceTypeDefinition.GetEntry(resource.Id.ToString(), path);
var response = await resourceTypeDefinition.LoadEntry(resource.Id.ToString(), path);
new FileExtensionContentTypeProvider().TryGetContentType(path, out string contentType);
LogAnalytics("Download File", resourceId, path, user);
return File(response, contentType ?? "application/octet-stream");
}
catch (Exception e)
{
_coscineLogger.Log("Get File failed", e);
return BadRequest("Error in communication with the resource");
}
}
/// <summary>
/// This method uploads a given File
/// </summary>
/// <param name="resourceId">Id of the resource </param>
/// <param name="path">Path to the file</param>
/// <param name="files">List of files.</param>
/// <returns>status code 204 if file is uploaded otherwise status code 400 or 403</returns>
[DisableRequestSizeLimit]
[RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue)]
[HttpPut("[controller]/{resourceId}/{*path}")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> UploadFileWithPath(string resourceId, string path, List<IFormFile> files)
{
return await UploadFile(resourceId, path, files);
}
/// <summary>
/// This method uploads a given File
/// </summary>
/// <param name="resourceId">Id of the resource </param>
/// <param name="path">Path to the file</param>
/// <param name="files">List of files.</param>
/// <returns>status code 204 if file is uploaded otherwise status code 400 or 403</returns>
[DisableRequestSizeLimit]
[RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue)]
[HttpPut("[controller]/{resourceId}/")]
public async Task<IActionResult> UploadFileWithParameter(string resourceId, [FromQuery] string path, List<IFormFile> files)
{
// Strip the first slash, to reuse the previous implementation.
if (path.StartsWith("/"))
{
path = path[1..];
}
return await UploadFile(resourceId, path, files);
}
/// <summary>
/// This method uploads a given File
/// </summary>
/// <param name="resourceId">Id of the resource </param>
/// <param name="path">Path to the file</param>
/// <param name="files">List of files.</param>
/// <returns>status code 204 if file is uploaded otherwise status code 400 or 403</returns>
public async Task<IActionResult> UploadFile(string resourceId, string path, List<IFormFile> files)
{
var user = _authenticator.GetUser();
var checkPath = CheckPath(path);
if (checkPath != null)
{
return checkPath;
}
var checkResourceId = CheckResource(resourceId, out Resource resource);
if (checkResourceId != null)
{
return checkResourceId;
}
// Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
// - Resource: Change Resource (RCV, Metadatamanager)
if (user is null || !_resourceModel.HasAccess(user, resource, UserRoles.Owner, UserRoles.Member))
{
return Forbid("User does not have permission to upload files in the resource.");
}
if (resource.Archived == "1")
{
return BadRequest("The resource is read only!");
}
if (files.Count != 1)
{
return BadRequest("Only one file can be uploaded per request.");
}
var id = _coscineLDPHelper.GetId(resourceId, $"/{path}", true);
if (!_rdfStoreConnector.HasGraph(id))
{
return StatusCode((int)HttpStatusCode.Forbidden,
"No metadata set has been added for this file.");
}
try
{
var stream = files[0].OpenReadStream();
var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource);
if (resourceTypeDefinition == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
ResourceEntry infos = null;
try
{
infos = await resourceTypeDefinition.GetEntry(resource.Id.ToString(), path, null);
}
catch
{
// do nothing
}
await resourceTypeDefinition.StoreEntry(resource.Id.ToString(), path, stream);
LogAnalytics(infos == null ? "Upload File" : "Update File", resourceId, path, user);
return NoContent();
}
catch (Exception e)
{
_coscineLogger.Log(LogType.High, "Upload File failed", e);
return BadRequest("Error in communication with the resource");
}
}
/// <summary>
/// This method deletes a given file
/// </summary>
/// <param name="resourceId">Id of the resource </param>
/// <param name="path">Path to the file</param>
/// <returns>status code 204 if deletion successful otherwise status code 400, 401 or 404 </returns>
[HttpDelete("[controller]/{resourceId}/{*path}")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> DeleteFileWithPath(string resourceId, string path)
{
return await DeleteFile(resourceId, path);
}
/// <summary>
/// This method deletes a given file
/// </summary>
/// <param name="resourceId">Id of the resource </param>
/// <param name="path">Path to the file</param>
/// <returns>status code 204 if deletion successful otherwise status code 400, 401 or 404 </returns>
[HttpDelete("[controller]/{resourceId}/")]
public async Task<IActionResult> DeleteFileWithParameter(string resourceId, [FromQuery] string path)
{
// Strip the first slash, to reuse the previous implementation.
if (path.StartsWith("/"))
{
path = path[1..];
}
return await DeleteFile(resourceId, path);
}
/// <summary>
/// This method deletes a given file
/// </summary>
/// <param name="resourceId">Id of the resource </param>
/// <param name="path">Path to the file</param>
/// <returns>status code 204 if deletion successful otherwise status code 400, 401 or 404 </returns>
public async Task<IActionResult> DeleteFile(string resourceId, string path)
{
var user = _authenticator.GetUser();
var checkPath = CheckPath(path);
if (checkPath != null)
{
return checkPath;
}
var checkResourceId = CheckResource(resourceId, out Resource resource);
if (checkResourceId != null)
{
return checkResourceId;
}
// Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
// - Resource: Change Resource (RCV, Metadatamanager)
if (user is null || !_resourceModel.HasAccess(user, resource, UserRoles.Owner, UserRoles.Member))
{
return Forbid("User does not have permission to delete from the resource.");
}
if (resource.Archived == "1")
{
return BadRequest("The resource is read only!");
}
try
{
var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource);
if (resourceTypeDefinition == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
await resourceTypeDefinition.DeleteEntry(resource.Id.ToString(), path);
LogAnalytics("Delete File", resourceId, path, user);
return NoContent();
}
catch (Exception e)
{
_coscineLogger.Log("Delete failed", e);
return BadRequest("Error in communication with the resource");
}
}
/// <summary>
/// Tries to establish connection with resource and validates whether the given file/folder exists
/// </summary>
private IActionResult CheckResource(string resourceId, out Resource resource)
{
resource = null;
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)
{
var resourceTypeModel = new ResourceTypeModel();
resource.Type = resourceTypeModel.GetById(resource.TypeId);
}
// All good
return null;
}
/// <summary>
/// Checks if the path is valid
/// </summary>
/// <param name="path">path</param>
/// <returns>status code 400 if the given path is not valid</returns>
public IActionResult CheckPath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return BadRequest($"Your path \"{path}\" is empty.");
}
var rgx = new Regex(@"[\:?*<>|]+");
if (rgx.IsMatch(path))
{
return BadRequest($"Your path \"{path}\" contains bad characters. The following characters are not permissible: {@"\/:?*<>|"}.");
}
if (path.Contains("%2F") || path.Contains("%2f"))
{
return BadRequest("Path can not contain the sequence %2F.");
}
return null;
}
/// <summary>
/// Writes an analytics log entry
/// </summary>
private void LogAnalytics(string operation, string resourceId, string path, User user)
{
if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
{
_analyticsLogObject.Type = "Action";
_analyticsLogObject.FileId = resourceId + "/" + HttpUtility.UrlDecode(path);
_analyticsLogObject.ResourceId = resourceId;
_analyticsLogObject.ProjectId = _projectResourceModel.GetProjectForResource(new Guid(resourceId)).ToString();
_analyticsLogObject.RoleId = _projectRoleModel.GetGetUserRoleForProject(new Guid(_analyticsLogObject.ProjectId), user.Id).ToString();
_analyticsLogObject.Operation = operation;
_coscineLogger.AnalyticsLog(_analyticsLogObject);
}
}
}
}