From 5f5bfe1ea5c33eea7576caba6abd74e1aa291363 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 18 May 2026 02:48:00 -0400 Subject: [PATCH] fix: make Admin LDAP sign-in work against GLAuth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs blocked sign-in entirely: - Login.razor is static-SSR but its form model lacked [SupplyParameterFromForm], so the posted username/password never bound — SignInAsync saw empty fields and bailed before LDAP was contacted. Annotate the model; seed it in OnInitialized since BL0008 forbids an initializer on a [SupplyParameterFromForm] property. - appsettings.json ServiceAccountDn used ou=svcaccts, which GLAuth reads as a (non-existent) group — the service-account bind failed with "Group not found". Use cn=serviceaccount,dc=lmxopcua,dc=local. - LdapAuthService resolved the user DN by searching (uid=...), but GLAuth keys users by cn. Add an LdapOptions.UserNameAttribute knob (default cn for GLAuth; set sAMAccountName for Active Directory) and use it for the search filter. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Login.razor | 9 ++++++++- .../ZB.MOM.WW.OtOpcUa.Admin/Security/LdapAuthService.cs | 4 ++-- .../ZB.MOM.WW.OtOpcUa.Admin/Security/LdapOptions.cs | 7 +++++++ src/Server/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Login.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Login.razor index 83aef58..e8c4731 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Login.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Login.razor @@ -47,10 +47,17 @@ public string Password { get; set; } = string.Empty; } - private Input _input = new(); + // Static-SSR form post: the model must be [SupplyParameterFromForm] or the + // submitted username/password never bind back onto _input. The property + // cannot carry an initializer (BL0008) — seed it in OnInitialized instead. + [SupplyParameterFromForm] + private Input _input { get; set; } = default!; + private string? _error; private bool _busy; + protected override void OnInitialized() => _input ??= new(); + private async Task SignInAsync() { _error = null; diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/LdapAuthService.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/LdapAuthService.cs index 1bf11d0..5328457 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/LdapAuthService.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/LdapAuthService.cs @@ -108,7 +108,7 @@ public sealed class LdapAuthService(IOptions options, ILogger conn.Bind(_options.ServiceAccountDn, _options.ServiceAccountPassword), ct); - var filter = $"(uid={EscapeLdapFilter(username)})"; + var filter = $"({_options.UserNameAttribute}={EscapeLdapFilter(username)})"; var results = await Task.Run(() => conn.Search(_options.SearchBase, LdapConnection.ScopeSub, filter, ["dn"], false), ct); @@ -116,7 +116,7 @@ public sealed class LdapAuthService(IOptions options, ILogger + /// Attribute the service-account search matches the login name against to resolve the + /// user's DN. cn for GLAuth (the dev default); set sAMAccountName for + /// Active Directory. + /// + public string UserNameAttribute { get; set; } = "cn"; + /// /// Maps LDAP group name → Admin role. Group match is case-insensitive. A user gets every /// role whose source group is in their membership list. Example dev mapping: diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json index 39fddd1..77e7cde 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json @@ -10,7 +10,7 @@ "UseTls": false, "AllowInsecureLdap": true, "SearchBase": "dc=lmxopcua,dc=local", - "ServiceAccountDn": "cn=serviceaccount,ou=svcaccts,dc=lmxopcua,dc=local", + "ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local", "ServiceAccountPassword": "serviceaccount123", "DisplayNameAttribute": "cn", "GroupAttribute": "memberOf",