fix(auth): canonical username is the attribute value the user matched (Auth-009)
ExtractUserName returned the first value of a possibly multi-valued UserNameAttribute (default cn), so jsmith could sign in as "John Smith" depending on server ordering. It now prefers the value equal to the typed name ignoring case (still canonicalising case) and falls back to the first value only when none matches. (scadaproj#46) Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -174,6 +174,54 @@ public class LdapAuthServiceTests
|
||||
Assert.Equal("ALICE", r.Username);
|
||||
}
|
||||
|
||||
// ---- Auth-009 (scadaproj#46): a multi-valued UserNameAttribute ----------------------------
|
||||
|
||||
[Fact]
|
||||
public async Task Username_FromAMultiValuedAttribute_IsTheValueTheUserMatched()
|
||||
{
|
||||
// OpenLDAP / 389-DS entries routinely carry both cn values; the server's ordering decides
|
||||
// which comes first. The search matched "jsmith", so that is the identity, not "John Smith".
|
||||
var fake = new FakeLdapConnection().WithUserEntry(
|
||||
"cn=jsmith,dc=x", new[] { "cn=g,dc=x" }, "John",
|
||||
new Dictionary<string, string[]> { ["cn"] = new[] { "John Smith", "jsmith" } });
|
||||
var svc = new LdapAuthService(Opts(), new FakeLdapConnectionFactory(fake));
|
||||
|
||||
var r = await svc.AuthenticateAsync("jsmith", "pw", default);
|
||||
|
||||
Assert.True(r.Succeeded);
|
||||
Assert.Equal("jsmith", r.Username);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Username_FromAMultiValuedAttribute_StillTakesTheDirectorysCase()
|
||||
{
|
||||
var fake = new FakeLdapConnection().WithUserEntry(
|
||||
"cn=jsmith,dc=x", new[] { "cn=g,dc=x" }, "John",
|
||||
new Dictionary<string, string[]> { ["cn"] = new[] { "John Smith", "JSmith" } });
|
||||
var svc = new LdapAuthService(Opts(), new FakeLdapConnectionFactory(fake));
|
||||
|
||||
var r = await svc.AuthenticateAsync("jsmith", "pw", default);
|
||||
|
||||
Assert.True(r.Succeeded);
|
||||
Assert.Equal("JSmith", r.Username);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Username_FallsBackToTheFirstValue_WhenNoValueMatchesTheTypedName()
|
||||
{
|
||||
// The directory matched on something the attribute values do not spell the same way
|
||||
// (a matching rule beyond case, say): keep 0.4.1's first-value behaviour.
|
||||
var fake = new FakeLdapConnection().WithUserEntry(
|
||||
"cn=jsmith,dc=x", new[] { "cn=g,dc=x" }, "John",
|
||||
new Dictionary<string, string[]> { ["cn"] = new[] { "J. Smith", "John Smith" } });
|
||||
var svc = new LdapAuthService(Opts(), new FakeLdapConnectionFactory(fake));
|
||||
|
||||
var r = await svc.AuthenticateAsync("jsmith", "pw", default);
|
||||
|
||||
Assert.True(r.Succeeded);
|
||||
Assert.Equal("J. Smith", r.Username);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
|
||||
Reference in New Issue
Block a user