Skip to content

Commit

Permalink
Merge pull request #168 from trikoder/make-token-firewall-context-aware
Browse files Browse the repository at this point in the history
Make the token aware of the firewall context
  • Loading branch information
X-Coder264 authored Feb 24, 2020
2 parents 24ad882 + 47c8fe4 commit d349329
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 12 deletions.
6 changes: 4 additions & 2 deletions DependencyInjection/Security/OAuth2Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
$providerId = 'security.authentication.provider.oauth2.' . $id;
$container
->setDefinition($providerId, new ChildDefinition(OAuth2Provider::class))
->replaceArgument('$userProvider', new Reference($userProvider));
->replaceArgument('$userProvider', new Reference($userProvider))
->replaceArgument('$providerKey', $id);

$listenerId = 'security.authentication.listener.oauth2.' . $id;
$container
->setDefinition($listenerId, new ChildDefinition(OAuth2Listener::class));
->setDefinition($listenerId, new ChildDefinition(OAuth2Listener::class))
->replaceArgument('$providerKey', $id);

return [$providerId, $listenerId, OAuth2EntryPoint::class];
}
Expand Down
18 changes: 14 additions & 4 deletions Security/Authentication/Provider/OAuth2Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,21 @@ final class OAuth2Provider implements AuthenticationProviderInterface
*/
private $oauth2TokenFactory;

public function __construct(UserProviderInterface $userProvider, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory)
{
/**
* @var string
*/
private $providerKey;

public function __construct(
UserProviderInterface $userProvider,
ResourceServer $resourceServer,
OAuth2TokenFactory $oauth2TokenFactory,
string $providerKey
) {
$this->userProvider = $userProvider;
$this->resourceServer = $resourceServer;
$this->oauth2TokenFactory = $oauth2TokenFactory;
$this->providerKey = $providerKey;
}

/**
Expand All @@ -60,7 +70,7 @@ public function authenticate(TokenInterface $token)
$request->getAttribute('oauth_user_id')
);

$token = $this->oauth2TokenFactory->createOAuth2Token($request, $user);
$token = $this->oauth2TokenFactory->createOAuth2Token($request, $user, $this->providerKey);
$token->setAuthenticated(true);

return $token;
Expand All @@ -71,7 +81,7 @@ public function authenticate(TokenInterface $token)
*/
public function supports(TokenInterface $token)
{
return $token instanceof OAuth2Token;
return $token instanceof OAuth2Token && $this->providerKey === $token->getProviderKey();
}

private function getAuthenticatedUser(string $userIdentifier): ?UserInterface
Expand Down
70 changes: 68 additions & 2 deletions Security/Authentication/Token/OAuth2Token.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@

final class OAuth2Token extends AbstractToken
{
public function __construct(ServerRequestInterface $serverRequest, ?UserInterface $user, string $rolePrefix)
{
/**
* @var string
*/
private $providerKey;

public function __construct(
ServerRequestInterface $serverRequest,
?UserInterface $user,
string $rolePrefix,
string $providerKey
) {
$this->setAttribute('server_request', $serverRequest);
$this->setAttribute('role_prefix', $rolePrefix);

Expand All @@ -25,6 +34,8 @@ public function __construct(ServerRequestInterface $serverRequest, ?UserInterfac
}

parent::__construct(array_unique($roles));

$this->providerKey = $providerKey;
}

/**
Expand All @@ -35,6 +46,61 @@ public function getCredentials()
return $this->getAttribute('server_request')->getAttribute('oauth_access_token_id');
}

public function getProviderKey(): string
{
return $this->providerKey;
}

public function __serialize(): array
{
if (method_exists(parent::class, '__serialize')) {
// this code path should be the only code path after dropping support for Symfony 3.4
return [$this->providerKey, parent::__serialize()];
}

return [$this->providerKey, $this->getUser(), $this->isAuthenticated(), $this->getRoles(), $this->getAttributes()];
}

public function __unserialize(array $data): void
{
if (method_exists(parent::class, '__unserialize')) {
// this code path should be the only code path after dropping support for Symfony 3.4
[$this->providerKey, $parentData] = $data;
parent::__unserialize($parentData);

return;
}

[$this->providerKey] = $data;

unset($data[0]);

parent::unserialize(array_values($data));
}

/**
* This entire function can be removed when dropping support for Symfony 3.4
*/
public function serialize()
{
$serialized = [$this->providerKey, parent::serialize(true)];

if (method_exists(parent::class, 'doSerialize')) {
return $this->doSerialize($serialized, \func_num_args() ? func_get_arg(0) : null);
}

return serialize($serialized);
}

/**
* This entire function can be removed when dropping support for Symfony 3.4
*/
public function unserialize($serialized)
{
[$this->providerKey, $parentStr] = \is_array($serialized) ? $serialized : unserialize($serialized);
parent::unserialize($parentStr);
}

private function buildRolesFromScopes(): array
{
$prefix = $this->getAttribute('role_prefix');
Expand Down
4 changes: 2 additions & 2 deletions Security/Authentication/Token/OAuth2TokenFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public function __construct(string $rolePrefix)
$this->rolePrefix = $rolePrefix;
}

public function createOAuth2Token(ServerRequestInterface $serverRequest, ?UserInterface $user): OAuth2Token
public function createOAuth2Token(ServerRequestInterface $serverRequest, ?UserInterface $user, string $providerKey): OAuth2Token
{
return new OAuth2Token($serverRequest, $user, $this->rolePrefix);
return new OAuth2Token($serverRequest, $user, $this->rolePrefix, $providerKey);
}
}
11 changes: 9 additions & 2 deletions Security/Firewall/OAuth2Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,23 @@ final class OAuth2Listener implements ListenerInterface
*/
private $oauth2TokenFactory;

/**
* @var string
*/
private $providerKey;

public function __construct(
TokenStorageInterface $tokenStorage,
AuthenticationManagerInterface $authenticationManager,
HttpMessageFactoryInterface $httpMessageFactory,
OAuth2TokenFactory $oauth2TokenFactory
OAuth2TokenFactory $oauth2TokenFactory,
string $providerKey
) {
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->httpMessageFactory = $httpMessageFactory;
$this->oauth2TokenFactory = $oauth2TokenFactory;
$this->providerKey = $providerKey;
}

/**
Expand All @@ -68,7 +75,7 @@ public function __invoke(GetResponseEvent $event)

try {
/** @var OAuth2Token $authenticatedToken */
$authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null));
$authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey));
} catch (AuthenticationException $e) {
throw Oauth2AuthenticationFailedException::create($e->getMessage());
}
Expand Down
49 changes: 49 additions & 0 deletions Tests/Unit/OAuth2ProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Tests\Unit;

use League\OAuth2\Server\ResourceServer;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Provider\OAuth2Provider;
use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token;
use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\FixtureFactory;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\User;

final class OAuth2ProviderTest extends TestCase
{
public function testItSupportsOnlyOAuthTokenWithSameProviderKey(): void
{
$providerKey = 'foo';

$tokenFactory = new OAuth2TokenFactory('ROLE_OAUTH2_');

$provider = new OAuth2Provider(
$this->createMock(UserProviderInterface::class),
$this->createMock(ResourceServer::class),
$tokenFactory,
$providerKey
);

$this->assertTrue($provider->supports($this->createToken($tokenFactory, $providerKey)));
$this->assertFalse($provider->supports($this->createToken($tokenFactory, $providerKey . 'bar')));
}

private function createToken(OAuth2TokenFactory $tokenFactory, string $providerKey): OAuth2Token
{
$scopes = [FixtureFactory::FIXTURE_SCOPE_FIRST];
$serverRequest = $this->createMock(ServerRequestInterface::class);
$serverRequest->expects($this->once())
->method('getAttribute')
->with('oauth_scopes', [])
->willReturn($scopes);

$user = new User();

return $tokenFactory->createOAuth2Token($serverRequest, $user, $providerKey);
}
}
43 changes: 43 additions & 0 deletions Tests/Unit/OAuth2TokenFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token;
use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\FixtureFactory;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\User;

final class OAuth2TokenFactoryTest extends TestCase
{
public function testCreatingToken(): void
{
$rolePrefix = 'ROLE_OAUTH2_';
$factory = new OAuth2TokenFactory($rolePrefix);

$scopes = [FixtureFactory::FIXTURE_SCOPE_FIRST];
$serverRequest = $this->createMock(ServerRequestInterface::class);
$serverRequest->expects($this->once())
->method('getAttribute')
->with('oauth_scopes', [])
->willReturn($scopes);

$user = new User();
$providerKey = 'main';

$token = $factory->createOAuth2Token($serverRequest, $user, $providerKey);

$this->assertInstanceOf(OAuth2Token::class, $token);

$roles = $token->getRoles();
$this->assertCount(1, $roles);
$this->assertSame($rolePrefix . strtoupper($scopes[0]), $roles[0]->getRole());

$this->assertFalse($token->isAuthenticated());
$this->assertSame($user, $token->getUser());
$this->assertSame($providerKey, $token->getProviderKey());
}
}
46 changes: 46 additions & 0 deletions Tests/Unit/OAuth2TokenTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\FixtureFactory;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\User;

final class OAuth2TokenTest extends TestCase
{
public function testTokenSerialization(): void
{
$scopes = [FixtureFactory::FIXTURE_SCOPE_FIRST];
$serverRequest = $this->createMock(ServerRequestInterface::class);
$serverRequest->expects($this->once())
->method('getAttribute')
->with('oauth_scopes', [])
->willReturn($scopes);

$user = new User();
$rolePrefix = 'ROLE_OAUTH2_';
$providerKey = 'main';
$token = new OAuth2Token($serverRequest, $user, $rolePrefix, $providerKey);

/** @var OAuth2Token $unserializedToken */
$unserializedToken = unserialize(serialize($token));

$this->assertSame($providerKey, $unserializedToken->getProviderKey());

$roles = $unserializedToken->getRoles();
$this->assertCount(1, $roles);
$expectedRole = $rolePrefix . strtoupper($scopes[0]);
$this->assertSame($expectedRole, $roles[0]->getRole());

$this->assertSame($user->getUsername(), $unserializedToken->getUser()->getUsername());
$this->assertFalse($unserializedToken->isAuthenticated());

if (method_exists($token, 'getRoleNames')) {
$this->assertSame([$expectedRole], $token->getRoleNames());
}
}
}

0 comments on commit d349329

Please sign in to comment.