Enrich AD users from SGU profile metadata
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
|
||||
namespace SGU.AuthBroker.Core.Profiles;
|
||||
|
||||
public static class SguProfileParser
|
||||
{
|
||||
private const string AdministrativeNameId = "ctl00_contenedor_decEncabezado_lblNombre";
|
||||
private const string EmployeeTypeId = "ctl00_contenedor_decEncabezado_lblIndicadorValue";
|
||||
private const string EmailId = "ctl00_contenedor_decEncabezado_lblCorreo";
|
||||
private const string JobTitleId = "ctl00_contenedor_decEncabezado_lblPuesto";
|
||||
private const string DepartmentId = "ctl00_contenedor_decEncabezado_lblDependencia";
|
||||
private const string MenuNameId = "ctl00_lblNombreUsuario";
|
||||
|
||||
public static InstitutionalProfile? ParseAdministrative(string html, string expectedEmployeeNumber)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(html);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(expectedEmployeeNumber);
|
||||
|
||||
string? identity = ExtractSpanText(html, AdministrativeNameId);
|
||||
if (!TrySplitAdministrativeIdentity(identity, out string? employeeNumber, out string? displayName) ||
|
||||
!string.Equals(employeeNumber, expectedEmployeeNumber, StringComparison.Ordinal))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
InstitutionalProfile profile = new(
|
||||
EmployeeNumber: employeeNumber,
|
||||
DisplayName: Limit(displayName, 256),
|
||||
Email: NormalizeEmail(ExtractSpanText(html, EmailId)),
|
||||
EmployeeType: Limit(ExtractSpanText(html, EmployeeTypeId), 256),
|
||||
JobTitle: Limit(ExtractSpanText(html, JobTitleId), 64),
|
||||
Department: Limit(ExtractSpanText(html, DepartmentId), 64));
|
||||
return profile.HasValues ? profile : null;
|
||||
}
|
||||
|
||||
public static InstitutionalProfile? ParseMenu(string html)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(html);
|
||||
|
||||
InstitutionalProfile profile = new(
|
||||
DisplayName: Limit(ExtractSpanText(html, MenuNameId), 256));
|
||||
return profile.HasValues ? profile : null;
|
||||
}
|
||||
|
||||
private static bool TrySplitAdministrativeIdentity(
|
||||
string? value,
|
||||
out string? employeeNumber,
|
||||
out string? displayName)
|
||||
{
|
||||
employeeNumber = null;
|
||||
displayName = null;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int separator = value.IndexOf(" - ", StringComparison.Ordinal);
|
||||
if (separator != 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string candidateNumber = value[..separator];
|
||||
string candidateName = value[(separator + 3)..].Trim();
|
||||
if (candidateNumber.Length != 6 ||
|
||||
!candidateNumber.All(char.IsAsciiDigit) ||
|
||||
string.IsNullOrWhiteSpace(candidateName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
employeeNumber = candidateNumber;
|
||||
displayName = candidateName;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? ExtractSpanText(string html, string id)
|
||||
{
|
||||
foreach (char quote in new[] { '"', '\'' })
|
||||
{
|
||||
string marker = $"id={quote}{id}{quote}";
|
||||
int searchFrom = 0;
|
||||
while (searchFrom < html.Length)
|
||||
{
|
||||
int idIndex = html.IndexOf(marker, searchFrom, StringComparison.OrdinalIgnoreCase);
|
||||
if (idIndex < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int spanStart = html.LastIndexOf("<span", idIndex, StringComparison.OrdinalIgnoreCase);
|
||||
int precedingTagEnd = html.LastIndexOf('>', idIndex);
|
||||
if (spanStart > precedingTagEnd)
|
||||
{
|
||||
int openingTagEnd = html.IndexOf('>', idIndex);
|
||||
int closingTagStart = openingTagEnd < 0
|
||||
? -1
|
||||
: html.IndexOf("</span", openingTagEnd + 1, StringComparison.OrdinalIgnoreCase);
|
||||
if (openingTagEnd >= 0 && closingTagStart >= 0)
|
||||
{
|
||||
string innerHtml = html[(openingTagEnd + 1)..closingTagStart];
|
||||
return NormalizeText(innerHtml);
|
||||
}
|
||||
}
|
||||
|
||||
searchFrom = idIndex + marker.Length;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? NormalizeText(string htmlFragment)
|
||||
{
|
||||
StringBuilder withoutTags = new(htmlFragment.Length);
|
||||
bool insideTag = false;
|
||||
foreach (char character in htmlFragment)
|
||||
{
|
||||
if (character == '<')
|
||||
{
|
||||
insideTag = true;
|
||||
}
|
||||
else if (character == '>')
|
||||
{
|
||||
insideTag = false;
|
||||
}
|
||||
else if (!insideTag)
|
||||
{
|
||||
withoutTags.Append(character);
|
||||
}
|
||||
}
|
||||
|
||||
string decoded = WebUtility.HtmlDecode(withoutTags.ToString());
|
||||
StringBuilder normalized = new(decoded.Length);
|
||||
bool previousWasWhitespace = true;
|
||||
foreach (char character in decoded)
|
||||
{
|
||||
bool whitespace = char.IsWhiteSpace(character) || character == '\u00A0';
|
||||
if (whitespace)
|
||||
{
|
||||
if (!previousWasWhitespace)
|
||||
{
|
||||
normalized.Append(' ');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
normalized.Append(character);
|
||||
}
|
||||
|
||||
previousWasWhitespace = whitespace;
|
||||
}
|
||||
|
||||
string result = normalized.ToString().Trim();
|
||||
return result.Length == 0 ? null : result;
|
||||
}
|
||||
|
||||
private static string? NormalizeEmail(string? value)
|
||||
{
|
||||
string? candidate = Limit(value, 256);
|
||||
if (candidate is null ||
|
||||
!MailAddress.TryCreate(candidate, out MailAddress? address) ||
|
||||
!string.Equals(address.Address, candidate, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return address.Address;
|
||||
}
|
||||
|
||||
private static string? Limit(string? value, int maximumLength)
|
||||
{
|
||||
string? candidate = value?.Trim();
|
||||
return string.IsNullOrEmpty(candidate) || candidate.Length > maximumLength
|
||||
? null
|
||||
: candidate;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user