Skip to content
Snippets Groups Projects
Select Git revision
  • master
  • feature/build
  • develop
  • VA_v2022a
  • v2021.a
  • v2020.a
  • v2019.a
  • v2018.b
  • v2017.c
  • v2017.a
  • v2016.a
11 results

AuralizationMode.meta

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    DataSourceController.cs 17.41 KiB
    using Coscine.Api.Project.Models;
    using Coscine.Api.Project.ReturnObjects;
    using Coscine.ApiCommons;
    using Coscine.ApiCommons.Factories;
    using Coscine.ApiCommons.Utils;
    using Coscine.Configuration;
    using Coscine.Database.Model;
    using Microsoft.AspNetCore.Mvc;
    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
    {
        public class DataSourceController : Controller
        {
            private readonly IConfiguration _configuration;
            private readonly JWTHandler _jwtHandler;
            // 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;
                _jwtHandler = new JWTHandler(_configuration);
                _authenticator = new Authenticator(this, _configuration);
                _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 FailedRequest(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 NoContent();
                        }
                        else
                        {
                            return FailedRequest(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
                        {
                            return FailedRequest(response, path);
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                        return BadRequest(e);
                    }
                }
            }
    
    
            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.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 FailedRequest(response, path);
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                        return BadRequest(e);
                    }
                }
            }
    
            [HttpPost("[controller]/validate")]
            public async Task<IActionResult> IsResourceValid()
            {
                var path = "/";
    
                JToken resource = ObjectFactory<JToken>.DeserializeFromStream(Request.Body);
    
    
                string authHeader = null;
                if (resource["type"]["displayName"].ToString().ToLower() == "rds")
                {
                    RDSResourceType rdsResourceType = new RDSResourceType();
                    rdsResourceType.BucketName = resource["resourceTypeOption"]["BucketName"].ToString();
                    rdsResourceType.AccessKey = resource["resourceTypeOption"]["AccessKey"].ToString();
                    rdsResourceType.SecretKey = resource["resourceTypeOption"]["SecretKey"].ToString();
                    authHeader = BuildRdsAuthHeader(rdsResourceType);
                }
                else if (resource["type"]["displayName"].ToString().ToLower() == "gitlab")
                {
                    GitlabResourceType gitlabResourceType = new GitlabResourceType();
                    gitlabResourceType.RepositoryNumber = (int)resource["resourceTypeOption"]["RepositoryNumber"];
                    gitlabResourceType.RepositoryUrl = resource["resourceTypeOption"]["RepositoryUrl"].ToString();
                    gitlabResourceType.Token = resource["resourceTypeOption"]["Token"].ToString();
                    authHeader = BuildGitlabAuthHeader(gitlabResourceType);
                }
    
                if (authHeader == null)
                {
                    return BadRequest($"No provider for: \"{resource["type"]["displayName"].ToString()}\".");
                }
                else
                {
                    // If the path is null, an empty string is added.
                    string url = $"{_configuration.GetString("coscine/global/waterbutler_url")}{resource["type"]["displayName"].ToString().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 FailedRequest(response, path);
                    }
                }
            }
    
            private IActionResult FailedRequest(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 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}");
                    }
                    var user = _authenticator.GetUserFromToken();
                    if (!_resourceModel.OwnsResource(user, resource))
                    {
                        return Forbid($"The user does not own the resource {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)
            {
                var data = new Dictionary<string, object>
                {
                    { "auth", auth },
                    { "credentials", credentials },
                    { "settings", settings },
                    { "callback_url", "rwth-aachen.de" }
                };
    
                var payload = new JwtPayload
                {
                    { "data", data }
                };
    
                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>();
    
                var credentials = new Dictionary<string, object>
                {
                    { "access_key", rdsResourceType.AccessKey },
                    { "secret_key", rdsResourceType.SecretKey }
                };
    
                var settings = new Dictionary<string, object>
                {
                    { "bucket", rdsResourceType.BucketName }
                };
    
                return BuildWaterbutlerPayload(auth, credentials, settings);
            }
    
            private string BuildGitlabAuthHeader(GitlabResourceType gitlabResourceType)
            {
    
                var auth = new Dictionary<string, object>();
    
                var credentials = new Dictionary<string, object>
                {
                    { "token", gitlabResourceType.Token }
                };
    
                var settings = new Dictionary<string, object>
                {
                    { "owner", "Tester"},
                    { "repo", gitlabResourceType.RepositoryUrl},
                    { "repo_id", gitlabResourceType.RepositoryNumber.ToString()},
                    { "host", "https://git.rwth-aachen.de"}
                };
    
                return BuildWaterbutlerPayload(auth, credentials, settings);
            }
        }
    }
    #endregion