diff options
Diffstat (limited to 'docker/crupest-api/CrupestApi/CrupestApi.Secrets')
3 files changed, 31 insertions, 219 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/ISecretsService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/ISecretsService.cs deleted file mode 100644 index b5de436..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/ISecretsService.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace CrupestApi.Secrets; - -public interface ISecretsService -{ - Task<SecretInfo?> GetSecretAsync(string secret); - - Task<List<SecretInfo>> GetSecretListAsync(bool includeExpired = false, bool includeRevoked = false); - - Task<List<SecretInfo>> GetSecretListByKeyAsync(string key, bool includeExpired = false, bool includeRevoked = false); - - Task 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 = null, 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 index 009bde9..e6af39b 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretInfo.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; +using System.Text; using CrupestApi.Commons.Crud; namespace CrupestApi.Secrets; @@ -6,7 +8,7 @@ public class SecretInfo { [Column(NotNull = true)] public string Key { get; set; } = default!; - [Column(NotNull = true, ClientGenerate = true)] + [Column(NotNull = true, ClientGenerate = true, NoUpdate = true)] public string Secret { get; set; } = default!; [Column(DefaultEmptyForString = true)] public string Description { get; set; } = default!; @@ -16,4 +18,31 @@ public class SecretInfo public bool Revoked { get; set; } [Column(NotNull = true)] public DateTime CreateTime { get; set; } + + private static RandomNumberGenerator RandomNumberGenerator = RandomNumberGenerator.Create(); + + private static string GenerateRandomKey(int length) + { + const string alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var result = new StringBuilder(length); + lock (RandomNumberGenerator) + { + for (int i = 0; i < length; i++) + { + result.Append(alphanum[RandomNumberGenerator.GetInt32(alphanum.Length)]); + } + } + return result.ToString(); + } + + + public static string SecretDefaultValueGenerator() + { + return GenerateRandomKey(16); + } + + public static DateTime CreateTimeDefaultValueGenerator() + { + return DateTime.UtcNow; + } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs index 5a49121..b8912cb 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Secrets/SecretsService.cs @@ -5,7 +5,7 @@ using Dapper; namespace CrupestApi.Secrets; -public class SecretsService : CrudService<SecretInfo>, ISecretsService +public class SecretsService : CrudService<SecretInfo> { private readonly ILogger<SecretsService> _logger; @@ -14,198 +14,4 @@ public class SecretsService : CrudService<SecretInfo>, ISecretsService { _logger = loggerFactory.CreateLogger<SecretsService>(); } - - public async Task<SecretInfo> CreateSecretAsync(SecretInfo secretInfo) - { - if (secretInfo.Secret is not null) - { - throw new ArgumentException("Secret is auto generated. Don't specify it explicit."); - } - - secretInfo.Secret = GenerateRandomKey(16); - secretInfo.CreateTime = DateTime.Now; - - await InsertAsync(_table.GenerateInsertClauseFromEntity(secretInfo)); - - return secretInfo; - } - - public async Task<List<SecretInfo>> GetSecretListAsync(bool includeExpired = false, bool includeRevoked = false) - { - return (await QueryAsync()).ToList(); - } - - public async Task<List<SecretInfo>> GetSecretListByKeyAsync(string key, bool includeExpired = false, bool includeRevoked = false) - { - WhereClause where = WhereClause.Create(); - - where.Eq(nameof(SecretInfo.Key), key); - - if (!includeExpired) - { - where.Add(nameof(SecretInfo.ExpireTime), "<=", ) - } - - if (!includeRevoked) - { - where.Eq(nameof(SecretInfo.Revoked), false); - } - - return (await QueryAsync(where)).ToList(); - } - - public async Task<SecretInfo> ModifySecretAsync(string secret, SecretModifyRequest modifyRequest) - { - var dbConnection = await EnsureDatabase(); - - var secretInfo = await GetSecretAsync(dbConnection, secret); - - if (secretInfo is null) - { - throw new EntityNotExistException("Secret not found."); - } - - var queryParams = new DynamicParameters(); - var updateColumnList = new List<string>(); - - if (modifyRequest.Key is not null) - { - queryParams.Add("Key", modifyRequest.Key); - updateColumnList.Add("Key"); - } - - if (modifyRequest.Description is not null) - { - queryParams.Add("Description", modifyRequest.Description); - updateColumnList.Add("Description"); - } - - if (modifyRequest.SetExpireTime is true) - { - queryParams.Add("ExpireTime", modifyRequest.ExpireTime?.ToString("O")); - updateColumnList.Add("ExpireTime"); - } - - if (modifyRequest.Revoked is true && secretInfo.Revoked is not true) - { - queryParams.Add("Revoked", true); - updateColumnList.Add("Revoked"); - } - - if (updateColumnList.Count == 0) - { - return secretInfo; - } - - queryParams.Add("Secret", secret); - - var updateColumnString = updateColumnList.GenerateUpdateColumnString(); - - var changeCount = await dbConnection.ExecuteAsync($@" -UPDATE secrets SET {updateColumnString} WHERE Secret = @Secret; - ", queryParams); - - Debug.Assert(changeCount == 1); - - return secretInfo; - } - - public async Task RevokeSecretAsync(string secret) - { - await ModifySecretAsync(secret, new SecretModifyRequest - { - Revoked = true, - }); - } - - public async Task VerifySecretAsync(string? key, string? secret) - { - var dbConnection = await EnsureDatabase(); - - if (secret is null) - { - if (key is not null) - { - throw new VerifySecretException(key, "A secret with given key is needed."); - } - } - - var entity = await dbConnection.QueryFirstOrDefaultAsync<SecretInfo>(@" -SELECT Id, Key, Secret, Description, ExpireTime, Revoked, CreateTime FROM secrets WHERE Key = @Key AND Secret = @Secret - ", new - { - Key = key, - Secret = secret, - }); - - if (entity is null) - { - throw new VerifySecretException(key, "Secret token is invalid."); - } - - if (entity.Revoked is true) - { - throw new VerifySecretException(key, "Secret token is revoked."); - } - - if (entity.ExpireTime is not null && DateTime.ParseExact(entity.ExpireTime, "O", null) > DateTime.Now) - { - throw new VerifySecretException(key, "Secret token is expired."); - } - - if (key is not null) - { - if (entity.Key != key) - { - throw new VerifySecretException(key, "Secret is not for this key", VerifySecretException.ErrorKind.Forbidden); - } - } - } - - public async Task VerifySecretForHttpRequestAsync(HttpRequest request, string? key, string queryKey = "secret") - { - string? secret = null; - - var authorizationHeaders = request.Headers.Authorization.ToList(); - if (authorizationHeaders.Count > 1) - { - _logger.LogWarning("There are multiple Authorization headers in the request. Will use the last one."); - } - if (authorizationHeaders.Count > 0) - { - var authorizationHeader = authorizationHeaders[^1] ?? ""; - if (!authorizationHeader.StartsWith("Bearer ")) - { - throw new VerifySecretException(key, "Authorization header must start with 'Bearer '."); - } - - secret = authorizationHeader.Substring("Bearer ".Length).Trim(); - } - - var secretQueryParam = request.Query[queryKey].ToList(); - if (secretQueryParam.Count > 1) - { - _logger.LogWarning($"There are multiple '{queryKey}' query parameters in the request. Will use the last one."); - } - if (secretQueryParam.Count > 0) - { - if (secret is not null) - { - _logger.LogWarning("Secret found both in Authorization header and query parameter. Will use the one in query parameter."); - } - secret = secretQueryParam[^1] ?? ""; - } - - await VerifySecretAsync(key, secret); - } - - public Task<SecretInfo?> GetSecretAsync(string secret) - { - throw new NotImplementedException(); - } - - public Task<SecretInfo> CreateSecretAsync(string key, string description, DateTime? expireTime = null) - { - throw new NotImplementedException(); - } } |