feat(auth): LdapAuthService user-bind flow (bind as the user, read own groups on the same connection)
This commit is contained in:
@@ -16,6 +16,8 @@ internal sealed class FakeLdapConnection : ILdapConnection
|
||||
private bool _throwOnConnect;
|
||||
private bool _throwOnServiceBind;
|
||||
private bool _throwOnUserBind;
|
||||
private int? _firstBindResultCode;
|
||||
private bool _throwOnSearch;
|
||||
|
||||
// ---- observation -----
|
||||
|
||||
@@ -32,6 +34,9 @@ internal sealed class FakeLdapConnection : ILdapConnection
|
||||
/// </summary>
|
||||
public int BindAttempts { get; private set; }
|
||||
|
||||
/// <summary>Every call in order: <c>connect</c>, <c>bind:<dn></c>, <c>search:<filter></c>.</summary>
|
||||
public List<string> Calls { get; } = new();
|
||||
|
||||
// ---- builder methods -----
|
||||
|
||||
/// <summary>
|
||||
@@ -83,6 +88,24 @@ internal sealed class FakeLdapConnection : ILdapConnection
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw <see cref="Novell.Directory.Ldap.LdapException"/> with <paramref name="resultCode"/> on the
|
||||
/// FIRST bind. In user-bind mode the first bind is the user's own, so 49 simulates a wrong
|
||||
/// password and e.g. 52 (Unavailable) a domain controller that cannot answer.
|
||||
/// </summary>
|
||||
public FakeLdapConnection ThrowOnFirstBind(int resultCode)
|
||||
{
|
||||
_firstBindResultCode = resultCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Throw <see cref="Novell.Directory.Ldap.LdapException"/> from <see cref="Search"/> (unhealthy directory).</summary>
|
||||
public FakeLdapConnection ThrowOnSearch()
|
||||
{
|
||||
_throwOnSearch = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw <see cref="Novell.Directory.Ldap.LdapException"/> from <see cref="Connect"/> to
|
||||
/// simulate an unreachable directory (infrastructure failure).
|
||||
@@ -120,6 +143,7 @@ internal sealed class FakeLdapConnection : ILdapConnection
|
||||
int timeoutMs,
|
||||
RemoteCertificateValidationCallback? serverCertificateValidationCallback = null)
|
||||
{
|
||||
Calls.Add("connect");
|
||||
ConnectArgs = (host, port, transport, allowInsecure, timeoutMs);
|
||||
ConnectCertCallback = serverCertificateValidationCallback;
|
||||
if (_throwOnConnect)
|
||||
@@ -130,8 +154,12 @@ internal sealed class FakeLdapConnection : ILdapConnection
|
||||
public void Bind(string dn, string password)
|
||||
{
|
||||
BindAttempts++;
|
||||
Calls.Add($"bind:{dn}");
|
||||
var isServiceBind = BindAttempts == 1;
|
||||
|
||||
if (isServiceBind && _firstBindResultCode is { } code)
|
||||
throw new Novell.Directory.Ldap.LdapException("Bind refused", code, dn);
|
||||
|
||||
if ((_throwOnServiceBind && isServiceBind)
|
||||
|| (_throwOnUserBind && !isServiceBind)
|
||||
|| _throwBindDns.Contains(dn))
|
||||
@@ -147,7 +175,13 @@ internal sealed class FakeLdapConnection : ILdapConnection
|
||||
string searchBase,
|
||||
string filter,
|
||||
IReadOnlyList<string> attributes)
|
||||
=> _scriptedEntries.AsReadOnly();
|
||||
{
|
||||
Calls.Add($"search:{filter}");
|
||||
if (_throwOnSearch)
|
||||
throw new Novell.Directory.Ldap.LdapException(
|
||||
"Search failed", Novell.Directory.Ldap.LdapException.Unavailable, searchBase);
|
||||
return _scriptedEntries.AsReadOnly();
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to clean up */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user