feat(security): carry LDAP roles onto session identity (T17)

Stop discarding the authenticator's resolved roles during impersonation.
HandleImpersonation now sets args.Identity to a RoleCarryingUserIdentity
(: UserIdentity) that carries result.Roles, so a downstream method handler
can read them off context.UserIdentity for the inbound AlarmAck gate (T18).

Verified via the decompiled SDK (1.5.378.106) that the instance we assign to
ImpersonateEventArgs.Identity is stored by reference onto Session.Identity /
EffectiveIdentity and surfaced unchanged on OperationContext.UserIdentity --
the custom subclass survives the round-trip. No auth-decision logic changes.
This commit is contained in:
Joseph Doherty
2026-06-11 05:42:27 -04:00
parent 2b890fa716
commit a6fed85ac9
3 changed files with 50 additions and 1 deletions
@@ -35,6 +35,24 @@ public sealed class OpcUaApplicationHostImpersonationTests
authenticator.LastPassword.ShouldBe("secret");
}
/// <summary>T17 — verifies the granted identity carries the authenticator's resolved roles
/// (so a downstream method handler can read them off <c>context.UserIdentity</c> for the
/// AlarmAck gate). The identity must be a <see cref="RoleCarryingUserIdentity"/> whose
/// <see cref="RoleCarryingUserIdentity.Roles"/> equals <c>result.Roles</c>.</summary>
[Fact]
public void HandleImpersonation_username_success_carries_roles_on_identity()
{
var roles = new[] { "ReadOnly", "WriteOperate", "AlarmAck" };
var token = new UserNameIdentityToken { UserName = "alice", DecryptedPassword = Encoding.UTF8.GetBytes("secret") };
var args = new ImpersonateEventArgs(token, UserNamePolicy, new EndpointDescription());
var authenticator = new RecordingAuthenticator(OpcUaUserAuthResult.Allow("Alice", roles));
OpcUaApplicationHost.HandleImpersonation(authenticator, args, NullLogger<object>.Instance);
var identity = args.Identity.ShouldBeOfType<RoleCarryingUserIdentity>();
identity.Roles.ShouldBe(roles);
}
/// <summary>Verifies failed UserName token impersonation sets validation error and clears identity.</summary>
[Fact]
public void HandleImpersonation_username_denial_sets_validation_error_and_no_identity()