From 87194df69717278a4020e8bc135b19ce1ac9627e Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 16 Mar 2026 13:43:27 +0100 Subject: [PATCH] Refactor and expand REST action authentication support Refactored authentication logic for REST actions to use a structured switch block, adding explicit support for ApiKey, BearerToken, JwtBearer, OAuth2, BasicAuth, and NTLM authentication types. Introduced dedicated HttpClient/Handler for NTLM with proper disposal. Improved extensibility, clarity, and resource management. Added IOptions and Options usage for configuration. --- .../Commands/InvokeRecActionViewCommand.cs | 178 ++++++++++-------- 1 file changed, 97 insertions(+), 81 deletions(-) diff --git a/src/ReC.Application/RecActions/Commands/InvokeRecActionViewCommand.cs b/src/ReC.Application/RecActions/Commands/InvokeRecActionViewCommand.cs index 1e5b36f..a7d5224 100644 --- a/src/ReC.Application/RecActions/Commands/InvokeRecActionViewCommand.cs +++ b/src/ReC.Application/RecActions/Commands/InvokeRecActionViewCommand.cs @@ -1,9 +1,11 @@ using MediatR; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; using ReC.Application.Common; using ReC.Application.Common.Constants; using ReC.Application.Common.Dto; using ReC.Application.Common.Exceptions; +using ReC.Application.Common.Options; using ReC.Application.Common.Procedures.InsertProcedure; using ReC.Application.Results.Commands; using ReC.Domain.Constants; @@ -32,8 +34,6 @@ public class InvokeRecActionViewCommandHandler( { var action = request.Action; - using var http = clientFactory.CreateClient(Http.ClientName); - if (action.RestType is not RestType restType) throw new DataIntegrityException( $"Rec action could not be invoked because the RestType value is null. " + @@ -50,54 +50,59 @@ public class InvokeRecActionViewCommandHandler( foreach (var header in action.Headers) httpReq.Headers.Add(header.Key, header.Value); - switch (action.EndpointAuthType) + HttpClient? ntlmClient = null; + HttpClientHandler? ntlmHandler = null; + + try { - case EndpointAuthType.NoAuth: - break; + switch (action.EndpointAuthType) + { + case EndpointAuthType.NoAuth: + break; + + case EndpointAuthType.ApiKey: + if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue) + { + switch (action.EndpointAuthApiKeyAddTo) + { + case ApiKeyLocation.Header: + httpReq.Headers.Add(apiKey, apiValue); + break; + case ApiKeyLocation.Query: + var uriBuilder = new UriBuilder(httpReq.RequestUri!); + var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query); + query[apiKey] = apiValue; + uriBuilder.Query = query.ToString(); + httpReq.RequestUri = uriBuilder.Uri; + break; + default: + throw new DataIntegrityException( + $"The API key location '{action.EndpointAuthApiKeyAddTo}' is not supported. " + + $"ProfileId: {action.ProfileId}, " + + $"Id: {action.Id}" + ); + } + } + break; + + case EndpointAuthType.BearerToken: + case EndpointAuthType.JwtBearer: + case EndpointAuthType.OAuth2: + if (action.EndpointAuthToken is string authToken) + httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + break; - case EndpointAuthType.ApiKey: - if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue) - { - switch (action.EndpointAuthApiKeyAddTo) + case EndpointAuthType.BasicAuth: + if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword) { - case ApiKeyLocation.Header: - httpReq.Headers.Add(apiKey, apiValue); - break; - case ApiKeyLocation.Query: - var uriBuilder = new UriBuilder(httpReq.RequestUri!); - var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query); - query[apiKey] = apiValue; - uriBuilder.Query = query.ToString(); - httpReq.RequestUri = uriBuilder.Uri; - break; - default: - throw new DataIntegrityException( - $"The API key location '{action.EndpointAuthApiKeyAddTo}' is not supported. " + - $"ProfileId: {action.ProfileId}, " + - $"Id: {action.Id}" - ); + var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}")); + httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth); } - } - break; - - case EndpointAuthType.BearerToken: - case EndpointAuthType.JwtBearer: - case EndpointAuthType.OAuth2: - if (action.EndpointAuthToken is string authToken) - httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); - break; - - case EndpointAuthType.BasicAuth: - if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword) - { - var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}")); - httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth); - } - break; - - case EndpointAuthType.NtlmAuth: - if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername)) - { + break; + + case EndpointAuthType.NtlmAuth: + if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername)) + { if (_options.UseHttp1ForNtlm) { httpReq.Version = HttpVersion.Version11; @@ -108,44 +113,55 @@ public class InvokeRecActionViewCommandHandler( ?.Replace("%NTLM_PW%", config?.GetValue("%NTLM_PW%")) #endif ; - var credentials = new NetworkCredential( - action.EndpointAuthUsername, + var credentials = new NetworkCredential( + action.EndpointAuthUsername, endpointAuthPassword, - action.EndpointAuthDomain); - var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } }; - httpReq.Options.Set(new HttpRequestOptionsKey("Credentials"), credentialCache); - } - break; - - case EndpointAuthType.DigestAuth: - case EndpointAuthType.OAuth1: - case EndpointAuthType.AwsSignature: - // These authentication methods require more complex implementations, - // often involving multi-step handshakes or specialized libraries. - // They are left as placeholders for future implementation. - default: - throw new NotImplementedException( - $"The authentication type '{action.EndpointAuthType}' is not supported yet. " + - $"ProfileId: {action.ProfileId}, " + - $"Id: {action.Id}" - ); - } - - using var response = await http.SendAsync(httpReq, cancel); - var resBody = await response.Content.ReadAsStringAsync(cancel); - var resHeaders = response.Headers.ToDictionary(); - - var statusCode = (short)response.StatusCode; - - await sender.ExecuteInsertProcedure(new InsertResultProcedure() - { - StatusId = statusCode, - ActionId = action.Id, - Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }), - Body = resBody + action.EndpointAuthDomain); + var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } }; + ntlmHandler = new HttpClientHandler + { + Credentials = credentialCache, + UseDefaultCredentials = false + }; + ntlmClient = new HttpClient(ntlmHandler); + } + break; + + case EndpointAuthType.DigestAuth: + case EndpointAuthType.OAuth1: + case EndpointAuthType.AwsSignature: + // These authentication methods require more complex implementations, + // often involving multi-step handshakes or specialized libraries. + // They are left as placeholders for future implementation. + default: + throw new NotImplementedException( + $"The authentication type '{action.EndpointAuthType}' is not supported yet. " + + $"ProfileId: {action.ProfileId}, " + + $"Id: {action.Id}" + ); + } + + using var http = ntlmClient ?? clientFactory.CreateClient(Http.ClientName); + using var response = await http.SendAsync(httpReq, cancel); + var resBody = await response.Content.ReadAsStringAsync(cancel); + var resHeaders = response.Headers.ToDictionary(); + + var statusCode = (short)response.StatusCode; + + await sender.ExecuteInsertProcedure(new InsertResultProcedure() + { + StatusId = statusCode, + ActionId = action.Id, + Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }), + Body = resBody }, _options.AddedWho, cancel); - return response.IsSuccessStatusCode; + return response.IsSuccessStatusCode; + } + finally + { + ntlmHandler?.Dispose(); + } } private static HttpRequestMessage CreateHttpRequestMessage(RestType restType, string? endpointUri)