Phase 6.2 Stream C foundation - AuthorizationGate + ILdapGroupsBearer #86

Merged
dohertj2 merged 1 commits from phase-6-2-stream-c-dispatch-wiring into v2 2026-04-19 09:35:49 -04:00
Owner

Integration seam between the OPC UA stack + Core.Authorization evaluator. DriverNodeManager dispatch wiring (Read/Write/HistoryRead/Browse/Call/Subscribe/Alarm) lands in the follow-up PR on this branch.

Summary

  • ILdapGroupsBearer marker interface parallel to IRoleBearer — control/data-plane separation per decision #150.
  • AuthorizationGate.IsAllowed(identity, operation, scope) materializes a UserAuthorizationState from the identity’s LDAP groups + delegates to IPermissionEvaluator. Returns one bool the dispatch paths use to decide BadUserAccessDenied.
  • StrictMode knob controls rollout: lax (default) fails-open to keep older deployments working; strict (production target after ACLs populated) fails-closed. Flip via Authorization:StrictMode = true.

Test plan

  • 9 new gate tests: null identity strict/lax; identity without LDAP strict/lax; matching grant allows; non-matching group in strict denies; wrong operation denied; BuildSessionState materializes + returns null when groups missing.
  • Full solution dotnet test: 1087 passing.
  • Follow-up: wire DriverNodeManager.OnReadValue + OnWriteValue + HistoryRead + Browse + Call through the gate.

🤖 Generated with Claude Code

Integration seam between the OPC UA stack + Core.Authorization evaluator. DriverNodeManager dispatch wiring (Read/Write/HistoryRead/Browse/Call/Subscribe/Alarm) lands in the follow-up PR on this branch. ## Summary - `ILdapGroupsBearer` marker interface parallel to `IRoleBearer` — control/data-plane separation per decision #150. - `AuthorizationGate.IsAllowed(identity, operation, scope)` materializes a `UserAuthorizationState` from the identity’s LDAP groups + delegates to `IPermissionEvaluator`. Returns one bool the dispatch paths use to decide `BadUserAccessDenied`. - `StrictMode` knob controls rollout: lax (default) fails-open to keep older deployments working; strict (production target after ACLs populated) fails-closed. Flip via `Authorization:StrictMode = true`. ## Test plan - [x] 9 new gate tests: null identity strict/lax; identity without LDAP strict/lax; matching grant allows; non-matching group in strict denies; wrong operation denied; BuildSessionState materializes + returns null when groups missing. - [x] Full solution `dotnet test`: 1087 passing. - [ ] Follow-up: wire DriverNodeManager.OnReadValue + OnWriteValue + HistoryRead + Browse + Call through the gate. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dohertj2 added 1 commit 2026-04-19 09:35:40 -04:00
Lands the integration seam between the Server project's OPC UA stack and the
Core.Authorization evaluator. Actual DriverNodeManager dispatch-path wiring
(Read/Write/HistoryRead/Browse/Call/Subscribe/Alarm surfaces) lands in the
follow-up PR on this branch — covered by Task #143 below.

Server.Security additions:
- ILdapGroupsBearer — marker interface a custom IUserIdentity implements to
  expose its resolved LDAP group DNs. Parallel to the existing IRoleBearer
  (admin roles) — control/data-plane separation per decision #150.
- AuthorizationGate — stateless bridge between Opc.Ua.IUserIdentity and
  IPermissionEvaluator. IsAllowed(identity, operation, scope) materializes a
  UserAuthorizationState from the identity's LDAP groups, delegates to the
  evaluator, and returns a single bool the dispatch paths use to decide
  whether to surface BadUserAccessDenied.
- StrictMode knob controls fail-open-during-transition vs fail-closed:
  - strict=false (default during rollout) — null identity, identity without
    ILdapGroupsBearer, or NotGranted outcome all return true so older
    deployments without ACL data keep working.
  - strict=true (production target) — any of the above returns false.
  The appsetting `Authorization:StrictMode = true` flips deployments over
  once their ACL data is populated.

Tests (9 new in Server.Tests/AuthorizationGateTests):
- Null identity — strict denies, lax allows.
- Identity without LDAP groups — strict denies, lax allows.
- LDAP group with matching grant allows.
- LDAP group without grant — strict denies.
- Wrong operation denied (Read-only grant, WriteOperate requested).
- BuildSessionState returns materialized state with LDAP groups + null when
  identity doesn't carry them.

Full solution dotnet test: 1087 passing (Phase 6.1 = 1042, Phase 6.2 A = +9,
B = +27, C foundation = +9 = 1087). Pre-existing Client.CLI Subscribe flake
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit 70f3ec0092 into v2 2026-04-19 09:35:49 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#86