using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using ReC.Application.Common.Constants; using ReC.Application.Common.Dto; using ReC.Application.Common.Exceptions; using ReC.Application.Common.Options; using ReC.Application.Results.Commands; using ReC.Domain.Constants; using System.Net; using System.Net.Http.Headers; using System.Text; using System.Text.Json; namespace ReC.Application.RecActions.Commands; public record InvokeRecActionViewCommand : IRequest { public RecActionViewDto Action { get; set; } = null!; } public class InvokeRecActionViewCommandHandler( IOptions options, ISender sender, IHttpClientFactory clientFactory, IConfiguration? config = null ) : IRequestHandler { private readonly RecActionOptions _options = options.Value; public async Task Handle(InvokeRecActionViewCommand request, CancellationToken cancel) { var action = request.Action; HttpClient? ntlmClient = null; try { if (action.RestType is not RestType restType) throw new DataIntegrityException( $"Rec action could not be invoked because the RestType value is null. " + $"ProfileId: {action.ProfileId}, " + $"Id: {action.Id}" ); using var httpReq = CreateHttpRequestMessage(restType, action.EndpointUri); if (action.Body is not null) httpReq.Content = new StringContent(action.Body); if (action.Headers is not null) foreach (var header in action.Headers) httpReq.Headers.Add(header.Key, header.Value); 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.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)) { if (_options.UseHttp1ForNtlm) { httpReq.Version = HttpVersion.Version11; httpReq.VersionPolicy = HttpVersionPolicy.RequestVersionExact; } var endpointAuthPassword = action.EndpointAuthPassword #if DEBUG ?.Replace("%NTLM_PW%", config?.GetValue("%NTLM_PW%")) #endif ; var credentials = new NetworkCredential( action.EndpointAuthUsername, endpointAuthPassword, action.EndpointAuthDomain); var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } }; var ntlmHandler = new HttpClientHandler { Credentials = credentialCache, UseDefaultCredentials = false }; ntlmClient = new HttpClient(ntlmHandler, disposeHandler: true); } 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}" ); } 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.Send(new InsertResultCommand() { StatusId = statusCode, ActionId = action.Id, Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }), Body = resBody }, cancel); } catch(Exception ex) { await sender.Send(new InsertResultCommand() { ActionId = action.Id, Error = ex.ToString() }, cancel); if (action.ErrorAction == ErrorAction.Stop) throw new RecActionException(action.Id, action.ProfileId, ex); } finally { ntlmClient?.Dispose(); } } private static HttpRequestMessage CreateHttpRequestMessage(RestType restType, string? endpointUri) { var method = restType switch { RestType.Get => HttpMethod.Get, RestType.Post => HttpMethod.Post, RestType.Put => HttpMethod.Put, RestType.Delete => HttpMethod.Delete, RestType.Patch => HttpMethod.Patch, RestType.Head => HttpMethod.Head, RestType.Options => HttpMethod.Options, RestType.Trace => HttpMethod.Trace, RestType.Connect => HttpMethod.Connect, RestType.None => throw new ArgumentOutOfRangeException(nameof(restType), $"The RestType value '{restType}' is not valid."), _ => new HttpMethod(restType.ToString().ToUpperInvariant()) }; return new HttpRequestMessage(method, endpointUri); } }