Resolve SGU staff addresses and provision local student user
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using SGU.AuthBroker.Core.Authentication;
|
||||
using SGU.AuthBroker.Core.Identity;
|
||||
using SGU.AuthBroker.Core.Profiles;
|
||||
@@ -431,9 +433,6 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
pages.Add((
|
||||
options.AdministrativePersonalProfilePath,
|
||||
SguProfileParser.ParseAdministrativePersonal));
|
||||
pages.Add((
|
||||
options.AdministrativeLocationProfilePath,
|
||||
SguProfileParser.ParseAdministrativeLocation));
|
||||
|
||||
foreach ((string path, Func<string, InstitutionalProfile?> parser) in pages)
|
||||
{
|
||||
@@ -474,7 +473,7 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
"SGU optional staff profile enrichment for role {Role} reached its total timeout after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
identity.Role,
|
||||
elapsed.ElapsedMilliseconds);
|
||||
break;
|
||||
return profile;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -487,6 +486,142 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
}
|
||||
}
|
||||
|
||||
return await TryEnrichStaffLocationAsync(
|
||||
client,
|
||||
profile,
|
||||
identity,
|
||||
allowedHosts,
|
||||
timeoutToken,
|
||||
requestCancellationToken,
|
||||
elapsed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<InstitutionalProfile> TryEnrichStaffLocationAsync(
|
||||
HttpClient client,
|
||||
InstitutionalProfile profile,
|
||||
UserIdentity identity,
|
||||
HashSet<string> allowedHosts,
|
||||
CancellationToken timeoutToken,
|
||||
CancellationToken requestCancellationToken,
|
||||
Stopwatch elapsed)
|
||||
{
|
||||
string path = options.AdministrativeLocationProfilePath;
|
||||
Uri locationPageUri = GetProfileUri(path);
|
||||
try
|
||||
{
|
||||
string? html = await TryFetchAdditionalProfilePageAsync(
|
||||
client,
|
||||
locationPageUri,
|
||||
allowedHosts,
|
||||
timeoutToken).ConfigureAwait(false);
|
||||
if (html is null)
|
||||
{
|
||||
logger.LogWarning(
|
||||
BrokerEventIds.ProfilePageUnavailable,
|
||||
"Optional SGU profile page {Path} did not return usable HTML for role {Role}; preserving fields already collected.",
|
||||
path,
|
||||
identity.Role);
|
||||
return profile;
|
||||
}
|
||||
|
||||
InstitutionalProfile? staticLocation = SguProfileParser.ParseAdministrativeLocation(html);
|
||||
if (staticLocation is null)
|
||||
{
|
||||
logger.LogWarning(
|
||||
BrokerEventIds.ProfileHtmlUnexpected,
|
||||
"Optional SGU profile page {Path} returned HTML without its supported field IDs for role {Role}; preserving fields already collected.",
|
||||
path,
|
||||
identity.Role);
|
||||
return profile;
|
||||
}
|
||||
|
||||
profile = profile.Overlay(staticLocation);
|
||||
if (string.IsNullOrWhiteSpace(staticLocation.PostalCode))
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
string? directionJson = await TryPostProfilePageMethodAsync(
|
||||
client,
|
||||
GetAdministrativeLocationMethodUri("GetDireccion"),
|
||||
locationPageUri,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["CodigoPostal"] = staticLocation.PostalCode
|
||||
},
|
||||
allowedHosts,
|
||||
timeoutToken).ConfigureAwait(false);
|
||||
if (directionJson is null)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
SguAdministrativeLocationSelection? selection =
|
||||
SguProfileParser.ParseAdministrativeLocationSelection(
|
||||
directionJson,
|
||||
staticLocation.PostalCode);
|
||||
if (selection is null)
|
||||
{
|
||||
logger.LogWarning(
|
||||
BrokerEventIds.ProfileHtmlUnexpected,
|
||||
"SGU location method GetDireccion returned an unexpected payload for role {Role}; preserving the static address fields.",
|
||||
identity.Role);
|
||||
return profile;
|
||||
}
|
||||
|
||||
string? localitiesJson = null;
|
||||
if (!string.IsNullOrWhiteSpace(selection.StateId))
|
||||
{
|
||||
localitiesJson = await TryPostProfilePageMethodAsync(
|
||||
client,
|
||||
GetAdministrativeLocationMethodUri("GetLocalidadListado"),
|
||||
locationPageUri,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["pIdEstado"] = selection.StateId
|
||||
},
|
||||
allowedHosts,
|
||||
timeoutToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string? neighborhoodsJson = await TryPostProfilePageMethodAsync(
|
||||
client,
|
||||
GetAdministrativeLocationMethodUri("GetColoniasListado"),
|
||||
locationPageUri,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["pIdEstado"] = string.Empty,
|
||||
["pLocalidad"] = string.Empty,
|
||||
["CodigoPostal"] = selection.PostalCode ?? staticLocation.PostalCode
|
||||
},
|
||||
allowedHosts,
|
||||
timeoutToken).ConfigureAwait(false);
|
||||
|
||||
InstitutionalProfile? resolvedLocation = SguProfileParser.ParseAdministrativeLocation(
|
||||
html,
|
||||
selection,
|
||||
localitiesJson,
|
||||
neighborhoodsJson);
|
||||
return profile.Overlay(resolvedLocation);
|
||||
}
|
||||
catch (OperationCanceledException) when (!requestCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogWarning(
|
||||
BrokerEventIds.ProfileEnrichmentTimeout,
|
||||
"SGU optional staff location enrichment for role {Role} reached its total timeout after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
identity.Role,
|
||||
elapsed.ElapsedMilliseconds);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogWarning(
|
||||
BrokerEventIds.ProfileEnrichmentFailure,
|
||||
exception,
|
||||
"SGU optional staff location enrichment failed for role {Role} after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
identity.Role,
|
||||
elapsed.ElapsedMilliseconds);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@@ -545,6 +680,46 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> TryPostProfilePageMethodAsync(
|
||||
HttpClient client,
|
||||
Uri requestedUri,
|
||||
Uri referrerUri,
|
||||
IReadOnlyDictionary<string, string> payload,
|
||||
HashSet<string> allowedHosts,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsAllowedHttpsUri(requestedUri, allowedHosts) ||
|
||||
!IsAllowedHttpsUri(referrerUri, allowedHosts))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using HttpRequestMessage request = new(HttpMethod.Post, requestedUri);
|
||||
request.Headers.Referrer = referrerUri;
|
||||
request.Content = new StringContent(
|
||||
JsonSerializer.Serialize(payload),
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
using HttpResponseMessage response = await client
|
||||
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
int statusCode = (int)response.StatusCode;
|
||||
if (statusCode is >= 200 and < 300)
|
||||
{
|
||||
return await ReadLimitedStringAsync(
|
||||
response.Content,
|
||||
options.MaxProfileBytes,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
logger.LogWarning(
|
||||
BrokerEventIds.ProfilePageUnavailable,
|
||||
"Optional SGU profile method {Path} returned HTTP {StatusCode}.",
|
||||
requestedUri.AbsolutePath,
|
||||
statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void AddCredential(
|
||||
Uri uri,
|
||||
CredentialCache credentialCache,
|
||||
@@ -658,6 +833,12 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
private Uri GetProfileUri(string path) =>
|
||||
new(new Uri(options.Endpoint, UriKind.Absolute), path);
|
||||
|
||||
private Uri GetAdministrativeLocationMethodUri(string methodName)
|
||||
{
|
||||
Uri pageUri = GetProfileUri(options.AdministrativeLocationProfilePath);
|
||||
return new Uri($"{pageUri.GetLeftPart(UriPartial.Path).TrimEnd('/')}/{methodName}");
|
||||
}
|
||||
|
||||
private async Task<InstitutionalProfile?> TryReadProfileAsync(
|
||||
HttpResponseMessage response,
|
||||
UserIdentity identity,
|
||||
|
||||
Reference in New Issue
Block a user