Files
scadaproj/ZB.MOM.WW.Auth/src/ZB.MOM.WW.Auth.Ldap/Internal/ILdapConnection.cs
T
Joseph Doherty b8d3fa2ba1 fix(auth): prove the AD domain of a u:DOMAIN\name user bind
Auth-007's sAMAccountName match assumed the search base covers one AD
domain; across a forest (Global Catalog) it could accept another domain's
same-named account and return its groups. For a u:DOMAIN\name Who-am-I
answer the entry must now also be that domain's account: AD's
msDS-PrincipalName (objectSid-derived DOMAIN\name, read by a base search
through the new ILdapConnection.Read) must equal the answer where offered;
otherwise the name must have exactly one holder under SearchBase and it must
be the entry found. Three forest cases fail pre-fix; single-domain and
sAMAccountName-search compat cases pass both ways. README notes this and
Auth-009.

(scadaproj#50)

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 15:18:46 -05:00

71 lines
3.1 KiB
C#

namespace ZB.MOM.WW.Auth.Ldap.Internal;
using System.Net.Security;
using ZB.MOM.WW.Auth.Abstractions.Ldap;
/// <summary>
/// A single LDAP search result entry: its DN and a flat attribute bag.
/// </summary>
internal sealed record LdapSearchEntry(
string Dn,
IReadOnlyDictionary<string, IReadOnlyList<string>> Attributes);
/// <summary>
/// Abstraction over a single LDAP connection. Allows unit-testing
/// <c>LdapAuthService</c> without a live directory server.
/// </summary>
internal interface ILdapConnection : IDisposable
{
/// <summary>
/// Opens (and optionally upgrades to TLS) a connection to the given host.
/// </summary>
/// <param name="host">The LDAP server hostname or IP.</param>
/// <param name="port">The LDAP server port.</param>
/// <param name="transport">The transport security mode.</param>
/// <param name="allowInsecure">
/// When <see langword="true"/> AND no <paramref name="serverCertificateValidationCallback"/> is
/// supplied, TLS server-certificate validation is bypassed (dev/test only). Ignored when a
/// validation callback is supplied (the callback wins) or for plaintext transport.
/// </param>
/// <param name="timeoutMs">The connection/operation timeout in milliseconds.</param>
/// <param name="serverCertificateValidationCallback">
/// Optional TLS server-certificate validation callback. When <see langword="null"/>, the OS trust
/// store is used (the client does not blind-accept).
/// </param>
void Connect(
string host,
int port,
LdapTransport transport,
bool allowInsecure,
int timeoutMs,
RemoteCertificateValidationCallback? serverCertificateValidationCallback);
/// <summary>Binds with the supplied DN and password. Throws <c>LdapException</c> on bad credentials.</summary>
void Bind(string dn, string password);
/// <summary>Executes a subtree search and returns all matching entries.</summary>
IReadOnlyList<LdapSearchEntry> Search(string searchBase, string filter, IReadOnlyList<string> attributes);
/// <summary>
/// Reads one entry by DN (a base-scope search) and returns the requested attributes the server
/// sent, or <see langword="null"/> when the entry does not exist. Throws <c>LdapException</c> on
/// any other failure. Used for constructed attributes such as AD's <c>msDS-PrincipalName</c>,
/// which a server may compute for a base read but not for a subtree search.
/// </summary>
LdapSearchEntry? Read(string dn, IReadOnlyList<string> attributes);
/// <summary>
/// RFC 4532 "Who am I?": the authorization identity the directory associates with the current
/// bind, as the server sends it (<c>dn:&lt;dn&gt;</c>, <c>u:DOMAIN\name</c>, or empty for an
/// anonymous bind). Throws <c>LdapException</c> when the server does not support the operation
/// (GLAuth answers protocol error 2).
/// </summary>
string WhoAmI();
}
/// <summary>Factory that produces <see cref="ILdapConnection"/> instances.</summary>
internal interface ILdapConnectionFactory
{
ILdapConnection Create();
}