From df4adb8435d74d69a57251b9eef0350c8ad613b8 Mon Sep 17 00:00:00 2001 From: Pilzinsel64 Date: Fri, 16 Aug 2024 09:19:03 +0200 Subject: [PATCH] some more work on api --- .../Collections/IEnumerableExtensions.cs | 10 ++++++ Pilz.Net/Api/ApiAuthCheckEventArgs.cs | 4 ++- Pilz.Net/Api/ApiClient.cs | 25 +++++++------ Pilz.Net/Api/ApiRequestInfo.cs | 9 +++++ .../{ApiResponseT.cs => ApiResponse{T}.cs} | 6 ++-- Pilz.Net/Api/ApiResult.cs | 24 ++++++++----- Pilz.Net/Api/ApiServer.cs | 35 ++++++++++++++++--- Pilz.Net/Api/IApiClient.cs | 4 +-- 8 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 Pilz.Extensions/Collections/IEnumerableExtensions.cs create mode 100644 Pilz.Net/Api/ApiRequestInfo.cs rename Pilz.Net/Api/{ApiResponseT.cs => ApiResponse{T}.cs} (59%) diff --git a/Pilz.Extensions/Collections/IEnumerableExtensions.cs b/Pilz.Extensions/Collections/IEnumerableExtensions.cs new file mode 100644 index 0000000..669589b --- /dev/null +++ b/Pilz.Extensions/Collections/IEnumerableExtensions.cs @@ -0,0 +1,10 @@ +namespace Pilz.Extensions.Collections; + +public static class IEnumerableExtensions +{ + public static void ForEach(this IEnumerable @this, Action action) + { + foreach (var t in @this) + action(t); + } +} diff --git a/Pilz.Net/Api/ApiAuthCheckEventArgs.cs b/Pilz.Net/Api/ApiAuthCheckEventArgs.cs index ba09a72..125baeb 100644 --- a/Pilz.Net/Api/ApiAuthCheckEventArgs.cs +++ b/Pilz.Net/Api/ApiAuthCheckEventArgs.cs @@ -1,9 +1,11 @@ namespace Pilz.Net.Api; -public record class ApiAuthCheckEventArgs(string AuthKey) +public class ApiAuthCheckEventArgs(string authKey) : EventArgs { private bool hasDenyed; + public string AuthKey { get; } = authKey; + public bool Valid { get; set; } public void Deny() diff --git a/Pilz.Net/Api/ApiClient.cs b/Pilz.Net/Api/ApiClient.cs index db8cfcb..dad7c58 100644 --- a/Pilz.Net/Api/ApiClient.cs +++ b/Pilz.Net/Api/ApiClient.cs @@ -14,24 +14,24 @@ public class ApiClient(string apiUrl) : IApiClient public ILogger Log { get; set; } = NullLogger.Instance; - public virtual async Task SendMessage(string url, ApiMessage message, IMessageSerializer? serializer) + public virtual async Task SendMessage(string route, ApiMessage message, IMessageSerializer? serializer = null) { serializer ??= Serializer; - Log.InfoFormat("Send message to {0}", url); + Log.InfoFormat("Send message to {0}", route); - var res = await Send(url, message, serializer); + var res = await Send(route, message, serializer); return new(res.StatusCode); } - public virtual async Task> SendRequest(string url, ApiMessage message, IMessageSerializer? serializer) where TResponse : ApiMessage + public virtual async Task> SendRequest(string route, ApiMessage message, IMessageSerializer? serializer = null) where TResponse : ApiMessage { serializer ??= Serializer; - Log.InfoFormat("Send request to {0}", url); + Log.InfoFormat("Send request to {0}", route); - var res = await Send(url, message, serializer); + var res = await Send(route, message, serializer); TResponse? result = null; if (res.IsSuccessStatusCode) @@ -40,11 +40,16 @@ public class ApiClient(string apiUrl) : IApiClient return new(res.StatusCode, result); } - protected virtual async Task Send(string url, ApiMessage message, IMessageSerializer serializer) + protected virtual async Task Send(string route, ApiMessage message, IMessageSerializer serializer) { - var fullRequestUrl = ApiUrl + url; + var url = ApiUrl + route; var content = new StringContent(serializer.Serialize(message)!, null, "application/json"); - content.Headers.Add("API-AUTH-KEY", AuthKey); - return await httpClient.PostAsync(fullRequestUrl, content); + content.Headers.Add("API-AUTH-KEY", EncodeAuthKey()); + return await httpClient.PostAsync(url, content); + } + + protected virtual string? EncodeAuthKey() + { + return AuthKey; } } diff --git a/Pilz.Net/Api/ApiRequestInfo.cs b/Pilz.Net/Api/ApiRequestInfo.cs new file mode 100644 index 0000000..2fe5e85 --- /dev/null +++ b/Pilz.Net/Api/ApiRequestInfo.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Pilz.Net.Api; + +public record class ApiRequestInfo( + ApiMessage Message, + [property: MemberNotNullWhen(true, "AuthKey")] + bool IsAuthenticated, + string? AuthKey); diff --git a/Pilz.Net/Api/ApiResponseT.cs b/Pilz.Net/Api/ApiResponse{T}.cs similarity index 59% rename from Pilz.Net/Api/ApiResponseT.cs rename to Pilz.Net/Api/ApiResponse{T}.cs index e7f1d73..f015dac 100644 --- a/Pilz.Net/Api/ApiResponseT.cs +++ b/Pilz.Net/Api/ApiResponse{T}.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Diagnostics.CodeAnalysis; +using System.Net; namespace Pilz.Net.Api; @@ -7,9 +8,10 @@ public record class ApiResponse( T? Message) where T : ApiMessage { + [MemberNotNull(nameof(Message))] public void EnsureOk() { - if (StatusCode != HttpStatusCode.OK) + if (StatusCode != HttpStatusCode.OK || Message is null) throw new Exception("Api return is not ok!"); } } diff --git a/Pilz.Net/Api/ApiResult.cs b/Pilz.Net/Api/ApiResult.cs index 60eb8f7..5f51a6a 100644 --- a/Pilz.Net/Api/ApiResult.cs +++ b/Pilz.Net/Api/ApiResult.cs @@ -6,13 +6,21 @@ public record class ApiResult( HttpStatusCode StatusCode, ApiMessage? Message = null) { - public static ApiResult Ok() - { - return new(HttpStatusCode.OK); - } + public static ApiResult Ok() => new(HttpStatusCode.OK); - public static ApiResult Ok(ApiMessage message) - { - return new(HttpStatusCode.OK, message); - } + public static ApiResult Ok(ApiMessage message) => new(HttpStatusCode.OK, message); + + public static ApiResult Unauthorized() => new(HttpStatusCode.Unauthorized); + + public static ApiResult NotFound() => new(HttpStatusCode.NotFound); + + public static ApiResult Forbidden() => new(HttpStatusCode.Forbidden); + + public static ApiResult Locked() => new(HttpStatusCode.Locked); + + public static ApiResult TooManyRequests() => new(HttpStatusCode.TooManyRequests); + + public static ApiResult ServiceUnavailable() => new(HttpStatusCode.ServiceUnavailable); + + public static ApiResult UnavailableForLegalReasons() => new(HttpStatusCode.UnavailableForLegalReasons); } \ No newline at end of file diff --git a/Pilz.Net/Api/ApiServer.cs b/Pilz.Net/Api/ApiServer.cs index 4d922b9..95e41f8 100644 --- a/Pilz.Net/Api/ApiServer.cs +++ b/Pilz.Net/Api/ApiServer.cs @@ -54,7 +54,9 @@ public class ApiServer(string apiUrl) : IApiServer // Sanity checks if (method.GetCustomAttribute() is not MessageHandlerAttribute attribute || method.GetParameters().FirstOrDefault() is not ParameterInfo parameter - || !parameter.ParameterType.IsAssignableTo(typeof(ApiMessage)) + || !parameter.ParameterType.IsGenericType + || parameter.ParameterType.GenericTypeArguments.Length != 1 + || !parameter.ParameterType.GenericTypeArguments[0].IsAssignableTo(typeof(ApiMessage)) || !method.ReturnType.IsAssignableTo(typeof(ApiResult))) throw new NotSupportedException("The first parameter needs to be of type ApiMessage and must return an ApiResult object and the method must have the MessageHandlerAttribute."); @@ -146,8 +148,11 @@ public class ApiServer(string apiUrl) : IApiServer // Check authentication Log.Debug("Check authentication"); - if (attribute.RequiesAuth && (string.IsNullOrWhiteSpace(authKey) || !CheckAuthentication(authKey))) - return null; + var isAuthenticated = false; + if (!string.IsNullOrWhiteSpace(authKey) && DecodeAuthKey(authKey) is string authKeyDecoded) + isAuthenticated = CheckAuthentication(authKeyDecoded); + else + authKeyDecoded = null!; // Get required infos Log.Debug("Find other infos"); @@ -161,7 +166,8 @@ public class ApiServer(string apiUrl) : IApiServer // Invoke handler Log.Debug("Invoke handler"); - if (handler.DynamicInvoke(message) is not ApiResult result) + var parameters = BuildParameters(handler, () => message, () => new(message, isAuthenticated, authKeyDecoded)); + if (handler.DynamicInvoke(parameters) is not ApiResult result) return null; // Return result without message @@ -179,6 +185,22 @@ public class ApiServer(string apiUrl) : IApiServer return new(result, resultStr); } + protected virtual object?[]? BuildParameters(Delegate handler, Func getMessage, Func getRequestInfo) + { + var infos = handler.Method.GetParameters(); + var objs = new List(); + + foreach (var info in infos) + { + if (info.ParameterType.IsAssignableTo(typeof(ApiMessage))) + objs.Add(getMessage()); + else if (info.ParameterType.IsAssignableTo(typeof(ApiRequestInfo))) + objs.Add(getRequestInfo()); + } + + return [.. objs]; + } + protected virtual IMessageSerializer GetSerializer(Type? t) { if (t is not null) @@ -204,4 +226,9 @@ public class ApiServer(string apiUrl) : IApiServer } return false; } + + protected virtual string? DecodeAuthKey(string authKey) + { + return authKey; + } } diff --git a/Pilz.Net/Api/IApiClient.cs b/Pilz.Net/Api/IApiClient.cs index affab99..0475e66 100644 --- a/Pilz.Net/Api/IApiClient.cs +++ b/Pilz.Net/Api/IApiClient.cs @@ -12,7 +12,7 @@ public interface IApiClient ILogger Log { get; set; } - Task SendMessage(string url, ApiMessage message, IMessageSerializer? serializer = null); + Task SendMessage(string route, ApiMessage message, IMessageSerializer? serializer = null); - Task> SendRequest(string url, ApiMessage message, IMessageSerializer? serializer = null) where TResponse : ApiMessage; + Task> SendRequest(string route, ApiMessage message, IMessageSerializer? serializer = null) where TResponse : ApiMessage; }