71 lines
3.4 KiB
C#
71 lines
3.4 KiB
C#
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Server.Security;
|
|
|
|
/// <summary>
|
|
/// Server-layer write-authorization policy. ACL enforcement lives here — drivers report
|
|
/// <see cref="SecurityClassification"/> 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 <see cref="LdapUserAuthenticator"/>) against the required
|
|
/// role for the attribute's classification.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Matches the table in <c>docs/Configuration.md</c>:
|
|
/// <list type="bullet">
|
|
/// <item><c>FreeAccess</c>: no role required — anonymous sessions can write (matches v1 default).</item>
|
|
/// <item><c>Operate</c> / <c>SecuredWrite</c>: <c>WriteOperate</c> role required.</item>
|
|
/// <item><c>Tune</c>: <c>WriteTune</c> role required.</item>
|
|
/// <item><c>VerifiedWrite</c> / <c>Configure</c>: <c>WriteConfigure</c> role required.</item>
|
|
/// <item><c>ViewOnly</c>: no role grants write access.</item>
|
|
/// </list>
|
|
/// <c>AlarmAck</c> is checked at the alarm-acknowledge path, not here.
|
|
/// </remarks>
|
|
public static class WriteAuthzPolicy
|
|
{
|
|
public const string RoleWriteOperate = "WriteOperate";
|
|
public const string RoleWriteTune = "WriteTune";
|
|
public const string RoleWriteConfigure = "WriteConfigure";
|
|
|
|
/// <summary>
|
|
/// Decide whether a session with <paramref name="userRoles"/> is allowed to write to an
|
|
/// attribute with the given <paramref name="classification"/>. Returns true for
|
|
/// <c>FreeAccess</c> regardless of roles (including empty / anonymous sessions) and
|
|
/// false for <c>ViewOnly</c> regardless of roles. Every other classification requires
|
|
/// the session to carry the mapped role — case-insensitive match.
|
|
/// </summary>
|
|
public static bool IsAllowed(SecurityClassification classification, IReadOnlyCollection<string> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Required role for a classification, or null when no role grants access
|
|
/// (<see cref="SecurityClassification.ViewOnly"/>) or no role is needed
|
|
/// (<see cref="SecurityClassification.FreeAccess"/> — also returns null; callers use
|
|
/// <see cref="IsAllowed"/> which handles the special-cases rather than branching on
|
|
/// null themselves).
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|