From 7462d2392f62db940d043466ecb04a72af76c986 Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Wed, 30 Oct 2024 17:23:39 +0100 Subject: [PATCH] Add support for SASL bind We introduce a ldap_sasl() method in Ldap and LdapMulti with an optionnal array of SASL options. A module that subclasses Ldap or Ldapmulti can use it instead of simple login(). This requires SASL bind support in Symfony, which has been merged in the 7.3 branch. SimpleSAMLphp uses Symfony 7.2. How should this ne handled? I can backport the patches for Symfony 7.2, but do we have a way to fold them in the simpleSAMLphp package? --- src/Auth/Source/Ldap.php | 26 +++++++++++++++++++++++--- src/Auth/Source/LdapMulti.php | 24 +++++++++++++++++++----- src/Connector/Ldap.php | 26 ++++++++++++++++++++++++++ src/ConnectorInterface.php | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/Auth/Source/Ldap.php b/src/Auth/Source/Ldap.php index 5d14e5cbc..463c8b8e9 100644 --- a/src/Auth/Source/Ldap.php +++ b/src/Auth/Source/Ldap.php @@ -67,13 +67,14 @@ public function __construct(array $info, array $config) /** - * Attempt to log in using the given username and password. + * Attempt to log in using SASL and the given username and password. * * @param string $username The username the user wrote. * @param string $password The password the user wrote. + * @param array|null $sasl_args SASL options * @return array Associative array with the users attributes. */ - protected function login(string $username, #[\SensitiveParameter]string $password): array + protected function login_sasl(string $username, #[\SensitiveParameter]string $password, ?array $sasl_args): array { if (preg_match('/^\s*$/', $password)) { // The empty string is considered an anonymous bind to Symfony @@ -128,7 +129,14 @@ protected function login(string $username, #[\SensitiveParameter]string $passwor } /* Verify the credentials */ - $this->connector->bind($dn, $password); + if (isset($sasl_args)) { + Assert::isArray($sasl_args); + + $this->connector->saslBind($dn, $password, $sasl_args['mech'], $sasl_args['realm'], $sasl_args['authc_id'], $sasl_args['authz_id'], $sasl_args['props']); + $dn = $this->connector->whoami(); + } else { + $this->connector->bind($dn, $password); + } /* If the credentials were correct, rebind using a privileged account to read attributes */ $readUsername = $this->ldapConfig->getOptionalString('priv.username', null); @@ -145,6 +153,18 @@ protected function login(string $username, #[\SensitiveParameter]string $passwor return $this->processAttributes(/** @scrutinizer-ignore-type */$entry); } + /** + * Attempt to log in using the given username and password. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @return array Associative array with the users attributes. + */ + protected function login(string $username, #[\SensitiveParameter]string $password): array + { + return $this->login_sasl($username, $password); + } + /** * Attempt to find a user's attributes given its username. diff --git a/src/Auth/Source/LdapMulti.php b/src/Auth/Source/LdapMulti.php index ff006a278..c47e77204 100644 --- a/src/Auth/Source/LdapMulti.php +++ b/src/Auth/Source/LdapMulti.php @@ -105,13 +105,15 @@ public function __construct(array $info, array $config) /** - * Attempt to log in using the given username and password. + * Attempt to log in using SASL and the given username and password. * * @param string $username The username the user wrote. * @param string $password The password the user wrote. + * @param string $organizaion The organization the user chose. + * @param array|null $sasl_args SASL options * @return array Associative array with the users attributes. */ - protected function login(string $username, #[\SensitiveParameter]string $password, string $organization): array + protected function login_sasl(string $username, #[\SensitiveParameter]string $password, string $organization, ?array $sasl_args): array { if ($this->includeOrgInUsername) { $username = $username . '@' . $organization; @@ -128,15 +130,27 @@ protected function login(string $username, #[\SensitiveParameter]string $passwor $ldap = new class (['AuthId' => $authsource], $sourceConfig->toArray()) extends Ldap { - public function loginOverload(string $username, #[\SensitiveParameter]string $password): array + public function loginOverload(string $username, #[\SensitiveParameter]string $password, ?array $sasl_args): array { - return $this->login($username, $password); + return $this->login_sasl($username, $password, $sasl_args); } }; - return $ldap->loginOverload($username, $password); + return $ldap->loginOverload($username, $password, $sasl_args); } + /** + * Attempt to log in using the given username and password. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @param string $organizaion The organization the user chose. + * @return array Associative array with the users attributes. + */ + protected function login(string $username, #[\SensitiveParameter]string $password, string $organization): array + { + return $this->login_sasl($username, $password, $organization); + } /** * Retrieve list of organizations. diff --git a/src/Connector/Ldap.php b/src/Connector/Ldap.php index a5e9208c8..475e42a72 100644 --- a/src/Connector/Ldap.php +++ b/src/Connector/Ldap.php @@ -109,6 +109,32 @@ public function bind(?string $username, #[\SensitiveParameter]?string $password) } } + /** + * @inheritDoc + */ + public function saslBind(?string $username, #[\SensitiveParameter]?string $password, ?string $mech, ?string $realm, ?string $authcId, ?string $authzId, ?string $props): void + + { + try { + $this->connection->saslBind($username, strval($password), $mech, $realm, $authcId, $authzId, $props); + } catch (InvalidCredentialsException $e) { + throw new Error\Error($this->resolveBindError($e)); + } + + if ($username === null) { + Logger::debug("LDAP bind(): Anonymous bind succesful."); + } else { + Logger::debug(sprintf("LDAP bind(): Bind successful for DN '%s'.", $username)); + } + } + + /** + * @inheritDoc + */ + public function whoami(): string + { + return $this->connection->whoami(); + } /** * @inheritDoc diff --git a/src/ConnectorInterface.php b/src/ConnectorInterface.php index fe6ef93b5..fdb0f686a 100644 --- a/src/ConnectorInterface.php +++ b/src/ConnectorInterface.php @@ -28,6 +28,39 @@ public function bind( ): void; + /** + * Bind to an LDAP-server using SASL + * + * @param string|null $username + * @param string|null $password Null for passwordless logon + * @param string|null $mech + * @param string|null $realm + * @param string|null $authcId + * @param string|null $authzId + * @param string|null $props + * @return void + * + * @throws \SimpleSAML\Error\Exception if none of the LDAP-servers could be contacted + */ + public function saslBind( + ?string $username, + ?string $password, + ?string $mech, + ?string $realm, + ?string $authcId, + ?string $authzId, + ?string $props + ): void; + + + /** + * Return the authenticated DN + * + * @return string + */ + public function whoami(): string; + + /** * Search the LDAP-directory for a specific object *