Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.yaml.example linguist-language=YAML
251 changes: 103 additions & 148 deletions Classes/Command/UtilityCommandController.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Neos\Ldap\Command;

/*
Expand All @@ -11,78 +12,58 @@
* source code.
*/

use Symfony\Component\Yaml\Yaml;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Cli\CommandController;
use Neos\Flow\Cli\Exception\StopCommandException;
use Neos\Ldap\Service\DirectoryService;
use Neos\Utility\Arrays;
use Neos\Utility\Files;
use Neos\Ldap\Service\DirectoryService;
use Symfony\Component\Ldap\Entry;
use Symfony\Component\Ldap\Exception\ConnectionException;
use Symfony\Component\Ldap\Exception\LdapException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

/**
* Command controller to test settings and query the directory
*/
class UtilityCommandController extends CommandController
{

/**
* @Flow\InjectConfiguration(path="security.authentication.providers", package="Neos.Flow")
* @var array
* @var array[]
*/
protected $authenticationProvidersConfiguration;

/**
* @var array
*/
protected $options;
protected array $authenticationProvidersConfiguration = [];

/**
* Simple bind command to test if a bind is possible at all
*
* @param string $username Username to be used while binding
* @param string $password Password to be used while binding
* @param string $providerName Name of the authentication provider to use
* @param string $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @param string|null $username Username to be used while binding
* @param string|null $password Password to be used while binding
* @param string|null $providerName Name of the authentication provider to use
* @param string|null $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @return void
* @throws StopCommandException
*/
public function bindCommand($username = null, $password = null, $providerName = null, $settingsFile = null)
{
$directoryService = $this->getDirectoryService($providerName, $settingsFile);

try {
if ($username === null && $password === null) {
$result = ldap_bind($directoryService->getConnection());
$this->outputLine('Anonymous bind attempt %s', [$result === false ? 'failed' : 'succeeded']);
if ($result === false) {
$this->quit(1);
}
} else {
$directoryService->bind($username, $password);
$this->outputLine('Bind successful with user %s, using password is %s', [$username, $password === null ? 'NO' : 'YES']);
}
} catch (\Exception $exception) {
$this->outputLine('Failed to bind with username %s, using password is %s', [$username, $password === null ? 'NO' : 'YES']);
$this->outputLine($exception->getMessage());
$this->quit(1);
public function bindCommand(
string $username = null,
string $password = null,
string $providerName = null,
string $settingsFile = null
): void {
$options = $this->getOptions($providerName, $settingsFile);
$bindDn = isset($options['bind']['dn'])
? sprintf($options['bind']['dn'], $username ?? '')
: null;
$message = 'Attempt to bind ' . ($bindDn === null ? 'anonymously' : 'to ' . $bindDn);
if ($password !== null) {
$message .= ', using password,';
}
}

/**
* Try authenticating a user using a DirectoryService that's connected to a directory
*
* @param string $username The username to authenticate
* @param string $password The password to use while authenticating
* @param string $providerName Name of the authentication provider to use
* @param string $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @return void
*/
public function authenticateCommand($username, $password, $providerName = null, $settingsFile = null)
{
$directoryService = $this->getDirectoryService($providerName, $settingsFile);

try {
$directoryService->authenticate($username, $password);
$this->outputLine('Successfully authenticated %s with given password', [$username]);
} catch (\Exception $exception) {
new DirectoryService($options, $username, $password);
$this->outputLine($message . ' succeeded');
} catch (ConnectionException $exception) {
$this->outputLine($message . ' failed');
$this->outputLine($exception->getMessage());
$this->quit(1);
}
Expand All @@ -91,142 +72,116 @@ public function authenticateCommand($username, $password, $providerName = null,
/**
* Query the directory
*
* @param string $query The query to use, for example (objectclass=*)
* @param string $baseDn The base dn to search in
* @param string $providerName Name of the authentication provider to use
* @param string $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @param string $displayColumns Comma separated list of columns to show, like: dn,objectclass
* @param string $query The query to use, for example "(objectclass=*)"
* @param string|null $providerName Name of the authentication provider to use
* @param string|null $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @param string|null $displayColumns Comma separated list of columns to show, like "cn,objectclass"
* @param string|null $username Username to be used to bind
* @param string|null $password Password to be used to bind
* @return void
* @throws StopCommandException
*/
public function queryCommand(
$query,
$baseDn = null,
$providerName = null,
$settingsFile = null,
$displayColumns = 'dn'
) {
$directoryService = $this->getDirectoryService($providerName, $settingsFile);

if ($baseDn === null) {
$baseDn = Arrays::getValueByPath($this->options, 'baseDn');
}
string $baseDn,
string $query,
string $providerName = null,
string $settingsFile = null,
string $displayColumns = null,
string $username = null,
string $password = null
): void {
$options = $this->getOptions($providerName, $settingsFile);

$this->outputLine('Query: %s', [$query]);
$this->outputLine('Base DN: %s', [$baseDn]);
$this->outputLine('Query: %s', [$query]);

$searchResult = @ldap_search(
$directoryService->getConnection(),
$baseDn,
$query
);
$columns = $displayColumns === null ? null : Arrays::trimExplode(',', $displayColumns);

if ($searchResult === false) {
$this->outputLine(ldap_error($directoryService->getConnection()));
try {
$directoryService = new DirectoryService($options, $username, $password);
$entries = $directoryService->query($baseDn, $query, $columns);
} catch (ConnectionException|LdapException $exception) {
$this->outputLine($exception->getMessage());
$this->quit(1);
}

$this->outputLdapSearchResultTable($directoryService->getConnection(), $searchResult, $displayColumns);
}

/**
* @param string $providerName Name of the authentication provider to use
* @param string $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @return DirectoryService
* @throws \Neos\Flow\Mvc\Exception\StopActionException
*/
protected function getDirectoryService($providerName, $settingsFile)
{
$directoryServiceOptions = $this->getOptions($providerName, $settingsFile);
if (!is_array($directoryServiceOptions)) {
$this->outputLine('No configuration found for given providerName / settingsFile');
$this->quit(3);
}

return new DirectoryService('cli', $directoryServiceOptions);
$this->outputEntriesTable($entries);
}

/**
* Load options by provider name or by a settings file (first has precedence)
*
* @param string $providerName Name of the authentication provider to use
* @param string $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @return array|mixed
* @throws \Neos\Flow\Mvc\Exception\StopActionException
* @param string|null $providerName Name of the authentication provider to use
* @param string|null $settingsFile Path to a yaml file containing the settings to use for testing purposes
* @return array
* @throws StopCommandException
*/
protected function getOptions($providerName = null, $settingsFile = null)
protected function getOptions(string $providerName = null, string $settingsFile = null): array
{
if ($providerName !== null && array_key_exists($providerName, $this->authenticationProvidersConfiguration)) {
$this->options = $this->authenticationProvidersConfiguration[$providerName]['providerOptions'];
return $this->options;
if ($providerName !== null) {
if (isset($this->authenticationProvidersConfiguration[$providerName]['providerOptions'])
&& \is_array($this->authenticationProvidersConfiguration[$providerName]['providerOptions'])
) {
return $this->authenticationProvidersConfiguration[$providerName]['providerOptions'];
}
$this->outputLine('No configuration found for given providerName');
if ($settingsFile === null) {
$this->quit(3);
}
}

if ($settingsFile !== null) {
if (!file_exists($settingsFile)) {
if (!\file_exists($settingsFile)) {
$this->outputLine('Could not find settings file on path %s', [$settingsFile]);
$this->quit(1);
}
$this->options = Yaml::parse(Files::getFileContents($settingsFile));
return $this->options;
try {
// Yaml::parseFile() introduced in symfony/yaml 3.4.0
// When above is required, we can drop dependency on neos/utility-files
$directoryServiceOptions = method_exists(Yaml::class, 'parseFile')
? Yaml::parseFile($settingsFile)
: Yaml::parse(Files::getFileContents($settingsFile));
} catch (ParseException $exception) {
$this->outputLine($exception->getMessage());
$this->quit(3);
}
if (!\is_array($directoryServiceOptions)) {
$this->outputLine('No configuration found in given settingsFile');
$this->quit(3);
}
return $directoryServiceOptions;
}

$this->outputLine('Neither providerName or settingsFile is passed as argument. You need to pass one of those.');
$this->outputLine(
'Neither providerName nor settingsFile is passed as argument. You need to pass one of those.'
);
$this->quit(1);
}

/**
* Outputs a table for given search result
* Outputs a table for given entries
*
* @param resource $connection
* @param resource $searchResult
* @param $displayColumns
* @param Entry[] $entries
* @return void
*/
protected function outputLdapSearchResultTable($connection, $searchResult, $displayColumns)
protected function outputEntriesTable(array $entries): void
{
$headers = [];
$headers = ['dn'];
$rows = [];

$displayColumns = Arrays::trimExplode(',', $displayColumns);

$entries = ldap_get_entries($connection, $searchResult);
$this->outputLine('%s results found', [$entries['count']]);
$this->outputLine('%s results found', [\count($entries)]);

foreach ($entries as $index => $ldapSearchResult) {
if ($index === 'count') {
continue;
}

if ($headers === []) {
foreach ($ldapSearchResult as $propertyName => $propertyValue) {
if (is_integer($propertyName)) {
continue;
}
if ($displayColumns === null || in_array($propertyName, $displayColumns)) {
$headers[] = $propertyName;
}
foreach ($entries as $index => $entry) {
$rows[$index] = ['dn' => $entry->getDn()];
foreach ($entry->getAttributes() as $propertyName => $propertyValue) {
if ($index === 0) {
$headers[] = $propertyName;
}
}

$row = [];
foreach ($ldapSearchResult as $propertyName => $propertyValue) {
if (is_integer($propertyName)) {
continue;
}
if ($displayColumns !== null && !in_array($propertyName, $displayColumns)) {
continue;
}

if (isset($propertyValue['count'])) {
unset($propertyValue['count']);
}

if (is_array($propertyValue)) {
$row[$propertyName] = implode(", ", $propertyValue);
} else {
$row[$propertyName] = $propertyValue;
}
$rows[$index][$propertyName] = \is_array($propertyValue)
? implode(', ', $propertyValue)
: $propertyValue;
}
$rows[] = $row;
}

$this->output->outputTable($rows, $headers);
Expand Down
Loading