diff --git a/Poort8.Ishare.Common.sln b/Poort8.Ishare.Common.sln index 37e532a..837a51d 100644 --- a/Poort8.Ishare.Common.sln +++ b/Poort8.Ishare.Common.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32328.378 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Poort8.Ishare.Common", "Poort8.Ishare.Common\Poort8.Ishare.Common.csproj", "{BF1AC9D7-3818-443A-97B2-2109C03898DC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Poort8.Ishare.Common", "Poort8.Ishare.Common\Poort8.Ishare.Common.csproj", "{BF1AC9D7-3818-443A-97B2-2109C03898DC}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{83A90D51-889C-4588-9A5F-9AB3A5FB8509}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {BF1AC9D7-3818-443A-97B2-2109C03898DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF1AC9D7-3818-443A-97B2-2109C03898DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {BF1AC9D7-3818-443A-97B2-2109C03898DC}.Release|Any CPU.Build.0 = Release|Any CPU + {83A90D51-889C-4588-9A5F-9AB3A5FB8509}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83A90D51-889C-4588-9A5F-9AB3A5FB8509}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83A90D51-889C-4588-9A5F-9AB3A5FB8509}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83A90D51-889C-4588-9A5F-9AB3A5FB8509}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Poort8.Ishare.Common/Controllers/WeatherForecastController.cs b/Poort8.Ishare.Common/Controllers/WeatherForecastController.cs deleted file mode 100644 index a356d0b..0000000 --- a/Poort8.Ishare.Common/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Poort8.Ishare.Common.Controllers; -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} diff --git a/Poort8.Ishare.Common/Dockerfile b/Poort8.Ishare.Common/Dockerfile index 844b323..6599115 100644 --- a/Poort8.Ishare.Common/Dockerfile +++ b/Poort8.Ishare.Common/Dockerfile @@ -7,10 +7,10 @@ EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src -COPY ["Poort8.Ishare.Common/Poort8.Ishare.Common.csproj", "Poort8.Ishare.Common/"] -RUN dotnet restore "Poort8.Ishare.Common/Poort8.Ishare.Common.csproj" +COPY ["Poort8.Ishare.Common.csproj", "."] +RUN dotnet restore "./Poort8.Ishare.Common.csproj" COPY . . -WORKDIR "/src/Poort8.Ishare.Common" +WORKDIR "/src/." RUN dotnet build "Poort8.Ishare.Common.csproj" -c Release -o /app/build FROM build AS publish diff --git a/Poort8.Ishare.Common/Poort8.Ishare.Common.csproj b/Poort8.Ishare.Common/Poort8.Ishare.Common.csproj index 64dec6f..6f98475 100644 --- a/Poort8.Ishare.Common/Poort8.Ishare.Common.csproj +++ b/Poort8.Ishare.Common/Poort8.Ishare.Common.csproj @@ -6,11 +6,13 @@ enable ee38f0c8-9d70-44f7-acea-f05954609c7e Linux + ..\docker-compose.dcproj - - + + + diff --git a/Poort8.Ishare.Common/Program.cs b/Poort8.Ishare.Common/Program.cs index 48863a6..75757d8 100644 --- a/Poort8.Ishare.Common/Program.cs +++ b/Poort8.Ishare.Common/Program.cs @@ -1,15 +1,17 @@ -var builder = WebApplication.CreateBuilder(args); +using Poort8.Ishare.Core; -// Add services to the container. +var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddHttpClient(); +builder.Services.AddMemoryCache(); + +builder.Services.AddIshareCoreServices(); var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/Poort8.Ishare.Common/Token/TokenController.cs b/Poort8.Ishare.Common/Token/TokenController.cs new file mode 100644 index 0000000..79661b4 --- /dev/null +++ b/Poort8.Ishare.Common/Token/TokenController.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Mvc; +using Poort8.Ishare.Core; +using System.ComponentModel.DataAnnotations; +using System.Text.Json; + +namespace Poort8.Ishare.Common.Token; + +[Route("api/[controller]")] +[ApiController] +public class TokenController : ControllerBase +{ + private readonly ILogger _logger; + private readonly IAuthenticationService _authenticationService; + private readonly ISchemeOwnerService _schemeOwnerService; + + public TokenController( + ILogger logger, + IAuthenticationService authenticationService, + ISchemeOwnerService schemeOwnerService) + { + _logger = logger; + _authenticationService = authenticationService; + _schemeOwnerService = schemeOwnerService; + } + + //TODO: Swagger + [HttpPost] + [Consumes("application/x-www-form-urlencoded")] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task Post([Required, FromForm] TokenRequest request) + { + _logger.LogInformation("TokenRequest from {clientId}: {request}", request.ClientId, JsonSerializer.Serialize(request)); + + if (request.GrantType != "client_credentials" || + request.Scope != "iSHARE" || + request.ClientAssertionType != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") + { + return new BadRequestObjectResult("Invalid grant_type, scope or client_assertion_type."); + } + + try + { + _authenticationService.ValidateToken(request.ClientId, request.ClientAssertion); + } + catch (Exception e) + { + _logger.LogWarning("Returning bad request: invalid client_assertion. {msg}", e.Message); + return new BadRequestObjectResult("Invalid client_assertion."); + } + + try + { + await _schemeOwnerService.VerifyCertificateIsTrustedAsync(request.ClientAssertion); + } + catch (Exception e) + { + _logger.LogWarning("Returning bad request: certificate chain is not trusted. {msg}", e.Message); + return new BadRequestObjectResult("Certificate chain is not trusted."); + } + + try + { + await _schemeOwnerService.VerifyPartyAsync(request.ClientId, request.ClientAssertion); + } + catch (Exception e) + { + _logger.LogWarning("Returning bad request: failed party checks. {msg}", e.Message); + return new BadRequestObjectResult("Failed party checks."); + } + + try + { + var token = _authenticationService.CreateAccessToken(request.ClientId); + var tokenResponse = new TokenResponse(token); + + _logger.LogInformation("Returning ok with token response {token}", token); + return new OkObjectResult(tokenResponse); + } + catch (Exception e) + { + _logger.LogCritical("Returning internal server error. {msg}", e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/Poort8.Ishare.Common/Token/TokenRequest.cs b/Poort8.Ishare.Common/Token/TokenRequest.cs new file mode 100644 index 0000000..fb07cd4 --- /dev/null +++ b/Poort8.Ishare.Common/Token/TokenRequest.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; + +namespace Poort8.Ishare.Common.Token; + +public class TokenRequest +{ + [Required] + [BindProperty(Name = "grant_type")] + public string GrantType { get; set; } = null!; + + [Required] + [BindProperty(Name = "scope")] + public string Scope { get; set; } = null!; + + [Required] + [BindProperty(Name = "client_id")] + public string ClientId { get; set; } = null!; + + [Required] + [BindProperty(Name = "client_assertion_type")] + public string ClientAssertionType { get; set; } = null!; + + [Required] + [BindProperty(Name = "client_assertion")] + public string ClientAssertion { get; set; } = null!; +} diff --git a/Poort8.Ishare.Common/Token/TokenResponse.cs b/Poort8.Ishare.Common/Token/TokenResponse.cs new file mode 100644 index 0000000..cbf4961 --- /dev/null +++ b/Poort8.Ishare.Common/Token/TokenResponse.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace Poort8.Ishare.Common.Token; + +public class TokenResponse +{ + [JsonPropertyName("access_token")] + public string AccessToken { get; set; } + + [JsonPropertyName("token_type")] + public string TokenType { get; } = "Bearer"; + + [JsonPropertyName("expires_in")] + public int ExpiresIn { get; } = 3600; + + public TokenResponse(string accessToken) + { + AccessToken = accessToken; + } +} diff --git a/Poort8.Ishare.Common/WeatherForecast.cs b/Poort8.Ishare.Common/WeatherForecast.cs deleted file mode 100644 index ef6c4c2..0000000 --- a/Poort8.Ishare.Common/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Poort8.Ishare.Common; - -public class WeatherForecast -{ - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} diff --git a/Poort8.Ishare.Common/appsettings.Development.json b/Poort8.Ishare.Common/appsettings.Development.json index 0c208ae..d8ff746 100644 --- a/Poort8.Ishare.Common/appsettings.Development.json +++ b/Poort8.Ishare.Common/appsettings.Development.json @@ -4,5 +4,12 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "ClientId": "EU.EORI.NL888888881", + "Certificate": "", + "CertificatePassword": "", + "CertificateChain": "", + "CertificateChainPassword": "", + "SchemeOwnerUrl": "https://scheme.isharetest.net", + "SchemeOwnerIdentifier": "EU.EORI.NL000000000" } diff --git a/docker-compose.dcproj b/docker-compose.dcproj new file mode 100644 index 0000000..6ebe737 --- /dev/null +++ b/docker-compose.dcproj @@ -0,0 +1,18 @@ + + + + 2.1 + Linux + 83a90d51-889c-4588-9a5f-9ab3a5fb8509 + LaunchBrowser + {Scheme}://localhost:{ServicePort}/swagger + poort8.ishare.common + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..cf0e23a --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,20 @@ +version: '3.4' + +services: + poort8.ishare.common: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=https://+:443;http://+:80 + - ClientId=EU.EORI.NL888888881 + - Certificate= + - CertificatePassword= + - CertificateChain= + - CertificateChainPassword= + - SchemeOwnerUrl=https://scheme.isharetest.net + - SchemeOwnerIdentifier=EU.EORI.NL000000000 + ports: + - "80" + - "443" + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..42f4ff5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.4' + +services: + poort8.ishare.common: + image: ${DOCKER_REGISTRY-}poort8isharecommon + build: + context: . + dockerfile: Poort8.Ishare.Common/Dockerfile