using Opc.Ua; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Authorization; namespace ZB.MOM.WW.OtOpcUa.Server.Security; /// /// Bridges the OPC UA stack's to the /// evaluator. Resolves the session's /// from whatever the identity claims + the stack's /// session handle, then delegates to the evaluator and returns a single bool the /// dispatch paths can use to short-circuit with BadUserAccessDenied. /// /// /// This class is deliberately the single integration seam between the Server /// project and the Core.Authorization evaluator. DriverNodeManager holds one /// reference and calls on every Read / Write / HistoryRead / /// Browse / Call / CreateMonitoredItems / etc. The evaluator itself stays pure — it /// doesn't know about the OPC UA stack types. /// /// Fail-open-during-transition: when the evaluator is configured with /// StrictMode = false, missing cluster tries OR sessions without resolved /// LDAP groups get true so existing deployments keep working while ACLs are /// populated. Flip to strict via Authorization:StrictMode = true in production. /// public sealed class AuthorizationGate { private readonly IPermissionEvaluator _evaluator; private readonly bool _strictMode; private readonly TimeProvider _timeProvider; public AuthorizationGate(IPermissionEvaluator evaluator, bool strictMode = false, TimeProvider? timeProvider = null) { _evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator)); _strictMode = strictMode; _timeProvider = timeProvider ?? TimeProvider.System; } /// True when strict authorization is enabled — no-grant = denied. public bool StrictMode => _strictMode; /// /// Authorize an OPC UA operation against the session identity + scope. Returns true to /// allow the dispatch to continue; false to surface BadUserAccessDenied. /// public bool IsAllowed(IUserIdentity? identity, OpcUaOperation operation, NodeScope scope) { // Anonymous / unknown identity — strict mode denies, lax mode allows so the fallback // auth layers (WriteAuthzPolicy) still see the call. if (identity is null) return !_strictMode; var session = BuildSessionState(identity, scope.ClusterId); if (session is null) { // Identity doesn't carry LDAP groups. In lax mode let the dispatch proceed so // older deployments keep working; strict mode denies. return !_strictMode; } var decision = _evaluator.Authorize(session, operation, scope); if (decision.IsAllowed) return true; return !_strictMode; } /// /// Materialize a from the session identity. /// Returns null when the identity doesn't carry LDAP group metadata. /// public UserAuthorizationState? BuildSessionState(IUserIdentity identity, string clusterId) { if (identity is not ILdapGroupsBearer bearer || bearer.LdapGroups.Count == 0) return null; var sessionId = identity.DisplayName ?? Guid.NewGuid().ToString("N"); return new UserAuthorizationState { SessionId = sessionId, ClusterId = clusterId, LdapGroups = bearer.LdapGroups, MembershipResolvedUtc = _timeProvider.GetUtcNow().UtcDateTime, AuthGenerationId = 0, MembershipVersion = 0, }; } }