Skip to content

Commit 172a40a

Browse files
committed
[Security] Ability to add roles in form_login_ldap by ldap group
1 parent d741f38 commit 172a40a

File tree

7 files changed

+109
-2
lines changed

7 files changed

+109
-2
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class LdapFactory implements UserProviderFactoryInterface
2626
{
2727
public function create(ContainerBuilder $container, string $id, array $config): void
2828
{
29+
$roleFetcher = $config['role_fetcher'] ? new Reference($config['role_fetcher']) : null;
2930
$container
3031
->setDefinition($id, new ChildDefinition('security.user.provider.ldap'))
3132
->replaceArgument(0, new Reference($config['service']))
@@ -37,6 +38,7 @@ public function create(ContainerBuilder $container, string $id, array $config):
3738
->replaceArgument(6, $config['filter'])
3839
->replaceArgument(7, $config['password_attribute'])
3940
->replaceArgument(8, $config['extra_fields'])
41+
->replaceArgument(9, $roleFetcher)
4042
;
4143
}
4244

@@ -63,6 +65,7 @@ public function addConfiguration(NodeDefinition $node): void
6365
->requiresAtLeastOneElement()
6466
->prototype('scalar')->end()
6567
->end()
68+
->scalarNode('role_fetcher')->defaultNull()->end()
6669
->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()
6770
->scalarNode('filter')->defaultValue('({uid_key}={username})')->end()
6871
->scalarNode('password_attribute')->defaultNull()->end()

src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@
256256
abstract_arg('filter'),
257257
abstract_arg('password_attribute'),
258258
abstract_arg('extra_fields (email etc)'),
259+
abstract_arg('role fetcher'),
259260
])
260261

261262
->set('security.user.provider.chain', ChainUserProvider::class)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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\Bundle\SecurityBundle\Tests\Functional;
13+
14+
use Symfony\Component\Ldap\Entry;
15+
use Symfony\Component\Ldap\Security\RoleFetcherInterface;
16+
17+
class DummyRoleFetcher implements RoleFetcherInterface
18+
{
19+
public function fetchRoles(Entry $entry): array
20+
{
21+
dd($entry); // Tests to be written
22+
}
23+
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ services:
44
Symfony\Component\Ldap\Ldap:
55
arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
66

7+
test_role_fetcher:
8+
class: Symfony\Bundle\SecurityBundle\Tests\Functional\DummyRoleFetcher
9+
710
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
811
arguments:
912
- host: 'localhost'
@@ -22,6 +25,7 @@ security:
2225
default_roles: ROLE_USER
2326
uid_key: uid
2427
extra_fields: ['email']
28+
role_fetcher: 'test_role_fetcher'
2529

2630
firewalls:
2731
main:

src/Symfony/Component/Ldap/Security/LdapUserProvider.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ class LdapUserProvider implements UserProviderInterface, PasswordUpgraderInterfa
4444
private string $defaultSearch;
4545
private ?string $passwordAttribute;
4646
private array $extraFields;
47+
private ?RoleFetcherInterface $roleFetcher;
4748

48-
public function __construct(LdapInterface $ldap, string $baseDn, string $searchDn = null, #[\SensitiveParameter] string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
49+
public function __construct(LdapInterface $ldap, string $baseDn, string $searchDn = null, #[\SensitiveParameter] string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [], RoleFetcherInterface $roleFetcher = null)
4950
{
5051
$uidKey ??= 'sAMAccountName';
5152
$filter ??= '({uid_key}={user_identifier})';
@@ -59,6 +60,7 @@ public function __construct(LdapInterface $ldap, string $baseDn, string $searchD
5960
$this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
6061
$this->passwordAttribute = $passwordAttribute;
6162
$this->extraFields = $extraFields;
63+
$this->roleFetcher = $roleFetcher;
6264
}
6365

6466
public function loadUserByIdentifier(string $identifier): UserInterface
@@ -154,7 +156,12 @@ protected function loadUser(string $identifier, Entry $entry): UserInterface
154156
$extraFields[$field] = $this->getAttributeValue($entry, $field);
155157
}
156158

157-
return new LdapUser($entry, $identifier, $password, $this->defaultRoles, $extraFields);
159+
$roles = $this->defaultRoles;
160+
if (null !== $this->roleFetcher) {
161+
$roles = $this->roleFetcher->fetchRoles($entry);
162+
}
163+
164+
return new LdapUser($entry, $identifier, $password, $roles, $extraFields);
158165
}
159166

160167
private function getAttributeValue(Entry $entry, string $attribute): mixed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
16+
/**
17+
* Fetches LDAP roles for a given entry.
18+
*/
19+
interface RoleFetcherInterface
20+
{
21+
/**
22+
* @return string[] The list of roles
23+
*/
24+
public function fetchRoles(Entry $entry): array;
25+
}

src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Ldap\LdapInterface;
2020
use Symfony\Component\Ldap\Security\LdapUser;
2121
use Symfony\Component\Ldap\Security\LdapUserProvider;
22+
use Symfony\Component\Ldap\Security\RoleFetcherInterface;
2223
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
2324
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
2425

@@ -388,4 +389,47 @@ public function testRefreshUserShouldReturnUserWithSameProperties()
388389

389390
$this->assertEquals($user, $provider->refreshUser($user));
390391
}
392+
393+
public function testLoadUserWithCorrectRoles()
394+
{
395+
// Given
396+
$result = $this->createMock(CollectionInterface::class);
397+
$query = $this->createMock(QueryInterface::class);
398+
$query
399+
->method('execute')
400+
->willReturn($result)
401+
;
402+
$ldap = $this->createMock(LdapInterface::class);
403+
$result
404+
->method('offsetGet')
405+
->with(0)
406+
->willReturn(new Entry('foo', ['sAMAccountName' => ['foo']]))
407+
;
408+
$result
409+
->method('count')
410+
->willReturn(1)
411+
;
412+
$ldap
413+
->method('escape')
414+
->willReturn('foo')
415+
;
416+
$ldap
417+
->method('query')
418+
->willReturn($query)
419+
;
420+
$roleFetcher = $this->createMock(RoleFetcherInterface::class);
421+
$roleFetcher
422+
->method('fetchRoles')
423+
->willReturn(['ROLE_FOO', 'ROLE_BAR'])
424+
;
425+
426+
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', roleFetcher: $roleFetcher);
427+
428+
// When
429+
$user = $provider->loadUserByIdentifier('foo');
430+
431+
// Then
432+
$this->assertInstanceOf(LdapUser::class, $user);
433+
$this->assertSame(['ROLE_FOO', 'ROLE_BAR'], $user->getRoles());
434+
}
391435
}

0 commit comments

Comments
 (0)