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

Skip to content

Commit 0ef6b32

Browse files
committed
bug #51104 [Security] Fix loading user from UserBadge (guillaumesmo)
This PR was merged into the 6.3 branch. Discussion ---------- [Security] Fix loading user from UserBadge | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #50511 | License | MIT | Doc PR | none Fixed a breaking change from https://github.com/symfony/symfony/pull/48272/files#diff-de9707bb338188f62878f2ebd42e7a7bf9547f6d0bf07a4fcd9c386c263c601b Commits ------- 21532cb Fix breaking change in AccessTokenAuthenticator
2 parents a00aa9c + 21532cb commit 0ef6b32

File tree

9 files changed

+247
-5
lines changed

9 files changed

+247
-5
lines changed

src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,18 @@ public function testSelfContainedTokens()
333333
$this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true));
334334
}
335335

336+
public function testCustomUserLoader()
337+
{
338+
$client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_custom_user_loader.yml']);
339+
$client->catchExceptions(false);
340+
$client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => 'Bearer SELF_CONTAINED_ACCESS_TOKEN']);
341+
$response = $client->getResponse();
342+
343+
$this->assertInstanceOf(Response::class, $response);
344+
$this->assertSame(200, $response->getStatusCode());
345+
$this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true));
346+
}
347+
336348
/**
337349
* @requires extension openssl
338350
*/
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
imports:
2+
- { resource: ./../config/framework.yml }
3+
4+
framework:
5+
http_method_override: false
6+
serializer: ~
7+
8+
security:
9+
password_hashers:
10+
Symfony\Component\Security\Core\User\InMemoryUser: plaintext
11+
12+
providers:
13+
in_memory:
14+
memory:
15+
users:
16+
dunglas: { password: foo, roles: [ROLE_MISSING] }
17+
18+
firewalls:
19+
main:
20+
pattern: ^/
21+
stateless: true
22+
access_token:
23+
token_handler: access_token.access_token_handler
24+
token_extractors: 'header'
25+
realm: 'My API'
26+
27+
access_control:
28+
- { path: ^/foo, roles: ROLE_USER }
29+
30+
services:
31+
access_token.access_token_handler:
32+
class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler

src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
2828
use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\InvalidSignatureException;
2929
use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\MissingClaimException;
30+
use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader;
3031
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
3132

3233
/**
@@ -93,7 +94,7 @@ public function getUserBadgeFrom(string $accessToken): UserBadge
9394
}
9495

9596
// UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate
96-
return new UserBadge($claims[$this->claim], fn () => $this->createUser($claims), $claims);
97+
return new UserBadge($claims[$this->claim], new FallbackUserLoader(fn () => $this->createUser($claims)), $claims);
9798
} catch (\Exception $e) {
9899
$this->logger?->error('An error occurred while decoding and validating the token.', [
99100
'error' => $e->getMessage(),

src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcUserInfoTokenHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
1616
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
1717
use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\MissingClaimException;
18+
use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader;
1819
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
1920
use Symfony\Contracts\HttpClient\HttpClientInterface;
2021

@@ -48,7 +49,7 @@ public function getUserBadgeFrom(string $accessToken): UserBadge
4849
}
4950

5051
// UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate
51-
return new UserBadge($claims[$this->claim], fn () => $this->createUser($claims), $claims);
52+
return new UserBadge($claims[$this->claim], new FallbackUserLoader(fn () => $this->createUser($claims)), $claims);
5253
} catch (\Exception $e) {
5354
$this->logger?->error('An error occurred on OIDC server.', [
5455
'error' => $e->getMessage(),

src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function authenticate(Request $request): Passport
5959
}
6060

6161
$userBadge = $this->accessTokenHandler->getUserBadgeFrom($accessToken);
62-
if ($this->userProvider) {
62+
if ($this->userProvider && (null === $userBadge->getUserLoader() || $userBadge->getUserLoader() instanceof FallbackUserLoader)) {
6363
$userBadge->setUserLoader($this->userProvider->loadUserByIdentifier(...));
6464
}
6565

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Http\Authenticator;
13+
14+
use Symfony\Component\Security\Core\User\UserInterface;
15+
16+
/**
17+
* This wrapper serves as a marker interface to indicate badge user loaders that should not be overridden by the
18+
* default user provider.
19+
*
20+
* @internal
21+
*/
22+
final class FallbackUserLoader
23+
{
24+
public function __construct(private $inner)
25+
{
26+
}
27+
28+
public function __invoke(mixed ...$args): ?UserInterface
29+
{
30+
return ($this->inner)(...$args);
31+
}
32+
}

src/Symfony/Component/Security/Http/Tests/AccessToken/Oidc/OidcTokenHandlerTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
2222
use Symfony\Component\Security\Core\User\OidcUser;
2323
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
24+
use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader;
2425
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
2526

2627
/**
@@ -61,7 +62,7 @@ public function testGetsUserIdentifierFromSignedToken(string $claim, string $exp
6162
))->getUserBadgeFrom($token);
6263
$actualUser = $userBadge->getUserLoader()();
6364

64-
$this->assertEquals(new UserBadge($expected, fn () => $expectedUser, $claims), $userBadge);
65+
$this->assertEquals(new UserBadge($expected, new FallbackUserLoader(fn () => $expectedUser), $claims), $userBadge);
6566
$this->assertInstanceOf(OidcUser::class, $actualUser);
6667
$this->assertEquals($expectedUser, $actualUser);
6768
$this->assertEquals($claims, $userBadge->getAttributes());

src/Symfony/Component/Security/Http/Tests/AccessToken/Oidc/OidcUserInfoTokenHandlerTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
1717
use Symfony\Component\Security\Core\User\OidcUser;
1818
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcUserInfoTokenHandler;
19+
use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader;
1920
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
2021
use Symfony\Contracts\HttpClient\HttpClientInterface;
2122
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -47,7 +48,7 @@ public function testGetsUserIdentifierFromOidcServerResponse(string $claim, stri
4748
$userBadge = (new OidcUserInfoTokenHandler($clientMock, null, $claim))->getUserBadgeFrom($accessToken);
4849
$actualUser = $userBadge->getUserLoader()();
4950

50-
$this->assertEquals(new UserBadge($expected, fn () => $expectedUser, $claims), $userBadge);
51+
$this->assertEquals(new UserBadge($expected, new FallbackUserLoader(fn () => $expectedUser), $claims), $userBadge);
5152
$this->assertInstanceOf(OidcUser::class, $actualUser);
5253
$this->assertEquals($expectedUser, $actualUser);
5354
$this->assertEquals($claims, $userBadge->getAttributes());
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Authenticator;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
17+
use Symfony\Component\Security\Core\User\InMemoryUser;
18+
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
19+
use Symfony\Component\Security\Http\AccessToken\AccessTokenExtractorInterface;
20+
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
21+
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
22+
use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader;
23+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
24+
25+
class AccessTokenAuthenticatorTest extends TestCase
26+
{
27+
private AccessTokenHandlerInterface $accessTokenHandler;
28+
private AccessTokenExtractorInterface $accessTokenExtractor;
29+
private InMemoryUserProvider $userProvider;
30+
31+
protected function setUp(): void
32+
{
33+
$this->accessTokenHandler = $this->createMock(AccessTokenHandlerInterface::class);
34+
$this->accessTokenExtractor = $this->createMock(AccessTokenExtractorInterface::class);
35+
$this->userProvider = new InMemoryUserProvider(['test' => ['password' => 's$cr$t']]);
36+
}
37+
38+
public function testAuthenticateWithoutAccessToken()
39+
{
40+
$this->expectException(BadCredentialsException::class);
41+
$this->expectExceptionMessage('Invalid credentials.');
42+
43+
$request = Request::create('/test');
44+
45+
$this->accessTokenExtractor
46+
->expects($this->once())
47+
->method('extractAccessToken')
48+
->with($request)
49+
->willReturn(null);
50+
51+
$authenticator = new AccessTokenAuthenticator(
52+
$this->accessTokenHandler,
53+
$this->accessTokenExtractor,
54+
);
55+
56+
$authenticator->authenticate($request);
57+
}
58+
59+
public function testAuthenticateWithoutProvider()
60+
{
61+
$request = Request::create('/test');
62+
63+
$this->accessTokenExtractor
64+
->expects($this->once())
65+
->method('extractAccessToken')
66+
->with($request)
67+
->willReturn('test');
68+
$this->accessTokenHandler
69+
->expects($this->once())
70+
->method('getUserBadgeFrom')
71+
->with('test')
72+
->willReturn(new UserBadge('john', fn () => new InMemoryUser('john', null)));
73+
74+
$authenticator = new AccessTokenAuthenticator(
75+
$this->accessTokenHandler,
76+
$this->accessTokenExtractor,
77+
$this->userProvider,
78+
);
79+
80+
$passport = $authenticator->authenticate($request);
81+
82+
$this->assertEquals('john', $passport->getUser()->getUserIdentifier());
83+
}
84+
85+
public function testAuthenticateWithoutUserLoader()
86+
{
87+
$request = Request::create('/test');
88+
89+
$this->accessTokenExtractor
90+
->expects($this->once())
91+
->method('extractAccessToken')
92+
->with($request)
93+
->willReturn('test');
94+
$this->accessTokenHandler
95+
->expects($this->once())
96+
->method('getUserBadgeFrom')
97+
->with('test')
98+
->willReturn(new UserBadge('test'));
99+
100+
$authenticator = new AccessTokenAuthenticator(
101+
$this->accessTokenHandler,
102+
$this->accessTokenExtractor,
103+
$this->userProvider,
104+
);
105+
106+
$passport = $authenticator->authenticate($request);
107+
108+
$this->assertEquals('test', $passport->getUser()->getUserIdentifier());
109+
}
110+
111+
public function testAuthenticateWithUserLoader()
112+
{
113+
$request = Request::create('/test');
114+
115+
$this->accessTokenExtractor
116+
->expects($this->once())
117+
->method('extractAccessToken')
118+
->with($request)
119+
->willReturn('test');
120+
$this->accessTokenHandler
121+
->expects($this->once())
122+
->method('getUserBadgeFrom')
123+
->with('test')
124+
->willReturn(new UserBadge('john', fn () => new InMemoryUser('john', null)));
125+
126+
$authenticator = new AccessTokenAuthenticator(
127+
$this->accessTokenHandler,
128+
$this->accessTokenExtractor,
129+
$this->userProvider,
130+
);
131+
132+
$passport = $authenticator->authenticate($request);
133+
134+
$this->assertEquals('john', $passport->getUser()->getUserIdentifier());
135+
}
136+
137+
public function testAuthenticateWithFallbackUserLoader()
138+
{
139+
$request = Request::create('/test');
140+
141+
$this->accessTokenExtractor
142+
->expects($this->once())
143+
->method('extractAccessToken')
144+
->with($request)
145+
->willReturn('test');
146+
$this->accessTokenHandler
147+
->expects($this->once())
148+
->method('getUserBadgeFrom')
149+
->with('test')
150+
->willReturn(new UserBadge('test', new FallbackUserLoader(fn () => new InMemoryUser('john', null))));
151+
152+
$authenticator = new AccessTokenAuthenticator(
153+
$this->accessTokenHandler,
154+
$this->accessTokenExtractor,
155+
$this->userProvider,
156+
);
157+
158+
$passport = $authenticator->authenticate($request);
159+
160+
$this->assertEquals('test', $passport->getUser()->getUserIdentifier());
161+
}
162+
}

0 commit comments

Comments
 (0)