Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 84d5996

Browse filesBrowse files
committed
feature #32824 [Ldap] Add security LdapUser and provider (chalasr)
This PR was merged into the 4.4 branch. Discussion ---------- [Ldap] Add security LdapUser and provider | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Moves `LdapUserProvider` from `Security\Core` to the Ldap component, the provider now deals with a new `LdapUser` aware of its ldap `Entry` (should help in #31843). Commits ------- 6736cdf [Ldap] Add security LdapUser and provider
2 parents b74ccda + 6736cdf commit 84d5996
Copy full SHA for 84d5996

File tree

13 files changed

+609
-111
lines changed
Filter options

13 files changed

+609
-111
lines changed

‎UPGRADE-4.4.md

Copy file name to clipboardExpand all lines: UPGRADE-4.4.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Routing
149149
Security
150150
--------
151151

152+
* The `LdapUserProvider` class has been deprecated, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead.
152153
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method
153154

154155
Stopwatch

‎UPGRADE-5.0.md

Copy file name to clipboardExpand all lines: UPGRADE-5.0.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ Routing
377377
Security
378378
--------
379379

380+
* The `LdapUserProvider` class has been removed, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead.
380381
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` must have a new `needsRehash()` method
381382
* The `Role` and `SwitchUserRole` classes have been removed.
382383
* The `getReachableRoles()` method of the `RoleHierarchy` class has been removed. It has been replaced by the new

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
<deprecated>The "%service_id%" service is deprecated since Symfony 4.1.</deprecated>
176176
</service>
177177

178-
<service id="security.user.provider.ldap" class="Symfony\Component\Security\Core\User\LdapUserProvider" abstract="true">
178+
<service id="security.user.provider.ldap" class="Symfony\Component\Ldap\Security\LdapUserProvider" abstract="true">
179179
<argument /> <!-- security.ldap.ldap -->
180180
<argument /> <!-- base dn -->
181181
<argument /> <!-- search dn -->

‎src/Symfony/Bundle/SecurityBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/composer.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"symfony/twig-bundle": "<4.4",
5252
"symfony/var-dumper": "<3.4",
5353
"symfony/framework-bundle": "<4.4",
54-
"symfony/console": "<3.4"
54+
"symfony/console": "<3.4",
55+
"symfony/ldap": "<4.4"
5556
},
5657
"autoload": {
5758
"psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" },
+91Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Ldap\Security;
13+
14+
use Symfony\Component\Ldap\Entry;
15+
use Symfony\Component\Security\Core\User\UserInterface;
16+
17+
/**
18+
* @author Robin Chalas <robin.chalas@gmail.com>
19+
*
20+
* @final
21+
*/
22+
class LdapUser implements UserInterface
23+
{
24+
private $entry;
25+
private $username;
26+
private $password;
27+
private $roles;
28+
private $extraFields;
29+
30+
public function __construct(Entry $entry, string $username, ?string $password, array $roles = [], array $extraFields = [])
31+
{
32+
if (!$username) {
33+
throw new \InvalidArgumentException('The username cannot be empty.');
34+
}
35+
36+
$this->entry = $entry;
37+
$this->username = $username;
38+
$this->password = $password;
39+
$this->roles = $roles;
40+
$this->extraFields = $extraFields;
41+
}
42+
43+
public function getEntry(): Entry
44+
{
45+
return $this->entry;
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function getRoles()
52+
{
53+
return $this->roles;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function getPassword()
60+
{
61+
return $this->password;
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function getSalt()
68+
{
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function getUsername()
75+
{
76+
return $this->username;
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function eraseCredentials()
83+
{
84+
$this->password = null;
85+
}
86+
87+
public function getExtraFields(): array
88+
{
89+
return $this->extraFields;
90+
}
91+
}
+155Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Ldap\Security;
13+
14+
use Symfony\Component\Ldap\Entry;
15+
use Symfony\Component\Ldap\Exception\ConnectionException;
16+
use Symfony\Component\Ldap\LdapInterface;
17+
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
18+
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
19+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
20+
use Symfony\Component\Security\Core\User\UserInterface;
21+
use Symfony\Component\Security\Core\User\UserProviderInterface;
22+
23+
/**
24+
* LdapUserProvider is a simple user provider on top of ldap.
25+
*
26+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
27+
* @author Charles Sarrazin <charles@sarraz.in>
28+
* @author Robin Chalas <robin.chalas@gmail.com>
29+
*/
30+
class LdapUserProvider implements UserProviderInterface
31+
{
32+
private $ldap;
33+
private $baseDn;
34+
private $searchDn;
35+
private $searchPassword;
36+
private $defaultRoles;
37+
private $uidKey;
38+
private $defaultSearch;
39+
private $passwordAttribute;
40+
private $extraFields;
41+
42+
public function __construct(LdapInterface $ldap, string $baseDn, string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
43+
{
44+
if (null === $uidKey) {
45+
$uidKey = 'sAMAccountName';
46+
}
47+
48+
if (null === $filter) {
49+
$filter = '({uid_key}={username})';
50+
}
51+
52+
$this->ldap = $ldap;
53+
$this->baseDn = $baseDn;
54+
$this->searchDn = $searchDn;
55+
$this->searchPassword = $searchPassword;
56+
$this->defaultRoles = $defaultRoles;
57+
$this->uidKey = $uidKey;
58+
$this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
59+
$this->passwordAttribute = $passwordAttribute;
60+
$this->extraFields = $extraFields;
61+
}
62+
63+
/**
64+
* {@inheritdoc}
65+
*/
66+
public function loadUserByUsername($username)
67+
{
68+
try {
69+
$this->ldap->bind($this->searchDn, $this->searchPassword);
70+
$username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER);
71+
$query = str_replace('{username}', $username, $this->defaultSearch);
72+
$search = $this->ldap->query($this->baseDn, $query);
73+
} catch (ConnectionException $e) {
74+
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e);
75+
}
76+
77+
$entries = $search->execute();
78+
$count = \count($entries);
79+
80+
if (!$count) {
81+
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
82+
}
83+
84+
if ($count > 1) {
85+
throw new UsernameNotFoundException('More than one user found');
86+
}
87+
88+
$entry = $entries[0];
89+
90+
try {
91+
if (null !== $this->uidKey) {
92+
$username = $this->getAttributeValue($entry, $this->uidKey);
93+
}
94+
} catch (InvalidArgumentException $e) {
95+
}
96+
97+
return $this->loadUser($username, $entry);
98+
}
99+
100+
/**
101+
* {@inheritdoc}
102+
*/
103+
public function refreshUser(UserInterface $user)
104+
{
105+
if (!$user instanceof LdapUser) {
106+
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
107+
}
108+
109+
return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles());
110+
}
111+
112+
/**
113+
* {@inheritdoc}
114+
*/
115+
public function supportsClass($class)
116+
{
117+
return LdapUser::class === $class;
118+
}
119+
120+
/**
121+
* Loads a user from an LDAP entry.
122+
*
123+
* @return LdapUser
124+
*/
125+
protected function loadUser($username, Entry $entry)
126+
{
127+
$password = null;
128+
$extraFields = [];
129+
130+
if (null !== $this->passwordAttribute) {
131+
$password = $this->getAttributeValue($entry, $this->passwordAttribute);
132+
}
133+
134+
foreach ($this->extraFields as $field) {
135+
$extraFields[$field] = $this->getAttributeValue($entry, $field);
136+
}
137+
138+
return new LdapUser($entry, $username, $password, $this->defaultRoles, $extraFields);
139+
}
140+
141+
private function getAttributeValue(Entry $entry, string $attribute)
142+
{
143+
if (!$entry->hasAttribute($attribute)) {
144+
throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn()));
145+
}
146+
147+
$values = $entry->getAttribute($attribute);
148+
149+
if (1 !== \count($values)) {
150+
throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute));
151+
}
152+
153+
return $values[0];
154+
}
155+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.