Skip to content

Commit

Permalink
Add per grant type configuration options
Browse files Browse the repository at this point in the history
  • Loading branch information
HypeMC committed Apr 17, 2020
1 parent 5cdbbdc commit 2e54c1e
Show file tree
Hide file tree
Showing 5 changed files with 602 additions and 88 deletions.
143 changes: 115 additions & 28 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Trikoder\Bundle\OAuth2Bundle\OAuth2Grants;

final class Configuration implements ConfigurationInterface
{
Expand All @@ -27,11 +28,11 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode
->children()
->scalarNode('exception_event_listener_priority')
->info('The priority of the event listener that converts an Exception to a Response')
->info('The priority of the event listener that converts an Exception to a Response.')
->defaultValue(10)
->end()
->scalarNode('role_prefix')
->info('Set a custom prefix that replaces the default \'ROLE_OAUTH2_\' role prefix')
->info('Set a custom prefix that replaces the default "ROLE_OAUTH2_" role prefix.')
->defaultValue('ROLE_OAUTH2_')
->cannotBeEmpty()
->end()
Expand All @@ -55,7 +56,7 @@ private function createAuthorizationServerNode(): NodeDefinition
->cannotBeEmpty()
->end()
->scalarNode('private_key_passphrase')
->info('Passphrase of the private key, if any')
->info('Passphrase of the private key, if any.')
->defaultValue(null)
->end()
->scalarNode('encryption_key')
Expand All @@ -64,48 +65,134 @@ private function createAuthorizationServerNode(): NodeDefinition
->cannotBeEmpty()
->end()
->enumNode('encryption_key_type')
->info("The type of value of 'encryption_key'")
->info('The type of value of "encryption_key".')
->values(['plain', 'defuse'])
->defaultValue('plain')
->end()
->scalarNode('access_token_ttl')
->info("How long the issued access token should be valid for.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->info("How long the issued access token should be valid for, used as a default if there is no grant type specific value set.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->cannotBeEmpty()
->defaultValue('PT1H')
->end()
->scalarNode('refresh_token_ttl')
->info("How long the issued refresh token should be valid for.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->info("How long the issued refresh token should be valid for, used as a default if there is no grant type specific value set.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->cannotBeEmpty()
->defaultValue('P1M')
->end()

// @TODO Remove in v4 start

->scalarNode('auth_code_ttl')
->info("How long the issued auth code should be valid for.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->info("How long the issued authorization code should be valid for.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->cannotBeEmpty()
->defaultValue('PT10M')
->end()
->booleanNode('enable_client_credentials_grant')
->info('Whether to enable the client credentials grant')
->defaultTrue()
->setDeprecated('"%path%.%node%" is deprecated, use "%path%.grant_types.authorization_code.auth_code_ttl" instead.')
->beforeNormalization()
->ifNull()
->thenUnset()
->end()
->end()
->booleanNode('enable_password_grant')
->info('Whether to enable the password grant')
->defaultTrue()
->booleanNode('require_code_challenge_for_public_clients')
->info('Whether to require code challenge for public clients for the authorization code grant.')
->setDeprecated('"%path%.%node%" is deprecated, use "%path%.grant_types.authorization_code.require_code_challenge_for_public_clients" instead.')
->beforeNormalization()
->ifNull()
->thenUnset()
->end()
->end()
->booleanNode('enable_refresh_token_grant')
->info('Whether to enable the refresh token grant')
->defaultTrue()
->end()
;

foreach (OAuth2Grants::MAP as $grantType => $grantTypeName) {
$oldGrantType = 'authorization_code' === $grantType ? 'auth_code' : $grantType;

$node
->children()
->booleanNode(sprintf('enable_%s_grant', $oldGrantType))
->info(sprintf('Whether to enable the %s grant.', $grantTypeName))
->setDeprecated(sprintf('"%%path%%.%%node%%" is deprecated, use "%%path%%.grant_types.%s.enable" instead.', $grantType))
->beforeNormalization()
->ifNull()
->thenUnset()
->end()
->end()
->end()
->booleanNode('enable_auth_code_grant')
->info('Whether to enable the authorization code grant')
->defaultTrue()
;
}

// @TODO Remove in v4 end

$node->append($this->createAuthorizationServerGrantTypesNode());

return $node;
}

private function createAuthorizationServerGrantTypesNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('grant_types');
$node = $treeBuilder->getRootNode();

$node
->info('Enable and configure grant types.')
->addDefaultsIfNotSet()
;

$nullOrNotEmptyValue = static function ($v): bool {
return null !== $v && empty($v);
};

foreach (OAuth2Grants::MAP as $grantType => $grantTypeName) {
$node
->children()
->arrayNode($grantType)
->addDefaultsIfNotSet()
->children()
->booleanNode('enable')
->info(sprintf('Whether to enable the %s grant.', $grantTypeName))
->defaultTrue()
->end()
->scalarNode('access_token_ttl')
->info(sprintf('How long the issued access token should be valid for the %s grant.', $grantTypeName))
->defaultNull()
->validate()
->ifTrue($nullOrNotEmptyValue)
->thenInvalid('The value cannot be empty.')
->end()
->end()
->end()
->end()
->end()
->booleanNode('require_code_challenge_for_public_clients')
->info('Whether to require code challenge for public clients for the auth code grant')
->defaultTrue()
;
}

foreach (['authorization_code', 'password', 'refresh_token'] as $grantType) {
$node
->find($grantType)
->children()
->scalarNode('refresh_token_ttl')
->info(sprintf('How long the issued refresh token should be valid for the %s grant.', OAuth2Grants::MAP[$grantType]))
->defaultNull()
->validate()
->ifTrue($nullOrNotEmptyValue)
->thenInvalid('The value cannot be empty.')
->end()
->end()
->end()
->end()
->booleanNode('enable_implicit_grant')
->info('Whether to enable the implicit grant')
->defaultTrue()
;
}

$node
->find('authorization_code')
->children()
->scalarNode('auth_code_ttl')
->info("How long the issued authorization code should be valid for.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->cannotBeEmpty()
->defaultValue('PT10M')
->end()
->booleanNode('require_code_challenge_for_public_clients')
->info('Whether to require code challenge for public clients for the authorization code grant.')
->defaultTrue()
->end()
->end()
->end()
;
Expand All @@ -122,7 +209,7 @@ private function createResourceServerNode(): NodeDefinition
->isRequired()
->children()
->scalarNode('public_key')
->info("Full path to the public key file\nHow to generate a public key: https://oauth2.thephpleague.com/installation/#generating-public-and-private-keys")
->info("Full path to the public key file.\nHow to generate a public key: https://oauth2.thephpleague.com/installation/#generating-public-and-private-keys")
->example('/var/oauth/public.key')
->isRequired()
->cannotBeEmpty()
Expand Down
78 changes: 62 additions & 16 deletions DependencyInjection/TrikoderOAuth2Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('services.xml');

$config = $this->processConfiguration(new Configuration(), $configs);
$config = $this->normalizeConfiguration($config);

$this->configurePersistence($loader, $container, $config['persistence']);
$this->configureAuthorizationServer($container, $config['authorization_server']);
Expand Down Expand Up @@ -110,6 +111,46 @@ public function process(ContainerBuilder $container)
$this->assertRequiredBundlesAreEnabled($container);
}

private function normalizeConfiguration(array $config): array
{
foreach ($config['authorization_server']['grant_types'] as $grantType => &$grantTypeConfig) {
$grantTypeConfig['access_token_ttl'] = $grantTypeConfig['access_token_ttl'] ?? $config['authorization_server']['access_token_ttl'];

if (\array_key_exists('refresh_token_ttl', $grantTypeConfig)) {
$grantTypeConfig['refresh_token_ttl'] = $grantTypeConfig['refresh_token_ttl'] ?? $config['authorization_server']['refresh_token_ttl'];
}

// @TODO Remove in v4 start

$oldGrantType = 'authorization_code' === $grantType ? 'auth_code' : $grantType;

$grantTypeConfig['enable'] = $config['authorization_server'][sprintf('enable_%s_grant', $oldGrantType)] ?? $grantTypeConfig['enable'];

if ('authorization_code' === $grantType) {
$grantTypeConfig['auth_code_ttl'] = $config['authorization_server']['auth_code_ttl'] ?? $grantTypeConfig['auth_code_ttl'];
$grantTypeConfig['require_code_challenge_for_public_clients'] = $config['authorization_server']['require_code_challenge_for_public_clients']
?? $grantTypeConfig['require_code_challenge_for_public_clients'];
}

// @TODO Remove in v4 end
}

unset($config['authorization_server']['access_token_ttl'], $config['authorization_server']['refresh_token_ttl']);

// @TODO Remove in v4
unset(
$config['authorization_server']['enable_auth_code_grant'],
$config['authorization_server']['enable_client_credentials_grant'],
$config['authorization_server']['enable_implicit_grant'],
$config['authorization_server']['enable_password_grant'],
$config['authorization_server']['enable_refresh_token_grant'],
$config['authorization_server']['auth_code_ttl'],
$config['authorization_server']['require_code_challenge_for_public_clients']
);

return $config;
}

private function assertRequiredBundlesAreEnabled(ContainerBuilder $container): void
{
$requiredBundles = [
Expand Down Expand Up @@ -150,38 +191,40 @@ private function configureAuthorizationServer(ContainerBuilder $container, array
$authorizationServer->replaceArgument('$encryptionKey', new Reference('trikoder.oauth2.defuse_key'));
}

if ($config['enable_client_credentials_grant']) {
$grantTypes = $config['grant_types'];

if ($grantTypes['client_credentials']['enable']) {
$authorizationServer->addMethodCall('enableGrantType', [
new Reference(ClientCredentialsGrant::class),
new Definition(DateInterval::class, [$config['access_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['client_credentials']['access_token_ttl']]),
]);
}

if ($config['enable_password_grant']) {
if ($grantTypes['password']['enable']) {
$authorizationServer->addMethodCall('enableGrantType', [
new Reference(PasswordGrant::class),
new Definition(DateInterval::class, [$config['access_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['password']['access_token_ttl']]),
]);
}

if ($config['enable_refresh_token_grant']) {
if ($grantTypes['refresh_token']['enable']) {
$authorizationServer->addMethodCall('enableGrantType', [
new Reference(RefreshTokenGrant::class),
new Definition(DateInterval::class, [$config['access_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['refresh_token']['access_token_ttl']]),
]);
}

if ($config['enable_auth_code_grant']) {
if ($grantTypes['authorization_code']['enable']) {
$authorizationServer->addMethodCall('enableGrantType', [
new Reference(AuthCodeGrant::class),
new Definition(DateInterval::class, [$config['access_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['authorization_code']['access_token_ttl']]),
]);
}

if ($config['enable_implicit_grant']) {
if ($grantTypes['implicit']['enable']) {
$authorizationServer->addMethodCall('enableGrantType', [
new Reference(ImplicitGrant::class),
new Definition(DateInterval::class, [$config['access_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['implicit']['access_token_ttl']]),
]);
}

Expand All @@ -190,34 +233,37 @@ private function configureAuthorizationServer(ContainerBuilder $container, array

private function configureGrants(ContainerBuilder $container, array $config): void
{
$grantTypes = $config['grant_types'];

$container
->getDefinition(PasswordGrant::class)
->addMethodCall('setRefreshTokenTTL', [
new Definition(DateInterval::class, [$config['refresh_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['password']['refresh_token_ttl']]),
])
;

$container
->getDefinition(RefreshTokenGrant::class)
->addMethodCall('setRefreshTokenTTL', [
new Definition(DateInterval::class, [$config['refresh_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['refresh_token']['refresh_token_ttl']]),
])
;

$authCodeGrantDefinition = $container->getDefinition(AuthCodeGrant::class);
$authCodeGrantDefinition->replaceArgument('$authCodeTTL', new Definition(DateInterval::class, [$config['auth_code_ttl']]))
$authCodeGrantDefinition
->replaceArgument('$authCodeTTL', new Definition(DateInterval::class, [$grantTypes['authorization_code']['auth_code_ttl']]))
->addMethodCall('setRefreshTokenTTL', [
new Definition(DateInterval::class, [$config['refresh_token_ttl']]),
new Definition(DateInterval::class, [$grantTypes['authorization_code']['refresh_token_ttl']]),
])
;

if (false === $config['require_code_challenge_for_public_clients']) {
if (false === $grantTypes['authorization_code']['require_code_challenge_for_public_clients']) {
$authCodeGrantDefinition->addMethodCall('disableRequireCodeChallengeForPublicClients');
}

$container
->getDefinition(ImplicitGrant::class)
->replaceArgument('$accessTokenTTL', new Definition(DateInterval::class, [$config['access_token_ttl']]))
->replaceArgument('$accessTokenTTL', new Definition(DateInterval::class, [$grantTypes['implicit']['access_token_ttl']]))
;
}

Expand Down
21 changes: 14 additions & 7 deletions OAuth2Grants.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,21 @@ final class OAuth2Grants
*/
public const REFRESH_TOKEN = 'refresh_token';

public const MAP = [
self::AUTHORIZATION_CODE => 'authorization code',
self::CLIENT_CREDENTIALS => 'client credentials',
self::IMPLICIT => 'implicit',
self::PASSWORD => 'password',
self::REFRESH_TOKEN => 'refresh token',
];

/**
* @deprecated Will be removed in v4, use {@see OAuth2Grants::MAP} instead
*
* @TODO Remove in v4.
*/
public static function has(string $grant): bool
{
return \in_array($grant, [
self::CLIENT_CREDENTIALS,
self::PASSWORD,
self::REFRESH_TOKEN,
self::AUTHORIZATION_CODE,
self::IMPLICIT,
]);
return isset(self::MAP[$grant]);
}
}
Loading

0 comments on commit 2e54c1e

Please sign in to comment.