diff options
| author | crupest <crupest@outlook.com> | 2022-12-02 11:04:34 +0800 | 
|---|---|---|
| committer | crupest <crupest@outlook.com> | 2022-12-02 13:35:35 +0800 | 
| commit | 300842e81a99db6da103f146d33eb47f43efc683 (patch) | |
| tree | 2f80b57945408b8af83556a4efe5a92dea98e025 /docker | |
| parent | 49ada0b891a93cf0f1c0dfc2cb5b1d103019b7c8 (diff) | |
| download | crupest-300842e81a99db6da103f146d33eb47f43efc683.tar.gz crupest-300842e81a99db6da103f146d33eb47f43efc683.tar.bz2 crupest-300842e81a99db6da103f146d33eb47f43efc683.zip  | |
...
Diffstat (limited to 'docker')
18 files changed, 250 insertions, 18 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/CrupestApi.Commons.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Commons/CrupestApi.Commons.csproj new file mode 100644 index 0000000..72a1294 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/CrupestApi.Commons.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk.Web">
 +
 +  <PropertyGroup>
 +    <TargetFramework>net7.0</TargetFramework>
 +    <Nullable>enable</Nullable>
 +    <ImplicitUsings>enable</ImplicitUsings>
 +  </PropertyGroup>
 +
 +</Project>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Error.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Error.cs new file mode 100644 index 0000000..b298f7a --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Error.cs @@ -0,0 +1,19 @@ +namespace CrupestApi.Commons; + +public class ErrorBody +{ +    public ErrorBody(string message) +    { +        Message = message; +    } + +    public string Message { get; set; } +} + +public static class CrupestApiErrorExtensions +{ +    public static async Task WriteErrorMessageAsync(this HttpResponse response, string message, int statusCode = 400, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) +    { +        await response.WriteJsonAsync(new ErrorBody(message), statusCode: statusCode, beforeWriteBody, cancellationToken); +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs new file mode 100644 index 0000000..4b76066 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/HttpResponseAction.cs @@ -0,0 +1,3 @@ +namespace CrupestApi.Commons; + +public delegate Task HttpResponseAction(HttpResponse response); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs new file mode 100644 index 0000000..0ec3ff0 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs @@ -0,0 +1,40 @@ +using System.Text; +using System.Text.Json; + +namespace CrupestApi.Commons; + + +public static class CrupestApiJsonExtensions +{ +    public static IServiceCollection AddJsonOptions(this IServiceCollection services) +    { +        services.AddOptions<JsonSerializerOptions>(); +        services.Configure<JsonSerializerOptions>(config => +        { +            config.AllowTrailingCommas = true; +            config.PropertyNameCaseInsensitive = true; +            config.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; +        }); + +        return services; +    } + + +    public static async Task WriteJsonAsync<T>(this HttpResponse response, T bodyObject, int statusCode = 200, HttpResponseAction? beforeWriteBody = null, CancellationToken cancellationToken = default) +    { +        var jsonOptions = response.HttpContext.RequestServices.GetRequiredService<JsonSerializerOptions>(); +        byte[] json = JsonSerializer.SerializeToUtf8Bytes<T>(bodyObject, jsonOptions); + +        var byteCount = json.Length; +        response.StatusCode = statusCode; +        response.Headers.ContentType = "application/json; charset=utf-8"; +        response.Headers.ContentLength = byteCount; + +        if (beforeWriteBody is not null) +        { +            await beforeWriteBody(response); +        } + +        await response.Body.WriteAsync(json, cancellationToken); +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj index 72a1294..2d78adb 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj +++ b/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj @@ -1,5 +1,9 @@  <Project Sdk="Microsoft.NET.Sdk.Web">
 +  <ItemGroup> +    <ProjectReference Include="..\CrupestApi.Commons\CrupestApi.Commons.csproj" /> +  </ItemGroup>
 +
    <PropertyGroup>
      <TargetFramework>net7.0</TargetFramework>
      <Nullable>enable</Nullable>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/CrupestApi.Secrets.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/CrupestApi.Secrets.csproj index 72a1294..2d78adb 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/CrupestApi.Secrets.csproj +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/CrupestApi.Secrets.csproj @@ -1,5 +1,9 @@  <Project Sdk="Microsoft.NET.Sdk.Web">
 +  <ItemGroup> +    <ProjectReference Include="..\CrupestApi.Commons\CrupestApi.Commons.csproj" /> +  </ItemGroup>
 +
    <PropertyGroup>
      <TargetFramework>net7.0</TargetFramework>
      <Nullable>enable</Nullable>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/ISecretsService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/ISecretsService.cs new file mode 100644 index 0000000..c42fbdc --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/ISecretsService.cs @@ -0,0 +1,21 @@ +namespace CrupestApi.Secrets; + +public interface ISecretsService +{ +    Task<List<SecretInfo>> GetSecretListAsync(bool includeExpired = false, bool includeRevoked = false); + +    Task<List<SecretInfo>> GetSecretListByKeyAsync(string key, bool includeExpired = false, bool includeRevoked = false); + +    Task<bool> VerifySecretAsync(string key, string secret); + +    // Check if "secret" query param exists and is only one. Then check the secret is valid for given key. +    // If check fails, will throw a VerifySecretException with proper message that can be send to client. +    Task VerifySecretForHttpRequestAsync(HttpRequest request, string key, string queryKey = "secret"); + +    Task<SecretInfo> CreateSecretAsync(string key, string description, DateTime? expireTime = null); + +    Task RevokeSecretAsync(string secret); + +    // Throw SecretNotExistException if request secret does not exist. +    Task<SecretInfo> ModifySecretAsync(string secret, SecretModifyRequest modifyRequest); +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs new file mode 100644 index 0000000..46ce501 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs @@ -0,0 +1,21 @@ +namespace CrupestApi.Secrets; + +public class SecretInfo +{ +    public SecretInfo(string key, string secret, string description, DateTime? expireTime, bool revoked, DateTime createdTime) +    { +        Key = key; +        Secret = secret; +        Description = description; +        ExpireTime = expireTime; +        Revoked = revoked; +        CreateTime = createdTime; +    } + +    public string Key { get; set; } +    public string Secret { get; set; } +    public string Description { get; set; } +    public DateTime? ExpireTime { get; set; } +    public bool Revoked { get; set; } +    public DateTime CreateTime { get; set; } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretModifyRequest.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretModifyRequest.cs new file mode 100644 index 0000000..dfff347 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretModifyRequest.cs @@ -0,0 +1,30 @@ +namespace CrupestApi.Secrets; + +public class SecretModifyRequest +{ +    public SecretModifyRequest() +    { + +    } + +    public SecretModifyRequest(string? key, string? description) +    { +        Key = key; +        Description = description; +        SetExpireTime = false; +        ExpireTime = null; +    } + +    public SecretModifyRequest(string? key, string? description, DateTime? expireTime) +    { +        Key = key; +        Description = description; +        SetExpireTime = true; +        ExpireTime = expireTime; +    } + +    public string? Key { get; set; } +    public string? Description { get; set; } +    public bool SetExpireTime { get; set; } +    public DateTime? ExpireTime { get; set; } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretNotExistException.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretNotExistException.cs new file mode 100644 index 0000000..ad082ee --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretNotExistException.cs @@ -0,0 +1,18 @@ +namespace CrupestApi.Secrets; + +public class SecretNotExistException : Exception +{ +    public SecretNotExistException(string requestSecret) +        : base($"Request secret {requestSecret} not found.") +    { +        RequestSecret = requestSecret; +    } + +    public SecretNotExistException(string requestSecret, string message) +        : base(message) +    { +        RequestSecret = requestSecret; +    } + +    public string RequestSecret { get; set; } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsConstants.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsConstants.cs new file mode 100644 index 0000000..ea659a9 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsConstants.cs @@ -0,0 +1,6 @@ +namespace CrupestApi.Secrets; + +public static class SecretsConstants +{ +    public const string SecretManagementKey = "crupest.secrets.management"; +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsWebApplicationExtensions.cs new file mode 100644 index 0000000..a771547 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsWebApplicationExtensions.cs @@ -0,0 +1,42 @@ +using CrupestApi.Commons; + +namespace CrupestApi.Secrets; + +public static class SecretsWebApplicationExtensions +{ +    public static WebApplication UseCatchVerifySecretException(this WebApplication app) +    { +        app.Use(async (context, next) => +        { +            try +            { +                await next(context); +            } +            catch (VerifySecretException e) +            { +                await context.Response.WriteErrorMessageAsync(e.Message, 401); +            } +        }); + +        return app; +    } + +    public static async Task CheckSecret(this HttpContext context, string key) +    { +        var secretsService = context.RequestServices.GetRequiredService<ISecretsService>(); +        await secretsService.VerifySecretForHttpRequestAsync(context.Request, SecretsConstants.SecretManagementKey); +    } + +    public static WebApplication MapSecrets(this WebApplication app, string path) +    { +        app.MapGet(path, async (context) => +        { +            await context.CheckSecret(SecretsConstants.SecretManagementKey); +            var secretsService = context.RequestServices.GetRequiredService<ISecretsService>(); +            var secrets = secretsService.GetSecretListAsync(); +            await context.Response.WriteJsonAsync(secrets); +        }); + +        return app; +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/VerifySecretException.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/VerifySecretException.cs new file mode 100644 index 0000000..c9f60a1 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/VerifySecretException.cs @@ -0,0 +1,11 @@ +namespace CrupestApi.Secrets; + +public class VerifySecretException : Exception +{ +    public VerifySecretException(string requestKey, string message) : base(message) +    { +        RequestKey = requestKey; +    } + +    public string RequestKey { get; set; } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj index 72a1294..2d78adb 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj @@ -1,5 +1,9 @@  <Project Sdk="Microsoft.NET.Sdk.Web">
 +  <ItemGroup> +    <ProjectReference Include="..\CrupestApi.Commons\CrupestApi.Commons.csproj" /> +  </ItemGroup>
 +
    <PropertyGroup>
      <TargetFramework>net7.0</TargetFramework>
      <Nullable>enable</Nullable>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs index 575dc4f..b3647f1 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs @@ -1,8 +1,4 @@ -using System; -using System.Text.Json; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; +using CrupestApi.Commons;  namespace CrupestApi.Todos; @@ -15,24 +11,19 @@ public static class TodosWebApplicationExtensions              throw new ArgumentNullException(nameof(app));          } -        app.MapGet(path, async ([FromServices] TodosService todosService) => +        app.MapGet(path, async (context) =>          { -            var jsonOptions = new JsonSerializerOptions -            { -                PropertyNamingPolicy = JsonNamingPolicy.CamelCase -            }; +            var todosService = context.RequestServices.GetRequiredService<TodosService>();              try              {                  var todos = await todosService.GetTodosAsync(); -                return Results.Json(todos, jsonOptions, statusCode: 200); +                await context.Response.WriteJsonAsync(todos); +              }              catch (Exception e)              { -                return Results.Json(new -                { -                    e.Message -                }, jsonOptions, statusCode: StatusCodes.Status503ServiceUnavailable); +                await context.Response.WriteErrorMessageAsync(e.Message, statusCode: StatusCodes.Status503ServiceUnavailable);              }          }); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.sln b/docker/crupest-api/CrupestApi/CrupestApi.sln index 9869d3b..5f21ff5 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.sln +++ b/docker/crupest-api/CrupestApi/CrupestApi.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Todos", "Crupest  EndProject
  Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Secrets", "CrupestApi.Secrets\CrupestApi.Secrets.csproj", "{9A7CC9F9-70CB-408A-ADFC-5119C0BDB236}"
  EndProject
 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Commons", "CrupestApi.Commons\CrupestApi.Commons.csproj", "{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}"
 +EndProject
  Global
  	GlobalSection(SolutionConfigurationPlatforms) = preSolution
  		Debug|Any CPU = Debug|Any CPU
 @@ -30,5 +32,9 @@ Global  		{9A7CC9F9-70CB-408A-ADFC-5119C0BDB236}.Debug|Any CPU.Build.0 = Debug|Any CPU
  		{9A7CC9F9-70CB-408A-ADFC-5119C0BDB236}.Release|Any CPU.ActiveCfg = Release|Any CPU
  		{9A7CC9F9-70CB-408A-ADFC-5119C0BDB236}.Release|Any CPU.Build.0 = Release|Any CPU
 +		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 +		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 +		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 +		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Release|Any CPU.Build.0 = Release|Any CPU
  	EndGlobalSection
  EndGlobal
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj b/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj index c15ec91..c408c7d 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj +++ b/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj @@ -3,6 +3,8 @@    <ItemGroup>      <ProjectReference Include="..\CrupestApi.Todos\CrupestApi.Todos.csproj" />      <ProjectReference Include="..\CrupestApi.Files\CrupestApi.Files.csproj" /> +    <ProjectReference Include="..\CrupestApi.Commons\CrupestApi.Commons.csproj" /> +    <ProjectReference Include="..\CrupestApi.Secrets\CrupestApi.Secrets.csproj" />    </ItemGroup>
    <PropertyGroup>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi/Program.cs b/docker/crupest-api/CrupestApi/CrupestApi/Program.cs index e18252f..ff8f02f 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi/Program.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi/Program.cs @@ -1,6 +1,5 @@ -using System;
 -using Microsoft.AspNetCore.Builder;
 -using Microsoft.Extensions.Configuration;
 +using CrupestApi.Commons;
 +using CrupestApi.Secrets;
  using CrupestApi.Todos;
  var builder = WebApplication.CreateBuilder(args);
 @@ -8,10 +7,12 @@ var builder = WebApplication.CreateBuilder(args);  string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/crupest-api-config.json";
  builder.Configuration.AddJsonFile(configFilePath, optional: false, reloadOnChange: true);
 +builder.Services.AddJsonOptions();
  builder.Services.AddTodos();
  var app = builder.Build();
  app.MapTodos("/api/todos");
 +app.MapSecrets("/api/secrets");
  app.Run();
  | 
