Aufgrund einer Störung des s3 Storage, könnten in nächster Zeit folgende GitLab Funktionen nicht zur Verfügung stehen: LFS, Container Registry, Job Artifacs, Uploads (Wiki, Bilder, Projekt-Exporte). Wir bitten um Verständnis. Es wird mit Hochdruck an der Behebung des Problems gearbeitet. Weitere Informationen zur Störung des Object Storage finden Sie hier: https://maintenance.itc.rwth-aachen.de/ticket/status/messages/59-object-storage-pilot

Commit f17eadfb authored by Benedikt Heinrichs's avatar Benedikt Heinrichs
Browse files

New: Add functionality for creation / updating and deleting a resource

Merge branch 'Sprint/20191011' into 'master'

See merge request coscine/api/project!24
parents 228859fb ad1cec83
...@@ -56,17 +56,17 @@ ...@@ -56,17 +56,17 @@
<Reference Include="Consul, Version=0.7.2.6, Culture=neutral, PublicKeyToken=20a6ad9a81df1d95, processorArchitecture=MSIL"> <Reference Include="Consul, Version=0.7.2.6, Culture=neutral, PublicKeyToken=20a6ad9a81df1d95, processorArchitecture=MSIL">
<HintPath>..\packages\Consul.0.7.2.6\lib\net45\Consul.dll</HintPath> <HintPath>..\packages\Consul.0.7.2.6\lib\net45\Consul.dll</HintPath>
</Reference> </Reference>
<Reference Include="Coscine.Action, Version=1.3.1.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Coscine.Action, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.Action.1.3.1\lib\net461\Coscine.Action.dll</HintPath> <HintPath>..\packages\Coscine.Action.1.4.0\lib\net461\Coscine.Action.dll</HintPath>
</Reference> </Reference>
<Reference Include="Coscine.ApiCommons, Version=1.2.1.0, Culture=neutral, PublicKeyToken=af4c1345df96546b, processorArchitecture=MSIL"> <Reference Include="Coscine.ApiCommons, Version=1.2.2.0, Culture=neutral, PublicKeyToken=af4c1345df96546b, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.ApiCommons.1.2.1\lib\net461\Coscine.ApiCommons.dll</HintPath> <HintPath>..\packages\Coscine.ApiCommons.1.2.2\lib\net461\Coscine.ApiCommons.dll</HintPath>
</Reference> </Reference>
<Reference Include="Coscine.Configuration, Version=1.4.0.0, Culture=neutral, PublicKeyToken=ce3d7a32d7dc1e5a, processorArchitecture=MSIL"> <Reference Include="Coscine.Configuration, Version=1.4.0.0, Culture=neutral, PublicKeyToken=ce3d7a32d7dc1e5a, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.Configuration.1.4.0\lib\net461\Coscine.Configuration.dll</HintPath> <HintPath>..\packages\Coscine.Configuration.1.4.0\lib\net461\Coscine.Configuration.dll</HintPath>
</Reference> </Reference>
<Reference Include="Coscine.Database, Version=1.6.0.0, Culture=neutral, PublicKeyToken=767d77427707b70a, processorArchitecture=MSIL"> <Reference Include="Coscine.Database, Version=1.7.0.0, Culture=neutral, PublicKeyToken=767d77427707b70a, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.Database.1.6.0\lib\net461\Coscine.Database.dll</HintPath> <HintPath>..\packages\Coscine.Database.1.7.0\lib\net461\Coscine.Database.dll</HintPath>
</Reference> </Reference>
<Reference Include="Coscine.ProxyApi, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Coscine.ProxyApi, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.ProxyApi.1.2.0\lib\net461\Coscine.ProxyApi.dll</HintPath> <HintPath>..\packages\Coscine.ProxyApi.1.2.0\lib\net461\Coscine.ProxyApi.dll</HintPath>
......
...@@ -90,6 +90,7 @@ namespace Coscine.Api.Project.Tests ...@@ -90,6 +90,7 @@ namespace Coscine.Api.Project.Tests
Guid.NewGuid(), Guid.NewGuid(),
"Test", "Test",
"testR", "testR",
"testD",
"keys", "keys",
"usageR", "usageR",
new ResourceTypeObject(Resources[0].Type.Id, Resources[0].Type.DisplayName), new ResourceTypeObject(Resources[0].Type.Id, Resources[0].Type.DisplayName),
......
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Coscine.Database" publicKeyToken="767d77427707b70a" culture="neutral" /> <assemblyIdentity name="Coscine.Database" publicKeyToken="767d77427707b70a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.6.0.0" newVersion="1.6.0.0" /> <bindingRedirect oldVersion="0.0.0.0-1.7.0.0" newVersion="1.7.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
<package id="AutoMapper.Extensions.Microsoft.DependencyInjection" version="6.0.0" targetFramework="net472" /> <package id="AutoMapper.Extensions.Microsoft.DependencyInjection" version="6.0.0" targetFramework="net472" />
<package id="Castle.Core" version="4.4.0" targetFramework="net472" /> <package id="Castle.Core" version="4.4.0" targetFramework="net472" />
<package id="Consul" version="0.7.2.6" targetFramework="net472" /> <package id="Consul" version="0.7.2.6" targetFramework="net472" />
<package id="Coscine.Action" version="1.3.1" targetFramework="net472" /> <package id="Coscine.Action" version="1.4.0" targetFramework="net472" />
<package id="Coscine.ApiCommons" version="1.2.1" targetFramework="net472" /> <package id="Coscine.ApiCommons" version="1.2.2" targetFramework="net472" />
<package id="Coscine.Configuration" version="1.4.0" targetFramework="net472" /> <package id="Coscine.Configuration" version="1.4.0" targetFramework="net472" />
<package id="Coscine.Database" version="1.6.0" targetFramework="net472" /> <package id="Coscine.Database" version="1.7.0" targetFramework="net472" />
<package id="Coscine.ProxyApi" version="1.2.0" targetFramework="net472" /> <package id="Coscine.ProxyApi" version="1.2.0" targetFramework="net472" />
<package id="Coscine.SharePoint.Webparts.Vue" version="1.4.0" targetFramework="net472" /> <package id="Coscine.SharePoint.Webparts.Vue" version="1.4.0" targetFramework="net472" />
<package id="EntityFramework" version="6.2.0" targetFramework="net472" /> <package id="EntityFramework" version="6.2.0" targetFramework="net472" />
......
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Coscine.Database" publicKeyToken="767d77427707b70a" culture="neutral" /> <assemblyIdentity name="Coscine.Database" publicKeyToken="767d77427707b70a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.6.0.0" newVersion="1.6.0.0" /> <bindingRedirect oldVersion="0.0.0.0-1.7.0.0" newVersion="1.7.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
......
...@@ -9,10 +9,15 @@ using Newtonsoft.Json.Linq; ...@@ -9,10 +9,15 @@ using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
#region DupFinder Exclusion
namespace Coscine.Api.Project.Controllers namespace Coscine.Api.Project.Controllers
{ {
...@@ -20,10 +25,19 @@ namespace Coscine.Api.Project.Controllers ...@@ -20,10 +25,19 @@ namespace Coscine.Api.Project.Controllers
{ {
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly JWTHandler _jwtHandler; 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 Authenticator _authenticator;
private readonly ResourceModel _resourceModel; private readonly ResourceModel _resourceModel;
static DataSourceController()
{
Client = new HttpClient
{
Timeout = TimeSpan.FromMinutes(30)
};
}
public DataSourceController() public DataSourceController()
{ {
_configuration = Program.Configuration; _configuration = Program.Configuration;
...@@ -32,66 +46,33 @@ namespace Coscine.Api.Project.Controllers ...@@ -32,66 +46,33 @@ namespace Coscine.Api.Project.Controllers
_resourceModel = new ResourceModel(); _resourceModel = new ResourceModel();
} }
// inferring a ../ (urlencoded) can manipulate the url. // inferring a ../ (urlencoded) can manipulate the url.
// However the constructed signature for s3 won't match and it will not be resolved. // However the constructed signature for s3 won't match and it will not be resolved.
// This may be a problem for other provider! // This may be a problem for other provider!
[HttpGet("[controller]/{resourceId}/{*path}")] [HttpGet("[controller]/{resourceId}/{path}")]
public async Task<IActionResult> Get(string resourceId, string path) public async Task<IActionResult> GetWaterButlerFolder(string resourceId, string path)
{ {
if (!string.IsNullOrWhiteSpace(path))
if (!Guid.TryParse(resourceId, out Guid resouceGuid))
{ {
return BadRequest($"{resourceId} is not a guid."); path = HttpUtility.UrlDecode(path);
} }
Resource resource; var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
try if (check != null)
{ {
resource = _resourceModel.GetById(resouceGuid); return check;
if (resource == null)
{
return NotFound($"Could not find resource with id: {resourceId}");
}
}
catch (Exception)
{
return NotFound($"Could not find resource with id: {resourceId}");
} }
var user = _authenticator.GetUserFromToken(); var authHeader = BuildAuthHeader(resource);
if (!_resourceModel.OwnsResource(user, resource))
{
return Forbid($"The user does not own the resource {resourceId}");
}
if (resource.Type == null) if (authHeader == null)
{ {
ResourceTypeModel resourceTypeModel = new ResourceTypeModel(); return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
resource.Type = resourceTypeModel.GetById(resource.TypeId);
}
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);
} }
else
if (authHeader != null)
{ {
// If the path is null, an empty string is added. // If the path is null, an empty string is added.
string url = $"{_configuration.GetString("coscine/global/waterbutler_url")}{resource.Type.DisplayName.ToLower()}/{path}"; 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.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authHeader); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);
...@@ -113,25 +94,237 @@ namespace Coscine.Api.Project.Controllers ...@@ -113,25 +94,237 @@ namespace Coscine.Api.Project.Controllers
} }
else else
{ {
if (response.StatusCode == System.Net.HttpStatusCode.NotFound) 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!
[HttpPut("[controller]/{resourceId}/{path}")]
[DisableRequestSizeLimit]
public async Task<IActionResult> PutUploadFile(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()}/?kind=file&name={path}";
try
{
var response = await UploadFile(url, authHeader, Request.Body);
if (response.IsSuccessStatusCode)
{ {
return NotFound($"Could not find object for: \"{path}\"."); return NoContent();
} }
else else
if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{ {
return Forbid("Not allowed to access the datasource."); return FailedRequeset(response, path);
}
}
catch (Exception e)
{
Console.WriteLine(e);
return BadRequest(e);
}
}
}
// 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)
{
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}?kind=file";
try
{
var response = await UploadFile(url, authHeader, Request.Body);
if (response.IsSuccessStatusCode)
{
return NoContent();
} }
else else
{ {
return BadRequest($"Error in communication with waterbutler: {response.StatusCode}"); return FailedRequeset(response, path);
} }
} }
catch (Exception e)
{
Console.WriteLine(e);
return BadRequest(e);
}
} }
else }
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}\"."); 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.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)
{
return NoContent();
}
else
{
return FailedRequeset(response, path);
}
}
catch (Exception e)
{
Console.WriteLine(e);
return BadRequest(e);
}
}
}
private IActionResult FailedRequeset(HttpResponseMessage response, string path)
{
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return NotFound($"Could not find object for: \"{path}\".");
}
else
if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
return Forbid("Not allowed to access the datasource.");
}
else
{
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.");
}
Regex rgx = new Regex(@"^[0-9a-zA-Z_\-/. ]+$");
if (!rgx.IsMatch(path))
{
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) private string BuildWaterbutlerPayload(Dictionary<string, object> auth, Dictionary<string, object> credentials, Dictionary<string, object> settings)
...@@ -152,6 +345,32 @@ namespace Coscine.Api.Project.Controllers ...@@ -152,6 +345,32 @@ namespace Coscine.Api.Project.Controllers
return _jwtHandler.GenerateJwtToken(payload); 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) private string BuildRdsAuthHeader(RDSResourceType rdsResourceType)
{ {
var auth = new Dictionary<string, object>(); var auth = new Dictionary<string, object>();
...@@ -192,3 +411,4 @@ namespace Coscine.Api.Project.Controllers ...@@ -192,3 +411,4 @@ namespace Coscine.Api.Project.Controllers
} }
} }
} }
#endregion
using Coscine.Api.Project.Models;
using Coscine.Api.Project.ReturnObjects;
using Coscine.ApiCommons;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Coscine.Api.Project.Controllers
{
public class LicenseController : Controller
{
private readonly Authenticator _authenticator;
private readonly LicenseModel _licenseModel;
public LicenseController()
{
_authenticator = new Authenticator(this, Program.Configuration);
_licenseModel = new LicenseModel();
}
[Route("[controller]")]
public IActionResult Index()
{
return Ok(_authenticator.ValidateAndExecute((user) =>
{
return _licenseModel.GetAll().Select((license) => new LicenseObject(license.Id, license.DisplayName));
}));
}
}
}
...@@ -28,13 +28,18 @@ namespace Coscine.Api.Project.Models ...@@ -28,13 +28,18 @@ namespace Coscine.Api.Project.Models
{ {
DisplayName = resourceObject.DisplayName, DisplayName = resourceObject.DisplayName,
ResourceName = resourceObject.ResourceName, ResourceName = resourceObject.ResourceName,
Description = resourceObject.Description,
Keywords = resourceObject.Keywords, Keywords = resourceObject.Keywords,
UsageRights = resourceObject.UsageRights,