21eac21409
Adds IOpcUaUserAuthenticator seam in OpcUaServer.Security with a deny-all NullOpcUaUserAuthenticator default. OpcUaApplicationHost subscribes to SessionManager.ImpersonateUser after _application.Start so UserName tokens flow through the authenticator and either attach a UserIdentity to the session (Allow) or set IdentityValidationError = BadIdentityTokenRejected (Deny / authenticator exception). Anonymous + X509 tokens fall through to SDK defaults. LdapOpcUaUserAuthenticator (Host project) bridges to the same ILdapAuthService that AddOtOpcUaAuth uses for Admin cookies / JWT, so a single LDAP source-of-truth governs both Admin control plane and OPC UA data plane. Program.cs registers LdapOptions + LdapAuthService + IOpcUaUserAuthenticator on driver-role hosts; admin-only nodes are unchanged. OtOpcUaServerHostedService threads the resolved authenticator into OpcUaApplicationHost so the seam respects Host DI. 10 new tests: 6 in OpcUaServer.Tests cover the pure HandleImpersonation static method (success / denial / anonymous fallthrough / authenticator- throw / null-username / Null authenticator); 4 in Host.IntegrationTests cover the LdapOpcUaUserAuthenticator adapter (LDAP allow → Allow with roles, LDAP deny → Deny, exception → backend-error denial, display-name fallback). OpcUaServer suite is 40 / 40 green. Closes #104. Unblocks Task 60 (dual-endpoint + ServiceLevel tests) once #81 residual lands.
37 lines
1.5 KiB
C#
37 lines
1.5 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using ZB.MOM.WW.OtOpcUa.OpcUaServer.Security;
|
|
using ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Host.OpcUa;
|
|
|
|
/// <summary>
|
|
/// Production <see cref="IOpcUaUserAuthenticator"/> adapter that bridges OPC UA UserName
|
|
/// tokens to the same <see cref="ILdapAuthService"/> the Admin UI cookie/JWT flows use, so a
|
|
/// single LDAP source-of-truth governs both control-plane (Admin) and data-plane (OPC UA)
|
|
/// session identities. Roles flow through unchanged — the data-plane ACL evaluator reads
|
|
/// them off <c>OperationContext.UserIdentity</c> downstream.
|
|
/// </summary>
|
|
public sealed class LdapOpcUaUserAuthenticator(
|
|
ILdapAuthService ldap,
|
|
ILogger<LdapOpcUaUserAuthenticator> logger)
|
|
: IOpcUaUserAuthenticator
|
|
{
|
|
public async Task<OpcUaUserAuthResult> AuthenticateUserNameAsync(string username, string password, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
var result = await ldap.AuthenticateAsync(username, password, ct).ConfigureAwait(false);
|
|
if (!result.Success)
|
|
{
|
|
return OpcUaUserAuthResult.Deny(result.Error ?? "Invalid credentials");
|
|
}
|
|
return OpcUaUserAuthResult.Allow(result.DisplayName ?? username, result.Roles);
|
|
}
|
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
|
{
|
|
logger.LogWarning(ex, "LDAP authentication threw for OPC UA user {User}", username);
|
|
return OpcUaUserAuthResult.Deny("Authentication backend error");
|
|
}
|
|
}
|
|
}
|