using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Server.Security; /// /// Server-layer write-authorization policy. ACL enforcement lives here — drivers report /// as discovery metadata only; the server decides /// whether a given session is allowed to write a given attribute by checking the session's /// roles (resolved at login via ) against the required /// role for the attribute's classification. /// /// /// Matches the table in docs/Configuration.md: /// /// FreeAccess: no role required — anonymous sessions can write (matches v1 default). /// Operate / SecuredWrite: WriteOperate role required. /// Tune: WriteTune role required. /// VerifiedWrite / Configure: WriteConfigure role required. /// ViewOnly: no role grants write access. /// /// AlarmAck is checked at the alarm-acknowledge path, not here. /// public static class WriteAuthzPolicy { public const string RoleWriteOperate = "WriteOperate"; public const string RoleWriteTune = "WriteTune"; public const string RoleWriteConfigure = "WriteConfigure"; /// /// Decide whether a session with is allowed to write to an /// attribute with the given . Returns true for /// FreeAccess regardless of roles (including empty / anonymous sessions) and /// false for ViewOnly regardless of roles. Every other classification requires /// the session to carry the mapped role — case-insensitive match. /// public static bool IsAllowed(SecurityClassification classification, IReadOnlyCollection userRoles) { if (classification == SecurityClassification.FreeAccess) return true; if (classification == SecurityClassification.ViewOnly) return false; var required = RequiredRole(classification); if (required is null) return false; foreach (var r in userRoles) { if (string.Equals(r, required, StringComparison.OrdinalIgnoreCase)) return true; } return false; } /// /// Required role for a classification, or null when no role grants access /// () or no role is needed /// ( — also returns null; callers use /// which handles the special-cases rather than branching on /// null themselves). /// public static string? RequiredRole(SecurityClassification classification) => classification switch { SecurityClassification.FreeAccess => null, // IsAllowed short-circuits SecurityClassification.Operate => RoleWriteOperate, SecurityClassification.SecuredWrite => RoleWriteOperate, SecurityClassification.Tune => RoleWriteTune, SecurityClassification.VerifiedWrite => RoleWriteConfigure, SecurityClassification.Configure => RoleWriteConfigure, SecurityClassification.ViewOnly => null, // IsAllowed short-circuits _ => null, }; /// /// Maps a driver-reported to the /// the Phase 6.2 evaluator consults /// for the matching bit. /// FreeAccess + ViewOnly fall back to WriteOperate — the evaluator never sees them /// because short-circuits first. /// public static Core.Abstractions.OpcUaOperation ToOpcUaOperation(SecurityClassification classification) => classification switch { SecurityClassification.Operate => Core.Abstractions.OpcUaOperation.WriteOperate, SecurityClassification.SecuredWrite => Core.Abstractions.OpcUaOperation.WriteOperate, SecurityClassification.Tune => Core.Abstractions.OpcUaOperation.WriteTune, SecurityClassification.VerifiedWrite => Core.Abstractions.OpcUaOperation.WriteConfigure, SecurityClassification.Configure => Core.Abstractions.OpcUaOperation.WriteConfigure, _ => Core.Abstractions.OpcUaOperation.WriteOperate, }; }