Skip to content

Commit 0d206be

Browse files
manu0401nicolas-grekas
authored andcommitted
[Ldap] Add support for sasl_bind and whoami LDAP operations
1 parent 3476f6a commit 0d206be

File tree

6 files changed

+119
-13
lines changed

6 files changed

+119
-13
lines changed

Adapter/ConnectionInterface.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
use Symfony\Component\Ldap\Exception\AlreadyExistsException;
1515
use Symfony\Component\Ldap\Exception\ConnectionTimeoutException;
1616
use Symfony\Component\Ldap\Exception\InvalidCredentialsException;
17+
use Symfony\Component\Ldap\Exception\LdapException;
1718

1819
/**
1920
* @author Charles Sarrazin <charles@sarraz.in>
21+
*
22+
* @method void saslBind(?string $dn = null, #[\SensitiveParameter] ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authcId = null, ?string $authzId = null, ?string $props = null)
23+
* @method string whoami()
2024
*/
2125
interface ConnectionInterface
2226
{
@@ -33,4 +37,19 @@ public function isBound(): bool;
3337
* @throws InvalidCredentialsException When the connection can't be created because of an LDAP_INVALID_CREDENTIALS error
3438
*/
3539
public function bind(?string $dn = null, #[\SensitiveParameter] ?string $password = null): void;
40+
41+
/*
42+
* Binds the connection against a user's DN and password using SASL.
43+
*
44+
* @throws LdapException When SASL support is not available
45+
* @throws AlreadyExistsException When the connection can't be created because of an LDAP_ALREADY_EXISTS error
46+
* @throws ConnectionTimeoutException When the connection can't be created because of an LDAP_TIMEOUT error
47+
* @throws InvalidCredentialsException When the connection can't be created because of an LDAP_INVALID_CREDENTIALS error
48+
*/
49+
// public function saslBind(?string $dn = null, #[\SensitiveParameter] ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authcId = null, ?string $authzId = null, ?string $props = null): void;
50+
51+
/*
52+
* Return authenticated and authorized (for SASL) DN.
53+
*/
54+
// public function whoami(): string;
3655
}

Adapter/ExtLdap/Connection.php

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,69 @@ public function bind(?string $dn = null, #[\SensitiveParameter] ?string $passwor
7070

7171
if (false === @ldap_bind($this->connection, $dn, $password)) {
7272
$error = ldap_error($this->connection);
73-
switch (ldap_errno($this->connection)) {
74-
case self::LDAP_INVALID_CREDENTIALS:
75-
throw new InvalidCredentialsException($error);
76-
case self::LDAP_TIMEOUT:
77-
throw new ConnectionTimeoutException($error);
78-
case self::LDAP_ALREADY_EXISTS:
79-
throw new AlreadyExistsException($error);
80-
}
81-
ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diagnostic_message);
82-
throw new ConnectionException($error.' '.$diagnostic_message);
73+
ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diagnostic);
74+
75+
throw match (ldap_errno($this->connection)) {
76+
self::LDAP_INVALID_CREDENTIALS => new InvalidCredentialsException($error),
77+
self::LDAP_TIMEOUT => new ConnectionTimeoutException($error),
78+
self::LDAP_ALREADY_EXISTS => new AlreadyExistsException($error),
79+
default => new ConnectionException($error.' '.$diagnostic),
80+
};
81+
}
82+
83+
$this->bound = true;
84+
}
85+
86+
/**
87+
* @param string $password WARNING: When the LDAP server allows unauthenticated binds, a blank $password will always be valid
88+
*/
89+
public function saslBind(?string $dn = null, #[\SensitiveParameter] ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authcId = null, ?string $authzId = null, ?string $props = null): void
90+
{
91+
if (!\function_exists('ldap_sasl_bind')) {
92+
throw new LdapException('The LDAP extension is missing SASL support.');
93+
}
94+
95+
if (!$this->connection) {
96+
$this->connect();
97+
}
98+
99+
if (false === @ldap_sasl_bind($this->connection, $dn, $password, $mech, $realm, $authcId, $authzId, $props)) {
100+
$error = ldap_error($this->connection);
101+
ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diagnostic);
102+
103+
throw match (ldap_errno($this->connection)) {
104+
self::LDAP_INVALID_CREDENTIALS => new InvalidCredentialsException($error),
105+
self::LDAP_TIMEOUT => new ConnectionTimeoutException($error),
106+
self::LDAP_ALREADY_EXISTS => new AlreadyExistsException($error),
107+
default => new ConnectionException($error.' '.$diagnostic),
108+
};
83109
}
84110

85111
$this->bound = true;
86112
}
87113

114+
/**
115+
* ldap_exop_whoami accessor, returns authenticated DN.
116+
*/
117+
public function whoami(): string
118+
{
119+
if (false === $authzId = ldap_exop_whoami($this->connection)) {
120+
throw new LdapException(ldap_error($this->connection));
121+
}
122+
123+
$parts = explode(':', $authzId, 2);
124+
if ('dn' !== $parts[0]) {
125+
/*
126+
* We currently do not handle u:login authzId, which
127+
* would require a configuration-dependent LDAP search
128+
* to be turned into a DN
129+
*/
130+
throw new LdapException(\sprintf('Unsupported authzId "%s".', $authzId));
131+
}
132+
133+
return $parts[1];
134+
}
135+
88136
/**
89137
* @internal
90138
*/

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add methods for `saslBind()` and `whoami()` to `ConnectionInterface` and `LdapInterface`
8+
49
7.1
510
---
611

Ldap.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ public function bind(?string $dn = null, #[\SensitiveParameter] ?string $passwor
3232
$this->adapter->getConnection()->bind($dn, $password);
3333
}
3434

35+
public function saslBind(?string $dn = null, #[\SensitiveParameter] ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authcId = null, ?string $authzId = null, ?string $props = null): void
36+
{
37+
$this->adapter->getConnection()->saslBind($dn, $password, $mech, $realm, $authcId, $authzId, $props);
38+
}
39+
40+
public function whoami(): string
41+
{
42+
return $this->adapter->getConnection()->whoami();
43+
}
44+
3545
public function query(string $dn, string $query, array $options = []): QueryInterface
3646
{
3747
return $this->adapter->createQuery($dn, $query, $options);

LdapInterface.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,35 @@
1616
use Symfony\Component\Ldap\Exception\ConnectionException;
1717

1818
/**
19-
* Ldap interface.
20-
*
2119
* @author Charles Sarrazin <charles@sarraz.in>
20+
*
21+
* @method void saslBind(?string $dn = null, #[\SensitiveParameter] ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authcId = null, ?string $authzId = null, ?string $props = null)
22+
* @method string whoami()
2223
*/
2324
interface LdapInterface
2425
{
2526
public const ESCAPE_FILTER = 0x01;
2627
public const ESCAPE_DN = 0x02;
2728

2829
/**
29-
* Return a connection bound to the ldap.
30+
* Returns a connection bound to the ldap.
3031
*
3132
* @throws ConnectionException if dn / password could not be bound
3233
*/
3334
public function bind(?string $dn = null, #[\SensitiveParameter] ?string $password = null): void;
3435

36+
/**
37+
* Returns a connection bound to the ldap using SASL.
38+
*
39+
* @throws ConnectionException if dn / password could not be bound
40+
*/
41+
// public function saslBind(?string $dn = null, #[\SensitiveParameter] ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authcId = null, ?string $authzId = null, ?string $props = null): void;
42+
43+
/**
44+
* Returns authenticated and authorized (for SASL) DN.
45+
*/
46+
// public function whoami(): string;
47+
3548
/**
3649
* Queries a ldap server for entries matching the given criteria.
3750
*/

Tests/Adapter/ExtLdap/AdapterTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ public function testLdapEscape()
3434
$this->assertEquals('\20foo\3dbar\0d(baz)*\20', $ldap->escape(" foo=bar\r(baz)* ", '', LdapInterface::ESCAPE_DN));
3535
}
3636

37+
/**
38+
* @group functional
39+
*/
40+
public function testSaslBind()
41+
{
42+
$ldap = new Adapter($this->getLdapConfig());
43+
44+
$ldap->getConnection()->saslBind('cn=admin,dc=symfony,dc=com', 'symfony');
45+
$this->assertEquals('cn=admin,dc=symfony,dc=com', $ldap->getConnection()->whoami());
46+
}
47+
3748
/**
3849
* @group functional
3950
*/

0 commit comments

Comments
 (0)