docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
This commit is contained in:
@@ -20,6 +20,10 @@ public sealed class CookieAuthenticationStateProvider : AuthenticationStateProvi
|
||||
private ClaimsPrincipal _current;
|
||||
private Task? _pingLoop;
|
||||
|
||||
/// <summary>Initializes a new instance of the CookieAuthenticationStateProvider class.</summary>
|
||||
/// <param name="initial">The initial claims principal from circuit boot.</param>
|
||||
/// <param name="http">The HTTP client for authentication ping requests.</param>
|
||||
/// <param name="logger">The logger for diagnostic messages.</param>
|
||||
public CookieAuthenticationStateProvider(
|
||||
ClaimsPrincipal initial,
|
||||
HttpClient http,
|
||||
@@ -30,6 +34,7 @@ public sealed class CookieAuthenticationStateProvider : AuthenticationStateProvi
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
_pingLoop ??= Task.Run(() => PingLoopAsync(_cts.Token));
|
||||
@@ -60,6 +65,7 @@ public sealed class CookieAuthenticationStateProvider : AuthenticationStateProvi
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_cts.Cancel();
|
||||
|
||||
@@ -4,6 +4,7 @@ public sealed class OtOpcUaCookieOptions
|
||||
{
|
||||
public const string SectionName = "Security:Cookie";
|
||||
|
||||
/// <summary>Gets or sets the cookie name.</summary>
|
||||
public string Name { get; set; } = "OtOpcUa.Auth";
|
||||
|
||||
/// <summary>Idle sliding window, in minutes (default 30).</summary>
|
||||
|
||||
@@ -16,8 +16,12 @@ public static class AuthEndpoints
|
||||
/// <summary>JSON body schema for API-side login callers (kept stable for tests).</summary>
|
||||
public sealed record LoginRequest(string Username, string Password);
|
||||
|
||||
/// <summary>Response for a token issue request.</summary>
|
||||
public sealed record TokenResponse(string Token);
|
||||
|
||||
/// <summary>Maps OtOpcUa authentication endpoints to the application route builder.</summary>
|
||||
/// <param name="app">The endpoint route builder.</param>
|
||||
/// <returns>The endpoint route builder for chaining.</returns>
|
||||
public static IEndpointRouteBuilder MapOtOpcUaAuth(this IEndpointRouteBuilder app)
|
||||
{
|
||||
// The login endpoint serves two callers with different ergonomics:
|
||||
|
||||
@@ -8,7 +8,9 @@ public sealed class JwtOptions
|
||||
/// <summary>HS256 signing key. Must be at least 32 bytes (256 bits) UTF-8.</summary>
|
||||
public string SigningKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the JWT issuer identifier.</summary>
|
||||
public string Issuer { get; set; } = "otopcua";
|
||||
/// <summary>Gets or sets the JWT audience identifier.</summary>
|
||||
public string Audience { get; set; } = "otopcua";
|
||||
|
||||
/// <summary>Default token expiry. Mirrors ScadaLink (15 min).</summary>
|
||||
|
||||
@@ -16,6 +16,9 @@ public sealed class JwtTokenService
|
||||
private readonly JwtOptions _options;
|
||||
private readonly ILogger<JwtTokenService> _logger;
|
||||
|
||||
/// <summary>Initializes a new instance of the JwtTokenService class.</summary>
|
||||
/// <param name="options">The JWT options.</param>
|
||||
/// <param name="logger">The logger for the service.</param>
|
||||
public JwtTokenService(IOptions<JwtOptions> options, ILogger<JwtTokenService> logger)
|
||||
{
|
||||
_options = options.Value;
|
||||
@@ -32,6 +35,11 @@ public sealed class JwtTokenService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Issues a JWT token for the specified user with the given roles.</summary>
|
||||
/// <param name="displayName">The display name of the user.</param>
|
||||
/// <param name="username">The username of the user.</param>
|
||||
/// <param name="roles">The roles assigned to the user.</param>
|
||||
/// <returns>The JWT token string.</returns>
|
||||
public string Issue(string displayName, string username, IReadOnlyList<string> roles)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SigningKey));
|
||||
@@ -55,6 +63,10 @@ public sealed class JwtTokenService
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
/// <summary>Attempts to validate a JWT token and extract the claims principal.</summary>
|
||||
/// <param name="token">The JWT token to validate.</param>
|
||||
/// <param name="principal">The claims principal extracted from the token, or null if validation failed.</param>
|
||||
/// <returns>True if the token is valid; otherwise false.</returns>
|
||||
public bool TryValidate(string token, out ClaimsPrincipal? principal)
|
||||
{
|
||||
principal = null;
|
||||
|
||||
@@ -2,5 +2,10 @@ namespace ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
||||
|
||||
public interface ILdapAuthService
|
||||
{
|
||||
/// <summary>Authenticates a user against the LDAP service.</summary>
|
||||
/// <param name="username">The username to authenticate.</param>
|
||||
/// <param name="password">The password to verify.</param>
|
||||
/// <param name="ct">A cancellation token.</param>
|
||||
/// <returns>A task that returns the authentication result.</returns>
|
||||
Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
||||
{
|
||||
private readonly LdapOptions _options = options.Value;
|
||||
|
||||
/// <summary>Authenticates a user via LDAP bind and retrieves their group memberships and roles.</summary>
|
||||
/// <param name="username">The username to authenticate.</param>
|
||||
/// <param name="password">The password to validate against the LDAP directory.</param>
|
||||
/// <param name="ct">A cancellation token to observe while waiting for the operation to complete.</param>
|
||||
public async Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
@@ -130,6 +134,9 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
||||
: $"cn={username},{_options.SearchBase}";
|
||||
}
|
||||
|
||||
/// <summary>Escapes special characters in an LDAP filter string according to RFC 4515.</summary>
|
||||
/// <param name="input">The unescaped string to escape.</param>
|
||||
/// <returns>The escaped LDAP filter string.</returns>
|
||||
internal static string EscapeLdapFilter(string input) =>
|
||||
input.Replace("\\", "\\5c")
|
||||
.Replace("*", "\\2a")
|
||||
@@ -142,6 +149,8 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
||||
/// group as an <c>ou=</c> RDN immediately above the user's <c>cn=</c>, so this recovers
|
||||
/// the group name when <see cref="LdapOptions.GroupAttribute"/> is absent from the entry.
|
||||
/// </summary>
|
||||
/// <param name="dn">The distinguished name to extract the OU from.</param>
|
||||
/// <returns>The extracted OU value, or null if no OU segment is found.</returns>
|
||||
internal static string? ExtractOuSegment(string dn)
|
||||
{
|
||||
var segments = dn.Split(',');
|
||||
@@ -154,6 +163,9 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Extracts the value portion of the first RDN (relative distinguished name) from a DN.</summary>
|
||||
/// <param name="dn">The distinguished name to extract from.</param>
|
||||
/// <returns>The value of the first RDN.</returns>
|
||||
internal static string ExtractFirstRdnValue(string dn)
|
||||
{
|
||||
var equalsIdx = dn.IndexOf('=');
|
||||
|
||||
@@ -9,9 +9,16 @@ public sealed class LdapOptions
|
||||
{
|
||||
public const string SectionName = "Authentication: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>Gets or sets a value indicating whether to use TLS for LDAP connection.</summary>
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
/// <summary>Dev-only escape hatch — must be <c>false</c> in production.</summary>
|
||||
@@ -24,6 +31,7 @@ public sealed class LdapOptions
|
||||
/// </summary>
|
||||
public bool DevStubMode { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the LDAP search base DN.</summary>
|
||||
public string SearchBase { get; set; } = "dc=lmxopcua,dc=local";
|
||||
|
||||
/// <summary>
|
||||
@@ -31,9 +39,14 @@ public sealed class LdapOptions
|
||||
/// <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>
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
||||
/// </summary>
|
||||
public static class RoleMapper
|
||||
{
|
||||
/// <summary>Maps LDAP groups to roles using the provided group-to-role mapping dictionary.</summary>
|
||||
/// <param name="ldapGroups">The LDAP groups to map.</param>
|
||||
/// <param name="groupToRole">The mapping dictionary from LDAP groups to roles.</param>
|
||||
/// <returns>The list of roles corresponding to the LDAP groups.</returns>
|
||||
public static IReadOnlyList<string> Map(
|
||||
IReadOnlyCollection<string> ldapGroups,
|
||||
IReadOnlyDictionary<string, string> groupToRole)
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace ZB.MOM.WW.OtOpcUa.Security;
|
||||
internal sealed class ConfigureJwtBearerFromTokenService(JwtTokenService tokenService)
|
||||
: IPostConfigureOptions<JwtBearerOptions>
|
||||
{
|
||||
/// <summary>Configures JWT bearer options from the token service.</summary>
|
||||
/// <param name="name">The options name.</param>
|
||||
/// <param name="options">The JWT bearer options to configure.</param>
|
||||
public void PostConfigure(string? name, JwtBearerOptions options)
|
||||
{
|
||||
if (name != JwtBearerDefaults.AuthenticationScheme) return;
|
||||
@@ -36,6 +39,8 @@ public static class ServiceCollectionExtensions
|
||||
/// tools, scripts). DataProtection keys persist to the shared ConfigDb so cookies survive
|
||||
/// failover between nodes.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="configuration">The application configuration.</param>
|
||||
public static IServiceCollection AddOtOpcUaAuth(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddOptions<JwtOptions>().Bind(configuration.GetSection(JwtOptions.SectionName));
|
||||
|
||||
Reference in New Issue
Block a user