diff options
17 files changed, 76 insertions, 192 deletions
| diff --git a/cspell.yaml b/cspell.yaml index 772a7a2..4272ef7 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -14,3 +14,4 @@ dictionaries:    - npm  words:    - crupest +  - Todos diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs index 184ac0a..27f0c85 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs @@ -12,9 +12,9 @@ public class CrudService<TEntity> : IDisposable where TEntity : class      protected readonly EntityJsonHelper<TEntity> _jsonHelper;      private readonly ILogger<CrudService<TEntity>> _logger; -    public CrudService(string? connectionName, ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, EntityJsonHelper<TEntity> jsonHelper, ILoggerFactory loggerFactory) +    public CrudService(ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, EntityJsonHelper<TEntity> jsonHelper, ILoggerFactory loggerFactory)      { -        _connectionName = connectionName; +        _connectionName = GetConnectionName();          _table = tableInfoFactory.Get(typeof(TEntity));          _dbConnection = dbConnectionFactory.Get(_connectionName);          _jsonHelper = jsonHelper; @@ -23,6 +23,11 @@ public class CrudService<TEntity> : IDisposable where TEntity : class          CheckDatabase(_dbConnection);      } +    protected virtual string GetConnectionName() +    { +        return typeof(TEntity).Name; +    } +      public EntityJsonHelper<TEntity> JsonHelper => _jsonHelper;      protected virtual void CheckDatabase(IDbConnection dbConnection) @@ -33,7 +38,7 @@ public class CrudService<TEntity> : IDisposable where TEntity : class          }      } -    private void DoInitializeDatabase(IDbConnection connection) +    protected virtual void DoInitializeDatabase(IDbConnection connection)      {          using var transaction = connection.BeginTransaction();          connection.Execute(_table.GenerateCreateTableSql(), transaction: transaction); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudServiceCollectionExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudServiceCollectionExtensions.cs index d51c0a1..e9f28bc 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudServiceCollectionExtensions.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using CrupestApi.Commons.Secrets;  using Microsoft.Extensions.DependencyInjection.Extensions;  namespace CrupestApi.Commons.Crud; @@ -9,6 +10,7 @@ public static class CrudServiceCollectionExtensions          services.TryAddSingleton<IDbConnectionFactory, SqliteConnectionFactory>();          services.TryAddSingleton<IColumnTypeProvider, ColumnTypeProvider>();          services.TryAddSingleton<ITableInfoFactory, TableInfoFactory>(); +        services.AddSecrets();          return services;      } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs index 9e85c68..b7bc6f1 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudWebApplicationExtensions.cs @@ -2,11 +2,11 @@ namespace CrupestApi.Commons.Crud;  public static class CrudWebApplicationExtensions  { -    public static WebApplication UseCrud<TEntity>(this WebApplication app, string path, string? key) where TEntity : class +    public static WebApplication MapCrud<TEntity>(this WebApplication app, string path, string? permission) where TEntity : class      {          app.MapGet(path, async (context) =>          { -             +            if (!context.RequirePermission(permission)) return;              var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>();              var allEntities = crudService.GetAll();              await context.ResponseJsonAsync(allEntities.Select(e => crudService.JsonHelper.ConvertEntityToDictionary(e))); @@ -14,6 +14,7 @@ public static class CrudWebApplicationExtensions          app.MapGet(path + "/{key}", async (context) =>          { +            if (!context.RequirePermission(permission)) return;              var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>();              var key = context.Request.RouteValues["key"]?.ToString();              if (key == null) @@ -28,6 +29,7 @@ public static class CrudWebApplicationExtensions          app.MapPost(path, async (context) =>          { +            if (!context.RequirePermission(permission)) return;              var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>();              var jsonDocument = await context.Request.ReadJsonAsync();              var key = crudService.Create(jsonDocument.RootElement); @@ -36,6 +38,7 @@ public static class CrudWebApplicationExtensions          app.MapPatch(path + "/{key}", async (context) =>          { +            if (!context.RequirePermission(permission)) return;              var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>();              var key = context.Request.RouteValues["key"]?.ToString();              if (key == null) @@ -52,6 +55,7 @@ public static class CrudWebApplicationExtensions          app.MapDelete(path + "/{key}", async (context) =>          { +            if (!context.RequirePermission(permission)) return;              var crudService = context.RequestServices.GetRequiredService<CrudService<TEntity>>();              var key = context.Request.RouteValues["key"]?.ToString();              if (key == null) diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs index b552e6b..3147e99 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -393,7 +393,7 @@ CREATE TABLE {tableName}(      /// <summary>      /// ConvertParameters. Select. Call hooks.      /// </summary> -    public virtual List<dynamic> SelectDynamic(IDbConnection dbConnection, string? what, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null) +    public virtual List<dynamic> SelectDynamic(IDbConnection dbConnection, string? what = null, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null)      {          var (sql, parameters) = GenerateSelectSql(what, where, orderBy, skip, limit);          var queryResult = dbConnection.Query<dynamic>(sql, ConvertParameters(parameters)); @@ -431,7 +431,7 @@ CREATE TABLE {tableName}(      /// <summary>      /// Select and call hooks.      /// </summary> -    public virtual List<TResult> Select<TResult>(IDbConnection dbConnection, string? what, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null) +    public virtual List<TResult> Select<TResult>(IDbConnection dbConnection, string? what = null, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null)      {          List<dynamic> queryResult = SelectDynamic(dbConnection, what, where, orderBy, skip, limit).ToList(); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/ISecretService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/ISecretService.cs index eeabb0d..c4c4467 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/ISecretService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/ISecretService.cs @@ -1,6 +1,6 @@  namespace CrupestApi.Commons; -interface ISecretService +public interface ISecretService  {      List<string> GetPermissions(string secret);  } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretInfo.cs index 3aacaa1..8b9420a 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretInfo.cs @@ -2,7 +2,7 @@ using System.Security.Cryptography;  using System.Text;  using CrupestApi.Commons.Crud; -namespace CrupestApi.Secrets; +namespace CrupestApi.Commons.Secrets;  public class SecretInfo  { @@ -23,13 +23,13 @@ public class SecretInfo      private static string GenerateRandomKey(int length)      { -        const string alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";          var result = new StringBuilder(length);          lock (RandomNumberGenerator)          {              for (int i = 0; i < length; i++)              { -                result.Append(alphanum[RandomNumberGenerator.GetInt32(alphanum.Length)]); +                result.Append(chars[RandomNumberGenerator.GetInt32(chars.Length)]);              }          }          return result.ToString(); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs new file mode 100644 index 0000000..fc13707 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretService.cs @@ -0,0 +1,20 @@ +using CrupestApi.Commons; +using CrupestApi.Commons.Crud; + +namespace CrupestApi.Commons.Secrets; + +public class SecretService : CrudService<SecretInfo>, ISecretService +{ +    public SecretService(ITableInfoFactory tableInfoFactory, IDbConnectionFactory dbConnectionFactory, EntityJsonHelper<SecretInfo> jsonHelper, ILoggerFactory loggerFactory) +        : base(tableInfoFactory, dbConnectionFactory, jsonHelper, loggerFactory) +    { + +    } + +    public List<string> GetPermissions(string secret) +    { +        var list = _table.Select<SecretInfo>(_dbConnection, +            where: WhereClause.Create().Eq(nameof(SecretInfo.Secret), secret)); +        return list.Select(x => x.Key).ToList(); +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretServiceCollectionExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretServiceCollectionExtensions.cs new file mode 100644 index 0000000..a9c0e5f --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretServiceCollectionExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace CrupestApi.Commons.Secrets; + +public static class SecretServiceCollectionExtensions +{ +    public static IServiceCollection AddSecrets(this IServiceCollection services) +    { +        services.TryAddScoped<ISecretService, SecretService>(); +        return services; +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsConstants.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretsConstants.cs index ea659a9..207cc45 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsConstants.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Secrets/SecretsConstants.cs @@ -1,4 +1,4 @@ -namespace CrupestApi.Secrets; +namespace CrupestApi.Commons.Secrets;  public static class SecretsConstants  { diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretCreateRequest.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretCreateRequest.cs deleted file mode 100644 index 5d0ea51..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretCreateRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CrupestApi.Secrets; - -public class SecretCreateRequest -{ -    public string Key { get; set; } = default!; -    public string Secret { get; set; } = default!; -    public string Description { get; set; } = default!; -    public DateTime? ExpireTime { get; set; } -}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretModifyRequest.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretModifyRequest.cs deleted file mode 100644 index f632c6d..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretModifyRequest.cs +++ /dev/null @@ -1,37 +0,0 @@ -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, bool revoked) -    { -        if (revoked is not true) -        { -            throw new ArgumentException("Revoked can only be set to true."); -        } - -        Key = key; -        Description = description; -        SetExpireTime = true; -        ExpireTime = expireTime; -        Revoked = revoked; -    } - -    public string? Key { get; set; } -    public string? Description { get; set; } -    public bool SetExpireTime { get; set; } -    public DateTime? ExpireTime { get; set; } -    public bool Revoked { get; set; } -} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretNotExistException.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretNotExistException.cs deleted file mode 100644 index ad082ee..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretNotExistException.cs +++ /dev/null @@ -1,18 +0,0 @@ -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/SecretsExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsExtensions.cs new file mode 100644 index 0000000..e09887b --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsExtensions.cs @@ -0,0 +1,19 @@ +using CrupestApi.Commons.Secrets; +using CrupestApi.Commons.Crud; + +namespace CrupestApi.Secrets; + +public static class SecretsExtensions +{ +    public static IServiceCollection AddSecrets(this IServiceCollection services) +    { +        services.AddCrud<SecretInfo, SecretService>(); +        return services; +    } + +    public static WebApplication MapSecrets(this WebApplication webApplication, string path = "/api/secrets") +    { +        webApplication.MapCrud<SecretInfo>(path, SecretsConstants.SecretManagementKey); +        return webApplication; +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsWebApplicationExtensions.cs deleted file mode 100644 index 12d939b..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsWebApplicationExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -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, e.Kind == VerifySecretException.ErrorKind.Unauthorized ? 401 : 403); -            } -        }); - -        return app; -    } - -    public static async Task CheckSecret(this HttpContext context, string? key) -    { -        var secretsService = context.RequestServices.GetRequiredService<ISecretsService>(); -        await secretsService.VerifySecretForHttpRequestAsync(context.Request, key); -    } - -    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); -        }); - -        app.MapGet(path + "/:secret", async (context) => -        { -            await context.CheckSecret(SecretsConstants.SecretManagementKey); -            var secretsService = context.RequestServices.GetRequiredService<ISecretsService>(); -            var secret = context.Request.RouteValues["secret"]; -            if (secret is null) -            { -                await context.Response.WriteErrorMessageAsync("Secret path parameter is invalid."); -                return; -            } -            var secretInfo = secretsService.GetSecretAsync((string)secret); -            await context.Response.WriteJsonAsync(secretInfo); -        }); - -        app.MapPost(path, async (context) => -        { -            await context.CheckSecret(SecretsConstants.SecretManagementKey); -            var secretsService = context.RequestServices.GetRequiredService<ISecretsService>(); -            var request = await context.Request.ReadFromJsonAsync<SecretCreateRequest>(); -            if (request is null) -            { -                await context.Response.WriteErrorMessageAsync("Failed to deserialize request body to SecretCreateRequest."); -                return; -            } -            var secret = await secretsService.CreateSecretAsync(request.Key, request.Description, request.ExpireTime); -            await context.Response.WriteJsonAsync(secret, 201, beforeWriteBody: (response) => -            { -                response.Headers.Location = context.Request.Path + "/" + secret.Secret; -            }); -        }); - -        app.MapPost(path + "/:secret/revoke", async (context) => -        { -            await context.CheckSecret(SecretsConstants.SecretManagementKey); -            var secretsService = context.RequestServices.GetRequiredService<ISecretsService>(); -            var secret = context.Request.RouteValues["secret"]; -            if (secret is null) -            { -                await context.Response.WriteErrorMessageAsync("Secret path parameter is invalid."); -                return; -            } - -            try -            { -                await secretsService.RevokeSecretAsync((string)secret); -                await context.Response.WriteMessageAsync("Secret revoked."); -            } -            catch (EntityNotExistException) -            { -                await context.Response.WriteErrorMessageAsync("Secret to revoke is invalid."); -            } -        }); - -        return app; -    } -} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/VerifySecretException.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/VerifySecretException.cs deleted file mode 100644 index 795fa3e..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/VerifySecretException.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace CrupestApi.Secrets; - -public class VerifySecretException : Exception -{ -    public VerifySecretException(string? requestKey, string message, ErrorKind kind = ErrorKind.Unauthorized) : base(message) -    { -        RequestKey = requestKey; -        Kind = kind; -    } - -    public enum ErrorKind -    { -        Unauthorized, -        Forbidden -    } - -    public ErrorKind Kind { get; set; } - -    public string? RequestKey { get; set; } -} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs index b3647f1..0ff05a0 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs @@ -23,7 +23,7 @@ public static class TodosWebApplicationExtensions              }              catch (Exception e)              { -                await context.Response.WriteErrorMessageAsync(e.Message, statusCode: StatusCodes.Status503ServiceUnavailable); +                await context.Response.WriteMessageAsync(e.Message, statusCode: StatusCodes.Status503ServiceUnavailable);              }          }); | 
