Skip to content
18 changes: 14 additions & 4 deletions src/Provider/AbstractProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use League\OAuth2\Client\OptionProvider\OptionProviderInterface;
use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\Exception\ResponseParsingException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Token\AccessTokenInterface;
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
Expand Down Expand Up @@ -520,6 +521,8 @@ protected function getAccessTokenRequest(array $params)
* @param mixed $grant
* @param array $options
* @throws IdentityProviderException
* @throws ResponseParsingException if the flag $mayThrowResponseParsingException is true and
* response body cannot be parsed.
* @return AccessTokenInterface
*/
public function getAccessToken($grant, array $options = [])
Expand Down Expand Up @@ -613,6 +616,8 @@ public function getResponse(RequestInterface $request)
*
* @param RequestInterface $request
* @throws IdentityProviderException
* @throws ResponseParsingException if the flag $mayThrowResponseParsingException is true and
* response body cannot be parsed.
* @return mixed
*/
public function getParsedResponse(RequestInterface $request)
Expand Down Expand Up @@ -665,9 +670,9 @@ protected function getContentType(ResponseInterface $response)
/**
* Parses the response according to its content-type header.
*
* @throws UnexpectedValueException
* @throws ResponseParsingException if response body cannot be parsed.
* @param ResponseInterface $response
* @return array
* @return array|string
*/
protected function parseResponse(ResponseInterface $response)
{
Expand All @@ -686,11 +691,14 @@ protected function parseResponse(ResponseInterface $response)
return $this->parseJson($content);
} catch (UnexpectedValueException $e) {
if (strpos($type, 'json') !== false) {
throw $e;
throw new ResponseParsingException($response, $content, $e->getMessage(), $e->getCode(), $e);
}

// for any other content types
if ($response->getStatusCode() == 500) {
throw new UnexpectedValueException(
throw new ResponseParsingException(
$response,
$content,
'An OAuth server error was encountered that did not contain a JSON body',
0,
$e
Expand Down Expand Up @@ -774,6 +782,8 @@ public function getResourceOwner(AccessToken $token)
*
* @param AccessToken $token
* @return mixed
* @throws ResponseParsingException if the flag $mayThrowResponseParsingException is true and
* response body cannot be parsed.
*/
protected function fetchResourceOwnerDetails(AccessToken $token)
{
Expand Down
79 changes: 79 additions & 0 deletions src/Provider/Exception/ResponseParsingException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* This file is part of the league/oauth2-client library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Alex Bilbie <[email protected]>
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/

namespace League\OAuth2\Client\Provider\Exception;

use Exception;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;

/**
* Exception thrown if the parser cannot parse the provider response.
*/
class ResponseParsingException extends UnexpectedValueException
{
/**
* @var ResponseInterface
*/
protected $response;

/**
* @var string
*/
protected $responseBody;

/**
* @param ResponseInterface $response The response
* @param string $responseBody The response body
* @param null $message
* @param int $code
* @param Exception|null $previous
*/
public function __construct(
ResponseInterface $response,
$responseBody,
$message = null,
$code = 0,
Exception $previous = null
) {
$this->response = $response;
$this->responseBody = $responseBody;

if (null === $message) {
$message = sprintf('Cannot parse response body: "%s"', $responseBody);
}

parent::__construct($message, $code, $previous);
}

/**
* Returns the exception's response.
*
* @return ResponseInterface
*/
public function getResponse()
{
return $this->response;
}

/**
* Returns the exception's response body.
*
* @return string
*/
public function getResponseBody()
{
return $this->responseBody;
}
}
47 changes: 37 additions & 10 deletions test/src/Provider/AbstractProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@

namespace League\OAuth2\Client\Test\Provider;

use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider;
use Mockery;
use ReflectionClass;
use UnexpectedValueException;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\ClientInterface;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Test\Provider\Fake as MockProvider;
use GuzzleHttp\Exception\BadResponseException;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Grant\GrantFactory;
use League\OAuth2\Client\Grant\Exception\InvalidGrantException;
use League\OAuth2\Client\Grant\GrantFactory;
use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\Exception\ResponseParsingException;
use League\OAuth2\Client\Test\Provider\Fake as MockProvider;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Token\AccessTokenInterface;
use League\OAuth2\Client\Tool\RequestFactory;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use ReflectionClass;
use UnexpectedValueException;

class AbstractProviderTest extends TestCase
{
Expand Down Expand Up @@ -649,6 +650,32 @@ public function testParseResponseNonJsonFailure()
$this->testParseResponse('<xml></xml>', 'application/xml', null, 500);
}

public function responseParsingExceptionProvider()
{
// List only combinations of content-type and status code that should lead to exception
// if the response body cannot be parsed as JSON
return [
['application/json', 401],
['application/xml', 500]
];
}

/**
* @dataProvider responseParsingExceptionProvider
*/
public function testResponseParsingException($type, $statusCode)
{
$exception = null;
try {
$this->testParseResponse('{13}', $type, null, $statusCode);
} catch (ResponseParsingException $exception) {
}
$this->assertInstanceOf(ResponseParsingException::class, $exception);
$response = $exception->getResponse();
$this->assertSame($statusCode, $response->getStatusCode());
$this->assertSame('{13}', $exception->getResponseBody());
}

public function getAppendQueryProvider()
{
return [
Expand Down
39 changes: 39 additions & 0 deletions test/src/Provider/Exception/ResponseParsingExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace League\OAuth2\Client\Test\Provider\Exception;

use GuzzleHttp\Psr7\Response;
use League\OAuth2\Client\Provider\Exception\ResponseParsingException;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;

class ResponseParsingExceptionTest extends TestCase
{
private $body = 'foo';

protected function generateResponseParsingException()
{
return new ResponseParsingException(new Response('401'), $this->body);
}

public function testGetResponse()
{
$this->assertInstanceOf(
ResponseInterface::class,
$this->generateResponseParsingException()->getResponse()
);
}

public function testGetResponseBody()
{
$this->assertSame(
$this->body,
$this->generateResponseParsingException()->getResponseBody()
);
}

public function testMissingMessage()
{
$this->assertNotEmpty($this->generateResponseParsingException()->getMessage());
}
}