Skip to content

Commit 93bfe64

Browse files
author
Robin Chalas
committed
[Ldap] Add security LdapUser and provider
1 parent b2dadc1 commit 93bfe64

File tree

12 files changed

+622
-109
lines changed

12 files changed

+622
-109
lines changed

UPGRADE-4.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ Routing
141141
Security
142142
--------
143143

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

146147
Stopwatch

UPGRADE-5.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ Routing
372372
Security
373373
--------
374374

375+
* The `LdapUserProvider` class has been removed, use `Symfony\Component\Ldap\Security\User\LdapUserProvider` instead.
375376
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` must have a new `needsRehash()` method
376377
* The `Role` and `SwitchUserRole` classes have been removed.
377378
* 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\User\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: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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\User;
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+
* @internal
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+
}
85+
86+
public function getExtraFields(): array
87+
{
88+
return $this->extraFields;
89+
}
90+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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\User;
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+
/**
142+
* Fetches a required unique attribute value from an LDAP entry.
143+
*
144+
* @param Entry|null $entry
145+
* @param string $attribute
146+
*/
147+
private function getAttributeValue(Entry $entry, $attribute)
148+
{
149+
if (!$entry->hasAttribute($attribute)) {
150+
throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn()));
151+
}
152+
153+
$values = $entry->getAttribute($attribute);
154+
155+
if (1 !== \count($values)) {
156+
throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute));
157+
}
158+
159+
return $values[0];
160+
}
161+
}

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