diff --git a/src/Auth/Source/Ldap.php b/src/Auth/Source/Ldap.php index 5d14e5cbc..462df579c 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 loginSasl(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,22 @@ protected function login(string $username, #[\SensitiveParameter]string $passwor } /* Verify the credentials */ - $this->connector->bind($dn, $password); + if (empty($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 +161,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->loginSasl($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..bce773404 100644 --- a/src/Auth/Source/LdapMulti.php +++ b/src/Auth/Source/LdapMulti.php @@ -105,14 +105,20 @@ 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 loginSasl( + string $username, + #[\SensitiveParameter]string $password, + string $organization, + ?array $sasl_args, + ): array { if ($this->includeOrgInUsername) { $username = $username . '@' . $organization; } @@ -128,15 +134,30 @@ 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 - { - return $this->login($username, $password); + public function loginOverload( + string $username, + #[\SensitiveParameter]string $password, + ?array $sasl_args, + ): array { + return $this->loginSasl($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->loginSasl($username, $password, $organization); + } /** * Retrieve list of organizations. diff --git a/src/Connector/Ldap.php b/src/Connector/Ldap.php index a5e9208c8..c62de6092 100644 --- a/src/Connector/Ldap.php +++ b/src/Connector/Ldap.php @@ -109,6 +109,46 @@ 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 { + if (!method_exists($this->connection, 'saslBind')) { + throw new Error\Error("SASL not implemented"); + } + + 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 + { + if (!method_exists($this->connection, 'whoami')) { + throw new Error\Error("SASL not implemented"); + } + + return $this->connection->whoami(); + } /** * @inheritDoc diff --git a/src/ConnectorInterface.php b/src/ConnectorInterface.php index fe6ef93b5..63f265f61 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 *