Initial commit: scadaproj umbrella — sister-project index, auth component normalization (design + GAPS), and the built ZB.MOM.WW.Auth shared library (0.1.0, flattened in).
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Auth — current state: MxAccessGateway (`mxaccessgw`)
|
||||
|
||||
Repo: `~/Desktop/MxAccessGateway` (Gitea `mxaccessgw`). Stack: .NET 10 gateway (x64) + x86/net48 worker.
|
||||
Auth lives entirely in the **gateway** (.NET 10); the worker never authenticates.
|
||||
All paths relative to repo root; auth code under `src/ZB.MOM.WW.MxGateway.Server/`. Verified 2026-06-01.
|
||||
|
||||
The gateway has **two independent auth models**: gRPC API keys (programmatic clients) and
|
||||
LDAP dashboard auth (web UI). They share nothing — different credentials, stores, and authz.
|
||||
|
||||
## Model A — gRPC API-key auth
|
||||
|
||||
Base: `Security/Authentication/` and `Security/Authorization/`.
|
||||
|
||||
- **Token format:** `Authorization: Bearer mxgw_<key-id>_<secret>`. Parsed by `ApiKeyParser.cs` (rejects malformed before any DB hit) → `ParsedApiKey(KeyId, Secret)`.
|
||||
- **Hashing:** `ApiKeySecretHasher.cs` — HMAC-SHA256 with an **external pepper** (config key `MxGateway:ApiKeyPepper`, never stored beside the hash). Secrets generated by `ApiKeySecretGenerator.cs` (32 random bytes, URL-safe base64).
|
||||
- **Verification:** `ApiKeyVerifier.cs` — parse → `IApiKeyStore.FindByKeyIdAsync` → reject if revoked → hash with pepper → **constant-time compare** (`CryptographicOperations.FixedTimeEquals`) → mark `LastUsedUtc`. Failures discriminated (`KeyNotFound/KeyRevoked/PepperUnavailable/SecretMismatch/MissingOrMalformedCredentials`) for audit without leaking to clients.
|
||||
- **Storage:** SQLite (`AuthSqliteConnectionFactory.cs`, WAL), default `C:\ProgramData\MxGateway\gateway-auth.db`. Schema v2 (`SqliteAuthSchema.cs`): tables `api_keys` (hash, scopes JSON, constraints JSON, created/last_used/revoked), `api_key_audit` (append-only), `schema_version`. Read/write/audit split across `SqliteApiKeyStore` / `SqliteApiKeyAdminStore` / `SqliteApiKeyAuditStore`.
|
||||
- **Scopes** (`GatewayScopes.cs`): `session:open/close`, `invoke:read/write/secure`, `events:read`, `metadata:read`, `admin`. Stored per key (ordinal-sorted JSON via `ApiKeyScopeSerializer.cs`).
|
||||
- **Enforcement:** `Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs` + `GatewayGrpcScopeResolver.cs` — per RPC: authenticate (`Unauthenticated` on failure) → resolve required scope → check (`PermissionDenied` if missing) → push `ApiKeyIdentity` into async-local `IGatewayRequestIdentityAccessor`.
|
||||
- **Constraints** (`ApiKeyConstraints.cs` + `ConstraintEnforcer.cs`): optional fine-grained limits — `ReadSubtrees/WriteSubtrees/ReadTagGlobs/WriteTagGlobs/BrowseSubtrees` (anchored case-insensitive globs), `MaxWriteClassification`, `ReadAlarmOnly`, `ReadHistorizedOnly`.
|
||||
- **Admin CLI:** `apikey` subcommand (`ApiKeyAdminCliRunner.cs`, `ApiKeyAdminCommandLineParser.cs`): `init-db / create-key / list-keys / revoke-key / rotate-key / delete-key` (+ constraint flags). `delete` only works on already-revoked keys.
|
||||
- **Config:** `Configuration/AuthenticationOptions.cs` → `MxGateway:Authentication:{Mode(ApiKey|Disabled), SqlitePath, PepperSecretName, RunMigrationsOnStartup}`.
|
||||
|
||||
## Model B — Dashboard LDAP auth (Blazor)
|
||||
|
||||
Base: `Dashboard/`.
|
||||
|
||||
- **Login:** `DashboardAuthenticator.cs` — connect (`MxGateway:Ldap`), bind service account, search user (`({UserNameAttribute}={escaped})`, RFC-escaped), re-bind as user DN to verify password, read `memberOf`. `/login` GET/POST with antiforgery (`DashboardEndpointRouteBuilderExtensions.cs`).
|
||||
- **Group→role:** `MxGateway:Dashboard:GroupToRole` (case-insensitive; tries full DN then leading RDN) → roles `Admin` / `Viewer` (`DashboardRoles.cs`). Zero matched roles ⇒ login denied.
|
||||
- **Cookie:** scheme `MxGateway.Dashboard`; cookie name **`MxGatewayDashboard`** (note: no `__Host-` prefix despite some docs); HttpOnly, SameSite=Strict, 8h sliding; `SecurePolicy` via `MxGateway:Dashboard:RequireHttpsCookie`. Config in `DashboardServiceCollectionExtensions.cs`.
|
||||
- **SignalR hubs** (`/hubs/snapshot|alarms|events`): policy accepts the cookie OR a 30-min Data-Protection bearer minted at `GET /hubs/token` (`HubTokenService.cs`, purpose `…Dashboard.HubToken.v1`; `HubTokenAuthenticationHandler.cs` also reads `access_token` query for WS upgrades).
|
||||
- **Loopback bypass:** `MxGateway:Dashboard:AllowAnonymousLocalhost` (default `true`) — `DashboardAuthorizationHandler.cs`.
|
||||
- **LDAP config:** `Configuration/LdapOptions.cs` → `MxGateway:Ldap:{Enabled, Server, Port(3893), UseTls, AllowInsecureLdap, SearchBase(dc=lmxopcua,dc=local), ServiceAccountDn, ServiceAccountPassword, UserNameAttribute(cn), DisplayNameAttribute, GroupAttribute(memberOf)}`. Dev users/groups in `glauth.md` (incl. the gateway-specific `GwAdmin` group).
|
||||
|
||||
## Secrets & config
|
||||
|
||||
Pepper resolved from config (`MxGateway:ApiKeyPepper`), never stored with hashes. CLAUDE.md: API keys, passwords, `WriteSecured` payloads, `AuthenticateUser` creds never logged. Docs: `docs/Authentication.md`, `docs/Authorization.md`, `docs/GatewayDashboardDesign.md`, `docs/DesignDecisions.md`, `glauth.md`.
|
||||
|
||||
## Notable limits / TODOs
|
||||
|
||||
EventsHub has no per-session ACL yet (any dashboard user can subscribe to any session); no reconnectable sessions; single event subscriber per session; authz is scope+constraint, not per-item ACL.
|
||||
|
||||
---
|
||||
|
||||
## Adoption plan → `ZB.MOM.WW.Auth`
|
||||
|
||||
**Replace with the shared library:**
|
||||
- **Model A is the reference implementation for `ZB.MOM.WW.Auth.ApiKeys`.** The whole `Security/Authentication/` key pipeline (parser, hasher, generator, verifier, SQLite store, audit, scope serializer, admin CLI) maps almost 1:1 onto the proposed contract — extract it here first and have ScadaBridge's Inbound API adopt the same package. Token prefix becomes configurable (`mxgw_`).
|
||||
- Model B LDAP: `DashboardAuthenticator`'s bind-then-search → `ZB.MOM.WW.Auth.Ldap`; cookie/claim wiring → `ZB.MOM.WW.Auth.AspNetCore`. Migrate `MxGateway:Ldap:*` to the canonical config schema.
|
||||
|
||||
**Keep bespoke:**
|
||||
- gRPC **scope** catalog + `GatewayGrpcAuthorizationInterceptor` + constraint globs (domain authz).
|
||||
- Dashboard `Admin`/`Viewer` role meaning, hub-token specifics, loopback bypass.
|
||||
|
||||
**Watch:** align the API-key `MaxWriteClassification`/constraint model with ScadaBridge's per-method approval when extracting — they are different shapes of "scope a key"; the shared contract should carry constraints as an opaque, project-supplied policy rather than hard-coding either.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Auth — current state: OtOpcUa
|
||||
|
||||
Repo: `~/Desktop/OtOpcUa` (Gitea `lmxopcua`). Stack: .NET 10, OPC Foundation UA stack.
|
||||
All paths below are relative to the repo root. Verified against source on 2026-06-01.
|
||||
|
||||
OtOpcUa has the richest auth of the three: OPC UA session-level identity, LDAP-backed
|
||||
authentication, transport security profiles, and a trie-based per-operation ACL system,
|
||||
plus a separate control-plane (Admin UI) auth stack.
|
||||
|
||||
## 1. Authentication
|
||||
|
||||
Three OPC UA identity-token types are accepted at session establishment
|
||||
(`src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs`, impersonation handler ~226–288):
|
||||
|
||||
- **Anonymous** — passes through without custom validation; gets no LDAP groups.
|
||||
- **UserName/password (LDAP-backed)** — the primary human path (see below).
|
||||
- **X.509 certificate** — validated at the secure-channel/PKI level; CN→role mapping not yet implemented.
|
||||
|
||||
**UserName flow:** the SDK decrypts the password with the server certificate, then
|
||||
`IOpcUaUserAuthenticator.AuthenticateUserNameAsync()` validates it.
|
||||
- Seam: `src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/IOpcUaUserAuthenticator.cs`
|
||||
- Prod impl: `src/Server/ZB.MOM.WW.OtOpcUa.Host/OpcUa/LdapOpcUaUserAuthenticator.cs` → wraps `ILdapAuthService`.
|
||||
- UserName tokens are **always encrypted** by the SDK (via the server cert) regardless of transport profile, so LDAP login works even on a `None` endpoint.
|
||||
|
||||
**LDAP service** (`src/Server/ZB.MOM.WW.OtOpcUa.Security/Ldap/`):
|
||||
- `ILdapAuthService` / `LdapAuthService.cs` — bind-then-search (or direct bind), re-bind as user DN to verify the password, read `memberOf`, strip leading `CN=`. GLAuth fallback extracts the primary group from the `ou=` RDN.
|
||||
- `LdapOptions.cs` — bound from config section **`Authentication.Ldap`**. Keys: `Enabled`, `Server`, `Port` (GLAuth `3893`), `UseTls`, `AllowInsecureLdap`, `SearchBase` (`dc=lmxopcua,dc=local`), `ServiceAccountDn`, `ServiceAccountPassword`, `UserNameAttribute` (`cn`; AD→`sAMAccountName`), `GroupAttribute` (`memberOf`), `DisplayNameAttribute`, `GroupToRole` (dict), `DevStubMode` (dev-only: accepts any non-empty creds).
|
||||
- `RoleMapper.cs` — maps LDAP groups → control-plane `AdminRole` via `GroupToRole`.
|
||||
- Dev LDAP server: GLAuth at `C:\publish\glauth\` (see `C:\publish\glauth\auth.md`).
|
||||
|
||||
## 2. Transport security
|
||||
|
||||
`OpcUaServer` config section; `EnabledSecurityProfiles` (default `[None, Basic256Sha256-Sign, Basic256Sha256-SignAndEncrypt]`; Aes128/Aes256 profiles also available), resolved by `SecurityProfileResolver` at startup. Server certificate auto-created under `PkiStoreRoot` (`own/ issuer/ trusted/ rejected/`). `AutoAcceptUntrustedClientCertificates` (default `false`). See `docs/security.md`.
|
||||
- Profile enum + policy build: `OpcUaApplicationHost.cs` (~15–23, ~374–410).
|
||||
|
||||
## 3. Authorization (data-plane ACLs — stays bespoke)
|
||||
|
||||
Bitmask permissions in `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NodePermissions.cs`:
|
||||
`Browse, Read, Subscribe, HistoryRead, WriteOperate, WriteTune, WriteConfigure, AlarmRead,
|
||||
AlarmAcknowledge, AlarmConfirm, AlarmShelve, MethodCall` + bundles `ReadOnly/Operator/Engineer/Admin`.
|
||||
The three write tiers mirror Galaxy `SecurityClassification` (Operate/Tune/Configure).
|
||||
|
||||
- Scope hierarchy: `NodeAclScopeKind.cs` — `Cluster, Namespace, UnsArea, UnsLine, Equipment, FolderSegment, Tag`.
|
||||
- Grant entity: `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/NodeAcl.cs` — `(LdapGroup, ScopeKind, ScopeId, PermissionFlags)`, generation-versioned.
|
||||
- Evaluation: `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/` — `TriePermissionEvaluator.cs`, `PermissionTrie.cs`, `PermissionTrieCache.cs`, `PermissionTrieBuilder.cs`, `IPermissionEvaluator.cs`. Per-operation (`OpcUaOperation` enum); denials return `BadUserAccessDenied`.
|
||||
- Additive-only grants (no Deny) in Phase 6.2.
|
||||
|
||||
**Control-plane (Admin UI) roles are independent** of data-plane ACLs (design decision #150):
|
||||
`AdminRole` enum (`ConfigViewer / ConfigEditor / FleetAdmin`); `LdapGroupRoleMapping` entity maps groups→AdminRole. Cookie + JWT stack lives in `src/Server/ZB.MOM.WW.OtOpcUa.Security/` (`ServiceCollectionExtensions.cs`, `Endpoints/AuthEndpoints.cs` — `/login`,`/logout`,`/ping`; `Jwt/JwtTokenService.cs`; `Blazor/CookieAuthenticationStateProvider.cs`). DataProtection keys persisted in the Config DB so cookies survive failover.
|
||||
|
||||
## 4. Session / identity model (stays bespoke)
|
||||
|
||||
`src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/UserAuthorizationState.cs`:
|
||||
`SessionId, ClusterId, LdapGroups, MembershipResolvedUtc, AuthGenerationId, MembershipVersion`,
|
||||
`MembershipFreshnessInterval` (5 min, async refresh) and `AuthCacheMaxStaleness` (15 min, fail-closed).
|
||||
Sessions are **generation-bound** at sign-in so grant changes can't take effect mid-session.
|
||||
Auth is evaluated **per request**, not cached per session.
|
||||
|
||||
## 5. Secrets & config
|
||||
|
||||
`Authentication.Ldap.ServiceAccountPassword` should come from user-secrets/env, not source.
|
||||
DataProtection keys in Config DB table `DataProtectionKeys`. Docs: `docs/security.md`, `docs/v2/acl-design.md`.
|
||||
|
||||
## 6. Notable limits / TODOs
|
||||
|
||||
No explicit Deny grants (Phase 6.2); no nested-group expansion (relies on directory flattening);
|
||||
LDAP unreachable >15 min fails all sessions closed; X.509 CN→role mapping deferred;
|
||||
`HistoryUpdate` currently mapped to the `HistoryRead` bit.
|
||||
|
||||
---
|
||||
|
||||
## Adoption plan → `ZB.MOM.WW.Auth`
|
||||
|
||||
**Replace with the shared library:**
|
||||
- `LdapAuthService` + `LdapOptions` + `RoleMapper` → `ZB.MOM.WW.Auth.Ldap` (`ILdapAuthService`, `LdapAuthService`, `LdapOptions`, `LdapAuthResult`) + `IGroupRoleMapper<CanonicalRole>`; OtOpcUa expands each canonical role into its `AdminRole` (control-plane) and `NodePermissions` (data-plane) per [`../../spec/CANONICAL-ROLES.md`](../../spec/CANONICAL-ROLES.md). No first-class `Deployer` (publish ⊂ `FleetAdmin`).
|
||||
- Migrate config from `Authentication.Ldap.*` to the canonical schema in [`../../spec/SPEC.md`](../../spec/SPEC.md) (notably `UseTls` → the canonical transport setting; `UserNameAttribute` keeps its name as the canonical one).
|
||||
- **Control-plane Admin UI** (Blazor, `src/Server/ZB.MOM.WW.OtOpcUa.Security/`): adopt `ZB.MOM.WW.Auth.AspNetCore` for the canonical cookie/claim conventions + DI helpers (`ServiceCollectionExtensions.cs`, `Blazor/CookieAuthenticationStateProvider.cs`, `Endpoints/AuthEndpoints.cs`). This is OtOpcUa's HTTP auth surface — distinct from the OPC UA data plane below.
|
||||
|
||||
**Keep bespoke (thin adapter only):**
|
||||
- `IOpcUaUserAuthenticator` / `LdapOpcUaUserAuthenticator` — keep as the OPC-UA-specific adapter that calls the shared `ILdapAuthService`.
|
||||
- ALL of §3 authZ (`NodePermissions`, ACL trie, `NodeAcl`), the control-plane `AdminRole` vocabulary, the JWT/`DataProtection` specifics, and §4 session model — domain-specific, not extracted (only the cookie/claim *conventions* are shared via `.AspNetCore`).
|
||||
- Transport security (§2) — OPC-UA-specific.
|
||||
|
||||
**No API-key work** — OtOpcUa has no API-key surface; it relies on OPC UA transport security instead.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Auth — current state: ScadaBridge
|
||||
|
||||
Repo: `~/Desktop/ScadaBridge`. Stack: .NET 10, Akka.NET; solution `ZB.MOM.WW.ScadaBridge.slnx`.
|
||||
Auth code centers on the `ZB.MOM.WW.ScadaBridge.Security` project. All paths relative to repo root.
|
||||
Verified 2026-06-01.
|
||||
|
||||
LDAP-centric identity with database-driven role mapping and **site-scoped** deployment;
|
||||
multiple authenticated surfaces (Blazor UI, CLI, Management API, Inbound API).
|
||||
|
||||
## 1. Authentication
|
||||
|
||||
`src/ZB.MOM.WW.ScadaBridge.Security/`:
|
||||
- `LdapAuthService.cs` — `AuthenticateAsync(username,password)` → `LdapAuthResult`. Direct LDAP bind (no Kerberos/NTLM). RFC 4514 DN escaping + LDAP filter escaping (injection-safe), username trim-normalization, per-operation socket timeout, distinct exception for service-account-bind failure vs bad user creds, **fails login if the group lookup fails** (won't admit with zero roles).
|
||||
- `SecurityOptions.cs` (config section **`ScadaBridge:Security`**, flat keys): `LdapServer`, `LdapPort`, `LdapTransport` (**enum** `Ldaps`/`StartTls`/`None`), `AllowInsecureLdap`, `LdapSearchBase` (`dc=scadabridge,dc=local`), `LdapServiceAccountDn`, `LdapServiceAccountPassword`, `LdapUserIdAttribute` (`uid`; AD→`sAMAccountName`), `LdapDisplayNameAttribute`, `LdapGroupAttribute` (`memberOf`), `LdapConnectionTimeoutMs`, plus `JwtSigningKey`, `JwtExpiryMinutes` (15), `IdleTimeoutMinutes` (30), `JwtRefreshThresholdMinutes` (5), `RequireHttpsCookie`.
|
||||
- `SecurityOptionsValidator.cs` — startup fail-fast if `LdapServer`/`LdapSearchBase` empty; `JwtSigningKey` ≥ 32 bytes.
|
||||
- Dev LDAP: GLAuth in `infra/glauth/config.toml`, base `dc=scadabridge,dc=local`, groups `SCADA-Admins/Designers/Deploy-All/Deploy-SiteA`, users incl. `multi-role` / `admin` (pw `password`).
|
||||
|
||||
## 2. Authorization (roles + site scope — stays bespoke)
|
||||
|
||||
- Roles (`Security/Roles.cs`): `Admin`, `Design`, `Deployment`, `Audit`, `AuditReadOnly`.
|
||||
- **Group→role is DB-driven** (not config): entity `Commons/Entities/Security/LdapGroupMapping.cs` (`LdapGroupName`→`Role`), repo `ConfigurationDatabase/Repositories/SecurityRepository.cs`, EF map `Configurations/SecurityConfiguration.cs`. `Security/RoleMapper.cs` → `MapGroupsToRolesAsync` returns roles + permitted site IDs + `isSystemWideDeployment` (union semantics: any unscoped Deployment mapping ⇒ system-wide).
|
||||
- **Site-scoping:** `Commons/Entities/Security/SiteScopeRule.cs` (`LdapGroupMappingId`→`SiteId`). No scope rules on a Deployment mapping ⇒ all sites.
|
||||
- ASP.NET policies (`Security/AuthorizationPolicies.cs`): `RequireAdmin/RequireDesign/RequireDeployment/OperationalAudit/AuditExport`.
|
||||
|
||||
## 3. Authenticated surfaces
|
||||
|
||||
| Surface | Entry | Mechanism | Role check | Site scope |
|
||||
|---|---|---|---|---|
|
||||
| Central UI (Blazor Server) | `/auth/login` form | LDAP → cookie | `[Authorize(Policy=…)]`, `AuthorizeView` | `CentralUI/Auth/SiteScopeService.cs` |
|
||||
| CLI | `--username/--password` | HTTP Basic → Management API → LDAP | at Management API | at Management API |
|
||||
| Management API | `POST /management` | HTTP Basic → LDAP | `ManagementActor.GetRequiredRole` then `Roles.Contains` | `ManagementActor.EnforceSiteScope` |
|
||||
| Inbound API | `X-API-Key` header | **API key** hash lookup (not LDAP) | per-method approval in DB | n/a |
|
||||
| Central↔Site | Akka ClusterClient + gRPC | none (cluster membership is the trust boundary; TLS in prod) | — | — |
|
||||
|
||||
Key files: `CentralUI/Auth/AuthEndpoints.cs` (`/auth/login`,`/auth/token`,`/auth/logout`,`/auth/ping`), `CentralUI/Auth/CookieAuthenticationStateProvider.cs`, `ManagementService/ManagementEndpoints.cs` + `ManagementActor.cs`, `CLI/ManagementHttpClient.cs`, `InboundAPI/ApiKeyValidator.cs`.
|
||||
|
||||
## 4. Session / identity model
|
||||
|
||||
Cookie auth (`Security/ServiceCollectionExtensions.cs`): cookie name `ZB.MOM.WW.ScadaBridge.Auth`, HttpOnly, SameSite=Strict, Secure conditional, sliding `IdleTimeoutMinutes` (30). Claims carry name/display/username/roles/site-ids. Separate **JWT** (`Security/JwtTokenService.cs`, HMAC-SHA256, issuer/aud `scadabridge-central`, 15-min expiry, refresh at 5-min threshold, idle-timeout enforced) used for programmatic/CLI via `/auth/token`. Shared `JwtSigningKey` across central nodes ⇒ no sticky sessions. Note: the cookie is the ASP.NET session token; JWT is *not* embedded in it.
|
||||
|
||||
**Inbound API keys** (`InboundAPI/ApiKeyValidator.cs`): `X-API-Key`, peppered HMAC-SHA256, **constant-time** compare, per-method approval, indistinguishable 403 for "no method"/"not approved".
|
||||
|
||||
## 5. Secrets & config
|
||||
|
||||
`ldap_login.txt` / `sql_login.txt` (real creds, git-ignored). Docker config in `docker/central-node-*/appsettings.Central.json`. Config hierarchy: `appsettings.json` → `appsettings.{Central|Site}.json` → env → CLI. Spec: `docs/requirements/Component-Security.md`.
|
||||
|
||||
## 6. Notable limits / TODOs
|
||||
|
||||
No Kerberos/NTLM; no dynamic role refresh (only on token expiry/LDAP re-query); no forced session revocation; `AddSecurityActors()` is a placeholder; no MFA; no password-reset (AD-managed).
|
||||
|
||||
---
|
||||
|
||||
## Adoption plan → `ZB.MOM.WW.Auth`
|
||||
|
||||
**Replace with the shared library:**
|
||||
- `LdapAuthService` + `SecurityOptions` (LDAP portion) + `LdapAuthResult` → `ZB.MOM.WW.Auth.Ldap`. ScadaBridge's escaping/timeout/fail-closed hygiene is strong — fold it into the shared impl. Reconcile config: flat `ScadaBridge:Security:Ldap*` + `LdapTransport` enum vs the canonical schema in [`../../spec/SPEC.md`](../../spec/SPEC.md) (canonical uses the transport **enum**, which ScadaBridge already has — adopt ScadaBridge's `LdapTransport` shape, rename keys to canonical).
|
||||
- `InboundAPI/ApiKeyValidator.cs` → `ZB.MOM.WW.Auth.ApiKeys` (same peppered-HMAC contract mxaccessgw extracts). Map "per-method approval" onto the contract's opaque constraint/policy hook.
|
||||
- Cookie/JWT wiring → `ZB.MOM.WW.Auth.AspNetCore` (claims + cookie conventions). `JwtTokenService` is a candidate for a shared token helper if mxaccessgw/OtOpcUa want the same refresh model — otherwise keep bespoke.
|
||||
|
||||
**Keep bespoke:**
|
||||
- Native role set (`Admin/Design/Deployment/Audit/AuditReadOnly`) + **site-scoping** (`SiteScopeRule`, union semantics) stay as the enforcement layer; implement `IGroupRoleMapper<CanonicalRole>` over the DB mapping and expand canonical→native. Per [`../../spec/CANONICAL-ROLES.md`](../../spec/CANONICAL-ROLES.md): `AuditReadOnly`→Viewer, `Audit`→Administrator (SoD collapse); no `Operator`/`Engineer`.
|
||||
- `ManagementActor` role/scope enforcement, Akka cluster trust model.
|
||||
|
||||
**Note the group→role *mechanism* divergence:** ScadaBridge maps groups→roles in the **database**; OtOpcUa and mxaccessgw map in **config**. The shared seam (`IGroupRoleMapper`) must allow either backing store — see [`../../GAPS.md`](../../GAPS.md).
|
||||
Reference in New Issue
Block a user