diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..d3c2122
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.yaml.example linguist-language=YAML
diff --git a/Classes/Command/UtilityCommandController.php b/Classes/Command/UtilityCommandController.php
index c4534a0..13f58b9 100644
--- a/Classes/Command/UtilityCommandController.php
+++ b/Classes/Command/UtilityCommandController.php
@@ -1,4 +1,5 @@
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);
}
@@ -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);
diff --git a/Classes/Security/Authentication/Provider/LdapProvider.php b/Classes/Security/Authentication/Provider/LdapProvider.php
index 6325851..1eeb5a9 100644
--- a/Classes/Security/Authentication/Provider/LdapProvider.php
+++ b/Classes/Security/Authentication/Provider/LdapProvider.php
@@ -1,4 +1,5 @@
directoryService = new DirectoryService($name, $options);
- }
+ protected array $rolesConfiguration = [];
/**
- * Authenticate the current token. If it's not possible to connect to the LDAP server the provider
- * tries to authenticate against cached credentials in the database that were
- * cached on the last successful login for the user to authenticate.
+ * Authenticate the current token. If it's not possible to connect to the LDAP server the provider tries to
+ * authenticate against cached credentials in the database that were cached on the last successful login for the
+ * user to authenticate.
*
* @param TokenInterface $authenticationToken The token to be authenticated
- * @throws UnsupportedAuthenticationTokenException
* @return void
+ * @throws MissingConfigurationException
+ * @throws UnsupportedAuthenticationTokenException
+ * @throws InvalidConfigurationTypeException
+ * @throws SecurityException
*/
public function authenticate(TokenInterface $authenticationToken)
{
if (!($authenticationToken instanceof UsernamePassword)) {
- throw new UnsupportedAuthenticationTokenException('This provider cannot authenticate the given token.', 1217339840);
+ throw new UnsupportedAuthenticationTokenException(
+ 'This provider cannot authenticate the given token.',
+ 1217339840
+ );
}
$credentials = $authenticationToken->getCredentials();
- if (!is_array($credentials) || !isset($credentials['username'])) {
- $authenticationToken->setAuthenticationStatus(TokenInterface::NO_CREDENTIALS_GIVEN);
+ if (!\is_array($credentials) || !isset($credentials['username'], $credentials['password'])) {
+ try {
+ $authenticationToken->setAuthenticationStatus(TokenInterface::NO_CREDENTIALS_GIVEN);
+ } catch (InvalidAuthenticationStatusException $exception) {
+ // This exception is never thrown
+ }
return;
}
+ // Retrieve account for the credentials
+ $account = $this->accountRepository->findActiveByAccountIdentifierAndAuthenticationProviderName(
+ $credentials['username'],
+ $this->name
+ );
try {
- $ldapUser = $this->directoryService->authenticate($credentials['username'], $credentials['password']);
+ $this->directoryService = new DirectoryService(
+ $this->options,
+ $credentials['username'],
+ $credentials['password']
+ );
+ $ldapUserData = $this->directoryService->getUserData($credentials['username']);
- // Retrieve or create account for the credentials
- $account = $this->accountRepository->findActiveByAccountIdentifierAndAuthenticationProviderName($credentials['username'], $this->name);
+ // Create account if not existent
if ($account === null) {
- $account = $this->createAccountForCredentials($credentials);
- $this->emitAccountCreated($account, $ldapUser);
+ $account = $this->createAccount($credentials, $ldapUserData);
+ if ($account === null) {
+ throw new LdapException('Only existing accounts allowed');
+ }
+ $this->emitAccountCreated($account, $ldapUserData);
}
+ } catch (ConnectionException|LdapException $exception) {
+ try {
+ $authenticationToken->setAuthenticationStatus(TokenInterface::WRONG_CREDENTIALS);
+ if ($account !== null) {
+ $account->authenticationAttempted(TokenInterface::WRONG_CREDENTIALS);
+ $this->accountRepository->update($account);
+ $this->persistenceManager->allowObject($account);
+ }
+ } catch (InvalidAuthenticationStatusException|IllegalObjectTypeException $exception) {
+ // This exception is never thrown
+ }
+ return;
+ }
- // Map security roles to account
- $this->setRoles($account, $ldapUser);
- $this->emitRolesSet($account, $ldapUser);
+ // Map security roles to account
+ $this->setRoles($account, $ldapUserData);
+ $this->emitRolesSet($account, $ldapUserData);
- // Mark authentication successful
+ // Mark authentication successful
+ try {
+ $account->authenticationAttempted(TokenInterface::AUTHENTICATION_SUCCESSFUL);
+ $this->accountRepository->update($account);
$authenticationToken->setAuthenticationStatus(TokenInterface::AUTHENTICATION_SUCCESSFUL);
- $authenticationToken->setAccount($account);
- $this->emitAccountAuthenticated($account, $ldapUser);
- } catch (\Exception $exception) {
- $this->logger->alert('Authentication failed: ' . $exception->getMessage());
- $authenticationToken->setAuthenticationStatus(TokenInterface::WRONG_CREDENTIALS);
+ } catch (InvalidAuthenticationStatusException|IllegalObjectTypeException $exception) {
+ // This exception is never thrown
}
+ $this->persistenceManager->allowObject($account);
+ $authenticationToken->setAccount($account);
+ $this->emitAccountAuthenticated($account, $ldapUserData);
}
/**
- * Create a new account for the given credentials. Return null if you
- * do not want to create a new account, that is, only authenticate
- * existing accounts from the database and fail on new logins.
+ * @Flow\Signal
+ * @param Account $account
+ * @param string[][] $ldapUserData
+ * @return void
+ */
+ public function emitAccountCreated(Account $account, array $ldapUserData): void
+ {
+ }
+
+ /**
+ * @Flow\Signal
+ * @param Account $account
+ * @param string[][] $ldapUserData
+ * @return void
+ */
+ public function emitAccountAuthenticated(Account $account, array $ldapUserData): void
+ {
+ }
+
+ /**
+ * @Flow\Signal
+ * @param Account $account
+ * @param string[][] $ldapUserData
+ * @return void
+ */
+ public function emitRolesSet(Account $account, array $ldapUserData): void
+ {
+ }
+
+ /**
+ * Create a new account for the given credentials. Return null if you do not want to create a new account, that is,
+ * only authenticate existing accounts from the database and fail on new logins.
*
- * @param array $credentials array containing username and password
- * @return Account
+ * @param string[] $credentials array containing username and password
+ * @param string[][] $ldapUserData
+ * @return Account|null
*/
- protected function createAccountForCredentials(array $credentials)
+ protected function createAccount(array $credentials, array $ldapUserData): ?Account
{
$account = new Account();
$account->setAccountIdentifier($credentials['username']);
$account->setAuthenticationProviderName($this->name);
- $this->accountRepository->add($account);
+ try {
+ $this->accountRepository->add($account);
+ } catch (IllegalObjectTypeException $exception) {
+ // This exception is never thrown
+ }
return $account;
}
/**
- * Sets the roles for the Ldap account.
- * Extend this Provider class and implement this method to update the party
- *
* @param Account $account
- * @param array $ldapSearchResult
* @return void
*/
- protected function setRoles(Account $account, array $ldapSearchResult)
+ protected function resetRoles(Account $account): void
{
- $this->setDefaultRoles($account);
- $this->setRolesMappedToUserDn($account, $ldapSearchResult);
- $this->setRolesBasedOnGroupMembership($account, $ldapSearchResult);
-
- $this->accountRepository->update($account);
+ try {
+ $account->setRoles([]);
+ } catch (\InvalidArgumentException $exception) {
+ // This exception is never thrown
+ }
}
/**
* Set all default roles
*
* @param Account $account
+ * @return void
+ * @throws InvalidConfigurationTypeException
+ * @throws SecurityException
*/
- protected function setDefaultRoles(Account $account)
+ protected function setDefaultRoles(Account $account): void
{
- if (!is_array($this->rolesConfiguration['default'])) {
+ if (!\is_array($this->rolesConfiguration['default'])) {
return;
}
foreach ($this->rolesConfiguration['default'] as $roleIdentifier) {
- $account->addRole($this->policyService->getRole($roleIdentifier));
+ try {
+ $account->addRole($this->policyService->getRole($roleIdentifier));
+ } catch (\InvalidArgumentException $exception) {
+ // This exception is never thrown
+ } catch (NoSuchRoleException $exception) {
+ // We ignore invalid roles
+ // todo: logging
+ continue;
+ }
}
}
/**
- * Map configured roles based on user dn
+ * Sets the roles for the Ldap account
+ *
+ * Extend this Provider class and implement this method to update the party
*
* @param Account $account
- * @param array $ldapSearchResult
+ * @param string[][] $ldapUserData
+ * @return void
+ * @throws InvalidConfigurationTypeException
+ * @throws SecurityException
*/
- protected function setRolesMappedToUserDn(Account $account, array $ldapSearchResult)
+ protected function setRoles(Account $account, array $ldapUserData): void
{
- if (!is_array($this->rolesConfiguration['userMapping'])) {
- return;
+ $this->resetRoles($account);
+ $this->setDefaultRoles($account);
+ $this->setRolesByUserProperties($account, $ldapUserData);
+ $this->setRolesByUserDn($account, $ldapUserData['dn'][0]);
+ try {
+ $this->setRolesByGroupDns($account, $this->directoryService->getGroupDnsOfUser($ldapUserData['dn'][0]));
+ } catch (MissingConfigurationException|LdapException $exception) {
+ // If groups cannot be retrieved, they won't get set
+ // todo: logging
}
- foreach ($this->rolesConfiguration['userMapping'] as $roleIdentifier => $userDns) {
- if (in_array($ldapSearchResult['dn'], $userDns)) {
- $account->addRole($this->policyService->getRole($roleIdentifier));
- }
+ try {
+ $this->accountRepository->update($account);
+ } catch (IllegalObjectTypeException $exception) {
+ // This exception is never thrown
}
}
@@ -180,50 +262,107 @@ protected function setRolesMappedToUserDn(Account $account, array $ldapSearchRes
* Map configured roles based on group membership
*
* @param Account $account
- * @param array $ldapSearchResult
+ * @param string[] $groupDns
+ * @return void
+ * @throws InvalidConfigurationTypeException
+ * @throws SecurityException
*/
- protected function setRolesBasedOnGroupMembership(Account $account, array $ldapSearchResult)
+ protected function setRolesByGroupDns(Account $account, array $groupDns): void
{
- if (!is_array($this->rolesConfiguration['groupMapping'])) {
+ if (!\is_array($this->rolesConfiguration['groupMapping'])) {
return;
}
- $memberOf = $this->directoryService->getMemberOf($ldapSearchResult['dn']);
- foreach ($this->rolesConfiguration['groupMapping'] as $roleIdentifier => $groupDns) {
- if (!empty(array_intersect($memberOf, $groupDns))) {
- $account->addRole($this->policyService->getRole($roleIdentifier));
+ foreach ($this->rolesConfiguration['groupMapping'] as $roleIdentifier => $roleGroupDns) {
+ if (\array_intersect($groupDns, $roleGroupDns) !== []) {
+ try {
+ $account->addRole($this->policyService->getRole($roleIdentifier));
+ } catch (\InvalidArgumentException $exception) {
+ // This exception is never thrown
+ } catch (NoSuchRoleException $exception) {
+ // We ignore invalid roles
+ // todo: logging
+ continue;
+ }
}
}
}
/**
+ * Map configured roles based on user dn
+ *
* @param Account $account
- * @param array $ldapSearchResult
+ * @param string $userDn
* @return void
- * @Flow\Signal
+ * @throws InvalidConfigurationTypeException
+ * @throws SecurityException
*/
- public function emitAccountCreated(Account $account, array $ldapSearchResult)
+ protected function setRolesByUserDn(Account $account, string $userDn): void
{
- }
+ if (!\is_array($this->rolesConfiguration['userMapping'])) {
+ return;
+ }
- /**
- * @param Account $account
- * @param array $ldapSearchResult
- * @return void
- * @Flow\Signal
- */
- public function emitAccountAuthenticated(Account $account, array $ldapSearchResult)
- {
+ foreach ($this->rolesConfiguration['userMapping'] as $roleIdentifier => $roleUserDns) {
+ if (\in_array($userDn, $roleUserDns, true)) {
+ try {
+ $account->addRole($this->policyService->getRole($roleIdentifier));
+ } catch (\InvalidArgumentException $exception) {
+ // This exception is never thrown
+ } catch (NoSuchRoleException $exception) {
+ // We ignore invalid roles
+ // todo: logging
+ continue;
+ }
+ }
+ }
}
/**
+ * Map configured roles base on user properties
+ *
* @param Account $account
- * @param array $ldapSearchResult
+ * @param string[][] $ldapUserData
* @return void
- * @Flow\Signal
+ * @throws InvalidConfigurationTypeException
+ * @throws SecurityException
*/
- public function emitRolesSet(Account $account, array $ldapSearchResult)
+ protected function setRolesByUserProperties(Account $account, array $ldapUserData): void
{
- }
+ if (!\is_array($this->rolesConfiguration['propertyMapping'])) {
+ return;
+ }
+ foreach ($this->rolesConfiguration['propertyMapping'] as $roleIdentifier => $propertyConditions) {
+ try {
+ $role = $this->policyService->getRole($roleIdentifier);
+ } catch (NoSuchRoleException $e) {
+ // We ignore invalid roles
+ // todo: logging
+ continue;
+ }
+
+ foreach ($propertyConditions as $propertyName => $conditions) {
+ if (!isset($ldapUserData[$propertyName])) {
+ continue;
+ }
+
+ if (!\is_array($conditions)) {
+ $conditions = [$conditions];
+ }
+ foreach ($conditions as $condition) {
+ foreach ($ldapUserData[$propertyName] as $value) {
+ if ($value === $condition || @\preg_match($condition, $value) === 1) {
+ try {
+ $account->addRole($role);
+ } catch (\InvalidArgumentException $e) {
+ // This exception is never thrown
+ }
+ continue 4;
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/Classes/Security/Authentication/Provider/NeosBackendLdapProvider.php b/Classes/Security/Authentication/Provider/NeosBackendLdapProvider.php
new file mode 100644
index 0000000..a121870
--- /dev/null
+++ b/Classes/Security/Authentication/Provider/NeosBackendLdapProvider.php
@@ -0,0 +1,78 @@
+ 'user.givenName[0]',
+ 'lastName' => 'user.sn[0]',
+ ],
+ $this->options['mapping'] ?? []
+ );
+ $eelContext = new Context(['user' => $ldapUserData]);
+
+ try {
+ $firstName = $this->eelEvaluator->evaluate($mapping['firstName'], $eelContext);
+ } catch (\Exception $exception) {
+ // todo: logging
+ $firstName = 'none';
+ }
+ try {
+ $lastName = $this->eelEvaluator->evaluate($mapping['lastName'], $eelContext);
+ } catch (\Exception $exception) {
+ // todo: logging
+ $lastName = 'none';
+ }
+
+ $user = $this->userService->createUser(
+ $credentials['username'],
+ '',
+ $firstName,
+ $lastName,
+ [],
+ $this->name
+ );
+ return $user->getAccounts()->get(0);
+ }
+}
diff --git a/Classes/Service/BindProvider/AbstractBindProvider.php b/Classes/Service/BindProvider/AbstractBindProvider.php
deleted file mode 100644
index a7ce481..0000000
--- a/Classes/Service/BindProvider/AbstractBindProvider.php
+++ /dev/null
@@ -1,116 +0,0 @@
-linkIdentifier = $linkIdentifier;
- $this->options = $options;
- }
-
- /**
- * Return the ldap connection identifier.
- *
- * @return resource
- */
- public function getLinkIdentifier()
- {
- return $this->linkIdentifier;
- }
-
- /**
- * Return the filtered username for directory search.
- *
- * @param string $username
- * @return string
- */
- public function filterUsername($username)
- {
- return $username;
- }
-
- /**
- * Bind to the directory server. Returns void but throws exception on failure.
- *
- * @param string $userDn The DN of the user.
- * @param string $password The user's password.
- * @throws \Exception
- */
- protected function bindWithDn($userDn, $password)
- {
- try {
- $bindIsSuccessful = ldap_bind($this->linkIdentifier, $userDn, $password);
- } catch (\Exception $exception) {
- $bindIsSuccessful = false;
- }
-
- if (!$bindIsSuccessful) {
- throw new \Exception('Failed to bind with DN: "' . $userDn . '"', 1327763970);
- }
- }
-
- /**
- * Bind anonymously to the directory server. Returns void but throws exception on failure.
- *
- * @throws \Exception
- */
- protected function bindAnonymously()
- {
- try {
- $bindIsSuccessful = ldap_bind($this->linkIdentifier);
- } catch (\Exception $exception) {
- $bindIsSuccessful = false;
- }
-
- if (!$bindIsSuccessful) {
- throw new \Exception('Failed to bind anonymously', 1327763970);
- }
- }
-
- /**
- * Verify the given user is known to the directory server and has valid credentials.
- * Does not return output but throws an exception if the credentials are invalid.
- *
- * @param string $dn The DN of the user.
- * @param string $password The user's password.
- * @throws \Exception
- */
- public function verifyCredentials($dn, $password)
- {
- $this->bindWithDn($dn, $password);
- }
-
-}
diff --git a/Classes/Service/BindProvider/ActiveDirectoryBind.php b/Classes/Service/BindProvider/ActiveDirectoryBind.php
deleted file mode 100644
index 1e4521b..0000000
--- a/Classes/Service/BindProvider/ActiveDirectoryBind.php
+++ /dev/null
@@ -1,66 +0,0 @@
-options['domain'])) {
- if (!strpos($username, '\\')) {
- $username = $this->options['domain'] . '\\' . $username;
- }
- }
-
- if (!empty($this->options['usernameSuffix'])) {
- if (!strpos($username, '@')) {
- $username = $username . '@' . $this->options['usernameSuffix'];
- }
- }
-
- $this->bindWithDn($username, $password);
- }
-
- /**
- * Return username in format used for directory search
- *
- * @param string $username
- * @return string
- */
- public function filterUsername($username)
- {
- if (!empty($this->options['domain'])) {
- $usernameSegments = explode('\\', $username);
- $usernameWithoutDomain = array_pop($usernameSegments);
- $username = $this->options['filter']['ignoreDomain'] ? $usernameWithoutDomain : addcslashes($username, '\\');
- }
- return $username;
- }
-
-}
-
diff --git a/Classes/Service/BindProvider/BindProviderInterface.php b/Classes/Service/BindProvider/BindProviderInterface.php
deleted file mode 100644
index 5a9021d..0000000
--- a/Classes/Service/BindProvider/BindProviderInterface.php
+++ /dev/null
@@ -1,55 +0,0 @@
-options, 'bind.dn');
- if (!empty($username) && !empty($password)) {
- // if credentials are given, use them to authenticate
- $this->bindWithDn(sprintf($bindDn, $username), $password);
- return;
- }
-
- $bindPassword = Arrays::getValueByPath($this->options, 'bind.password');
- if (!empty($bindPassword)) {
- // if the settings specify a bind password, we are safe to assume no anonymous authentication is needed
- $this->bindWithDn($bindDn, $bindPassword);
- }
-
- $anonymousBind = Arrays::getValueByPath($this->options, 'bind.anonymous');
- if ($anonymousBind === true) {
- // if allowed, bind without username or password
- $this->bindAnonymously();
- }
- }
-
-}
diff --git a/Classes/Service/DirectoryService.php b/Classes/Service/DirectoryService.php
index 4348197..7c5e9a5 100644
--- a/Classes/Service/DirectoryService.php
+++ b/Classes/Service/DirectoryService.php
@@ -1,4 +1,5 @@
name = $name;
$this->options = $options;
- }
- /**
- * @return resource
- */
- public function getConnection()
- {
$this->ldapConnect();
- return $this->bindProvider->getLinkIdentifier();
+ $this->ldapBind($username, $password);
}
/**
- * Initialize the Ldap server connection
- *
- * Connect to the server and set communication options. Further bindings will be done
- * by a server specific bind provider.
- *
- * @throws Exception
+ * @param string $userDn User DN
+ * @return string[] Group DNs
+ * @throws MissingConfigurationException
+ * @throws LdapException
*/
- public function ldapConnect()
+ public function getGroupDnsOfUser(string $userDn): array
{
- if ($this->bindProvider instanceof BindProviderInterface) {
- // Already connected
- return;
+ if (!isset($this->options['queries']['group']['baseDn'], $this->options['queries']['group']['query'])) {
+ throw new MissingConfigurationException('Both baseDn and query have to be set for queries.group');
}
- $bindProviderClass = LdapBind::class;
- $connectionType = Arrays::getValueByPath($this->options, 'type');
- if ($connectionType === 'ActiveDirectory') {
- $bindProviderClass = ActiveDirectoryBind::class;
- }
- if (!class_exists($bindProviderClass)) {
- throw new Exception("Bind provider '$bindProviderClass' for the service '$this->name' could not be resolved.", 1327756744);
- }
+ $entries = $this->query(
+ $this->options['queries']['group']['baseDn'],
+ sprintf($this->options['queries']['group']['query'], $userDn),
+ ['dn']
+ );
- $connection = ldap_connect($this->options['host'], $this->options['port']);
- $this->bindProvider = new $bindProviderClass($connection, $this->options);
+ $groupDns = [];
+ foreach ($entries as $entry) {
+ $groupDns[] = $entry->getDn();
+ }
- $this->setLdapOptions();
+ return $groupDns;
}
/**
- * Set the Ldap options configured in the settings.
- *
- * Loops over the ldapOptions array, and finds the corresponding Ldap option by prefixing
- * LDAP_OPT_ to the uppercased array key.
- *
- * Example:
- * protocol_version: 3
- * Becomes:
- * LDAP_OPT_PROTOCOL_VERSION 3
+ * Get account data from ldap server
*
- * @return void
+ * @param string $username
+ * @return string[][] Search result from Ldap
+ * @throws MissingConfigurationException
+ * @throws LdapException
*/
- protected function setLdapOptions()
+ public function getUserData(string $username): array
{
- if (!isset($this->options['ldapOptions']) || !is_array($this->options['ldapOptions'])) {
- return;
+ if (!isset($this->options['queries']['account']['baseDn'], $this->options['queries']['account']['query'])) {
+ throw new MissingConfigurationException('Both baseDn and query have to be set for queries.account');
}
- foreach ($this->options['ldapOptions'] as $ldapOption => $value) {
- $constantName = 'LDAP_OPT_' . strtoupper($ldapOption);
- ldap_set_option($this->bindProvider->getLinkIdentifier(), constant($constantName), $value);
+ $entries = $this->query(
+ $this->options['queries']['account']['baseDn'],
+ sprintf($this->options['queries']['account']['query'], $username),
+ $this->options['attributesFilter'] ?? []
+ );
+ if ($entries === []) {
+ throw new LdapException('User not found');
}
+
+ return Arrays::arrayMergeRecursiveOverrule($entries[0]->getAttributes(), ['dn' => [$entries[0]->getDn()]]);
}
/**
- * Authenticate a username / password against the Ldap server
- *
- * @param string $username
- * @param string $password
- * @return array Search result from Ldap
- * @throws Exception
+ * @param string $baseDn
+ * @param string $queryString
+ * @param string[]|null $filter
+ * @return Entry[]
+ * @throws LdapException
*/
- public function authenticate($username, $password)
+ public function query(string $baseDn, string $queryString, array $filter = null): array
{
- $this->bind($username, $password);
-
- $searchResult = @ldap_search(
- $this->bindProvider->getLinkIdentifier(),
- $this->options['baseDn'],
- sprintf($this->options['filter']['account'], $this->bindProvider->filterUsername($username))
- );
-
- if (!$searchResult) {
- throw new Exception('Error during Ldap user search: ' . ldap_errno($this->bindProvider->getLinkIdentifier()), 1443798372);
- }
-
- $entries = ldap_get_entries($this->bindProvider->getLinkIdentifier(), $searchResult);
- if (empty($entries) || !isset($entries[0])) {
- throw new Exception('Error while authenticating: authenticated user could not be fetched from the directory', 1488289104);
+ $query = $this->ldap->query($baseDn, $queryString, ['filter' => $filter ?? []]);
+ /** @var Entry[] $entries */
+ try {
+ $entries = $query->execute()->toArray();
+ } catch (NotBoundException $exception) {
+ // This exception should never be thrown, since we bind in constructor
}
-
- return $entries[0];
+ return $entries;
}
/**
* @param string|null $username
* @param string|null $password
* @return void
- * @throws Exception
+ * @throws ConnectionException
*/
- public function bind($username = null, $password = null)
+ protected function ldapBind(string $username = null, string $password = null): void
{
- $this->ldapConnect();
- $this->bindProvider->bind($username, $password);
+ $this->ldap->bind(
+ (isset($this->options['bind']['dn'])
+ ? sprintf($this->options['bind']['dn'], $username ?? '')
+ : null
+ ),
+ $this->options['bind']['password'] ?? $password
+ );
}
/**
- * @param string $dn User or group DN.
- * @return array group DN => CN mapping
- * @throws Exception
+ * Initialize the Ldap server connection
+ *
+ * Connect to the server and set communication options. Further bindings will be done by a server specific bind
+ * provider.
+ *
+ * @return void
*/
- public function getMemberOf($dn)
+ protected function ldapConnect(): void
{
- $searchResult = @ldap_search(
- $this->bindProvider->getLinkIdentifier(),
- $this->options['baseDn'],
- sprintf($this->options['filter']['memberOf'], $dn)
- );
-
- if (!$searchResult) {
- throw new Exception('Error during Ldap group search: ' . ldap_errno($this->bindProvider->getLinkIdentifier()), 1443476083);
+ try {
+ $this->ldap = Ldap::create('ext_ldap', $this->options['connection'] ?? []);
+ } catch (DriverNotFoundException $e) {
+ // since we use the default driver, this cannot happen
}
-
- return array_map(
- function (array $memberOf) { return $memberOf['dn']; },
- array_filter(
- ldap_get_entries($this->bindProvider->getLinkIdentifier(), $searchResult),
- function ($element) { return is_array($element); }
- )
- );
}
-
}
diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml
index 5fef3b9..1984a78 100644
--- a/Configuration/Settings.yaml
+++ b/Configuration/Settings.yaml
@@ -3,4 +3,5 @@ Neos:
roles:
default: []
groupMapping: []
+ propertyMapping: []
userMapping: []
diff --git a/Configuration/Settings.yaml.ad.example b/Configuration/Settings.yaml.ad.example
deleted file mode 100644
index 73d7232..0000000
--- a/Configuration/Settings.yaml.ad.example
+++ /dev/null
@@ -1,51 +0,0 @@
-Neos:
- Flow:
- security:
- authentication:
- providers:
- ActiveDirectoryProvider:
- provider: Neos\Ldap\Security\Authentication\Provider\LdapProvider
- providerOptions:
- host: localhost
- port: 389
-
- baseDn: dc=my-domain,dc=com
-
- type: 'ActiveDirectory'
-
- # All PHP Ldap options can be set here. Make the constant lowercase
- # and remove the ldap_opt_ prefix.
- # Example: LDAP_OPT_PROTOCOL_VERSION becomes protocol_version
- ldapOptions:
- protocol_version: 3
- network_timeout: 10
- referrals: 0
-
- filter:
- # %s will be replaced with the username / dn provided
- account: '(samaccountname=%s)'
- memberOf: '(&(member=%s)(objectClass=group))'
-
- # this will use the filter with domain, set it to yes to remove it for search
- ignoreDomain: FALSE
-
- # will be prefixed to a given username if no other domain was specified
- domain: 'MY-DOMAIN'
-
-Neos:
- Ldap:
- roles:
- default: []
- - 'Neos.Neos:RestrictedEditor'
- # map group memberships to roles
- groupMapping: []
- 'Neos.Neos:Administrator':
- - 'CN=Administrators,OU=Groups,DC=domain,DC=tld'
- 'Neos.Neos:Editor':
- - 'CN=Editors,OU=Groups,DC=domain,DC=tld'
- # map certain users to roles
- userMapping: []
- 'Neos.Neos:Administrator':
- - 'CN=Admin,OU=Users,DC=domain,DC=tld'
- 'Neos.Neos:Editor':
- - 'CN=Mustermann,OU=Users,DC=domain,DC=tld'
diff --git a/Configuration/Settings.yaml.example b/Configuration/Settings.yaml.example
new file mode 100644
index 0000000..339958a
--- /dev/null
+++ b/Configuration/Settings.yaml.example
@@ -0,0 +1,97 @@
+---
+Neos:
+ Flow:
+ security:
+ authentication:
+ providers:
+ Neos.Neos:Backend:
+ # Either the generic LdapProvider, oder the NeosBackendLdapProvider for Neos CMS
+ provider: Neos\Ldap\Security\Authentication\Provider\NeosBackendLdapProvider
+ providerOptions:
+ connection:
+ # 'none', 'ssl', 'tls'
+# encryption: none
+ host: localhost
+ # default: 3, maps to options.protocol_version
+# version: 3
+ # default is built automatically from host (with optional port and encryption)
+# connection_string: ''
+ # default: `$encryption == 'ssl' ? 636 : 389`
+# port: 389
+ # if true, options.debug_level will be set to 7
+# debug: false
+ # default value for options.referrals
+# referrals: false
+
+ # All PHP Ldap options can be set here. Make the constant lowercase and remove the ldap_opt_ prefix
+ # Example: LDAP_OPT_PROTOCOL_VERSION becomes protocol_version
+# options:
+# client_controls: []
+# debug_level: 7
+# deref: 0
+# error_number: 0
+# error_string: ''
+# host_name: ''
+# matched_dn: ''
+# network_timeout: 0
+# protocol_version: 3
+# referrals: false
+# restart: false
+# server_controls: []
+# sizelimit: 0
+# timelimit: 0
+# x_sasl_authcid: ''
+# x_sasl_authzid: ''
+# x_sasl_mech: ''
+# x_sasl_realm: ''
+
+ # How to authenticate towards the server. Normally this is a given service account and password.
+ # You can also bind for each user individually, using their password: %s will be replaced with username
+ bind:
+ # For AD this can also be '%s@domain.com' or 'DOMAIN\%s'
+ dn: CN=ldapserviceuser,OU=Users,DC=domain,DC=tld
+# password: secret
+
+ queries:
+ # %s will be replaced with the username provided
+ account:
+ baseDn: OU=Users,DC=domain,DC=com
+ query: (uid=%s)
+ # Must be set if groupMapping will be used, %s will be replaced by full user dn!
+# group:
+# baseDn: OU=Groups,DC=domain,DC=com
+# query: (&(objectClass=posixGroup)(memberUid=%s))
+
+ # Define what attributes are really fetched from the directory
+# attributesFilter: [dn]
+
+ # If using the NeosBackendLdapProvider, the User will have his name mapped like this
+# mapping:
+# firstName: user.givenName[0]
+# lastName: user.sn[0]
+
+# Ldap:
+# roles:
+# default:
+# - Neos.Neos:RestrictedEditor
+ # map group memberships to roles
+# groupMapping:
+# Neos.Neos:Administrator:
+# - CN=Administrators,OU=Groups,DC=domain,DC=tld
+# Neos.Neos:Editor:
+# - CN=Administrators,OU=Groups,DC=domain,DC=tld
+# - CN=Editors,OU=Groups,DC=domain,DC=tld
+ # map certain properties to a role, can be a regular expression (including delimeters and modifiers)
+# propertyMapping:
+# Neos.Neos:Administrator:
+# objectClass: administrator
+# Neos.Neos:Editor:
+# department:
+# - ~.*mathematics~i
+# - /computer science/
+ # map certain users to roles
+# userMapping:
+# Neos.Neos:Administrator:
+# - CN=Admin,OU=Users,DC=domain,DC=tld
+# Neos.Neos:Editor:
+# - CN=Mustermann,OU=Users,DC=domain,DC=tld
diff --git a/Configuration/Settings.yaml.ldap.example b/Configuration/Settings.yaml.ldap.example
deleted file mode 100644
index 12641eb..0000000
--- a/Configuration/Settings.yaml.ldap.example
+++ /dev/null
@@ -1,56 +0,0 @@
-Neos:
- Flow:
- security:
- authentication:
- providers:
- LdapProvider:
- provider: Neos\Ldap\Security\Authentication\Provider\LdapProvider
- providerOptions:
- host: localhost
- port: 389
-
- baseDn: dc=my-domain,dc=com
-
- # How to authenticate towards the server. Normally this is a given
- # service account and password. Other options are also available,
- # consult the bind provider class LdapBind for more examples.
- bind:
- dn: 'uid=ldapserviceuser,dc=example,dc=com'
- password: 'secret'
- anonymous: FALSE
-
- # All PHP Ldap options can be set here. Make the constant lowercase
- # and remove the ldap_opt_ prefix.
- # Example: LDAP_OPT_PROTOCOL_VERSION becomes protocol_version
- ldapOptions:
- protocol_version: 3
- network_timeout: 10
-
- filter:
- # %s will be replaced with the username / dn provided
- account: '(uid=%s)'
- memberOf: '(&(objectClass=posixGroup)(memberUid=%s))'
-
- # this will use the filter with domain, set it to yes to remove it for search
- ignoreDomain: TRUE
-
- # will be prefixed to a given username if no other domain was specified
- domain: 'MY-DOMAIN'
-
-Neos:
- Ldap:
- roles:
- default: []
- - 'Neos.Neos:RestrictedEditor'
- # map group memberships to roles
- groupMapping: []
- 'Neos.Neos:Administrator':
- - 'CN=Administrators,OU=Groups,DC=domain,DC=tld'
- 'Neos.Neos:Editor':
- - 'CN=Editors,OU=Groups,DC=domain,DC=tld'
- # map certain users to roles
- userMapping: []
- 'Neos.Neos:Administrator':
- - 'CN=Admin,OU=Users,DC=domain,DC=tld'
- 'Neos.Neos:Editor':
- - 'CN=Mustermann,OU=Users,DC=domain,DC=tld'
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..b6a4c2d
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,105 @@
+# Neos.Ldap Documentation
+
+## Example
+
+### `LoginController.php`
+```php
+view->assign('username', $username);
+ }
+
+ /**
+ * @param ActionRequest $originalRequest
+ * @return string|void
+ */
+ public function onAuthenticationSuccess(ActionRequest $originalRequest = null) {
+ $this->redirect('status');
+ }
+
+ /**
+ * Logs out a - possibly - currently logged in account.
+ *
+ * @return void
+ */
+ public function logoutAction() {
+ $this->authenticationManager->logout();
+ $this->addFlashMessage('Successfully logged out.');
+ $this->redirect('index');
+ }
+
+ /**
+ * @return void
+ */
+ public function statusAction() {
+ $this->view->assign('activeTokens', $this->securityContext->getAuthenticationTokens());
+ }
+}
+```
+
+### `Index.html`
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### `Status.html`
+```html
+Status: Logged in
+User: {activeTokens.LdapProvider.account.accountIdentifier}
+Logout
+```
+
+### `Policy.yaml`
+Make sure you configure the policies so that the login and logout actions are available for the user.
+```yaml
+resources:
+ methods:
+ My_Package_LoginController: method(My\Package\Controller\LoginController->(index|status|login|authenticate|logout)Action())
+
+ acls:
+ Everybody:
+ methods:
+ My_Package_LoginController: GRANT
+```
+
+## Configuration examples
+You can find examples of a ``Settings.yaml`` file for Ldap and Active Directory [here](Configuration/Settings.yaml.example) in the `Configuration` folder of the
+Neos.Ldap package.
diff --git a/Readme.rst b/Readme.rst
deleted file mode 100644
index 7bd3f63..0000000
--- a/Readme.rst
+++ /dev/null
@@ -1,96 +0,0 @@
-Neos Ldap Documentation
-=======================
-
-Example LoginController
------------------------
-
-LoginController.php::
-
- view->assign('username', $username);
- }
-
- /**
- * @param \Neos\Flow\Mvc\ActionRequest $originalRequest
- * @return string|void
- */
- public function onAuthenticationSuccess(\Neos\Flow\Mvc\ActionRequest $originalRequest = NULL) {
- $this->redirect('status');
- }
-
- /**
- * Logs out a - possibly - currently logged in account.
- *
- * @return void
- */
- public function logoutAction() {
- $this->authenticationManager->logout();
- $this->addFlashMessage('Successfully logged out.');
- $this->redirect('index');
- }
-
- /**
- * @return void
- */
- public function statusAction() {
- $this->view->assign('activeTokens', $this->securityContext->getAuthenticationTokens());
- }
-
- }
-
-Index.html::
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Status.html::
-
- Status: Logged in
- User: {activeTokens.LdapProvider.account.accountIdentifier}
- Logout
-
-Make sure you configure the policies so that the login and logout actions are available for the user. For that use a Policy.yaml
-like::
-
- resources:
- methods:
- My_Package_LoginController: 'method(My\Package\Controller\LoginController->(index|status|login|authenticate|logout)Action())'
-
- acls:
- Everybody:
- methods:
- My_Package_LoginController: GRANT
-
-Configuration examples
-----------------------
-
-You can find examples of a ``Settings.yaml`` file for Ldap and Active Directory in the Configuration/ folder
-of the Neos.Ldap package.
diff --git a/Resources/Private/Schema/Settings.Neos.Flow.security.authentication.providers.schema.yaml b/Resources/Private/Schema/Settings.Neos.Flow.security.authentication.providers.schema.yaml
new file mode 100644
index 0000000..8800c3a
--- /dev/null
+++ b/Resources/Private/Schema/Settings.Neos.Flow.security.authentication.providers.schema.yaml
@@ -0,0 +1,199 @@
+type: dictionary
+additionalProperties:
+ type: dictionary
+ properties:
+ providerOptions:
+ type: dictionary
+ additionalProperties: false
+ properties:
+ attributesFilter:
+ type: array
+ items:
+ type: string
+ bind:
+ required: true
+ type: dictionary
+ additionalProperties: false
+ properties:
+ dn:
+ required: true
+ type: string
+ password:
+ type: string
+ connection:
+ type: dictionary
+ additionalProperties: false
+ properties:
+ connection_string:
+ type: string
+ format: uri
+ debug:
+ type: boolean
+ encryption:
+ type: string
+ enum:
+ - none
+ - ssl
+ - tls
+ host:
+ type: string
+ format: host-name
+ options:
+ type: dictionary
+ additionalProperties: false
+ properties:
+ client_controls: &controls
+ type: array
+ items:
+ type: dictionary
+ additionalProperties: false
+ properties:
+ oid:
+ required: true
+ type: string
+ enum:
+ - LDAP_CONTROL_AUTHZID_REQUEST
+ - LDAP_CONTROL_DONTUSECOPY
+ - LDAP_CONTROL_MANAGEDSAIT
+ - LDAP_CONTROL_PASSWORDPOLICYREQUEST
+ - LDAP_CONTROL_PROXY_AUTHZ
+ - LDAP_CONTROL_SUBENTRIES
+ - LDAP_CONTROL_SYNC
+ - LDAP_CONTROL_X_DOMAIN_SCOPE
+ - LDAP_CONTROL_X_EXTENDED_DN
+ - LDAP_CONTROL_X_INCREMENTAL_VALUES
+ - LDAP_CONTROL_X_PERMISSIVE_MODIFY
+ - LDAP_CONTROL_X_SEARCH_OPTIONS
+ - LDAP_CONTROL_X_TREE_DELETE
+
+ - LDAP_CONTROL_PAGEDRESULTS
+ - LDAP_CONTROL_ASSERT
+ - LDAP_CONTROL_VALUESRETURNFILTER
+ - LDAP_CONTROL_PRE_READ
+ - LDAP_CONTROL_POST_READ
+ - LDAP_CONTROL_SORTREQUEST
+ - LDAP_CONTROL_VLVREQUEST
+ iscritical:
+ type: boolean
+ value:
+ type:
+ - string
+ - type: array
+ items:
+ type: dictionary
+ additionalProperties: false
+ properties:
+ attr:
+ required: true
+ type: string
+ oid:
+ type: string
+ reverse:
+ type: boolean
+ - type: dictionary
+ additionalProperties: false
+ properties:
+ after:
+ type: number
+ attrs:
+ type: array
+ items:
+ type: string
+ attrvalue:
+ type: string
+ before:
+ type: number
+ context:
+ type: string
+ cookie:
+ type: string
+ count:
+ type: number
+ filter:
+ type: string
+ offset:
+ type: number
+ size:
+ type: number
+
+ debug_level:
+ type: integer
+ minimum: 0
+ deref:
+ type: integer
+ error_number:
+ type: integer
+ error_string:
+ type: string
+ host_name:
+ type: string
+ matched_dn:
+ type: string
+ network_timeout:
+ type: integer
+ minimum: -1
+ protocol_version:
+ type: integer
+ minimum: 1
+ maximum: 3
+ referrals:
+ type: boolean
+ restart:
+ type: boolean
+ server_controls: *controls
+ sizelimit:
+ type: integer
+ minimum: 0
+ timelimit:
+ type: integer
+ minimum: 0
+ x_sasl_authcid:
+ type: string
+ x_sasl_authzid:
+ type: string
+ x_sasl_mech:
+ type: string
+ x_sasl_realm:
+ type: string
+ port:
+ type: integer
+ minimum: 1
+ maximum: 65535
+ referrals:
+ type: boolean
+ version:
+ type: integer
+ minimum: 1
+ maximum: 3
+ mapping:
+ type: dictionary
+ additionalProperties: false
+ properties:
+ firstName:
+ type: string
+ lastName:
+ type: string
+ queries:
+ required: true
+ type: dictionary
+ additionalProperties: false
+ properties:
+ account:
+ required: true
+ type: dictionary
+ properties:
+ baseDn:
+ required: true
+ type: string
+ query:
+ required: true
+ type: string
+ group:
+ type: dictionary
+ properties:
+ baseDn:
+ required: true
+ type: string
+ query:
+ required: true
+ type: string
diff --git a/Resources/Private/Schema/Settings.Neos.Ldap.roles.schema.yaml b/Resources/Private/Schema/Settings.Neos.Ldap.roles.schema.yaml
new file mode 100644
index 0000000..d8b4966
--- /dev/null
+++ b/Resources/Private/Schema/Settings.Neos.Ldap.roles.schema.yaml
@@ -0,0 +1,29 @@
+type: dictionary
+additionalProperties: false
+properties:
+ default:
+ type: array
+ items:
+ type: string
+ groupMapping:
+ type: dictionary
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ propertyMapping:
+ type: dictionary
+ additionalProperties:
+ type: dictionary
+ additionalProperties:
+ type:
+ - string
+ - type: array
+ items:
+ type: string
+ userMapping:
+ type: dictionary
+ additionalProperties:
+ type: array
+ items:
+ type: string
diff --git a/composer.json b/composer.json
index 1616fb6..f5d4d9c 100644
--- a/composer.json
+++ b/composer.json
@@ -2,12 +2,16 @@
"name": "neos/ldap",
"type": "neos-package",
"description": "Ldap Authentication for Flow",
- "license": [
- "MIT"
- ],
+ "license": "MIT",
"require": {
+ "php": "^7.4 || ^8.0",
+ "neos/eel": "^7.0 || ^8.0",
"neos/flow": "^7.0 || ^8.0",
- "ext-ldap": "*"
+ "neos/neos": "^7.0 || ^8.0",
+ "neos/utility-arrays": "^7.0 || ^8.0",
+ "neos/utility-files": "^7.0 || ^8.0",
+ "symfony/ldap": "^5.0",
+ "symfony/yaml": "^5.0"
},
"autoload": {
"psr-4": {
@@ -15,8 +19,36 @@
}
},
"extra": {
+ "neos": {
+ "package-key": "Neos.Ldap"
+ },
"branch-alias": {
- "dev-master": "3.0.x-dev"
- }
+ "dev-master": "3.1.x-dev"
+ },
+ "applied-flow-migrations": [
+ "TYPO3.FLOW3-201209201112",
+ "TYPO3.FLOW3-201201261636",
+ "TYPO3.Form-20160601101500",
+ "Neos.Twitter.Bootstrap-20161124204912",
+ "Neos.Form-20161124205254",
+ "Neos.Party-20161124225257",
+ "Neos.Imagine-20161124231742",
+ "Neos.SwiftMailer-20161130105617",
+ "Neos.ContentRepository.Search-20161210231100",
+ "Neos.Seo-20170127154600",
+ "Neos.Flow-20180415105700",
+ "Neos.Neos-20180907103800",
+ "Neos.Neos.Ui-20190319094900",
+ "Neos.Flow-20190425144900",
+ "Neos.Flow-20190515215000",
+ "Neos.NodeTypes-20190917101945",
+ "Networkteam.Neos.MailObfuscator-20190919145400",
+ "Neos.NodeTypes-20200120114136",
+ "Neos.Flow-20200813181400",
+ "Neos.Flow-20201003165200",
+ "Neos.Flow-20201109224100",
+ "Neos.Flow-20201205172733",
+ "Neos.Flow-20201207104500"
+ ]
}
-}
+}
\ No newline at end of file