|  | @@ -17,6 +17,7 @@ namespace League\OAuth2\Client\Provider;
 | 
	
		
			
				|  |  |  use GuzzleHttp\Client as HttpClient;
 | 
	
		
			
				|  |  |  use GuzzleHttp\ClientInterface as HttpClientInterface;
 | 
	
		
			
				|  |  |  use GuzzleHttp\Exception\BadResponseException;
 | 
	
		
			
				|  |  | +use InvalidArgumentException;
 | 
	
		
			
				|  |  |  use League\OAuth2\Client\Grant\AbstractGrant;
 | 
	
		
			
				|  |  |  use League\OAuth2\Client\Grant\GrantFactory;
 | 
	
		
			
				|  |  |  use League\OAuth2\Client\OptionProvider\OptionProviderInterface;
 | 
	
	
		
			
				|  | @@ -44,7 +45,7 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |      use QueryBuilderTrait;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  | -     * @var string Key used in a token response to identify the resource owner.
 | 
	
		
			
				|  |  | +     * @var string|null Key used in a token response to identify the resource owner.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      const ACCESS_TOKEN_RESOURCE_OWNER_ID = null;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -58,6 +59,19 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      const METHOD_POST = 'POST';
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * @var string PKCE method used to fetch authorization token.
 | 
	
		
			
				|  |  | +     * The PKCE code challenge will be hashed with sha256 (recommended).
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    const PKCE_METHOD_S256 = 'S256';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * @var string PKCE method used to fetch authorization token.
 | 
	
		
			
				|  |  | +     * The PKCE code challenge will be sent as plain text, this is NOT recommended.
 | 
	
		
			
				|  |  | +     * Only use `plain` if no other option is possible.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    const PKCE_METHOD_PLAIN = 'plain';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * @var string
 | 
	
		
			
				|  |  |       */
 | 
	
	
		
			
				|  | @@ -78,6 +92,11 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      protected $state;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * @var string|null
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    protected $pkceCode = null;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * @var GrantFactory
 | 
	
		
			
				|  |  |       */
 | 
	
	
		
			
				|  | @@ -264,6 +283,32 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |          return $this->state;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Set the value of the pkceCode parameter.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * When using PKCE this should be set before requesting an access token.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param string $pkceCode
 | 
	
		
			
				|  |  | +     * @return self
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public function setPkceCode($pkceCode)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        $this->pkceCode = $pkceCode;
 | 
	
		
			
				|  |  | +        return $this;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns the current value of the pkceCode parameter.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * This can be accessed by the redirect handler during authorization.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @return string|null
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public function getPkceCode()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return $this->pkceCode;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Returns the base URL for authorizing a client.
 | 
	
		
			
				|  |  |       *
 | 
	
	
		
			
				|  | @@ -305,6 +350,27 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |          return bin2hex(random_bytes($length / 2));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns a new random string to use as PKCE code_verifier and
 | 
	
		
			
				|  |  | +     * hashed as code_challenge parameters in an authorization flow.
 | 
	
		
			
				|  |  | +     * Must be between 43 and 128 characters long.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param  int $length Length of the random string to be generated.
 | 
	
		
			
				|  |  | +     * @return string
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    protected function getRandomPkceCode($length = 64)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return substr(
 | 
	
		
			
				|  |  | +            strtr(
 | 
	
		
			
				|  |  | +                base64_encode(random_bytes($length)),
 | 
	
		
			
				|  |  | +                '+/',
 | 
	
		
			
				|  |  | +                '-_'
 | 
	
		
			
				|  |  | +            ),
 | 
	
		
			
				|  |  | +            0,
 | 
	
		
			
				|  |  | +            $length
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Returns the default scopes used by this provider.
 | 
	
		
			
				|  |  |       *
 | 
	
	
		
			
				|  | @@ -326,6 +392,14 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |          return ',';
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * @return string|null
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    protected function getPkceMethod()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return null;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Returns authorization parameters based on provided options.
 | 
	
		
			
				|  |  |       *
 | 
	
	
		
			
				|  | @@ -355,6 +429,26 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |          // Store the state as it may need to be accessed later on.
 | 
	
		
			
				|  |  |          $this->state = $options['state'];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        $pkceMethod = $this->getPkceMethod();
 | 
	
		
			
				|  |  | +        if (!empty($pkceMethod)) {
 | 
	
		
			
				|  |  | +            $this->pkceCode = $this->getRandomPkceCode();
 | 
	
		
			
				|  |  | +            if ($pkceMethod === static::PKCE_METHOD_S256) {
 | 
	
		
			
				|  |  | +                $options['code_challenge'] = trim(
 | 
	
		
			
				|  |  | +                    strtr(
 | 
	
		
			
				|  |  | +                        base64_encode(hash('sha256', $this->pkceCode, true)),
 | 
	
		
			
				|  |  | +                        '+/',
 | 
	
		
			
				|  |  | +                        '-_'
 | 
	
		
			
				|  |  | +                    ),
 | 
	
		
			
				|  |  | +                    '='
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +            } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) {
 | 
	
		
			
				|  |  | +                $options['code_challenge'] = $this->pkceCode;
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                throw new InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".');
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            $options['code_challenge_method'] = $pkceMethod;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          // Business code layer might set a different redirect_uri parameter
 | 
	
		
			
				|  |  |          // depending on the context, leave it as-is
 | 
	
		
			
				|  |  |          if (!isset($options['redirect_uri'])) {
 | 
	
	
		
			
				|  | @@ -517,8 +611,8 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Requests an access token using a specified grant and option set.
 | 
	
		
			
				|  |  |       *
 | 
	
		
			
				|  |  | -     * @param  mixed $grant
 | 
	
		
			
				|  |  | -     * @param  array $options
 | 
	
		
			
				|  |  | +     * @param  mixed                $grant
 | 
	
		
			
				|  |  | +     * @param  array<string, mixed> $options
 | 
	
		
			
				|  |  |       * @throws IdentityProviderException
 | 
	
		
			
				|  |  |       * @return AccessTokenInterface
 | 
	
		
			
				|  |  |       */
 | 
	
	
		
			
				|  | @@ -532,6 +626,10 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |              'redirect_uri'  => $this->redirectUri,
 | 
	
		
			
				|  |  |          ];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        if (!empty($this->pkceCode)) {
 | 
	
		
			
				|  |  | +            $params['code_verifier'] = $this->pkceCode;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          $params   = $grant->prepareRequestParameters($params, $options);
 | 
	
		
			
				|  |  |          $request  = $this->getAccessTokenRequest($params);
 | 
	
		
			
				|  |  |          $response = $this->getParsedResponse($request);
 | 
	
	
		
			
				|  | @@ -564,7 +662,7 @@ abstract class AbstractProvider
 | 
	
		
			
				|  |  |       *
 | 
	
		
			
				|  |  |       * @param  string $method
 | 
	
		
			
				|  |  |       * @param  string $url
 | 
	
		
			
				|  |  | -     * @param  AccessTokenInterface|string $token
 | 
	
		
			
				|  |  | +     * @param  AccessTokenInterface|string|null $token
 | 
	
		
			
				|  |  |       * @param  array $options Any of "headers", "body", and "protocolVersion".
 | 
	
		
			
				|  |  |       * @return RequestInterface
 | 
	
		
			
				|  |  |       */
 |