Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.Security/Ldap/LdapOptions.cs
T
Joseph Doherty c1619d95f5 feat(auth)!: OtOpcUa canonical control-plane roles + config-DB migration (Task 1.7)
Standardize the control-plane admin role VALUES on the canonical six
(ZB.MOM.WW.Auth CanonicalRole). OtOpcUa uses four:
  ConfigViewer   -> Viewer
  ConfigEditor   -> Designer
  FleetAdmin     -> Administrator
  DriverOperator -> Operator   (appsettings-only string role)

This is a rename, not a permission change: enforcement semantics are
preserved (whoever could deploy/administer/operate before still can).

- AdminRole enum members renamed (persisted as string names via
  HasConversion<string>); RoleGrants.razor dropdown default updated.
- EF DATA migration CanonicalizeAdminRoles rewrites existing
  LdapGroupRoleMapping.Role rows old->new (Up) and back (Down); schema /
  model snapshot byte-identical (no pending model changes).
- Enforcement role STRINGS canonicalized:
  * Security policies keep their NAMES ("DriverOperator"/"FleetAdmin")
    but require canonical roles: RequireRole("Operator","Administrator")
    and RequireRole("Administrator").
  * Deployments.razor [Authorize(Roles="Administrator,Designer")].
  * DevStub now grants "Administrator"; LdapOptions/doc-comment examples
    canonicalized.
- Data-plane authorization (NodePermissions/NodeAcl/IPermissionEvaluator/
  TriePermissionEvaluator/UserAuthorizationState) UNTOUCHED.
- New CanonicalAdminRolesTests pins canonical claim values end-to-end and
  the real registered policies; existing role-string tests updated.
2026-06-02 07:30:00 -04:00

107 lines
5.2 KiB
C#

using ZB.MOM.WW.Auth.Abstractions.Ldap;
using LibLdapOptions = ZB.MOM.WW.Auth.Abstractions.Ldap.LdapOptions;
namespace ZB.MOM.WW.OtOpcUa.Security.Ldap;
/// <summary>
/// LDAP + role-mapping configuration for the Admin UI. Bound from <c>appsettings.json</c>
/// <c>Security:Ldap</c> section. Defaults point at the local GLAuth dev instance (see
/// <c>C:\publish\glauth\auth.md</c>).
/// </summary>
/// <remarks>
/// Carries both the wire fields the shared <c>ZB.MOM.WW.Auth.Ldap</c> directory client needs
/// (<see cref="Server"/>/<see cref="Port"/>/<see cref="Transport"/>/…) AND the app-only concerns
/// the shared library has no notion of (<see cref="Enabled"/> master switch,
/// <see cref="DevStubMode"/> dev bypass, <see cref="GroupToRole"/> appsettings role baseline).
/// The app wrapper (<c>OtOpcUaLdapAuthService</c>) projects this onto the library's
/// <see cref="LibLdapOptions"/> at construction; see <see cref="ToLibraryOptions"/>.
/// </remarks>
public sealed class LdapOptions
{
public const string SectionName = "Security:Ldap";
/// <summary>Gets or sets a value indicating whether LDAP authentication is enabled.</summary>
public bool Enabled { get; set; } = true;
/// <summary>Gets or sets the LDAP server hostname.</summary>
public string Server { get; set; } = "localhost";
/// <summary>Gets or sets the LDAP server port.</summary>
public int Port { get; set; } = 3893;
/// <summary>
/// Transport security for the LDAP connection — <see cref="LdapTransport.Ldaps"/> (implicit
/// TLS), <see cref="LdapTransport.StartTls"/> (upgrade), or <see cref="LdapTransport.None"/>
/// (plaintext, dev/test only — requires <see cref="AllowInsecure"/>). Replaces the former
/// <c>UseTls</c> bool (Task 1.4): <c>true</c>→<see cref="LdapTransport.Ldaps"/>,
/// <c>false</c>→<see cref="LdapTransport.None"/>.
/// </summary>
public LdapTransport Transport { get; set; } = LdapTransport.None;
/// <summary>Dev-only escape hatch — must be <c>false</c> in production. Maps to the shared
/// library's <see cref="LibLdapOptions.AllowInsecure"/> (renamed from <c>AllowInsecureLdap</c>).</summary>
public bool AllowInsecure { get; set; }
/// <summary>
/// Dev-only stub: when <c>true</c>, <see cref="OtOpcUaLdapAuthService"/> bypasses the real LDAP
/// bind and accepts any non-empty username/password, returning a single Administrator role
/// so the operator can navigate the full Admin UI. MUST be <c>false</c> in production.
/// </summary>
public bool DevStubMode { get; set; }
/// <summary>Gets or sets the LDAP search base DN.</summary>
public string SearchBase { get; set; } = "dc=zb,dc=local";
/// <summary>
/// Service-account DN used for search-then-bind. When empty, a direct-bind with
/// <c>cn={user},{SearchBase}</c> is attempted.
/// </summary>
public string ServiceAccountDn { get; set; } = string.Empty;
/// <summary>Gets or sets the service account password for LDAP authentication.</summary>
public string ServiceAccountPassword { get; set; } = string.Empty;
/// <summary>Gets or sets the LDAP attribute name for user display name.</summary>
public string DisplayNameAttribute { get; set; } = "cn";
/// <summary>Gets or sets the LDAP attribute name for group membership.</summary>
public string GroupAttribute { get; set; } = "memberOf";
/// <summary>
/// Attribute the service-account search matches the login name against to resolve the
/// user's DN. <c>cn</c> for GLAuth (the dev default); set <c>sAMAccountName</c> for
/// Active Directory.
/// </summary>
public string UserNameAttribute { get; set; } = "cn";
/// <summary>
/// Maps LDAP group name → Admin role. Group match is case-insensitive. A user gets every
/// role whose source group is in their membership list. Values are the canonical control-plane
/// roles (Task 1.7). Example dev mapping:
/// <code>"ReadOnly":"Viewer","ReadWrite":"Designer","AlarmAck":"Administrator"</code>
/// </summary>
public Dictionary<string, string> GroupToRole { get; set; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Projects the wire fields onto the shared <c>ZB.MOM.WW.Auth.Ldap</c>
/// <see cref="LibLdapOptions"/> the directory client consumes. App-only concerns
/// (<see cref="DevStubMode"/>, <see cref="GroupToRole"/>) have no library counterpart and are
/// handled by the app wrapper around the library service; <see cref="Enabled"/> is carried
/// through so the library's own feature gate stays consistent with the app master switch.
/// </summary>
public LibLdapOptions ToLibraryOptions() => new()
{
Enabled = Enabled,
Server = Server,
Port = Port,
Transport = Transport,
AllowInsecure = AllowInsecure,
SearchBase = SearchBase,
ServiceAccountDn = ServiceAccountDn,
ServiceAccountPassword = ServiceAccountPassword,
UserNameAttribute = UserNameAttribute,
DisplayNameAttribute = DisplayNameAttribute,
GroupAttribute = GroupAttribute,
};
}