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 fa50a1e

Browse filesBrowse files
committed
Allow to use ldap in a chain provider
1 parent 532dcda commit fa50a1e
Copy full SHA for fa50a1e

File tree

6 files changed

+260
-16
lines changed
Filter options

6 files changed

+260
-16
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php
+3-4Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ public function onCheckPassport(CheckPassportEvent $event)
4848

4949
/** @var LdapBadge $ldapBadge */
5050
$ldapBadge = $passport->getBadge(LdapBadge::class);
51-
if ($ldapBadge->isResolved()) {
52-
return;
53-
}
5451

5552
if (!$passport->hasBadge(PasswordCredentials::class)) {
5653
throw new \LogicException(sprintf('LDAP authentication requires a passport containing password credentials, authenticator "%s" does not fulfill these requirements.', $event->getAuthenticator()::class));
@@ -72,6 +69,9 @@ public function onCheckPassport(CheckPassportEvent $event)
7269
}
7370

7471
$user = $passport->getUser();
72+
if (!$user instanceof LdapUser) {
73+
return;
74+
}
7575

7676
/** @var LdapInterface $ldap */
7777
$ldap = $this->ldapLocator->get($ldapBadge->getLdapServiceId());
@@ -105,7 +105,6 @@ public function onCheckPassport(CheckPassportEvent $event)
105105
}
106106

107107
$passwordCredentials->markResolved();
108-
$ldapBadge->markResolved();
109108
}
110109

111110
public static function getSubscribedEvents(): array

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Security/LdapAuthenticator.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function supports(Request $request): ?bool
6060
public function authenticate(Request $request): Passport
6161
{
6262
$passport = $this->authenticator->authenticate($request);
63-
$passport->addBadge(new LdapBadge($this->ldapServiceId, $this->dnString, $this->searchDn, $this->searchPassword, $this->queryString));
63+
$passport->addBadge(new LdapBadge($this->ldapServiceId, $this->dnString, $this->searchDn, $this->searchPassword, $this->queryString, true));
6464

6565
return $passport;
6666
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Security/LdapBadge.php
+8-2Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
*/
2525
class LdapBadge implements BadgeInterface
2626
{
27-
private bool $resolved = false;
27+
private bool $resolved = true;
2828
private string $ldapServiceId;
2929
private string $dnString;
3030
private string $searchDn;
3131
private string $searchPassword;
3232
private ?string $queryString;
3333

34-
public function __construct(string $ldapServiceId, string $dnString = '{user_identifier}', string $searchDn = '', string $searchPassword = '', string $queryString = null)
34+
public function __construct(string $ldapServiceId, string $dnString = '{user_identifier}', string $searchDn = '', string $searchPassword = '', string $queryString = null, bool $resolved = false)
3535
{
3636
$this->ldapServiceId = $ldapServiceId;
3737
$dnString = str_replace('{username}', '{user_identifier}', $dnString, $replaceCount);
@@ -46,6 +46,10 @@ public function __construct(string $ldapServiceId, string $dnString = '{user_ide
4646
trigger_deprecation('symfony/ldap', '6.2', 'Using "{username}" parameter in LDAP configuration is deprecated, consider using "{user_identifier}" instead.');
4747
}
4848
$this->queryString = $queryString;
49+
$this->resolved = $resolved;
50+
if (false === $this->resolved) {
51+
trigger_deprecation('symfony/ldap', '6.4', 'Passing false as resolved initial value is deprecated, use true instead.');
52+
}
4953
}
5054

5155
public function getLdapServiceId(): string
@@ -75,6 +79,8 @@ public function getQueryString(): ?string
7579

7680
public function markResolved(): void
7781
{
82+
trigger_deprecation('symfony/ldap', '6.4', 'Calling %s is deprecated.', __METHOD__);
83+
7884
$this->resolved = true;
7985
}
8086

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Tests/Security/CheckLdapCredentialsListenerTest.php
+16-9Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\Ldap\LdapInterface;
2424
use Symfony\Component\Ldap\Security\CheckLdapCredentialsListener;
2525
use Symfony\Component\Ldap\Security\LdapBadge;
26+
use Symfony\Component\Ldap\Security\LdapUser;
2627
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2728
use Symfony\Component\Security\Core\Exception\AuthenticationException;
2829
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
@@ -62,9 +63,10 @@ public static function provideShouldNotCheckPassport()
6263
yield [new TestAuthenticator(), new Passport(new UserBadge('test'), new PasswordCredentials('s3cret'))];
6364

6465
// ldap already resolved
65-
$badge = new LdapBadge('app.ldap');
66-
$badge->markResolved();
67-
yield [new TestAuthenticator(), new Passport(new UserBadge('test'), new PasswordCredentials('s3cret'), [$badge])];
66+
$ldapBadge = new LdapBadge('app.ldap', '{user_identifier}', '', '', null, true);
67+
$userBadge = new UserBadge('test');
68+
$userBadge->setUserLoader(function () { return new InMemoryUser('test', 'pass', ['ROLE_USER']); });
69+
yield [new TestAuthenticator(), new Passport($userBadge, new PasswordCredentials('s3cret'), [$ldapBadge])];
6870
}
6971

7072
public function testPasswordCredentialsAlreadyResolvedThrowsException()
@@ -74,7 +76,7 @@ public function testPasswordCredentialsAlreadyResolvedThrowsException()
7476

7577
$badge = new PasswordCredentials('s3cret');
7678
$badge->markResolved();
77-
$passport = new Passport(new UserBadge('test'), $badge, [new LdapBadge('app.ldap')]);
79+
$passport = new Passport(new UserBadge('test'), $badge, [new LdapBadge('app.ldap', '{user_identifier}', '', '', null, true)]);
7880

7981
$listener = $this->createListener();
8082
$listener->onCheckPassport(new CheckPassportEvent(new TestAuthenticator(), $passport));
@@ -86,7 +88,7 @@ public function testInvalidLdapServiceId()
8688
$this->expectExceptionMessage('Cannot check credentials using the "not_existing_ldap_service" ldap service, as such service is not found. Did you maybe forget to add the "ldap" service tag to this service?');
8789

8890
$listener = $this->createListener();
89-
$listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('not_existing_ldap_service')));
91+
$listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('not_existing_ldap_service', '{user_identifier}', '', '', null, true)));
9092
}
9193

9294
/**
@@ -104,7 +106,10 @@ public function testWrongPassport($passport)
104106
public static function provideWrongPassportData()
105107
{
106108
// no password credentials
107-
yield [new SelfValidatingPassport(new UserBadge('test'), [new LdapBadge('app.ldap')])];
109+
yield [new SelfValidatingPassport(
110+
new UserBadge('test'),
111+
[new LdapBadge('app.ldap', '{user_identifier}', '', '', null, true)]
112+
)];
108113
}
109114

110115
public function testEmptyPasswordShouldThrowAnException()
@@ -198,7 +203,7 @@ public function toArray(): array
198203
$this->ldap->expects($this->once())->method('query')->with('{user_identifier}', 'wouter_test')->willReturn($query);
199204

200205
$listener = $this->createListener();
201-
$listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{user_identifier}', 'elsa', 'test1234A$', '{user_identifier}_test')));
206+
$listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{user_identifier}', 'elsa', 'test1234A$', '{user_identifier}_test', true)));
202207
}
203208

204209
public function testEmptyQueryResultShouldThrowAnException()
@@ -226,14 +231,16 @@ public function testEmptyQueryResultShouldThrowAnException()
226231
$this->ldap->expects($this->once())->method('query')->willReturn($query);
227232

228233
$listener = $this->createListener();
229-
$listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{user_identifier}', 'elsa', 'test1234A$', '{user_identifier}_test')));
234+
$listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{user_identifier}', 'elsa', 'test1234A$', '{user_identifier}_test', true)));
230235
}
231236

232237
private function createEvent($password = 's3cr3t', $ldapBadge = null)
233238
{
239+
$ldapUser = new LdapUser(new Entry('cn=Wouter,dc=example,dc=com'), 'Wouter', null, ['ROLE_USER']);
240+
234241
return new CheckPassportEvent(
235242
new TestAuthenticator(),
236-
new Passport(new UserBadge('Wouter', fn () => new InMemoryUser('Wouter', null, ['ROLE_USER'])), new PasswordCredentials($password), [$ldapBadge ?? new LdapBadge('app.ldap')])
243+
new Passport(new UserBadge('Wouter', fn () => $ldapUser), new PasswordCredentials($password), [$ldapBadge ?? new LdapBadge('app.ldap', '{user_identifier}', '', '', null, true)])
237244
);
238245
}
239246

+42Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 Security;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
16+
use Symfony\Component\Ldap\Security\LdapBadge;
17+
18+
final class LdapBadgeTest extends TestCase
19+
{
20+
use ExpectDeprecationTrait;
21+
22+
/**
23+
* @group legacy
24+
*/
25+
public function testDeprecationOnResolvedInitialValue()
26+
{
27+
$this->expectDeprecation('Since symfony/ldap 6.4: Passing false as resolved initial value is deprecated, use true instead.');
28+
29+
new LdapBadge('foo');
30+
}
31+
32+
/**
33+
* @group legacy
34+
*/
35+
public function testDeprecationOnMarkAsResolved()
36+
{
37+
$this->expectDeprecation('Since symfony/ldap 6.4: Calling Symfony\Component\Ldap\Security\LdapBadge::markResolved is deprecated.');
38+
39+
$sut = new LdapBadge('foo', '{user_identifier}', '', '', null, true);
40+
$sut->markResolved();
41+
}
42+
}
+190Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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\Security\Core\Tests\Authentication\Provider;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Psr\Container\ContainerInterface;
16+
use Symfony\Component\EventDispatcher\EventDispatcher;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpFoundation\Response;
19+
use Symfony\Component\HttpFoundation\Session\Session;
20+
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
21+
use Symfony\Component\Ldap\Adapter\AdapterInterface;
22+
use Symfony\Component\Ldap\Adapter\CollectionInterface;
23+
use Symfony\Component\Ldap\Adapter\ConnectionInterface;
24+
use Symfony\Component\Ldap\Adapter\QueryInterface;
25+
use Symfony\Component\Ldap\Entry;
26+
use Symfony\Component\Ldap\Exception\ConnectionException;
27+
use Symfony\Component\Ldap\Ldap;
28+
use Symfony\Component\Ldap\Security\CheckLdapCredentialsListener;
29+
use Symfony\Component\Ldap\Security\LdapAuthenticator;
30+
use Symfony\Component\Ldap\Security\LdapUserProvider;
31+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
32+
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
33+
use Symfony\Component\Security\Core\User\ChainUserProvider;
34+
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
35+
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
36+
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
37+
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
38+
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
39+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
40+
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
41+
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
42+
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
43+
use Symfony\Component\Security\Http\HttpUtils;
44+
45+
class ChainProviderWithLdapTest extends TestCase
46+
{
47+
public function provideChainWithLdapAndInMemory(): array
48+
{
49+
return [
50+
'in memory' => ['foo', 'foopass'],
51+
'ldap' => ['bar', 'barpass'],
52+
];
53+
}
54+
55+
/**
56+
* @dataProvider provideChainWithLdapAndInMemory
57+
*/
58+
public function testChainWithLdapAndInMemory(string $userIdentifier, string $pass)
59+
{
60+
$inMemoryProvider = new InMemoryUserProvider([
61+
'foo' => ['password' => 'foopass', 'roles' => ['ROLE_USER']],
62+
]);
63+
64+
$ldapAdapteur = $this->createMock(AdapterInterface::class);
65+
$ldapAdapteur
66+
->method('getConnection')
67+
->willReturn($connection = $this->createMock(ConnectionInterface::class))
68+
;
69+
70+
$connection
71+
->method('bind')
72+
->willReturnCallback(static function (?string $user, ?string $pass): void {
73+
if ('admin' === $user && 'adminpass' === $pass) {
74+
return;
75+
}
76+
77+
if ('bar' === $user && 'barpass' === $pass) {
78+
return;
79+
}
80+
81+
throw new ConnectionException('failure when binding');
82+
})
83+
;
84+
85+
$ldapAdapteur
86+
->method('escape')
87+
->willReturnArgument(0)
88+
;
89+
90+
$ldapAdapteur
91+
->method('createQuery')
92+
->willReturn($query = $this->createMock(QueryInterface::class))
93+
;
94+
95+
$query
96+
->method('execute')
97+
->willReturn($collection = $this->createMock(CollectionInterface::class));
98+
99+
$collection
100+
->method('count')
101+
->willReturn(1)
102+
;
103+
104+
$collection
105+
->method('offsetGet')
106+
->with(0)
107+
->willReturn(new Entry('cn=bar,dc=example,dc=com', ['sAMAccountName' => ['bar'], 'userPassword' => ['barpass']]))
108+
;
109+
110+
$ldapProvider = new LdapUserProvider($ldap = new Ldap($ldapAdapteur), 'dc=example,dc=com', 'admin', 'adminpass', [], null, null, 'userPassword');
111+
112+
$chainUserProvider = new ChainUserProvider([$inMemoryProvider, $ldapProvider]);
113+
114+
$httpUtils = $this->createMock(HttpUtils::class);
115+
$httpUtils
116+
->method('checkRequestPath')
117+
->willReturn(true)
118+
;
119+
120+
$failureHandler = $this->createMock(AuthenticationFailureHandlerInterface::class);
121+
$failureHandler
122+
->method('onAuthenticationFailure')
123+
->willReturn(new Response())
124+
;
125+
126+
$formLoginAuthenticator = new FormLoginAuthenticator(
127+
$httpUtils,
128+
$chainUserProvider,
129+
$this->createMock(AuthenticationSuccessHandlerInterface::class),
130+
$failureHandler,
131+
[]
132+
);
133+
134+
$ldapAuthenticator = new LdapAuthenticator($formLoginAuthenticator, 'ldap-id');
135+
136+
$ldapLocator = new class($ldap) implements ContainerInterface {
137+
private $ldap;
138+
139+
public function __construct(Ldap $ldap)
140+
{
141+
$this->ldap = $ldap;
142+
}
143+
144+
public function get(string $id): Ldap
145+
{
146+
return $this->ldap;
147+
}
148+
149+
public function has(string $id): bool
150+
{
151+
return 'ldap-id' === $id;
152+
}
153+
};
154+
155+
$eventDispatcher = new EventDispatcher();
156+
$eventDispatcher->addListener(CheckPassportEvent::class, [new UserProviderListener($chainUserProvider), 'checkPassport']);
157+
$eventDispatcher->addListener(CheckPassportEvent::class, [new CheckLdapCredentialsListener($ldapLocator), 'onCheckPassport']);
158+
$eventDispatcher->addListener(CheckPassportEvent::class, function (CheckPassportEvent $event): void {
159+
$passport = $event->getPassport();
160+
$userBadge = $passport->getBadge(UserBadge::class);
161+
if (null === $userBadge || null === $userBadge->getUser()) {
162+
return;
163+
}
164+
$credentials = $passport->getBadge(PasswordCredentials::class);
165+
if ($credentials->isResolved()) {
166+
return;
167+
}
168+
169+
if ($credentials && 'foopass' === $credentials->getPassword()) {
170+
$credentials->markResolved();
171+
}
172+
});
173+
174+
$authenticatorManager = new AuthenticatorManager(
175+
[$ldapAuthenticator],
176+
$tokenStorage = new TokenStorage(),
177+
$eventDispatcher,
178+
'main'
179+
);
180+
181+
$request = Request::create('/login', 'POST', ['_username' => $userIdentifier, '_password' => $pass]);
182+
$request->setSession(new Session(new MockArraySessionStorage()));
183+
184+
$this->assertTrue($authenticatorManager->supports($request));
185+
$authenticatorManager->authenticateRequest($request);
186+
187+
$this->assertInstanceOf(UsernamePasswordToken::class, $token = $tokenStorage->getToken());
188+
$this->assertSame($userIdentifier, $token->getUserIdentifier());
189+
}
190+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.