Compare commits
10 Commits
5d316e43b9
...
754ef88644
| Author | SHA1 | Date | |
|---|---|---|---|
| 754ef88644 | |||
| 95ece6fdcf | |||
| 87194df697 | |||
| b38d53248c | |||
| 56b604bd35 | |||
| f67579dba9 | |||
| 636397efb8 | |||
| ef4d0767e9 | |||
| f15725ade2 | |||
| 382eef0089 |
@@ -1,7 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using ReC.API.Extensions;
|
|
||||||
using ReC.API.Models;
|
|
||||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace ReC.API.Extensions;
|
|
||||||
|
|
||||||
public static class ConfigurationExtensions
|
|
||||||
{
|
|
||||||
public static int GetFakeProfileId(this IConfiguration config) => config.GetValue("FakeProfileId", 2);
|
|
||||||
}
|
|
||||||
@@ -10,13 +10,14 @@
|
|||||||
<Product>ReC.API</Product>
|
<Product>ReC.API</Product>
|
||||||
<PackageIcon>Assets\icon.ico</PackageIcon>
|
<PackageIcon>Assets\icon.ico</PackageIcon>
|
||||||
<PackageTags>digital data rest-caller rec api</PackageTags>
|
<PackageTags>digital data rest-caller rec api</PackageTags>
|
||||||
<Version>2.0.1-beta</Version>
|
<Version>2.0.2-beta</Version>
|
||||||
<AssemblyVersion>2.0.1.0</AssemblyVersion>
|
<AssemblyVersion>2.0.2.0</AssemblyVersion>
|
||||||
<FileVersion>2.0.1.0</FileVersion>
|
<FileVersion>2.0.2.0</FileVersion>
|
||||||
<InformationalVersion>2.0.1-beta</InformationalVersion>
|
<InformationalVersion>2.0.1-beta</InformationalVersion>
|
||||||
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||||
|
<UserSecretsId>cf893b96-c71a-4a96-a6a7-40004249e1a3</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,13 +6,12 @@
|
|||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"LuckyPennySoftwareLicenseKey": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg",
|
"LuckyPennySoftwareLicenseKey": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg",
|
||||||
"RecAction": {
|
"RecAction": {
|
||||||
"MaxConcurrentInvocations": 5
|
"AddedWho": "ReC.API",
|
||||||
|
"UseHttp1ForNtlm": false
|
||||||
},
|
},
|
||||||
// Bad request SqlException numbers numbers can be updated at runtime; no restart required.
|
// Bad request SqlException numbers numbers can be updated at runtime; no restart required.
|
||||||
"SqlException": {
|
"SqlException": {
|
||||||
// https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlexception.number
|
// https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlexception.number
|
||||||
"BadRequestSqlExceptionNumbers": [ 515, 547, 2601, 2627, 50000 ]
|
"BadRequestSqlExceptionNumbers": [ 515, 547, 2601, 2627, 50000 ]
|
||||||
},
|
}
|
||||||
"AddedWho": "ReC.API",
|
|
||||||
"FakeProfileId": 2
|
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,7 @@
|
|||||||
|
|
||||||
public class RecActionOptions
|
public class RecActionOptions
|
||||||
{
|
{
|
||||||
public int MaxConcurrentInvocations { get; set; } = 5;
|
public string AddedWho { get; set; } = null!;
|
||||||
}
|
|
||||||
|
public bool UseHttp1ForNtlm { get; set; } = false;
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using ReC.Application.Common;
|
using ReC.Application.Common;
|
||||||
using ReC.Application.Common.Constants;
|
using ReC.Application.Common.Constants;
|
||||||
using ReC.Application.Common.Dto;
|
using ReC.Application.Common.Dto;
|
||||||
using ReC.Application.Common.Exceptions;
|
using ReC.Application.Common.Exceptions;
|
||||||
|
using ReC.Application.Common.Options;
|
||||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||||
using ReC.Application.Results.Commands;
|
using ReC.Application.Results.Commands;
|
||||||
using ReC.Domain.Constants;
|
using ReC.Domain.Constants;
|
||||||
@@ -20,17 +22,18 @@ public record InvokeRecActionViewCommand : IRequest<bool>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class InvokeRecActionViewCommandHandler(
|
public class InvokeRecActionViewCommandHandler(
|
||||||
|
IOptions<RecActionOptions> options,
|
||||||
ISender sender,
|
ISender sender,
|
||||||
IHttpClientFactory clientFactory,
|
IHttpClientFactory clientFactory,
|
||||||
IConfiguration? config = null
|
IConfiguration? config = null
|
||||||
) : IRequestHandler<InvokeRecActionViewCommand, bool>
|
) : IRequestHandler<InvokeRecActionViewCommand, bool>
|
||||||
{
|
{
|
||||||
|
private readonly RecActionOptions _options = options.Value;
|
||||||
|
|
||||||
public async Task<bool> Handle(InvokeRecActionViewCommand request, CancellationToken cancel)
|
public async Task<bool> Handle(InvokeRecActionViewCommand request, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
var action = request.Action;
|
var action = request.Action;
|
||||||
|
|
||||||
using var http = clientFactory.CreateClient(Http.ClientName);
|
|
||||||
|
|
||||||
if (action.RestType is not RestType restType)
|
if (action.RestType is not RestType restType)
|
||||||
throw new DataIntegrityException(
|
throw new DataIntegrityException(
|
||||||
$"Rec action could not be invoked because the RestType value is null. " +
|
$"Rec action could not be invoked because the RestType value is null. " +
|
||||||
@@ -47,92 +50,118 @@ public class InvokeRecActionViewCommandHandler(
|
|||||||
foreach (var header in action.Headers)
|
foreach (var header in action.Headers)
|
||||||
httpReq.Headers.Add(header.Key, header.Value);
|
httpReq.Headers.Add(header.Key, header.Value);
|
||||||
|
|
||||||
switch (action.EndpointAuthType)
|
HttpClient? ntlmClient = null;
|
||||||
{
|
HttpClientHandler? ntlmHandler = null;
|
||||||
case EndpointAuthType.NoAuth:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EndpointAuthType.ApiKey:
|
try
|
||||||
if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue)
|
{
|
||||||
{
|
switch (action.EndpointAuthType)
|
||||||
switch (action.EndpointAuthApiKeyAddTo)
|
{
|
||||||
|
case EndpointAuthType.NoAuth:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EndpointAuthType.ApiKey:
|
||||||
|
if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue)
|
||||||
{
|
{
|
||||||
case ApiKeyLocation.Header:
|
switch (action.EndpointAuthApiKeyAddTo)
|
||||||
httpReq.Headers.Add(apiKey, apiValue);
|
{
|
||||||
break;
|
case ApiKeyLocation.Header:
|
||||||
case ApiKeyLocation.Query:
|
httpReq.Headers.Add(apiKey, apiValue);
|
||||||
var uriBuilder = new UriBuilder(httpReq.RequestUri!);
|
break;
|
||||||
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
|
case ApiKeyLocation.Query:
|
||||||
query[apiKey] = apiValue;
|
var uriBuilder = new UriBuilder(httpReq.RequestUri!);
|
||||||
uriBuilder.Query = query.ToString();
|
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||||
httpReq.RequestUri = uriBuilder.Uri;
|
query[apiKey] = apiValue;
|
||||||
break;
|
uriBuilder.Query = query.ToString();
|
||||||
default:
|
httpReq.RequestUri = uriBuilder.Uri;
|
||||||
throw new DataIntegrityException(
|
break;
|
||||||
$"The API key location '{action.EndpointAuthApiKeyAddTo}' is not supported. " +
|
default:
|
||||||
$"ProfileId: {action.ProfileId}, " +
|
throw new DataIntegrityException(
|
||||||
$"Id: {action.Id}"
|
$"The API key location '{action.EndpointAuthApiKeyAddTo}' is not supported. " +
|
||||||
);
|
$"ProfileId: {action.ProfileId}, " +
|
||||||
|
$"Id: {action.Id}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case EndpointAuthType.BearerToken:
|
case EndpointAuthType.BearerToken:
|
||||||
case EndpointAuthType.JwtBearer:
|
case EndpointAuthType.JwtBearer:
|
||||||
case EndpointAuthType.OAuth2:
|
case EndpointAuthType.OAuth2:
|
||||||
if (action.EndpointAuthToken is string authToken)
|
if (action.EndpointAuthToken is string authToken)
|
||||||
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EndpointAuthType.BasicAuth:
|
case EndpointAuthType.BasicAuth:
|
||||||
if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword)
|
if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword)
|
||||||
{
|
{
|
||||||
var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}"));
|
var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}"));
|
||||||
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
|
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EndpointAuthType.NtlmAuth:
|
case EndpointAuthType.NtlmAuth:
|
||||||
if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername))
|
if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername))
|
||||||
{
|
{
|
||||||
var credentials = new NetworkCredential(
|
if (_options.UseHttp1ForNtlm)
|
||||||
action.EndpointAuthUsername,
|
{
|
||||||
action.EndpointAuthPassword,
|
httpReq.Version = HttpVersion.Version11;
|
||||||
action.EndpointAuthDomain);
|
httpReq.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
|
||||||
var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } };
|
}
|
||||||
httpReq.Options.Set(new HttpRequestOptionsKey<CredentialCache>("Credentials"), credentialCache);
|
var endpointAuthPassword = action.EndpointAuthPassword
|
||||||
}
|
#if DEBUG
|
||||||
break;
|
?.Replace("%NTLM_PW%", config?.GetValue<string>("%NTLM_PW%"))
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
var credentials = new NetworkCredential(
|
||||||
|
action.EndpointAuthUsername,
|
||||||
|
endpointAuthPassword,
|
||||||
|
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.DigestAuth:
|
||||||
case EndpointAuthType.OAuth1:
|
case EndpointAuthType.OAuth1:
|
||||||
case EndpointAuthType.AwsSignature:
|
case EndpointAuthType.AwsSignature:
|
||||||
// These authentication methods require more complex implementations,
|
// These authentication methods require more complex implementations,
|
||||||
// often involving multi-step handshakes or specialized libraries.
|
// often involving multi-step handshakes or specialized libraries.
|
||||||
// They are left as placeholders for future implementation.
|
// They are left as placeholders for future implementation.
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException(
|
throw new NotImplementedException(
|
||||||
$"The authentication type '{action.EndpointAuthType}' is not supported yet. " +
|
$"The authentication type '{action.EndpointAuthType}' is not supported yet. " +
|
||||||
$"ProfileId: {action.ProfileId}, " +
|
$"ProfileId: {action.ProfileId}, " +
|
||||||
$"Id: {action.Id}"
|
$"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;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
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,
|
ntlmHandler?.Dispose();
|
||||||
ActionId = action.Id,
|
}
|
||||||
Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }),
|
|
||||||
Body = resBody
|
|
||||||
}, config?["AddedWho"], cancel);
|
|
||||||
|
|
||||||
return response.IsSuccessStatusCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpRequestMessage CreateHttpRequestMessage(RestType restType, string? endpointUri)
|
private static HttpRequestMessage CreateHttpRequestMessage(RestType restType, string? endpointUri)
|
||||||
|
|||||||
Reference in New Issue
Block a user