Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4241413

Browse files
committed
bug #18881 [Security][Ldap] Fixed issue with password attribute containing an array of values. (csarrazi)
This PR was merged into the 3.1 branch. Discussion ---------- [Security][Ldap] Fixed issue with password attribute containing an array of values. | Q | A | ------------- | --- | Branch? | 3.1 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #18401 | License | MIT | Doc PR | This PR fixes #18401, as well as other possible issues: * First, the user provider no longer requires a password attribute by default. While this is not mandatory, it is more explicit to not set a password when using the `form_login_ldap` or `http_basic_ldap`, as these two providers don't use a password comparison mechanism, but `ldap_bind()` instead. * Second, the attribute is now configurable. Some implementations actually use different properties to store the user's password attribute. This will enable some users to correctly work with specific configurations. * Third, the user provider normalises the attribute array into a single string. Also, if the attribute has more than one value (which should not be possible), or if is not set, an exception will be thrown, with a clear error message. Commits ------- dbf45e4 [Ldap] Fixed issue with Entry password attribute containing array of values and made password attribute configurable
2 parents 4974a8b + dbf45e4 commit 4241413

File tree

3 files changed

+177
-5
lines changed

3 files changed

+177
-5
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function create(ContainerBuilder $container, $id, $config)
3535
->replaceArgument(4, $config['default_roles'])
3636
->replaceArgument(5, $config['uid_key'])
3737
->replaceArgument(6, $config['filter'])
38+
->replaceArgument(7, $config['password_attribute'])
3839
;
3940
}
4041

@@ -58,6 +59,7 @@ public function addConfiguration(NodeDefinition $node)
5859
->end()
5960
->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()
6061
->scalarNode('filter')->defaultValue('({uid_key}={username})')->end()
62+
->scalarNode('password_attribute')->defaultNull()->end()
6163
->end()
6264
;
6365
}

src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry()
105105
$provider->loadUserByUsername('foo');
106106
}
107107

108-
public function testSuccessfulLoadUserByUsername()
108+
/**
109+
* @expectedException \Symfony\Component\Security\Core\Exception\InvalidArgumentException
110+
*/
111+
public function testLoadUserByUsernameFailsIfMoreThanOneLdapPasswordsInEntry()
109112
{
110113
$result = $this->getMock(CollectionInterface::class);
111114
$query = $this->getMock(QueryInterface::class);
@@ -120,8 +123,95 @@ public function testSuccessfulLoadUserByUsername()
120123
->method('offsetGet')
121124
->with(0)
122125
->will($this->returnValue(new Entry('foo', array(
123-
'sAMAccountName' => 'foo',
124-
'userpassword' => 'bar',
126+
'sAMAccountName' => array('foo'),
127+
'userpassword' => array('bar', 'baz'),
128+
)
129+
)))
130+
;
131+
$result
132+
->expects($this->once())
133+
->method('count')
134+
->will($this->returnValue(1))
135+
;
136+
$ldap
137+
->expects($this->once())
138+
->method('escape')
139+
->will($this->returnValue('foo'))
140+
;
141+
$ldap
142+
->expects($this->once())
143+
->method('query')
144+
->will($this->returnValue($query))
145+
;
146+
147+
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, array(), 'sAMAccountName', '({uid_key}={username})', 'userpassword');
148+
$this->assertInstanceOf(
149+
'Symfony\Component\Security\Core\User\User',
150+
$provider->loadUserByUsername('foo')
151+
);
152+
}
153+
154+
/**
155+
* @expectedException \Symfony\Component\Security\Core\Exception\InvalidArgumentException
156+
*/
157+
public function testLoadUserByUsernameFailsIfEntryHasNoPasswordAttribute()
158+
{
159+
$result = $this->getMock(CollectionInterface::class);
160+
$query = $this->getMock(QueryInterface::class);
161+
$query
162+
->expects($this->once())
163+
->method('execute')
164+
->will($this->returnValue($result))
165+
;
166+
$ldap = $this->getMock(LdapInterface::class);
167+
$result
168+
->expects($this->once())
169+
->method('offsetGet')
170+
->with(0)
171+
->will($this->returnValue(new Entry('foo', array(
172+
'sAMAccountName' => array('foo'),
173+
)
174+
)))
175+
;
176+
$result
177+
->expects($this->once())
178+
->method('count')
179+
->will($this->returnValue(1))
180+
;
181+
$ldap
182+
->expects($this->once())
183+
->method('escape')
184+
->will($this->returnValue('foo'))
185+
;
186+
$ldap
187+
->expects($this->once())
188+
->method('query')
189+
->will($this->returnValue($query))
190+
;
191+
192+
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, array(), 'sAMAccountName', '({uid_key}={username})', 'userpassword');
193+
$this->assertInstanceOf(
194+
'Symfony\Component\Security\Core\User\User',
195+
$provider->loadUserByUsername('foo')
196+
);
197+
}
198+
199+
public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttribute()
200+
{
201+
$result = $this->getMock(CollectionInterface::class);
202+
$query = $this->getMock(QueryInterface::class);
203+
$query
204+
->expects($this->once())
205+
->method('execute')
206+
->will($this->returnValue($result))
207+
;
208+
$ldap = $this->getMock(LdapInterface::class);
209+
$result
210+
->expects($this->once())
211+
->method('offsetGet')
212+
->with(0)
213+
->will($this->returnValue(new Entry('foo', array(
214+
'sAMAccountName' => array('foo'),
125215
)
126216
)))
127217
;
@@ -147,4 +237,47 @@ public function testSuccessfulLoadUserByUsername()
147237
$provider->loadUserByUsername('foo')
148238
);
149239
}
240+
241+
public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute()
242+
{
243+
$result = $this->getMock(CollectionInterface::class);
244+
$query = $this->getMock(QueryInterface::class);
245+
$query
246+
->expects($this->once())
247+
->method('execute')
248+
->will($this->returnValue($result))
249+
;
250+
$ldap = $this->getMock(LdapInterface::class);
251+
$result
252+
->expects($this->once())
253+
->method('offsetGet')
254+
->with(0)
255+
->will($this->returnValue(new Entry('foo', array(
256+
'sAMAccountName' => array('foo'),
257+
'userpassword' => array('bar'),
258+
)
259+
)))
260+
;
261+
$result
262+
->expects($this->once())
263+
->method('count')
264+
->will($this->returnValue(1))
265+
;
266+
$ldap
267+
->expects($this->once())
268+
->method('escape')
269+
->will($this->returnValue('foo'))
270+
;
271+
$ldap
272+
->expects($this->once())
273+
->method('query')
274+
->will($this->returnValue($query))
275+
;
276+
277+
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, array(), 'sAMAccountName', '({uid_key}={username})', 'userpassword');
278+
$this->assertInstanceOf(
279+
'Symfony\Component\Security\Core\User\User',
280+
$provider->loadUserByUsername('foo')
281+
);
282+
}
150283
}

src/Symfony/Component/Security/Core/User/LdapUserProvider.php

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Security\Core\User;
1313

1414
use Symfony\Component\Ldap\Entry;
15+
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
1516
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
1617
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
1718
use Symfony\Component\Ldap\Exception\ConnectionException;
@@ -31,6 +32,7 @@ class LdapUserProvider implements UserProviderInterface
3132
private $searchPassword;
3233
private $defaultRoles;
3334
private $defaultSearch;
35+
private $passwordAttribute;
3436

3537
/**
3638
* @param LdapInterface $ldap
@@ -41,14 +43,15 @@ class LdapUserProvider implements UserProviderInterface
4143
* @param string $uidKey
4244
* @param string $filter
4345
*/
44-
public function __construct(LdapInterface $ldap, $baseDn, $searchDn = null, $searchPassword = null, array $defaultRoles = array(), $uidKey = 'sAMAccountName', $filter = '({uid_key}={username})')
46+
public function __construct(LdapInterface $ldap, $baseDn, $searchDn = null, $searchPassword = null, array $defaultRoles = array(), $uidKey = 'sAMAccountName', $filter = '({uid_key}={username})', $passwordAttribute = null)
4547
{
4648
$this->ldap = $ldap;
4749
$this->baseDn = $baseDn;
4850
$this->searchDn = $searchDn;
4951
$this->searchPassword = $searchPassword;
5052
$this->defaultRoles = $defaultRoles;
5153
$this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
54+
$this->passwordAttribute = $passwordAttribute;
5255
}
5356

5457
/**
@@ -99,8 +102,42 @@ public function supportsClass($class)
99102
return $class === 'Symfony\Component\Security\Core\User\User';
100103
}
101104

105+
/**
106+
* Loads a user from an LDAP entry.
107+
*
108+
* @param string $username
109+
* @param Entry $entry
110+
*
111+
* @return User
112+
*/
102113
private function loadUser($username, Entry $entry)
103114
{
104-
return new User($username, $entry->getAttribute('userpassword'), $this->defaultRoles);
115+
$password = $this->getPassword($entry);
116+
117+
return new User($username, $password, $this->defaultRoles);
118+
}
119+
120+
/**
121+
* Fetches the password from an LDAP entry.
122+
*
123+
* @param null|Entry $entry
124+
*/
125+
private function getPassword(Entry $entry)
126+
{
127+
if (null === $this->passwordAttribute) {
128+
return;
129+
}
130+
131+
if (!$entry->hasAttribute($this->passwordAttribute)) {
132+
throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $this->passwordAttribute, $entry->getDn()));
133+
}
134+
135+
$values = $entry->getAttribute($this->passwordAttribute);
136+
137+
if (1 !== count($values)) {
138+
throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $this->passwordAttribute));
139+
}
140+
141+
return $values[0];
105142
}
106143
}

0 commit comments

Comments
 (0)