Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/load-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
uses: shivammathur/cache-extensions@v1
with:
php-version: '8.5'
extensions: swoole, pdo_sqlite, pcntl
key: load-test-ext-v1
extensions: swoole, pdo_sqlite, pcntl, opcache
key: load-test-ext-v2

- name: Cache extensions
uses: actions/cache@v3
Expand All @@ -52,9 +52,10 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
extensions: swoole, pdo_sqlite, pcntl
extensions: swoole, pdo_sqlite, pcntl, opcache
coverage: none
tools: composer:2.9
ini-values: opcache.enable_cli=1, opcache.jit_buffer_size=128M, opcache.jit=tracing

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
Expand Down Expand Up @@ -112,8 +113,8 @@ jobs:
uses: shivammathur/cache-extensions@v1
with:
php-version: '8.5'
extensions: swoole, pdo_mysql, pcntl
key: load-test-mysql-ext-v1
extensions: swoole, pdo_mysql, pcntl, opcache
key: load-test-mysql-ext-v2

- name: Cache extensions
uses: actions/cache@v3
Expand All @@ -126,9 +127,10 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
extensions: swoole, pdo_mysql, pcntl
extensions: swoole, pdo_mysql, pcntl, opcache
coverage: none
tools: composer:2.9
ini-values: opcache.enable_cli=1, opcache.jit_buffer_size=128M, opcache.jit=tracing

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ CHANGELOG

1.0.0 (202x-xx-xx)
------------------
* Removed `ServerOptions::setDseNamingContexts()`. RootDSE `namingContexts` is now derived from the backend.
* Updated the minimum version of PHP to version 8.1.
* All classes now use "strict_types=1". This internal change should not impact external usage.
* Client and server options arrays have been replaced by ClientOptions and ServerOptions classes.
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
"docker compose -f tests/resources/openldap/docker-compose.yml up -d --build --wait",
"LDAP_TESTS_ENABLED=1 php -d xdebug.mode=off vendor/bin/phpunit --testsuite integration"
],
"test-load": "@php -d xdebug.mode=off tests/bin/ldap-load-test.php --backend=sqlite --runner=swoole --duration=10 --warmup=2 --clients=8 --output=text",
"test-load-compare": "@php -d xdebug.mode=off tests/bin/ldap-bench-compare.php",
"test-load": "@php -d xdebug.mode=off -d opcache.enable_cli=1 -d opcache.jit_buffer_size=128M -d opcache.jit=tracing tests/bin/ldap-load-test.php --backend=sqlite --runner=swoole --duration=10 --warmup=2 --clients=8 --output=text",
"test-load-compare": "@php -d xdebug.mode=off -d opcache.enable_cli=1 -d opcache.jit_buffer_size=128M -d opcache.jit=tracing tests/bin/ldap-bench-compare.php",
"profile": "tests/profile/profile.sh",
"profile-up": "docker compose -f tests/profile/docker-compose.yml up -d --build --wait",
"profile-down": "docker compose -f tests/profile/docker-compose.yml down",
Expand Down
8 changes: 1 addition & 7 deletions docs/Server/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ LDAP Server Configuration
* [ServerOptions:setSchemaValidationMode](#setschemavalidationmode)
* [ServerOptions:setSchema](#setschema)
* [RootDSE Options](#rootdse-options)
* [ServerOptions:setDseNamingContexts](#setdsenamingcontexts)
* [ServerOptions:setDseAltServer](#setdsealtserver)
* [ServerOptions:setDseVendorName](#setdsevendorname)
* [ServerOptions:setDseVendorVersion](#setdsevendorversion)
Expand Down Expand Up @@ -423,12 +422,7 @@ Replaces the active schema used for validation and operational attributes. See

## RootDSE Options

------------------
#### setDseNamingContexts

The namingContexts attribute for the RootDSE as an array of strings.

**Default**: `['dc=FreeDSx,dc=local']`
The `namingContexts` attribute is derived from the backend. No configuration is needed.

------------------
#### setDseAltServer
Expand Down
23 changes: 9 additions & 14 deletions docs/Server/General-Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ use FreeDSx\Ldap\ServerOptions;

$passwordHash = '{SHA}' . base64_encode(sha1('secret', true));

$server = new LdapServer(
(new ServerOptions())->setDseNamingContexts('dc=example,dc=com')
);
$server = new LdapServer();

$server->useStorage(new InMemoryStorage([
new Entry(new Dn('dc=example,dc=com'), new Attribute('dc', 'example')),
Expand Down Expand Up @@ -94,9 +92,7 @@ use FreeDSx\Ldap\Server\Backend\Storage\Adapter\JsonFileStorage;
use FreeDSx\Ldap\ServerOptions;

$server = new LdapServer(
(new ServerOptions())
->setDseNamingContexts('dc=example,dc=com')
->setSaslMechanisms(ServerOptions::SASL_PLAIN)
(new ServerOptions())->setSaslMechanisms(ServerOptions::SASL_PLAIN),
);

$server->useStorage(JsonFileStorage::forPcntl('/var/lib/myapp/ldap.json'));
Expand Down Expand Up @@ -152,12 +148,10 @@ class MyAuthenticator implements PasswordAuthenticatableInterface
}

$server = new LdapServer(
(new ServerOptions())
->setDseNamingContexts('dc=example,dc=com')
->setSaslMechanisms(
ServerOptions::SASL_PLAIN,
ServerOptions::SASL_SCRAM_SHA_256,
)
(new ServerOptions())->setSaslMechanisms(
ServerOptions::SASL_PLAIN,
ServerOptions::SASL_SCRAM_SHA_256,
),
);

$server->useStorage(JsonFileStorage::forPcntl('/var/lib/myapp/ldap.json'));
Expand Down Expand Up @@ -919,8 +913,9 @@ $server = (new LdapServer())->usePasswordAuthenticator(new MyAuthenticator());

## Handling the RootDSE

The server generates a default RootDSE from `ServerOptions` values (`setDseNamingContexts()`, `setDseVendorName()`,
etc.). For most deployments this is sufficient. The default entry always advertises:
The server generates a default RootDSE. `namingContexts` is derived from the backend (storage contents, or whatever a
custom backend declares); other attributes such as `vendorName` come from `ServerOptions`. For most deployments this is
sufficient. The default entry always advertises:

- `supportedControl`: paging (RFC 2696)
- `supportedExtension`: WhoAmI (RFC 4532), Password Modify (RFC 3062), and StartTLS (RFC 4511) if an SSL certificate is configured
Expand Down
3 changes: 1 addition & 2 deletions src/FreeDSx/Ldap/LdapServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ public function useStorage(EntryStorageInterface $storage): self
storage: $storage,
limits: $this->options->makeSearchLimits(),
validator: $this->buildSchemaValidator(),
namingContexts: $this->options->getDseNamingContexts(),
operationalAttrs: new OperationalAttributeGenerator($schema),
));
}
Expand Down Expand Up @@ -248,7 +247,7 @@ public function dump(

$output->write((new DirectoryDumper(
$backend,
$this->options->getDseNamingContexts(),
$backend->namingContexts(),
$this->options->getFilterEvaluator(),
))->dump($options));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ private function getRootDseHandler(): ServerProtocolHandler\ServerRootDseHandler
return new ServerProtocolHandler\ServerRootDseHandler(
options: $this->options,
queue: $this->queue,
backend: $this->handlerFactory->makeBackend(),
rootDseHandler: $this->handlerFactory->makeRootDseHandler(),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
use FreeDSx\Ldap\Protocol\Queue\ServerQueue;
use FreeDSx\Ldap\Server\Operation\OperationOutcomeResult;
use FreeDSx\Ldap\Server\Operation\OperationResult;
use FreeDSx\Ldap\Entry\Dn;
use FreeDSx\Ldap\Server\Backend\LdapBackendInterface;
use FreeDSx\Ldap\Server\RequestContext;
use FreeDSx\Ldap\Server\RequestHandler\RootDseHandlerInterface;
use FreeDSx\Ldap\Server\Token\TokenInterface;
Expand Down Expand Up @@ -53,6 +55,7 @@ class ServerRootDseHandler implements ServerProtocolHandlerInterface
public function __construct(
private readonly ServerOptions $options,
private readonly ServerQueue $queue,
private readonly LdapBackendInterface $backend,
private readonly ?RootDseHandlerInterface $rootDseHandler = null,
) {}

Expand All @@ -65,7 +68,10 @@ public function handleRequest(
TokenInterface $token,
): OperationResult {
$entry = Entry::fromArray('', [
'namingContexts' => $this->options->getDseNamingContexts(),
'namingContexts' => array_map(
fn(Dn $dn): string => $dn->toString(),
$this->backend->namingContexts(),
),
'subschemaSubentry' => [$this->options->getSubschemaEntry()->toString()],
'supportedControl' => [
Control::OID_PAGING,
Expand Down
7 changes: 7 additions & 0 deletions src/FreeDSx/Ldap/Server/Backend/LdapBackendInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ public function compare(
Dn $dn,
EqualityFilter $filter,
): bool;

/**
* Normalised DNs the backend hosts. Advertised by the server as RootDSE namingContexts.
*
* @return list<Dn>
*/
public function namingContexts(): array;
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public function querySubtree(): string;
*/
public function queryHasChildren(): string;

/**
* SELECT dn for entries whose parent is not in `entries` (i.e. naming-context roots). No parameters.
*/
public function queryNamingContexts(): string;

/**
* Upsert a single entry. Parameters: [lc_dn, dn, lc_parent_dn, attributes]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ public function queryHasChildren(): string
SQL;
}

public function queryNamingContexts(): string
{
return <<<SQL
SELECT dn
FROM entries
WHERE lc_parent_dn = ''
OR lc_parent_dn NOT IN (SELECT lc_dn FROM entries)
SQL;
}

public function queryDelete(): string
{
return <<<SQL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,9 @@ public function atomic(callable $operation): void
{
$operation($this);
}

public function namingContexts(): array
{
return $this->namingContextsFromArray($this->entries);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ public function atomic(callable $operation): void
});
}

public function namingContexts(): array
{
return $this->namingContextsFromArray($this->read());
}

/**
* @param callable(string): string $mutation
*/
Expand Down
15 changes: 15 additions & 0 deletions src/FreeDSx/Ldap/Server/Backend/Storage/Adapter/PdoStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,21 @@ public function hasChildren(Dn $dn): bool
return $stmt->fetch() !== false;
}

public function namingContexts(): array
{
$stmt = $this->prepareAndExecute($this->dialect->queryNamingContexts());

$contexts = [];
while (($row = $stmt->fetch()) !== false) {
if (!is_array($row) || !isset($row['dn']) || !is_string($row['dn'])) {
continue;
}
$contexts[] = (new Dn($row['dn']))->normalize();
}

return $contexts;
}

public function atomic(callable $operation): void
{
$pdo = $this->provider->get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ private function sortedStreamFromArray(
);
}

/**
* @param array<string, Entry> $entries Entries keyed by normalised DN string
* @return list<Dn>
*/
private function namingContextsFromArray(array $entries): array
{
$roots = [];
foreach (array_keys($entries) as $normDn) {
$parent = (new Dn($normDn))->getParent()?->normalize()->toString() ?? '';
if ($parent === '' || !isset($entries[$parent])) {
$roots[] = new Dn($normDn);
}
}

return $roots;
}

/**
* @param array<string, Entry> $entries Entries keyed by normalised DN string
* @return Generator<Entry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ public function atomic(callable $operation): void
$operation($this);
}

public function namingContexts(): array
{
$roots = [];

foreach (array_keys($this->data) as $normDn) {
$parent = (new Dn($normDn))->getParent()?->normalize()->toString() ?? '';
if ($parent === '' || !isset($this->data[$parent])) {
$roots[] = new Dn($normDn);
}
}

return $roots;
}

/**
* @return array<string, mixed>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public function atomic(callable $operation): void
});
}

public function namingContexts(): array
{
return $this->reads->namingContexts();
}

public function reset(): void
{
if ($this->reads instanceof ResettableInterface) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,11 @@ public function remove(Dn $dn): void;
* @param callable(EntryStorageInterface): void $operation
*/
public function atomic(callable $operation): void;

/**
* Normalised DNs of entries whose parent is not in storage. Advertised by the server as RootDSE namingContexts.
*
* @return list<Dn>
*/
public function namingContexts(): array;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
final readonly class DirectoryDumper
{
/**
* @param string[] $namingContexts dump roots when DumpOptions::baseDn is not set
* @param list<Dn> $namingContexts dump roots when DumpOptions::baseDn is not set
*/
public function __construct(
private WritableStorageBackend $backend,
Expand Down Expand Up @@ -71,10 +71,7 @@ private function resolveBases(DumpOptions $options): array
return [$options->getBaseDn()];
}

return array_values(array_map(
fn(string $namingContext): Dn => new Dn($namingContext),
$this->namingContexts,
));
return $this->namingContexts;
}

/**
Expand Down
Loading
Loading