Skip to content

Commit 84d5996

Browse 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

File tree

13 files changed

+609
-111
lines changed

13 files changed

+609
-111
lines changed

UPGRADE-4.4.md

Lines 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

Lines 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

Lines 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

Lines 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\\": "" },
Lines 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+
}
Lines 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy