Skip to content

Commit 5a21ac9

Browse files
committed
fix: #225: GrantConfigurator should accept League\OAuth2\Server\Grant\GrantTypeInterface
1 parent cee1e71 commit 5a21ac9

11 files changed

+198
-9
lines changed

Diff for: docs/implementing-custom-grant-type.md

+70
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,75 @@
11
# Implementing custom grant type
22

3+
1. Create a class that implements the `League\OAuth2\Server\Grant\GrantTypeInterface` interface.
4+
5+
Example:
6+
7+
```php
8+
<?php
9+
10+
declare(strict_types=1);
11+
12+
namespace App\Grant;
13+
14+
use DateInterval;
15+
use League\OAuth2\Server\Grant\AbstractGrant;
16+
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
17+
use Nyholm\Psr7\Response;
18+
use Psr\Http\Message\ServerRequestInterface;
19+
use League\OAuth2\Server\Grant\GrantTypeInterface;
20+
21+
final class FakeGrant extends AbstractGrant implements GrantTypeInterface
22+
{
23+
/**
24+
* @var SomeDependency
25+
*/
26+
private $foo;
27+
28+
public function __construct(SomeDependency $foo)
29+
{
30+
$this->foo = $foo;
31+
}
32+
33+
public function getIdentifier()
34+
{
35+
return 'fake_grant';
36+
}
37+
38+
public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL)
39+
{
40+
return new Response();
41+
}
42+
}
43+
```
44+
45+
1. In order to enable the new grant type in the authorization server you must register the service in the container.
46+
And the service must be tagged with the `league.oauth2_server.authorization_server.grant` tag:
47+
48+
```yaml
49+
services:
50+
51+
App\Grant\FakeGrant:
52+
tags:
53+
- {name: league.oauth2_server.authorization_server.grant}
54+
```
55+
56+
You could define a custom access token TTL for your grant using `accessTokenTTL` tag attribute :
57+
58+
```yaml
59+
services:
60+
61+
App\Grant\FakeGrant:
62+
tags:
63+
- {name: league.oauth2_server.authorization_server.grant, accessTokenTTL: PT5H}
64+
```
65+
66+
If `accessTokenTTL` tag attribute is not defined, then bundle config is used `league_oauth2_server.authorization_server.access_token_ttl` (same as `league.oauth2_server.access_token_ttl.default` service container parameter). \
67+
`null` is considered as defined, to allow to unset ttl. \
68+
`league_oauth2_server.authorization_server.refresh_token_ttl` is also accessible for your implementation using `league.oauth2_server.refresh_token_ttl.default` service container parameter.
69+
70+
71+
# Implementing custom grant type (deprecated method)
72+
373
1. Create a class that implements the `\League\Bundle\OAuth2ServerBundle\League\AuthorizationServer\GrantTypeInterface` interface.
474

575
Example:

Diff for: src/AuthorizationServer/GrantConfigurator.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use League\OAuth2\Server\AuthorizationServer;
88

9+
/**
10+
* @deprecated
11+
*/
912
final class GrantConfigurator
1013
{
1114
/**
@@ -24,7 +27,9 @@ public function __construct(iterable $grants)
2427
public function __invoke(AuthorizationServer $authorizationServer): void
2528
{
2629
foreach ($this->grants as $grant) {
27-
$authorizationServer->enableGrantType($grant, $grant->getAccessTokenTTL());
30+
if ($grant instanceof GrantTypeInterface) {
31+
$authorizationServer->enableGrantType($grant, $grant->getAccessTokenTTL());
32+
}
2833
}
2934
}
3035
}

Diff for: src/AuthorizationServer/GrantTypeInterface.php

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use League\OAuth2\Server\Grant\GrantTypeInterface as LeagueGrantTypeInterface;
88

9+
/**
10+
* @deprecated use League\OAuth2\Server\Grant\GrantTypeInterface with accessTokenTTL tag attribute instead
11+
*/
912
interface GrantTypeInterface extends LeagueGrantTypeInterface
1013
{
1114
public function getAccessTokenTTL(): ?\DateInterval;
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace League\Bundle\OAuth2ServerBundle\DependencyInjection\CompilerPass;
4+
5+
use League\Bundle\OAuth2ServerBundle\AuthorizationServer\GrantTypeInterface;
6+
use League\OAuth2\Server\AuthorizationServer;
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Definition;
10+
use Symfony\Component\DependencyInjection\Reference;
11+
12+
class GrantTypePass implements CompilerPassInterface
13+
{
14+
/**
15+
* @inheritDoc
16+
*/
17+
public function process(ContainerBuilder $container): void
18+
{
19+
// check if AuthorizationServer service is defined
20+
if (!$container->has(AuthorizationServer::class)) {
21+
return;
22+
}
23+
24+
$definition = $container->findDefinition(AuthorizationServer::class);
25+
26+
// find all service IDs with the league.oauth2_server.authorization_server.grant tag
27+
$taggedServices = $container->findTaggedServiceIds('league.oauth2_server.authorization_server.grant');
28+
29+
// enable grant type for each
30+
foreach ($taggedServices as $id => $tags) {
31+
// skip of custom grant using \League\Bundle\OAuth2ServerBundle\AuthorizationServer\GrantTypeInterface
32+
// since there are handled by \League\Bundle\OAuth2ServerBundle\AuthorizationServer\GrantConfigurator
33+
// TODO remove code bloc when bundle interface and configurator will be deleted
34+
try {
35+
$grantDefinition = $container->findDefinition($id);
36+
/** @var class-string|null $grantClass */
37+
$grantClass = $grantDefinition->getClass();
38+
if (null !== $grantClass) {
39+
$refGrantClass = new \ReflectionClass($grantClass);
40+
if ($refGrantClass->implementsInterface(GrantTypeInterface::class)) {
41+
continue;
42+
}
43+
}
44+
} catch (\ReflectionException) {
45+
// handling of this service as native one
46+
}
47+
48+
foreach ($tags as $attributes) {
49+
$definition->addMethodCall('enableGrantType', [
50+
new Reference($id),
51+
// use accessTokenTTL tag attribute if exists, otherwise use global bundle config
52+
new Definition(\DateInterval::class, [\array_key_exists('accessTokenTTL', $attributes)
53+
? $attributes['accessTokenTTL']
54+
: $container->getParameter('league.oauth2_server.access_token_ttl.default')]),
55+
]);
56+
}
57+
}
58+
}
59+
}

Diff for: src/DependencyInjection/LeagueOAuth2ServerExtension.php

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public function load(array $configs, ContainerBuilder $container)
6969
$container->findDefinition(OAuth2Authenticator::class)
7070
->setArgument(3, $config['role_prefix']);
7171

72+
// TODO remove code bloc when bundle interface and configurator will be deleted
7273
$container->registerForAutoconfiguration(GrantTypeInterface::class)
7374
->addTag('league.oauth2_server.authorization_server.grant');
7475

@@ -139,6 +140,7 @@ private function configureAuthorizationServer(ContainerBuilder $container, array
139140
{
140141
$container->setParameter('league.oauth2_server.encryption_key', $config['encryption_key']);
141142
$container->setParameter('league.oauth2_server.encryption_key.type', $config['encryption_key_type']);
143+
$container->setParameter('league.oauth2_server.access_token_ttl.default', $config['access_token_ttl']);
142144

143145
$authorizationServer = $container
144146
->findDefinition(AuthorizationServer::class)
@@ -195,6 +197,8 @@ private function configureAuthorizationServer(ContainerBuilder $container, array
195197
*/
196198
private function configureGrants(ContainerBuilder $container, array $config): void
197199
{
200+
$container->setParameter('league.oauth2_server.refresh_token_ttl.default', $config['refresh_token_ttl']);
201+
198202
$container
199203
->findDefinition(PasswordGrant::class)
200204
->addMethodCall('setRefreshTokenTTL', [

Diff for: src/LeagueOAuth2ServerBundle.php

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass;
88
use League\Bundle\OAuth2ServerBundle\DependencyInjection\CompilerPass\EncryptionKeyPass;
9+
use League\Bundle\OAuth2ServerBundle\DependencyInjection\CompilerPass\GrantTypePass;
910
use League\Bundle\OAuth2ServerBundle\DependencyInjection\LeagueOAuth2ServerExtension;
1011
use League\Bundle\OAuth2ServerBundle\DependencyInjection\Security\OAuth2Factory;
1112
use League\Bundle\OAuth2ServerBundle\Persistence\Mapping\Driver;
@@ -26,6 +27,8 @@ public function build(ContainerBuilder $container)
2627

2728
$this->configureDoctrineExtension($container);
2829
$this->configureSecurityExtension($container);
30+
31+
$container->addCompilerPass(new GrantTypePass());
2932
}
3033

3134
public function getContainerExtension(): ExtensionInterface

Diff for: src/Resources/config/services.php

+2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
->set('league.oauth2_server.emitter', EventEmitter::class)
134134
->call('subscribeListenersFrom', [service('league.oauth2_server.symfony_league_listener_provider')])
135135

136+
// TODO remove code bloc when bundle interface and configurator will be deleted
136137
->set('league.oauth2_server.authorization_server.grant_configurator', GrantConfigurator::class)
137138
->args([
138139
tagged_iterator('league.oauth2_server.authorization_server.grant'),
@@ -150,6 +151,7 @@
150151
null,
151152
])
152153
->call('setEmitter', [service('league.oauth2_server.emitter')])
154+
// TODO remove next line when bundle interface and configurator will be deleted
153155
->configurator(service(GrantConfigurator::class))
154156
->alias(AuthorizationServer::class, 'league.oauth2_server.authorization_server')
155157

Diff for: tests/Fixtures/FakeGrant.php

+1-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
namespace League\Bundle\OAuth2ServerBundle\Tests\Fixtures;
66

7-
use League\Bundle\OAuth2ServerBundle\AuthorizationServer\GrantTypeInterface;
87
use League\OAuth2\Server\Grant\AbstractGrant;
8+
use League\OAuth2\Server\Grant\GrantTypeInterface;
99
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
1010
use Nyholm\Psr7\Response;
1111
use Psr\Http\Message\ServerRequestInterface;
@@ -21,9 +21,4 @@ public function respondToAccessTokenRequest(ServerRequestInterface $request, Res
2121
{
2222
return new Response();
2323
}
24-
25-
public function getAccessTokenTTL(): ?\DateInterval
26-
{
27-
return new \DateInterval('PT5H');
28-
}
2924
}

Diff for: tests/Fixtures/FakeLegacyGrant.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace League\Bundle\OAuth2ServerBundle\Tests\Fixtures;
6+
7+
use League\Bundle\OAuth2ServerBundle\AuthorizationServer\GrantTypeInterface;
8+
use League\OAuth2\Server\Grant\AbstractGrant;
9+
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
10+
use Nyholm\Psr7\Response;
11+
use Psr\Http\Message\ServerRequestInterface;
12+
13+
// TODO remove code bloc when bundle interface and configurator will be deleted
14+
/**
15+
* @deprecated
16+
*/
17+
final class FakeLegacyGrant extends AbstractGrant implements GrantTypeInterface
18+
{
19+
public function getIdentifier(): string
20+
{
21+
return 'fake_legacy_grant';
22+
}
23+
24+
public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $responseType, \DateInterval $accessTokenTTL): ResponseTypeInterface
25+
{
26+
return new Response();
27+
}
28+
29+
public function getAccessTokenTTL(): ?\DateInterval
30+
{
31+
return new \DateInterval('PT5H');
32+
}
33+
}

Diff for: tests/Integration/AuthorizationServerCustomGrantTest.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace League\Bundle\OAuth2ServerBundle\Tests\Integration;
66

77
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeGrant;
8+
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeLegacyGrant;
89
use League\OAuth2\Server\AuthorizationServer;
910
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1011

@@ -21,10 +22,20 @@ public function testAuthorizationServerHasOurCustomGrantEnabled(): void
2122
$reflectionProperty = $reflectionClass->getProperty('enabledGrantTypes');
2223
$reflectionProperty->setAccessible(true);
2324

25+
$reflectionTTLProperty = $reflectionClass->getProperty('grantTypeAccessTokenTTL');
26+
$reflectionTTLProperty->setAccessible(true);
27+
2428
$enabledGrantTypes = $reflectionProperty->getValue($authorizationServer);
29+
$grantTypeAccessTokenTTL = $reflectionTTLProperty->getValue($authorizationServer);
2530

2631
$this->assertArrayHasKey('fake_grant', $enabledGrantTypes);
2732
$this->assertInstanceOf(FakeGrant::class, $enabledGrantTypes['fake_grant']);
28-
$this->assertEquals(new \DateInterval('PT5H'), $enabledGrantTypes['fake_grant']->getAccessTokenTTL());
33+
$this->assertArrayHasKey('fake_grant', $grantTypeAccessTokenTTL);
34+
$this->assertEquals(new \DateInterval('PT5H'), $grantTypeAccessTokenTTL['fake_grant']);
35+
36+
// TODO remove code bloc when bundle interface and configurator will be deleted
37+
$this->assertArrayHasKey('fake_legacy_grant', $enabledGrantTypes);
38+
$this->assertInstanceOf(FakeLegacyGrant::class, $enabledGrantTypes['fake_legacy_grant']);
39+
$this->assertEquals(new \DateInterval('PT5H'), $enabledGrantTypes['fake_legacy_grant']->getAccessTokenTTL());
2940
}
3041
}

Diff for: tests/TestKernel.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeClientManager;
1717
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeCredentialsRevoker;
1818
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeGrant;
19+
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeLegacyGrant;
1920
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FakeRefreshTokenManager;
2021
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\FixtureFactory;
2122
use League\Bundle\OAuth2ServerBundle\Tests\Fixtures\SecurityTestController;
@@ -246,7 +247,10 @@ private function configureCustomPersistenceServices(ContainerBuilder $container)
246247

247248
private function registerFakeGrant(ContainerBuilder $container): void
248249
{
249-
$container->register(FakeGrant::class)->setAutoconfigured(true);
250+
$container->register(FakeGrant::class)
251+
->addTag('league.oauth2_server.authorization_server.grant', ['accessTokenTTL' => 'PT5H']);
252+
// TODO remove line when bundle interface and configurator will be deleted
253+
$container->register(FakeLegacyGrant::class)->setAutoconfigured(true);
250254
}
251255

252256
private function initializeEnvironmentVariables(): void

0 commit comments

Comments
 (0)