10000 bug #51104 [Security] Fix loading user from UserBadge (guillaumesmo) · symfony/symfony@0ef6b32 · GitHub
[go: up one dir, main page]

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
  • src/Symfony
    • Bundle/SecurityBundle/Tests/Functional
    • Component/Security/Http
      • < 8000 div class="PRIVATE_VisuallyHidden prc-TreeView-TreeViewVisuallyHidden-4-mPv" aria-hidden="true" id=":R2mudtddabH1:">
        AccessToken/Oidc
  • Authenticator
  • Tests
  • 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 <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\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 <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 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)
    0