Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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

Sign up
Appearance settings

Commitcdd873f

Browse files
committed
[Security] Ability to add roles in form_login_ldap by ldap group
This update allows LDAP to fetch roles for a given user entry by using the new RoleFetcherInterface. The LdapUserProvider class has been adjusted to use this new functionality.
1 parentcad21a9 commitcdd873f

File tree

12 files changed

+294
-4
lines changed

12 files changed

+294
-4
lines changed

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

Lines changed: 4 additions & 2 deletions
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'] ?newReference($config['role_fetcher']) :null)
4041
;
4142
}
4243

@@ -45,9 +46,9 @@ public function getKey(): string
4546
return'ldap';
4647
}
4748

48-
publicfunctionaddConfiguration(NodeDefinition$node):void
49+
publicfunctionaddConfiguration(NodeDefinition$builder):void
4950
{
50-
$node
51+
$builder
5152
->fixXmlConfig('extra_field')
5253
->fixXmlConfig('default_role')
5354
->children()
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@
268268
abstract_arg('filter'),
269269
abstract_arg('password_attribute'),
270270
abstract_arg('extra_fields (email etc)'),
271+
abstract_arg('role fetcher'),
271272
])
272273

273274
->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+
namespaceSymfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLdapLoginBundle\Controller;
13+
14+
useSymfony\Component\HttpFoundation\JsonResponse;
15+
useSymfony\Component\Security\Core\User\UserInterface;
16+
17+
class TestController
18+
{
19+
publicfunctionloginCheckAction(UserInterface$user)
20+
{
21+
returnnewJsonResponse([
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+
namespaceSymfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLdapLoginBundle\Security\Ldap;
13+
14+
useSymfony\Component\Ldap\Entry;
15+
useSymfony\Component\Ldap\Security\RoleFetcherInterface;
16+
17+
class DummyRoleFetcherimplements RoleFetcherInterface
18+
{
19+
publicfunctionfetchRoles(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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111

1212
namespaceSymfony\Bundle\SecurityBundle\Tests\Functional;
1313

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

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

2330
$this->assertInstanceOf(Kernel::class,$kernel);
2431
}
32+
33+
publicfunctiontestDefaultJsonLdapLoginSuccess()
34+
{
35+
// Given
36+
$client =$this->createClient(['test_case' =>'JsonLoginLdap','root_config' =>'config.yml','debug' =>true]);
37+
$container =$client->getContainer();
38+
$connectionMock =$this->createMock(ConnectionInterface::class);
39+
$collection =newclass([newEntry('', ['uid' => ['spomky']])])extends \ArrayObjectimplements CollectionInterface {
40+
publicfunctiontoArray():array
41+
{
42+
return$this->getArrayCopy();
43+
}
44+
};
45+
$queryMock =$this->createMock(QueryInterface::class);
46+
$queryMock
47+
->method('execute')
48+
->willReturn($collection)
49+
;
50+
$ldapAdapterMock =$this->createMock(AdapterInterface::class);
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

Lines changed: 6 additions & 1 deletion
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,8 @@ security:
2125
search_password:''
2226
default_roles:ROLE_USER
2327
uid_key:uid
24-
extra_fields:['email']
28+
#extra_fields: ['email']
29+
role_fetcher:'test_role_fetcher'
2530

2631
firewalls:
2732
main:
Lines changed: 3 additions & 0 deletions
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 }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
namespaceSymfony\Component\Ldap\Security;
13+
14+
useSymfony\Component\Ldap\Entry;
15+
16+
finalreadonlyclass AssignDefaultRolesimplements RoleFetcherInterface
17+
{
18+
/**
19+
* @param string[] $roles
20+
*/
21+
publicfunction__construct(
22+
privatearray$roles,
23+
) {
24+
}
25+
26+
/**
27+
* @return string[]
28+
*/
29+
publicfunctionfetchRoles(Entry$entry):array
30+
{
31+
return$this->roles;
32+
}
33+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class LdapUserProvider implements UserProviderInterface, PasswordUpgraderInterfa
3737
{
3838
privatestring$uidKey;
3939
privatestring$defaultSearch;
40+
privateRoleFetcherInterface$roleFetcher;
4041

4142
publicfunction__construct(
4243
privateLdapInterface$ldap,
@@ -48,12 +49,14 @@ public function __construct(
4849
?string$filter =null,
4950
private ?string$passwordAttribute =null,
5051
privatearray$extraFields = [],
52+
?RoleFetcherInterface$roleFetcher =null,
5153
) {
5254
$uidKey ??='sAMAccountName';
5355
$filter ??='({uid_key}={user_identifier})';
5456

5557
$this->uidKey =$uidKey;
5658
$this->defaultSearch =str_replace('{uid_key}',$uidKey,$filter);
59+
$this->roleFetcher =$roleFetcher ??newAssignDefaultRoles($defaultRoles);
5760
}
5861

5962
publicfunctionloadUserByIdentifier(string$identifier):UserInterface
@@ -147,7 +150,9 @@ protected function loadUser(string $identifier, Entry $entry): UserInterface
147150
$extraFields[$field] =$this->getAttributeValue($entry,$field);
148151
}
149152

150-
returnnewLdapUser($entry,$identifier,$password,$this->defaultRoles,$extraFields);
153+
$roles =$this->roleFetcher->fetchRoles($entry);
154+
155+
returnnewLdapUser($entry,$identifier,$password,$roles,$extraFields);
151156
}
152157

153158
privatefunctiongetAttributeValue(Entry$entry,string$attribute):mixed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
namespaceSymfony\Component\Ldap\Security;
13+
14+
useSymfony\Component\Ldap\Entry;
15+
16+
finalreadonlyclass MemberOfRolesimplements RoleFetcherInterface
17+
{
18+
/**
19+
* @param array<string, string> $mapping
20+
*/
21+
publicfunction__construct(
22+
privatearray$mapping,
23+
privatestring$attributeName ='ismemberof',
24+
privatestring$groupNameRegex ='/^CN=(?P<group>[^,]+),ou.*$/i',
25+
) {
26+
}
27+
28+
/**
29+
* @return string[]
30+
*/
31+
publicfunctionfetchRoles(Entry$entry):array
32+
{
33+
if (!$entry->hasAttribute($this->attributeName)) {
34+
return [];
35+
}
36+
37+
$roles = [];
38+
foreach ($entry->getAttribute($this->attributeName)as$group) {
39+
$groupName =$this->getGroupName($group);
40+
if (\array_key_exists($groupName,$this->mapping)) {
41+
$roles[] =$this->mapping[$groupName];
42+
}
43+
}
44+
45+
returnarray_unique($roles);
46+
}
47+
48+
privatefunctiongetGroupName(string$group):string
49+
{
50+
if (preg_match($this->groupNameRegex,$group,$matches)) {
51+
return$matches['group'];
52+
}
53+
54+
return$group;
55+
}
56+
}
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+
namespaceSymfony\Component\Ldap\Security;
13+
14+
useSymfony\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+
publicfunctionfetchRoles(Entry$entry):array;
25+
}

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
useSymfony\Component\Ldap\LdapInterface;
2020
useSymfony\Component\Ldap\Security\LdapUser;
2121
useSymfony\Component\Ldap\Security\LdapUserProvider;
22+
useSymfony\Component\Ldap\Security\MemberOfRoles;
23+
useSymfony\Component\Ldap\Security\RoleFetcherInterface;
2224
useSymfony\Component\Security\Core\Exception\InvalidArgumentException;
2325
useSymfony\Component\Security\Core\Exception\UserNotFoundException;
2426

@@ -388,4 +390,64 @@ public function testRefreshUserShouldReturnUserWithSameProperties()
388390

389391
$this->assertEquals($user,$provider->refreshUser($user));
390392
}
393+
394+
publicfunctiontestLoadUserWithCorrectRoles()
395+
{
396+
// Given
397+
$result =$this->createMock(CollectionInterface::class);
398+
$query =$this->createMock(QueryInterface::class);
399+
$query
400+
->method('execute')
401+
->willReturn($result)
402+
;
403+
$ldap =$this->createMock(LdapInterface::class);
404+
$result
405+
->method('offsetGet')
406+
->with(0)
407+
->willReturn(newEntry('foo', ['sAMAccountName' => ['foo']]))
408+
;
409+
$result
410+
->method('count')
411+
->willReturn(1)
412+
;
413+
$ldap
414+
->method('escape')
415+
->willReturn('foo')
416+
;
417+
$ldap
418+
->method('query')
419+
->willReturn($query)
420+
;
421+
$roleFetcher =$this->createMock(RoleFetcherInterface::class);
422+
$roleFetcher
423+
->method('fetchRoles')
424+
->willReturn(['ROLE_FOO','ROLE_BAR'])
425+
;
426+
427+
$provider =newLdapUserProvider($ldap,'ou=MyBusiness,dc=symfony,dc=com', roleFetcher:$roleFetcher);
428+
429+
// When
430+
$user =$provider->loadUserByIdentifier('foo');
431+
432+
// Then
433+
$this->assertInstanceOf(LdapUser::class,$user);
434+
$this->assertSame(['ROLE_FOO','ROLE_BAR'],$user->getRoles());
435+
}
436+
437+
publicfunctiontestMemberOfRoleFetch()
438+
{
439+
// Given
440+
$roleFetcher =newMemberOfRoles(
441+
['Staff' =>'ROLE_STAFF','Admin' =>'ROLE_ADMIN'],
442+
'memberOf'
443+
);
444+
445+
$entry =newEntry('uid=elliot.alderson,ou=staff,ou=people,dc=example,dc=com', ['memberOf' => ['cn=Staff,ou=Groups,dc=example,dc=com','cn=Admin,ou=Groups,dc=example,dc=com']]);
446+
447+
// When
448+
$roles =$roleFetcher->fetchRoles($entry);
449+
450+
// Then
451+
$this->assertSame(['ROLE_STAFF','ROLE_ADMIN'],$roles);
452+
}
391453
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp