Files
projectsend/tests/Support/FakeLdapDirectory.php
T
veenone 9c43f9cb9a Let directory clients sign in with their username as well as their address
LDAP sign-in only took an email address. The LDAP settings now have an
optional username attribute (cn, uid, sAMAccountName and so on). Once it
is set, the login field also takes a username. The service account looks
the username up, and the login carries on with the address the directory
holds for it, through the same checks, single user bind, provisioning
and rate limiting as an email login.

Whether the input is an address is decided by the same email rule that
accepted every stored address, so an address such as someone@localhost
is never taken for a username. The username goes through the query
builder, so it is escaped, and it has to match exactly one entry. The
directory is client-only, so a username never signs in a staff account.
With the attribute left empty, nothing changes.

This ports feat/ldap_signin_by_username, which was written against v1
and has no history in common with this codebase.
2026-10-05 07:51:21 +07:00

72 lines
2.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Support;
use App\Modules\Identity\Ldap\LdapDirectory;
use App\Modules\Identity\Ldap\LdapIdentity;
use App\Modules\Identity\Ldap\LdapProbeResult;
/**
* A directory that lives in an array.
*
* Swapped in with `$this->swap(LdapDirectory::class, ...)`, this exercises
* everything above the wire — which account types may authenticate,
* provisioning, the two-factor hand-off, rate limiting — with no server
* anywhere. It also counts calls, so a test can assert the directory was
* *not* consulted, which is how the "staff never reach LDAP" and "a valid
* local password costs no directory traffic" properties are proven.
*/
class FakeLdapDirectory implements LdapDirectory
{
public int $calls = 0;
/** @var list<string> */
public array $attemptedEmails = [];
/** @var list<string> */
public array $lookedUpUsernames = [];
/**
* @param array<string, array{password: string, name?: string, dn?: string, username?: string}> $entries keyed by email
*/
public function __construct(private readonly array $entries = []) {}
public function authenticate(string $email, string $password): ?LdapIdentity
{
$this->calls++;
$this->attemptedEmails[] = $email;
$entry = $this->entries[$email] ?? null;
if ($entry === null || $entry['password'] !== $password) {
return null;
}
return new LdapIdentity(
dn: $entry['dn'] ?? "uid={$email},ou=people,dc=example,dc=test",
email: $email,
name: $entry['name'] ?? 'Directory Person',
);
}
public function emailForUsername(string $username): ?string
{
$this->calls++;
$this->lookedUpUsernames[] = $username;
$matches = array_keys(array_filter(
$this->entries,
fn (array $entry): bool => ($entry['username'] ?? null) === $username,
));
return count($matches) === 1 ? $matches[0] : null;
}
public function probe(?string $email = null, ?string $password = null): LdapProbeResult
{
return LdapProbeResult::ok(LdapProbeResult::STAGE_SERVICE_BIND, 'Fake directory reachable.');
}
}