vendor/hwi/oauth-bundle/src/DependencyInjection/Configuration.php line 288

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the HWIOAuthBundle package.
  4.  *
  5.  * (c) Hardware Info <opensource@hardware.info>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace HWI\Bundle\OAuthBundle\DependencyInjection;
  11. use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth1ResourceOwner;
  12. use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner;
  13. use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
  14. use Symfony\Component\Config\Definition\BaseNode;
  15. use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
  16. use Symfony\Component\Config\Definition\Builder\TreeBuilder;
  17. use Symfony\Component\Config\Definition\ConfigurationInterface;
  18. use Symfony\Component\Finder\Finder;
  19. /**
  20.  * Configuration for the extension.
  21.  *
  22.  * @author Alexander <iam.asm89@gmail.com>
  23.  */
  24. final class Configuration implements ConfigurationInterface
  25. {
  26.     /**
  27.      * type => ResourceOwner mapping for hwi_oauth.resource_owner.*.class parameters.
  28.      *
  29.      * @var array<string, class-string<GenericOAuth1ResourceOwner|GenericOAuth2ResourceOwner|ResourceOwnerInterface>>
  30.      */
  31.     private static array $resourceOwnerTypesClassMap = [];
  32.     /**
  33.      * Array of supported resource owners.
  34.      *
  35.      * @var array<string, string>
  36.      */
  37.     private static array $resourceOwnerTypes = [];
  38.     public function __construct()
  39.     {
  40.         if ([] === self::$resourceOwnerTypes) {
  41.             self::loadResourceOwners();
  42.         }
  43.     }
  44.     public static function getResourceOwnerTypesClassMap(): array
  45.     {
  46.         return self::$resourceOwnerTypesClassMap;
  47.     }
  48.     /**
  49.      * Return the type (oauth1 or oauth2) of given resource owner.
  50.      */
  51.     public static function getResourceOwnerType(string $resourceOwner): ?string
  52.     {
  53.         $resourceOwner strtolower($resourceOwner);
  54.         return self::$resourceOwnerTypes[$resourceOwner] ?? null;
  55.     }
  56.     /**
  57.      * Checks that given resource owner is supported by this bundle.
  58.      */
  59.     public static function isResourceOwnerSupported(string $resourceOwner): bool
  60.     {
  61.         return isset(self::$resourceOwnerTypes[strtolower($resourceOwner)]);
  62.     }
  63.     public static function registerResourceOwner(string $resourceOwnerClass): void
  64.     {
  65.         $reflection = new \ReflectionClass($resourceOwnerClass);
  66.         if (!$reflection->implementsInterface(ResourceOwnerInterface::class)) {
  67.             throw new \LogicException('Resource owner class should implement "ResourceOwnerInterface", or extended class "GenericOAuth1ResourceOwner"/"GenericOAuth2ResourceOwner".');
  68.         }
  69.         $type = \defined("$resourceOwnerClass::TYPE") ? $resourceOwnerClass::TYPE null;
  70.         if (null === $type) {
  71.             if (preg_match('~(?P<resource_owner>[^\\\\]+)ResourceOwner$~'$resourceOwnerClass$match)) {
  72.                 $type strtolower(preg_replace('/([a-z])([A-Z])/''$1_$2'$match['resource_owner']));
  73.             } else {
  74.                 throw new \LogicException(sprintf('Resource owner class either should have "TYPE" const defined or end with "ResourceOwner" so that type can be calculated by converting its class name without suffix to "snake_case". Given class name is "%s"'$resourceOwnerClass));
  75.             }
  76.         }
  77.         $oAuth 'unknown';
  78.         if ($reflection->isSubclassOf(GenericOAuth2ResourceOwner::class)) {
  79.             $oAuth 'oauth2';
  80.         } elseif ($reflection->isSubclassOf(GenericOAuth1ResourceOwner::class)) {
  81.             $oAuth 'oauth1';
  82.         }
  83.         self::$resourceOwnerTypes[$type] = $oAuth;
  84.         self::$resourceOwnerTypesClassMap[$type] = $resourceOwnerClass;
  85.     }
  86.     /**
  87.      * Generates the configuration tree builder.
  88.      */
  89.     public function getConfigTreeBuilder(): TreeBuilder
  90.     {
  91.         $builder = new TreeBuilder('hwi_oauth');
  92.         /** @var ArrayNodeDefinition $rootNode */
  93.         $rootNode $builder->getRootNode();
  94.         $rootNode
  95.             ->fixXmlConfig('firewall_name')
  96.             ->children()
  97.                 ->arrayNode('firewall_names')
  98.                     ->setDeprecated(...$this->getDeprecationParams())
  99.                     ->defaultValue([])
  100.                     ->prototype('scalar')->end()
  101.                 ->end()
  102.                 ->scalarNode('target_path_parameter')->defaultNull()->end()
  103.                 ->arrayNode('target_path_domains_whitelist')
  104.                     ->defaultValue([])
  105.                     ->prototype('scalar')->end()
  106.                 ->end()
  107.                 ->booleanNode('use_referer')->defaultFalse()->end()
  108.                 ->booleanNode('failed_use_referer')->defaultFalse()->end()
  109.                 ->scalarNode('failed_auth_path')->defaultValue('hwi_oauth_connect')->end()
  110.                 ->scalarNode('grant_rule')
  111.                     ->defaultValue('IS_AUTHENTICATED_REMEMBERED')
  112.                     ->validate()
  113.                         ->ifTrue(function ($role) {
  114.                             return !('IS_AUTHENTICATED_REMEMBERED' === $role || 'IS_AUTHENTICATED_FULLY' === $role);
  115.                         })
  116.                         ->thenInvalid('Unknown grant role set "%s".')
  117.                     ->end()
  118.                 ->end()
  119.             ->end()
  120.         ;
  121.         $this->addConnectConfiguration($rootNode);
  122.         $this->addResourceOwnersConfiguration($rootNode);
  123.         return $builder;
  124.     }
  125.     private function addResourceOwnersConfiguration(ArrayNodeDefinition $node): void
  126.     {
  127.         $node
  128.             ->fixXmlConfig('resource_owner')
  129.             ->children()
  130.                 ->arrayNode('resource_owners')
  131.                     ->isRequired()
  132.                     ->useAttributeAsKey('name')
  133.                     ->prototype('array')
  134.                         ->ignoreExtraKeys()
  135.                         ->children()
  136.                             ->scalarNode('base_url')->end()
  137.                             ->scalarNode('access_token_url')
  138.                                 ->validate()
  139.                                     ->ifEmpty()
  140.                                     ->thenUnset()
  141.                                 ->end()
  142.                             ->end()
  143.                             ->scalarNode('authorization_url')
  144.                                 ->validate()
  145.                                     ->ifEmpty()
  146.                                     ->thenUnset()
  147.                                 ->end()
  148.                             ->end()
  149.                             ->scalarNode('request_token_url')
  150.                                 ->validate()
  151.                                     ->ifEmpty()
  152.                                     ->thenUnset()
  153.                                 ->end()
  154.                             ->end()
  155.                             ->scalarNode('revoke_token_url')
  156.                                 ->validate()
  157.                                     ->ifEmpty()
  158.                                     ->thenUnset()
  159.                                 ->end()
  160.                             ->end()
  161.                             ->scalarNode('infos_url')
  162.                                 ->validate()
  163.                                     ->ifEmpty()
  164.                                     ->thenUnset()
  165.                                 ->end()
  166.                             ->end()
  167.                             ->scalarNode('client_id')->cannotBeEmpty()->end()
  168.                             ->scalarNode('client_secret')->cannotBeEmpty()->end()
  169.                             ->scalarNode('realm')
  170.                                 ->validate()
  171.                                     ->ifEmpty()
  172.                                     ->thenUnset()
  173.                                 ->end()
  174.                             ->end()
  175.                             ->scalarNode('scope')
  176.                                 ->validate()
  177.                                     ->ifEmpty()
  178.                                     ->thenUnset()
  179.                                 ->end()
  180.                             ->end()
  181.                             ->scalarNode('user_response_class')
  182.                                 ->validate()
  183.                                     ->ifEmpty()
  184.                                     ->thenUnset()
  185.                                 ->end()
  186.                             ->end()
  187.                             ->scalarNode('service')
  188.                                 ->validate()
  189.                                     ->ifEmpty()
  190.                                     ->thenUnset()
  191.                                 ->end()
  192.                             ->end()
  193.                             ->scalarNode('class')
  194.                                 ->validate()
  195.                                     ->ifEmpty()
  196.                                     ->thenUnset()
  197.                                 ->end()
  198.                             ->end()
  199.                             ->scalarNode('type')
  200.                                 // will be validated in ResourceOwnerCompilerPass, other apps can register own resource
  201.                                 // owner maps later with tag hwi_oauth.resource_owner
  202.                                 ->validate()
  203.                                     ->ifEmpty()
  204.                                     ->thenUnset()
  205.                                 ->end()
  206.                             ->end()
  207.                             ->scalarNode('use_authorization_to_get_token')
  208.                                 ->validate()
  209.                                     ->ifEmpty()
  210.                                     ->thenUnset()
  211.                                 ->end()
  212.                             ->end()
  213.                             ->arrayNode('paths')
  214.                                 ->useAttributeAsKey('name')
  215.                                 ->prototype('variable')
  216.                                     ->validate()
  217.                                         ->ifTrue(function ($v) {
  218.                                             if (null === $v) {
  219.                                                 return true;
  220.                                             }
  221.                                             if (\is_array($v)) {
  222.                                                 return === \count($v);
  223.                                             }
  224.                                             if (\is_string($v)) {
  225.                                                 return empty($v);
  226.                                             }
  227.                                             return !is_numeric($v);
  228.                                         })
  229.                                         ->thenInvalid('Path can be only string or array type.')
  230.                                     ->end()
  231.                                 ->end()
  232.                             ->end()
  233.                             ->arrayNode('options')
  234.                                 ->useAttributeAsKey('name')
  235.                                 ->prototype('scalar')->end()
  236.                             ->end()
  237.                         ->end()
  238.                         ->validate()
  239.                             ->ifTrue(function ($c) {
  240.                                 // skip if this contains a service
  241.                                 if (isset($c['service'])) {
  242.                                     return false;
  243.                                 }
  244.                                 // for each type at least these have to be set
  245.                                 foreach (['client_id''client_secret'] as $child) {
  246.                                     if (!isset($c[$child])) {
  247.                                         return true;
  248.                                     }
  249.                                 }
  250.                                 if (!isset($c['type']) && !isset($c['class'])) {
  251.                                     return true;
  252.                                 }
  253.                                 return false;
  254.                             })
  255.                             ->thenInvalid("You should set at least the 'type' or 'class' with 'client_id' and the 'client_secret' of a resource owner.")
  256.                         ->end()
  257.                         ->validate()
  258.                             ->ifTrue(function ($c) {
  259.                                 return isset($c['type'], $c['class']);
  260.                             })
  261.                             ->then(function ($c) {
  262.                                 trigger_deprecation('hwi/oauth-bundle''2.0''No need to set both "type" and "class" for resource owner.');
  263.                                 return $c;
  264.                             })
  265.                         ->end()
  266.                         ->validate()
  267.                             ->ifTrue(function ($c) {
  268.                                 // Skip if this contains a service or a class
  269.                                 if (isset($c['service']) || isset($c['class'])) {
  270.                                     return false;
  271.                                 }
  272.                                 // Only validate the 'oauth2' and 'oauth1' type
  273.                                 if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
  274.                                     return false;
  275.                                 }
  276.                                 $children = ['authorization_url''access_token_url''request_token_url''infos_url'];
  277.                                 foreach ($children as $child) {
  278.                                     // This option exists only for OAuth1.0a
  279.                                     if ('request_token_url' === $child && 'oauth2' === $c['type']) {
  280.                                         continue;
  281.                                     }
  282.                                     if (!isset($c[$child])) {
  283.                                         return true;
  284.                                     }
  285.                                 }
  286.                                 return false;
  287.                             })
  288.                             ->thenInvalid("All parameters are mandatory for types 'oauth2' and 'oauth1'. Check if you're missing one of: 'access_token_url', 'authorization_url', 'infos_url' and 'request_token_url' for 'oauth1'.")
  289.                         ->end()
  290.                         ->validate()
  291.                             ->ifTrue(function ($c) {
  292.                                 // skip if this contains a service
  293.                                 if (isset($c['service']) || isset($c['class'])) {
  294.                                     return false;
  295.                                 }
  296.                                 // Only validate the 'oauth2' and 'oauth1' type
  297.                                 if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
  298.                                     return false;
  299.                                 }
  300.                                 // one of this two options must be set
  301.                                 if (=== \count($c['paths'])) {
  302.                                     return !isset($c['user_response_class']);
  303.                                 }
  304.                                 foreach (['identifier''nickname''realname'] as $child) {
  305.                                     if (!isset($c['paths'][$child])) {
  306.                                         return true;
  307.                                     }
  308.                                 }
  309.                                 return false;
  310.                             })
  311.                             ->thenInvalid("At least the 'identifier', 'nickname' and 'realname' paths should be configured for 'oauth2' and 'oauth1' types.")
  312.                         ->end()
  313.                         ->validate()
  314.                             ->ifTrue(function ($c) {
  315.                                 if (isset($c['service'])) {
  316.                                     // ignore paths & options if none were set
  317.                                     return !== \count($c['paths']) || !== \count($c['options']) || < \count($c);
  318.                                 }
  319.                                 return false;
  320.                             })
  321.                             ->thenInvalid("If you're setting a 'service', no other arguments should be set.")
  322.                         ->end()
  323.                         ->validate()
  324.                             ->ifTrue(function ($c) {
  325.                                 return isset($c['class']);
  326.                             })
  327.                             ->then(function ($c) {
  328.                                 self::registerResourceOwner($c['class']);
  329.                                 return $c;
  330.                             })
  331.                         ->end()
  332.                     ->end()
  333.                 ->end()
  334.             ->end()
  335.         ;
  336.     }
  337.     private function addConnectConfiguration(ArrayNodeDefinition $node): void
  338.     {
  339.         $node
  340.             ->children()
  341.                 ->arrayNode('connect')
  342.                     ->children()
  343.                         ->booleanNode('confirmation')->defaultTrue()->end()
  344.                         ->scalarNode('account_connector')->cannotBeEmpty()->end()
  345.                         ->scalarNode('registration_form_handler')->cannotBeEmpty()->end()
  346.                         ->scalarNode('registration_form')->cannotBeEmpty()->end()
  347.                     ->end()
  348.                 ->end()
  349.             ->end()
  350.         ;
  351.     }
  352.     private static function loadResourceOwners(): void
  353.     {
  354.         $files = (new Finder())
  355.             ->in(__DIR__.'/../OAuth/ResourceOwner')
  356.             ->name('~^(.+)ResourceOwner\.php$~')
  357.             ->files();
  358.         foreach ($files as $f) {
  359.             if (!str_contains($f->getFilename(), 'ResourceOwner')) {
  360.                 continue;
  361.             }
  362.             // Skip known abstract classes
  363.             if (\in_array($f->getFilename(), ['AbstractResourceOwner.php''GenericOAuth1ResourceOwner.php''GenericOAuth2ResourceOwner.php'], true)) {
  364.                 continue;
  365.             }
  366.             self::registerResourceOwner('HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\\'.str_replace('.php'''$f->getFilename()));
  367.         }
  368.     }
  369.     /**
  370.      * Returns the correct deprecation params as an array for setDeprecated().
  371.      *
  372.      * symfony/config v5.1 introduces a deprecation notice when calling
  373.      * setDeprecated() with less than 3 args and the getDeprecation() method was
  374.      * introduced at the same time. By checking if getDeprecation() exists,
  375.      * we can determine the correct param count to use when calling setDeprecated().
  376.      *
  377.      * @return string[]
  378.      */
  379.     private function getDeprecationParams(): array
  380.     {
  381.         if (method_exists(BaseNode::class, 'getDeprecation')) {
  382.             return [
  383.                 'hwi/oauth-bundle',
  384.                 '2.0',
  385.                 'option "%path%.%node%" is deprecated. Firewall names are collected automatically.',
  386.             ];
  387.         }
  388.         return ['Since hwi/oauth-bundle 2.0: option "hwi_oauth.firewall_names" is deprecated. Firewall names are collected automatically.'];
  389.     }
  390. }