vendor/hwi/oauth-bundle/OAuth/ResourceOwner/AbstractResourceOwner.php line 300

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\OAuth\ResourceOwner;
  11. use Http\Client\Common\HttpMethodsClient;
  12. use Http\Client\Exception;
  13. use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
  14. use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface;
  15. use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
  16. use HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse;
  17. use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
  18. use HWI\Bundle\OAuthBundle\OAuth\State\State;
  19. use HWI\Bundle\OAuthBundle\OAuth\StateInterface;
  20. use Psr\Http\Message\ResponseInterface;
  21. use Symfony\Component\HttpFoundation\Request as HttpRequest;
  22. use Symfony\Component\OptionsResolver\OptionsResolver;
  23. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  24. use Symfony\Component\Security\Http\HttpUtils;
  25. /**
  26.  * AbstractResourceOwner.
  27.  *
  28.  * @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
  29.  * @author Alexander <iam.asm89@gmail.com>
  30.  * @author Francisco Facioni <fran6co@gmail.com>
  31.  * @author Joseph Bielawski <stloyd@gmail.com>
  32.  */
  33. abstract class AbstractResourceOwner implements ResourceOwnerInterface
  34. {
  35.     /**
  36.      * @var array
  37.      */
  38.     protected $options = [];
  39.     /**
  40.      * @var array
  41.      */
  42.     protected $paths = [];
  43.     /**
  44.      * @var HttpMethodsClient
  45.      */
  46.     protected $httpClient;
  47.     /**
  48.      * @var HttpUtils
  49.      */
  50.     protected $httpUtils;
  51.     /**
  52.      * @var string
  53.      */
  54.     protected $name;
  55.     /**
  56.      * @var StateInterface
  57.      */
  58.     protected $state;
  59.     /**
  60.      * @var RequestDataStorageInterface
  61.      */
  62.     protected $storage;
  63.     /**
  64.      * @var bool
  65.      */
  66.     private $stateLoaded false;
  67.     /**
  68.      * @param HttpMethodsClient           $httpClient Httplug client
  69.      * @param HttpUtils                   $httpUtils  Http utils
  70.      * @param array                       $options    Options for the resource owner
  71.      * @param string                      $name       Name for the resource owner
  72.      * @param RequestDataStorageInterface $storage    Request token storage
  73.      */
  74.     public function __construct(
  75.         HttpMethodsClient $httpClient,
  76.         HttpUtils $httpUtils,
  77.         array $options,
  78.         string $name,
  79.         RequestDataStorageInterface $storage
  80.     ) {
  81.         $this->httpClient $httpClient;
  82.         $this->httpUtils $httpUtils;
  83.         $this->name $name;
  84.         $this->storage $storage;
  85.         if (!empty($options['paths'])) {
  86.             $this->addPaths($options['paths']);
  87.         }
  88.         unset($options['paths']);
  89.         if (!empty($options['options'])) {
  90.             $options += $options['options'];
  91.             unset($options['options']);
  92.         }
  93.         unset($options['options']);
  94.         // Resolve merged options
  95.         $resolver = new OptionsResolver();
  96.         $this->configureOptions($resolver);
  97.         $this->options $resolver->resolve($options);
  98.         $this->state = new State($this->options['state'] ?: null);
  99.         $this->configure();
  100.     }
  101.     /**
  102.      * Gives a chance for extending providers to customize stuff.
  103.      */
  104.     public function configure()
  105.     {
  106.     }
  107.     /**
  108.      * {@inheritdoc}
  109.      */
  110.     public function getName()
  111.     {
  112.         return $this->name;
  113.     }
  114.     /**
  115.      * {@inheritdoc}
  116.      */
  117.     public function setName($name)
  118.     {
  119.         $this->name $name;
  120.     }
  121.     /**
  122.      * {@inheritdoc}
  123.      */
  124.     public function getOption($name)
  125.     {
  126.         if (!\array_key_exists($name$this->options)) {
  127.             throw new \InvalidArgumentException(sprintf('Unknown option "%s"'$name));
  128.         }
  129.         return $this->options[$name];
  130.     }
  131.     /**
  132.      * {@inheritdoc}
  133.      */
  134.     public function addPaths(array $paths)
  135.     {
  136.         $this->paths array_merge($this->paths$paths);
  137.     }
  138.     /**
  139.      * {@inheritdoc}
  140.      */
  141.     public function getState(): StateInterface
  142.     {
  143.         if ($this->stateLoaded) {
  144.             return $this->state;
  145.         }
  146.         // lazy-loading for stored states
  147.         try {
  148.             $storedData $this->storage->fetch($thisState::class, 'state');
  149.         } catch (\Throwable $e) {
  150.             $storedData null;
  151.         }
  152.         if (null !== $storedData && false !== $storedState unserialize($storedData)) {
  153.             foreach ($storedState->getAll() as $key => $value) {
  154.                 $this->addStateParameter($key$value);
  155.             }
  156.         }
  157.         $this->stateLoaded true;
  158.         return $this->state;
  159.     }
  160.     /**
  161.      * {@inheritdoc}
  162.      */
  163.     public function addStateParameter(string $keystring $value): void
  164.     {
  165.         if (!$this->state->has($key)) {
  166.             $this->state->add($key$value);
  167.         }
  168.     }
  169.     /**
  170.      * {@inheritdoc}
  171.      */
  172.     public function storeState(StateInterface $state null): void
  173.     {
  174.         if (null === $state || === \count($state->getAll())) {
  175.             return;
  176.         }
  177.         $this->storage->save($this$state'state');
  178.     }
  179.     /**
  180.      * Retrieve an access token for a given code.
  181.      *
  182.      * @param HttpRequest $request         The request object from where the code is going to extracted
  183.      * @param mixed       $redirectUri     The uri to redirect the client back to
  184.      * @param array       $extraParameters An array of parameters to add to the url
  185.      *
  186.      * @throws AuthenticationException If an OAuth error occurred or no access token is found
  187.      * @throws HttpTransportException
  188.      *
  189.      * @return array array containing the access token and it's 'expires_in' value,
  190.      *               along with any other parameters returned from the authentication
  191.      *               provider
  192.      */
  193.     abstract public function getAccessToken(HttpRequest $request$redirectUri, array $extraParameters = []);
  194.     /**
  195.      * Refresh an access token using a refresh token.
  196.      *
  197.      * @param string $refreshToken    Refresh token
  198.      * @param array  $extraParameters An array of parameters to add to the url
  199.      *
  200.      * @throws AuthenticationException If an OAuth error occurred or no access token is found
  201.      * @throws HttpTransportException
  202.      *
  203.      * @return array array containing the access token and it's 'expires_in' value,
  204.      *               along with any other parameters returned from the authentication
  205.      *               provider
  206.      */
  207.     public function refreshAccessToken($refreshToken, array $extraParameters = [])
  208.     {
  209.         throw new AuthenticationException('OAuth error: "Method unsupported."');
  210.     }
  211.     /**
  212.      * Revoke an OAuth access token or refresh token.
  213.      *
  214.      * @param string $token the token (access token or a refresh token) that should be revoked
  215.      *
  216.      * @throws AuthenticationException If an OAuth error occurred
  217.      * @throws HttpTransportException
  218.      *
  219.      * @return bool returns True if the revocation was successful, otherwise False
  220.      */
  221.     public function revokeToken($token)
  222.     {
  223.         throw new AuthenticationException('OAuth error: "Method unsupported."');
  224.     }
  225.     /**
  226.      * Get the response object to return.
  227.      *
  228.      * @return UserResponseInterface
  229.      */
  230.     protected function getUserResponse()
  231.     {
  232.         $response = new $this->options['user_response_class']();
  233.         if ($response instanceof PathUserResponse) {
  234.             $response->setPaths($this->paths);
  235.         }
  236.         return $response;
  237.     }
  238.     /**
  239.      * @param string $url
  240.      *
  241.      * @return string
  242.      */
  243.     protected function normalizeUrl($url, array $parameters = [])
  244.     {
  245.         $normalizedUrl $url;
  246.         if (!empty($parameters)) {
  247.             $normalizedUrl .= (false !== strpos($url'?') ? '&' '?').http_build_query($parameters'''&');
  248.         }
  249.         return $normalizedUrl;
  250.     }
  251.     /**
  252.      * Performs an HTTP request.
  253.      *
  254.      * @param string       $url     The url to fetch
  255.      * @param string|array $content The content of the request
  256.      * @param array        $headers The headers of the request
  257.      * @param string       $method  The HTTP method to use
  258.      *
  259.      * @throws HttpTransportException
  260.      *
  261.      * @return ResponseInterface The response content
  262.      */
  263.     protected function httpRequest($url$content null, array $headers = [], $method null)
  264.     {
  265.         if (null === $method) {
  266.             $method null === $content || '' === $content 'GET' 'POST';
  267.         }
  268.         $headers += ['User-Agent' => 'HWIOAuthBundle (https://github.com/hwi/HWIOAuthBundle)'];
  269.         if (\is_string($content)) {
  270.             if (!isset($headers['Content-Length'])) {
  271.                 $headers += ['Content-Length' => (string) \strlen($content)];
  272.             }
  273.         } elseif (\is_array($content)) {
  274.             $content http_build_query($content'''&');
  275.         }
  276.         try {
  277.             return $this->httpClient->send(
  278.                 $method,
  279.                 $url,
  280.                 $headers,
  281.                 $content
  282.             );
  283.         } catch (Exception $e) {
  284.             throw new HttpTransportException('Error while sending HTTP request'$this->getName(), $e->getCode(), $e);
  285.         }
  286.     }
  287.     /**
  288.      * Get the 'parsed' content based on the response headers.
  289.      *
  290.      * @return array
  291.      */
  292.     protected function getResponseContent(ResponseInterface $rawResponse)
  293.     {
  294.         // First check that content in response exists, due too bug: https://bugs.php.net/bug.php?id=54484
  295.         $content = (string) $rawResponse->getBody();
  296.         if (!$content) {
  297.             return [];
  298.         }
  299.         $response json_decode($contenttrue);
  300.         if (\JSON_ERROR_NONE !== json_last_error()) {
  301.             parse_str($content$response);
  302.         }
  303.         return $response;
  304.     }
  305.     /**
  306.      * @param string $url
  307.      *
  308.      * @throws HttpTransportException
  309.      *
  310.      * @return ResponseInterface
  311.      */
  312.     abstract protected function doGetTokenRequest($url, array $parameters = []);
  313.     /**
  314.      * @param string $url
  315.      *
  316.      * @throws HttpTransportException
  317.      *
  318.      * @return ResponseInterface
  319.      */
  320.     abstract protected function doGetUserInformationRequest($url, array $parameters = []);
  321.     /**
  322.      * Configure the option resolver.
  323.      *
  324.      * @throws \Symfony\Component\OptionsResolver\Exception\AccessException
  325.      * @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
  326.      */
  327.     protected function configureOptions(OptionsResolver $resolver)
  328.     {
  329.         $resolver->setRequired([
  330.             'client_id',
  331.             'client_secret',
  332.             'authorization_url',
  333.             'access_token_url',
  334.             'infos_url',
  335.         ]);
  336.         $resolver->setDefaults([
  337.             'scope' => null,
  338.             'state' => null,
  339.             'csrf' => false,
  340.             'user_response_class' => PathUserResponse::class,
  341.             'auth_with_one_url' => false,
  342.         ]);
  343.         $resolver->setAllowedValues('csrf', [truefalse]);
  344.     }
  345. }