feat(auth): normalise DOMAIN\name and name@suffix logins for user bind
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
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"/>:
|
||||
/// one spelling flows into the bind template, the search filter and the returned username, so audit
|
||||
/// rows never carry <c>DOMAIN\jdoe</c> for one sign-in and <c>jdoe</c> for the next.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using ZB.MOM.WW.Auth.Ldap.Internal;
|
||||
|
||||
namespace ZB.MOM.WW.Auth.Ldap.Tests;
|
||||
|
||||
public class LdapUserNameTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("jdoe")]
|
||||
[InlineData(" jdoe ")]
|
||||
[InlineData(@"NAM\jdoe")]
|
||||
[InlineData("jdoe@corp.test")]
|
||||
public void Accepting_ReducesToBareName(string input)
|
||||
{
|
||||
Assert.True(LdapUserName.TryNormalize(input, acceptDomainQualified: true, out var name));
|
||||
Assert.Equal("jdoe", name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"NAM\jdoe")]
|
||||
[InlineData("jdoe@corp.test")]
|
||||
public void Strict_RefusesQualifiedNames(string input) =>
|
||||
Assert.False(LdapUserName.TryNormalize(input, acceptDomainQualified: false, out _));
|
||||
|
||||
[Fact]
|
||||
public void Strict_AcceptsBareName()
|
||||
{
|
||||
Assert.True(LdapUserName.TryNormalize(" jdoe ", acceptDomainQualified: false, out var name));
|
||||
Assert.Equal("jdoe", name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("@x")]
|
||||
[InlineData(@"\jdoe")]
|
||||
[InlineData("jdoe@")]
|
||||
[InlineData(@"NAM\")]
|
||||
[InlineData(@"a\b\c")]
|
||||
[InlineData("a@b@c")]
|
||||
[InlineData(@"NAM\jdoe@corp.test")]
|
||||
public void Unusable_IsRefused(string? input)
|
||||
{
|
||||
Assert.False(LdapUserName.TryNormalize(input, acceptDomainQualified: true, out var name));
|
||||
Assert.Equal("", name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user