e3d146aa21
- Request UserNameAttribute in the search (de-duplicated, case-insensitive) and return its first value as LdapAuthResult.Username, falling back to the typed name (I2). - The empty-password guard now applies in ServiceAccount mode too: AD treats an empty-password simple bind as an unauthenticated bind and reports success (I3). - Restore the dropped explanatory comments in CompleteWithServiceAccount / SearchUser.
61 lines
2.3 KiB
C#
61 lines
2.3 KiB
C#
namespace ZB.MOM.WW.Auth.Ldap.Internal;
|
|
|
|
/// <summary>
|
|
/// Reduces a typed login to the bare account name used by <see cref="Abstractions.Ldap.LdapBindMode.User"/>:
|
|
/// the qualifier is stripped before the name reaches the bind template and the search filter, so audit
|
|
/// rows never carry <c>DOMAIN\jdoe</c> for one sign-in and <c>jdoe</c> for the next. Case is NOT
|
|
/// normalised here: the returned username is canonicalised from the directory entry's
|
|
/// <c>UserNameAttribute</c> value, so <c>JDoe</c> and <c>jdoe</c> also yield one identity; this name is
|
|
/// only the fallback when the entry has no such value.
|
|
/// </summary>
|
|
internal static class LdapUserName
|
|
{
|
|
/// <summary>
|
|
/// Trims <paramref name="input"/> and, when <paramref name="acceptDomainQualified"/>, strips one
|
|
/// leading <c>DOMAIN\</c> or one trailing <c>@suffix</c>. Returns <see langword="false"/> (with
|
|
/// <paramref name="name"/> empty) for blank input, for both forms at once, for a repeated
|
|
/// separator, for an empty side, or for any qualified form when <paramref name="acceptDomainQualified"/>
|
|
/// is <see langword="false"/>.
|
|
/// </summary>
|
|
public static bool TryNormalize(string? input, bool acceptDomainQualified, out string name)
|
|
{
|
|
name = "";
|
|
if (string.IsNullOrWhiteSpace(input))
|
|
return false;
|
|
|
|
var s = input.Trim();
|
|
var slash = s.IndexOf('\\');
|
|
var at = s.IndexOf('@');
|
|
|
|
if (slash >= 0 || at >= 0)
|
|
{
|
|
if (!acceptDomainQualified || (slash >= 0 && at >= 0))
|
|
return false;
|
|
|
|
if (slash >= 0)
|
|
{
|
|
if (slash == 0 || s.IndexOf('\\', slash + 1) >= 0)
|
|
return false;
|
|
s = s[(slash + 1)..];
|
|
}
|
|
else
|
|
{
|
|
// Unlike the slash branch (whose extracted suffix is checked for emptiness by the
|
|
// final length check below), the extracted piece here is the *prefix* — so an empty
|
|
// domain part (a trailing "@", e.g. "jdoe@") must be rejected explicitly.
|
|
if (at == s.Length - 1 || s.IndexOf('@', at + 1) >= 0)
|
|
return false;
|
|
s = s[..at];
|
|
}
|
|
|
|
s = s.Trim();
|
|
}
|
|
|
|
if (s.Length == 0)
|
|
return false;
|
|
|
|
name = s;
|
|
return true;
|
|
}
|
|
}
|