Add SGU credential provider and authentication broker
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
using System.Net;
|
||||
using SGU.AuthBroker.Core.Authentication;
|
||||
using SGU.AuthBroker.Options;
|
||||
|
||||
namespace SGU.AuthBroker.Services;
|
||||
|
||||
public sealed class NtlmCredentialValidator(BrokerOptions options) : INtlmCredentialValidator
|
||||
{
|
||||
private readonly NtlmOptions options = options.Ntlm;
|
||||
|
||||
public async Task<NtlmValidationResult> ValidateAsync(
|
||||
string userName,
|
||||
string password,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Uri current = new(this.options.Endpoint, UriKind.Absolute);
|
||||
HashSet<string> allowedHosts = new(
|
||||
this.options.AllowedRedirectHosts,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
NetworkCredential credential = new(userName, password, this.options.Domain);
|
||||
CredentialCache credentialCache = new();
|
||||
HashSet<string> credentialedAuthorities = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
using HttpClientHandler handler = new()
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = true,
|
||||
Credentials = credentialCache,
|
||||
MaxConnectionsPerServer = 4,
|
||||
MaxResponseHeadersLength = 64,
|
||||
PreAuthenticate = false,
|
||||
UseCookies = false,
|
||||
UseDefaultCredentials = false,
|
||||
UseProxy = false
|
||||
};
|
||||
|
||||
using HttpClient client = new(handler)
|
||||
{
|
||||
Timeout = Timeout.InfiniteTimeSpan,
|
||||
DefaultRequestVersion = HttpVersion.Version11,
|
||||
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact
|
||||
};
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("SGU-AuthBroker/1.0");
|
||||
|
||||
try
|
||||
{
|
||||
for (int hop = 0; hop <= this.options.MaxRedirects; hop++)
|
||||
{
|
||||
if (!IsAllowedHttpsUri(current, allowedHosts))
|
||||
{
|
||||
return NtlmValidationResult.Unavailable("NTLM_REDIRECT_REJECTED");
|
||||
}
|
||||
|
||||
string authority = current.GetLeftPart(UriPartial.Authority);
|
||||
if (credentialedAuthorities.Add(authority))
|
||||
{
|
||||
credentialCache.Add(new Uri(authority + "/"), "NTLM", credential);
|
||||
}
|
||||
|
||||
using HttpRequestMessage request = new(HttpMethod.Get, current);
|
||||
using CancellationTokenSource timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(this.options.TimeoutSeconds));
|
||||
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
response = await client
|
||||
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeout.Token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return NtlmValidationResult.Unavailable("NTLM_TIMEOUT");
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return NtlmValidationResult.Unavailable();
|
||||
}
|
||||
|
||||
using (response)
|
||||
{
|
||||
if (response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||
{
|
||||
return NtlmValidationResult.Invalid();
|
||||
}
|
||||
|
||||
int statusCode = (int)response.StatusCode;
|
||||
if (statusCode >= 500)
|
||||
{
|
||||
return NtlmValidationResult.Unavailable("NTLM_UPSTREAM_ERROR");
|
||||
}
|
||||
|
||||
if (statusCode is >= 300 and < 400)
|
||||
{
|
||||
Uri? location = response.Headers.Location;
|
||||
if (location is null)
|
||||
{
|
||||
return NtlmValidationResult.Unavailable("NTLM_INVALID_REDIRECT");
|
||||
}
|
||||
|
||||
current = location.IsAbsoluteUri ? location : new Uri(current, location);
|
||||
continue;
|
||||
}
|
||||
|
||||
return statusCode is >= 200 and < 300
|
||||
? NtlmValidationResult.Valid()
|
||||
: NtlmValidationResult.Invalid();
|
||||
}
|
||||
}
|
||||
|
||||
return NtlmValidationResult.Unavailable("NTLM_REDIRECT_LIMIT");
|
||||
}
|
||||
finally
|
||||
{
|
||||
credential.Password = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllowedHttpsUri(Uri uri, HashSet<string> allowedHosts) =>
|
||||
uri.Scheme == Uri.UriSchemeHttps &&
|
||||
string.IsNullOrEmpty(uri.UserInfo) &&
|
||||
allowedHosts.Contains(uri.IdnHost);
|
||||
}
|
||||
Reference in New Issue
Block a user