oauth2-client: `AbstractProvider::getParsedResponse` should always return array or throw exception
Uncaught Exception "TypeError" with message: "Argument 1 passed to League\OAuth2\Client\Provider\AbstractProvider::prepareAccessTokenResponse() must be of the type array, string given
It happens because AbstractProvider::getParsedResponse returns string when content can’t be parsed.
// AbstractProvider::getAccessToken
// ...
$response = $this->getParsedResponse($request);
$prepared = $this->prepareAccessTokenResponse($response);
// ...
/**
* Sends a request and returns the parsed response.
*
* @param RequestInterface $request
* @return mixed
*/
public function getParsedResponse(RequestInterface $request)
{
try {
$response = $this->getResponse($request);
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
$parsed = $this->parseResponse($response);
$this->checkResponse($response, $parsed);
return $parsed;
}
Attention to docs — “return mixed”. It can not return “mixed” (though actually it is), because the result goes to prepareAccessTokenResponse which receives array only.
/**
* Prepares an parsed access token response for a grant.
*
* Custom mapping of expiration, etc should be done here. Always call the
* parent method when overloading this method.
*
* @param mixed $result
* @return array
*/
protected function prepareAccessTokenResponse(array $result)
Docs say param mixed $result (was changed here https://github.com/thephpleague/oauth2-client/commit/365e61c6b1a092f79d81c723680852213dbdedac with no visible reason), but typehint says array $result.
And the last thing — where mixed comes from?
/**
* Parses the response according to its content-type header.
*
* @throws UnexpectedValueException
* @param ResponseInterface $response
* @return array
*/
protected function parseResponse(ResponseInterface $response)
{
$content = (string) $response->getBody();
$type = $this->getContentType($response);
if (strpos($type, 'urlencoded') !== false) {
parse_str($content, $parsed);
return $parsed;
}
// Attempt to parse the string as JSON regardless of content type,
// since some providers use non-standard content types. Only throw an
// exception if the JSON could not be parsed when it was expected to.
try {
return $this->parseJson($content);
} catch (UnexpectedValueException $e) {
if (strpos($type, 'json') !== false) {
throw $e;
}
return $content;
}
}
return array — one more lie here. Look at the catch block: value is considered unexpected only if type was “expected” — wut? Otherwise $content is returned, which is string. This was called fallback on failure.
So, long story short
- provider returns some wierd response
- oauth2-client tries to parse it as json and fails to
- then the parse method returns a string (raw response body) instead of an exception or at least promised array
- string goes to another method with
arraytypehint - ???
- TypeError (PHP 7)
I haven’t done PR because I don’t know what’s the right solution in this situation. We should respect BC, I understand. But I’d just throw an exception despite any other condition: we tried to do something and failed — there is nothing we can do with it later.
About this issue
- Original URL
- State: open
- Created 7 years ago
- Reactions: 4
- Comments: 19 (8 by maintainers)
My main concern still remains: The
UnexpectedValueExceptionprovides no way to see the contents of the response for debugging purposes. All you can see is that a request failed, and any content the provider might have returned in a non-JSON format (e.g. an HTML error page) is swallowed by the library. I’d like to see this addressed, personally.As this issue is still open, I’d like to raise some concern and share my thought. This is how it is currently implemented
I assume the intended task of parseResponse() is to correctly parse the response body and return it. Currently, parseResponse is able to parse urlencoded or valid json body. In general, the result of a json parsing does not need to be an array. It could even be null, empty string, integer, etc. So the value of $parsed can be of different type, which I don’t really care much. It is up to the calling function to correctly handle that value.
The @return array type hint is surely wrong.
The problem with current implementation of parseResponse() is that it throws an exception which does not help much in the calling function if the response body cannot be parsed as JSON. This includes body having empty string as value. Thus, I’d suggest to throw a new exception type called ResponseParsingException which encapsulates the response and its body, so that the calling function can reacts accordingly by retrieving the response and/or the body from the catched exception. To ensure BC we can define a flag in AbstractProvider, e.g.
protected $mayThrowResponseParsingException = false;
which can be used to control the behavior of parseResponse().
` What do you think? I know that the method checkResponse() can be (mis)used to re-parse the response later. But this is most probably not what this method is supposed to do. I assume that checkResponse should be used to check for error messages in the successfully parsed body returned by a well-behaved identity provider.
Same here, too. Version 2.3.0 on PHP 7.2. The OAuth Server on the other end sends a error string, which goes into prepareAccessTokenResponse without hesitation, which in turn just fails with the exception the thread owner stated in his very first sentence.
I’m open to additional PR(s) to improve the behavior.