Skip to content
Snippets Groups Projects
Commit ab975ae4 authored by Benedikt Heinrichs's avatar Benedikt Heinrichs
Browse files

Merge remote-tracking branch 'origin/Product/269-contentMD' into Sprint/20191011

parents a3669894 f29ed6fb
No related branches found
No related tags found
1 merge request!24New: Add functionality for creation / updating and deleting a resource
......@@ -9,10 +9,15 @@ using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
#region DupFinder Exclusion
namespace Coscine.Api.Project.Controllers
{
......@@ -20,10 +25,19 @@ namespace Coscine.Api.Project.Controllers
{
private readonly IConfiguration _configuration;
private readonly JWTHandler _jwtHandler;
private static readonly HttpClient Client = new HttpClient();
// make to lazy property
private static readonly HttpClient Client;
private readonly Authenticator _authenticator;
private readonly ResourceModel _resourceModel;
static DataSourceController()
{
Client = new HttpClient
{
Timeout = TimeSpan.FromMinutes(30)
};
}
public DataSourceController()
{
_configuration = Program.Configuration;
......@@ -32,86 +46,221 @@ namespace Coscine.Api.Project.Controllers
_resourceModel = new ResourceModel();
}
// inferring a ../ (urlencoded) can manipulate the url.
// However the constructed signature for s3 won't match and it will not be resolved.
// This may be a problem for other provider!
[HttpGet("[controller]/{resourceId}/{path}")]
public async Task<IActionResult> GetWaterButlerFolder(string resourceId, string path)
{
if (!string.IsNullOrWhiteSpace(path))
{
path = HttpUtility.UrlDecode(path);
}
var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
if (check != null)
{
return check;
}
var authHeader = BuildAuthHeader(resource);
if (authHeader == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
else
{
// If the path is null, an empty string is added.
string url = $"{_configuration.GetString("coscine/global/waterbutler_url")}{resource.Type.DisplayName.ToLower()}{path}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);
// Thread safe according to msdn and HttpCompletionOption sets it to get only headers first.
var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
if (response.Content.Headers.Contains("Content-Disposition"))
{
return File(await response.Content.ReadAsStreamAsync(),
response.Content.Headers.GetValues("Content-Type").First());
}
else
{
var data = JObject.Parse(await response.Content.ReadAsStringAsync())["data"];
return Ok(new WaterbutlerObject(path, data));
}
}
else
{
return FailedRequeset(response, path);
}
}
}
// inferring a ../ (urlencoded) can manipulate the url.
// However the constructed signature for s3 won't match and it will not be resolved.
// This may be a problem for other provider!
[HttpGet("[controller]/{resourceId}/{*path}")]
public async Task<IActionResult> Get(string resourceId, string path)
[HttpPut("[controller]/{resourceId}/{path}")]
[DisableRequestSizeLimit]
public async Task<IActionResult> PutUploadFile(string resourceId, string path)
{
if (!string.IsNullOrWhiteSpace(path))
{
path = HttpUtility.UrlDecode(path);
}
if (!Guid.TryParse(resourceId, out Guid resouceGuid))
var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
if (check != null)
{
return BadRequest($"{resourceId} is not a guid.");
return check;
}
Resource resource;
var authHeader = BuildAuthHeader(resource, new string[] { "gitlab" });
if (authHeader == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
else
{
// If the path is null, an empty string is added.
string url = $"{_configuration.GetString("coscine/global/waterbutler_url")}{resource.Type.DisplayName.ToLower()}/?kind=file&name={path}";
try
{
resource = _resourceModel.GetById(resouceGuid);
if (resource == null)
var response = await UploadFile(url, authHeader, Request.Body);
if (response.IsSuccessStatusCode)
{
return NotFound($"Could not find resource with id: {resourceId}");
return NoContent();
}
else
{
return FailedRequeset(response, path);
}
catch (Exception)
}
catch (Exception e)
{
return NotFound($"Could not find resource with id: {resourceId}");
Console.WriteLine(e);
return BadRequest(e);
}
}
}
var user = _authenticator.GetUserFromToken();
if (!_resourceModel.OwnsResource(user, resource))
// inferring a ../ (urlencoded) can manipulate the url.
// However the constructed signature for s3 won't match and it will not be resolved.
// This may be a problem for other provider!
[HttpPut("[controller]/{resourceId}/{path}/update")]
[DisableRequestSizeLimit]
public async Task<IActionResult> PutUpdateFile(string resourceId, string path)
{
return Forbid($"The user does not own the resource {resourceId}");
if (!string.IsNullOrWhiteSpace(path))
{
path = HttpUtility.UrlDecode(path);
}
if (resource.Type == null)
var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
if (check != null)
{
ResourceTypeModel resourceTypeModel = new ResourceTypeModel();
resource.Type = resourceTypeModel.GetById(resource.TypeId);
return check;
}
string authHeader = null;
var authHeader = BuildAuthHeader(resource, new string[] { "gitlab" });
if (resource.Type.DisplayName.ToLower() == "rds")
if (authHeader == null)
{
RDSResourceTypeModel rdsResourceTypeModel = new RDSResourceTypeModel();
var rdsResourceType = rdsResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value);
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
else
{
// If the path is null, an empty string is added.
string url = $"{_configuration.GetString("coscine/global/waterbutler_url")}{resource.Type.DisplayName.ToLower()}/{path}?kind=file";
authHeader = BuildRdsAuthHeader(rdsResourceType);
try
{
var response = await UploadFile(url, authHeader, Request.Body);
if (response.IsSuccessStatusCode)
{
return NoContent();
}
else if (resource.Type.DisplayName.ToLower() == "gitlab")
else
{
return FailedRequeset(response, path);
}
}
catch (Exception e)
{
GitlabResourceTypeModel gitlabResourceTypeModel = new GitlabResourceTypeModel();
var gitlabResourceType = gitlabResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value);
authHeader = BuildGitlabAuthHeader(gitlabResourceType);
Console.WriteLine(e);
return BadRequest(e);
}
}
}
if (authHeader != null)
public async Task<HttpResponseMessage> UploadFile(string url, string authHeader, Stream stream)
{
var request = new HttpRequestMessage(HttpMethod.Put, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);
request.Content = new StreamContent(stream);
return await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
}
[HttpDelete("[controller]/{resourceId}/{path}")]
public async Task<IActionResult> Delete(string resourceId, string path)
{
if (!string.IsNullOrWhiteSpace(path))
{
path = HttpUtility.UrlDecode(path);
}
var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
if (check != null)
{
return check;
}
var authHeader = BuildAuthHeader(resource, new string[] { "gitlab" });
if (authHeader == null)
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
}
else
{
// If the path is null, an empty string is added.
string url = $"{_configuration.GetString("coscine/global/waterbutler_url")}{resource.Type.DisplayName.ToLower()}/{path}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var request = new HttpRequestMessage(HttpMethod.Delete, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);
// Thread safe according to msdn and HttpCompletionOption sets it to get only headers first.
try
{
var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
if (response.Content.Headers.Contains("Content-Disposition"))
{
return File(await response.Content.ReadAsStreamAsync(),
response.Content.Headers.GetValues("Content-Type").First());
return NoContent();
}
else
{
var data = JObject.Parse(await response.Content.ReadAsStringAsync())["data"];
return Ok(new WaterbutlerObject(path, data));
return FailedRequeset(response, path);
}
}
else
catch (Exception e)
{
Console.WriteLine(e);
return BadRequest(e);
}
}
}
private IActionResult FailedRequeset(HttpResponseMessage response, string path)
{
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
......@@ -127,11 +276,55 @@ namespace Coscine.Api.Project.Controllers
return BadRequest($"Error in communication with waterbutler: {response.StatusCode}");
}
}
private IActionResult CheckResourceIdAndPath(string resourceId, string path, out Resource resource)
{
resource = null;
if (string.IsNullOrWhiteSpace(path))
{
return BadRequest($"Your path \"{path}\" is empty.");
}
else
Regex rgx = new Regex(@"^[0-9a-zA-Z_\-/. ]+$");
if (!rgx.IsMatch(path))
{
return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
return BadRequest($"Your path \"{path}\" contains bad chars. Only {@"^[0-9a-zA-Z_\-./ ]+"} are allowed as chars.");
}
if (!Guid.TryParse(resourceId, out Guid resouceGuid))
{
return BadRequest($"{resourceId} is not a guid.");
}
#if! DEBUG
var user = _authenticator.GetUserFromToken();
if (!_resourceModel.OwnsResource(user, resource))
{
return Forbid($"The user does not own the resource {resourceId}");
}
#endif
try
{
resource = _resourceModel.GetById(resouceGuid);
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 BuildWaterbutlerPayload(Dictionary<string, object> auth, Dictionary<string, object> credentials, Dictionary<string, object> settings)
......@@ -152,6 +345,32 @@ namespace Coscine.Api.Project.Controllers
return _jwtHandler.GenerateJwtToken(payload);
}
private string BuildAuthHeader(Resource resource, IEnumerable<string> exclude = null)
{
if (exclude != null && exclude.Contains(resource.Type.DisplayName.ToLower()))
{
return null;
}
string authHeader = null;
if (resource.Type.DisplayName.ToLower() == "rds")
{
RDSResourceTypeModel rdsResourceTypeModel = new RDSResourceTypeModel();
var rdsResourceType = rdsResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value);
authHeader = BuildRdsAuthHeader(rdsResourceType);
}
else if (resource.Type.DisplayName.ToLower() == "gitlab")
{
GitlabResourceTypeModel gitlabResourceTypeModel = new GitlabResourceTypeModel();
var gitlabResourceType = gitlabResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value);
authHeader = BuildGitlabAuthHeader(gitlabResourceType);
}
return authHeader;
}
private string BuildRdsAuthHeader(RDSResourceType rdsResourceType)
{
var auth = new Dictionary<string, object>();
......@@ -192,3 +411,4 @@ namespace Coscine.Api.Project.Controllers
}
}
}
#endregion
......@@ -9,6 +9,9 @@ namespace Coscine.Api.Project.ReturnObjects
public bool IsFolder { get; set; } = false;
public string Absolutepath { get; set; } = null;
public string Name { get; set; } = null;
public string LastModified { get; set; }
public string Created { get; set; }
public string Size { get; set; }
// Shallow! Only one level deep.
// Should the folder contain additional folders, they will be empty/null.
public List<WaterbutlerObject> Content { get; set; } = null;
......@@ -20,7 +23,7 @@ namespace Coscine.Api.Project.ReturnObjects
public WaterbutlerObject(string path, JToken data)
{
Absolutepath = string.IsNullOrWhiteSpace(path) ? "/" : $"/{path}";
Absolutepath = path;
Content = new List<WaterbutlerObject>();
IsFolder = true;
......@@ -37,7 +40,10 @@ namespace Coscine.Api.Project.ReturnObjects
{
IsFolder = obj["attributes"]["kind"].ToObject<string>() == "folder",
Absolutepath = obj["attributes"]["path"].ToObject<string>(),
Name = obj["attributes"]["name"].ToObject<string>()
Name = obj["attributes"]["name"].ToObject<string>(),
Created = obj["attributes"]["modified_utc"]?.ToObject<string>(),
LastModified = obj["attributes"]["LastModified_utc"]?.ToObject<string>(),
Size = obj["attributes"]["size"]?.ToObject<string>(),
});
}
}
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment