Skip to content

Navigation Menu

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 3c74d5f

Browse filesBrowse files
committed
[Security] Ability to add roles in form_login_ldap by ldap group
1 parent 0102e1e commit 3c74d5f
Copy full SHA for 3c74d5f

File tree

10 files changed

+188
-3
lines changed
Filter options

10 files changed

+188
-3
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php
+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function create(ContainerBuilder $container, string $id, array $config):
3737
->replaceArgument(6, $config['filter'])
3838
->replaceArgument(7, $config['password_attribute'])
3939
->replaceArgument(8, $config['extra_fields'])
40+
->replaceArgument(9, $config['role_fetcher'] ? new Reference($config['role_fetcher']) : null)
4041
;
4142
}
4243

@@ -63,6 +64,7 @@ public function addConfiguration(NodeDefinition $node): void
6364
->requiresAtLeastOneElement()
6465
->prototype('scalar')->end()
6566
->end()
67+
->scalarNode('role_fetcher')->defaultNull()->end()
6668
->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()
6769
->scalarNode('filter')->defaultValue('({uid_key}={user_identifier})')->end()
6870
->scalarNode('password_attribute')->defaultNull()->end()

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.php
+1
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)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\Bundle\JsonLdapLoginBundle\Controller;
13+
14+
use Symfony\Component\HttpFoundation\JsonResponse;
15+
use Symfony\Component\Security\Core\User\UserInterface;
16+
17+
class TestController
18+
{
19+
public function loginCheckAction(UserInterface $user)
20+
{
21+
return new JsonResponse([
22+
'message' => sprintf('Welcome @%s!', $user->getUserIdentifier()),
23+
'roles' => $user->getRoles(),
24+
]);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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\Bundle\JsonLdapLoginBundle\Security\Ldap;
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+
if ($entry->getAttribute('uid') === ['spomky']) {
22+
return ['ROLE_SUPER_ADMIN', 'ROLE_USER'];
23+
}
24+
25+
return ['ROLE_LDAP_USER_42', 'ROLE_USER'];
26+
}
27+
}

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php
+45
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
1313

14+
use Symfony\Component\HttpFoundation\JsonResponse;
1415
use Symfony\Component\HttpKernel\Kernel;
16+
use Symfony\Component\Ldap\Adapter\AdapterInterface;
17+
use Symfony\Component\Ldap\Adapter\CollectionInterface;
18+
use Symfony\Component\Ldap\Adapter\ConnectionInterface;
19+
use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter;
20+
use Symfony\Component\Ldap\Adapter\QueryInterface;
21+
use Symfony\Component\Ldap\Entry;
1522

1623
class JsonLoginLdapTest extends AbstractWebTestCase
1724
{
@@ -22,4 +29,42 @@ public function testKernelBoot()
2229

2330
$this->assertInstanceOf(Kernel::class, $kernel);
2431
}
32+
33+
public function testDefaultJsonLdapLoginSuccess()
34+
{
35+
// Given
36+
$client = $this->createClient(['test_case' => 'JsonLoginLdap', 'root_config' => 'config.yml', 'debug' => true]);
37+
$container = $client->getContainer();
38+
$connectionMock = $this->getMockBuilder(ConnectionInterface::class)->getMock();
39+
$collection = new class([new Entry('', ['uid' => ['spomky']])]) extends \ArrayObject implements CollectionInterface {
40+
public function toArray(): array
41+
{
42+
return $this->getArrayCopy();
43+
}
44+
};
45+
$queryMock = $this->getMockBuilder(QueryInterface::class)->getMock();
46+
$queryMock
47+
->method('execute')
48+
->willReturn($collection)
49+
;
50+
$ldapAdapterMock = $this->getMockBuilder(AdapterInterface::class)->getMock();
51+
$ldapAdapterMock
52+
->method('getConnection')
53+
->willReturn($connectionMock)
54+
;
55+
$ldapAdapterMock
56+
->method('createQuery')
57+
->willReturn($queryMock)
58+
;
59+
$container->set(Adapter::class, $ldapAdapterMock);
60+
61+
// When
62+
$client->request('POST', '/login', [], [], ['CONTENT_TYPE' => 'application/json'], '{"user": {"login": "spomky", "password": "foo"}}');
63+
$response = $client->getResponse();
64+
65+
// Then
66+
$this->assertInstanceOf(JsonResponse::class, $response);
67+
$this->assertSame(200, $response->getStatusCode());
68+
$this->assertSame(['message' => 'Welcome @spomky!', 'roles' => ['ROLE_SUPER_ADMIN', 'ROLE_USER']], json_decode($response->getContent(), true));
69+
}
2570
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml
+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ imports:
33
services:
44
Symfony\Component\Ldap\Ldap:
55
arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
6+
tags: ['ldap']
7+
8+
test_role_fetcher:
9+
class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLdapLoginBundle\Security\Ldap\DummyRoleFetcher
610

711
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
812
arguments:
@@ -21,7 +25,7 @@ security:
2125
search_password: ''
2226
default_roles: ROLE_USER
2327
uid_key: uid
24-
extra_fields: ['email']
28+
role_fetcher: 'test_role_fetcher'
2529

2630
firewalls:
2731
main:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
login_check:
2+
path: /login
3+
defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLdapLoginBundle\Controller\TestController::loginCheckAction }

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Security/LdapUserProvider.php
+10-2
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 $search
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,13 @@ 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+
161+
if (null !== $this->roleFetcher) {
162+
$roles = $this->roleFetcher->fetchRoles($entry);
163+
}
164+
165+
return new LdapUser($entry, $identifier, $password, $roles, $extraFields);
158166
}
159167

160168
private function getAttributeValue(Entry $entry, string $attribute): mixed
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php
+44
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.