diff --git a/CHANGELOG.md b/CHANGELOG.md index f3df84d0..891b6f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # OAuth 2.0 Client Changelog +## 2.8.1 + +_Released: 2025-02-26_ + +* Only provide scopes in access token when set in options [#1053](/~https://github.com/thephpleague/oauth2-client/pull/1053) +* Add missing `@throws` annotations for Guzzle exceptions [#1055](/~https://github.com/thephpleague/oauth2-client/pull/1055) + ## 2.8.0 _Released: 2024-12-11_ diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 04743a50..ecd09b95 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -235,7 +235,7 @@ GEM minitest (5.25.4) net-http (0.6.0) uri - nokogiri (1.17.0) + nokogiri (1.18.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (4.25.1) diff --git a/docs/providers/thirdparty.md b/docs/providers/thirdparty.md index 7c555a11..98097ce9 100755 --- a/docs/providers/thirdparty.md +++ b/docs/providers/thirdparty.md @@ -53,6 +53,7 @@ Gateway | Composer Package | Maintainer [Dribbble](/~https://github.com/crewlabs/oauth2-dribbble) | crewlabs/oauth2-dribbble | [Crew Labs](https://crew.co/labs) [Dropbox](/~https://github.com/stevenmaguire/oauth2-dropbox) | stevenmaguire/oauth2-dropbox | [Steven Maguire](/~https://github.com/stevenmaguire) [Drupal](/~https://github.com/chrishemmings/oauth2-drupal) | chrishemmings/oauth2-drupal | [Chris Hemmings](/~https://github.com/chrishemmings) +[Ebay](/~https://github.com/Gugiman/oauth2-ebay) | gugiman/oauth2-ebay | [Dennis Steffen](/~https://github.com/gugiman) [Ecwid](/~https://github.com/mugnate/oauth2-ecwid) | mugnate/oauth2-ecwid | [Nikolay Votintsev](/~https://github.com/votintsev) [Edenred](/~https://github.com/jzecca/oauth2-edenred) | jzecca/oauth2-edenred | [Jérôme Zecca](/~https://github.com/jzecca) [Elance](/~https://github.com/stevenmaguire/oauth2-elance) | stevenmaguire/oauth2-elance | [Steven Maguire](/~https://github.com/stevenmaguire) diff --git a/src/Provider/AbstractProvider.php b/src/Provider/AbstractProvider.php index c2a047c3..d2c706e1 100644 --- a/src/Provider/AbstractProvider.php +++ b/src/Provider/AbstractProvider.php @@ -18,6 +18,7 @@ namespace League\OAuth2\Client\Provider; use GuzzleHttp\Exception\BadResponseException; +use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; use League\OAuth2\Client\Grant\AbstractGrant; use League\OAuth2\Client\Grant\GrantFactory; @@ -621,16 +622,13 @@ protected function getAccessTokenRequest(array $params): RequestInterface * @throws ClientExceptionInterface * @throws IdentityProviderException * @throws UnexpectedValueException + * @throws GuzzleException */ public function getAccessToken(mixed $grant, array $options = []): AccessTokenInterface { $grant = $this->verifyGrant($grant); - if (!isset($options['scope'])) { - $options['scope'] = $this->getDefaultScopes(); - } - - if (is_array($options['scope'])) { + if (isset($options['scope']) && is_array($options['scope'])) { $separator = $this->getScopeSeparator(); $options['scope'] = implode($separator, $options['scope']); } @@ -758,6 +756,7 @@ public function getResponse(RequestInterface $request): ResponseInterface * @throws ClientExceptionInterface * @throws IdentityProviderException * @throws UnexpectedValueException + * @throws GuzzleException */ public function getParsedResponse(RequestInterface $request): array { @@ -914,6 +913,7 @@ abstract protected function createResourceOwner(array $response, AccessToken $to * @throws ClientExceptionInterface * @throws IdentityProviderException * @throws UnexpectedValueException + * @throws GuzzleException */ public function getResourceOwner(AccessToken $token): ResourceOwnerInterface { @@ -930,6 +930,7 @@ public function getResourceOwner(AccessToken $token): ResourceOwnerInterface * @throws ClientExceptionInterface * @throws IdentityProviderException * @throws UnexpectedValueException + * @throws GuzzleException */ protected function fetchResourceOwnerDetails(AccessToken $token): array { diff --git a/test/src/Grant/PasswordTest.php b/test/src/Grant/PasswordTest.php index 93630115..17c1ace8 100644 --- a/test/src/Grant/PasswordTest.php +++ b/test/src/Grant/PasswordTest.php @@ -25,8 +25,7 @@ protected function getParamExpectation(): Closure return fn (array $body) => isset($body['grant_type']) && $body['grant_type'] === 'password' && isset($body['username']) - && isset($body['password']) - && isset($body['scope']); + && isset($body['password']); } public function testToString(): void diff --git a/test/src/Provider/AbstractProviderTest.php b/test/src/Provider/AbstractProviderTest.php index 592b367a..dda2e6bf 100644 --- a/test/src/Provider/AbstractProviderTest.php +++ b/test/src/Provider/AbstractProviderTest.php @@ -714,7 +714,7 @@ public function testGetAccessToken(string $method): void ->once() ->with( ['client_id' => 'mock_client_id', 'client_secret' => 'mock_secret', 'redirect_uri' => 'none'], - ['code' => 'mock_authorization_code', 'scope' => 'test'], + ['code' => 'mock_authorization_code'], ) ->andReturn([]); @@ -755,6 +755,61 @@ public function testGetAccessToken(string $method): void && (string) $request->getUri() === $provider->getBaseAccessTokenUrl([])); } + #[DataProvider('getAccessTokenMethodProvider')] + public function testGetAccessTokenWithScope(string $method): void + { + $provider = $this->getMockProvider(); + $provider->setAccessTokenMethod($method); + + $rawResponse = ['access_token' => 'okay', 'expires' => time() + 3600, 'resource_owner_id' => 3]; + + $grant = Mockery::mock(AbstractGrant::class); + $grant + ->shouldReceive('prepareRequestParameters') + ->once() + ->with( + ['client_id' => 'mock_client_id', 'client_secret' => 'mock_secret', 'redirect_uri' => 'none'], + ['code' => 'mock_authorization_code', 'scope' => 'foo,bar'], + ) + ->andReturn([]); + + $stream = Mockery::mock(StreamInterface::class); + $stream + ->shouldReceive('__toString') + ->once() + ->andReturn(json_encode($rawResponse)); + + $response = Mockery::mock(ResponseInterface::class); + $response + ->shouldReceive('getBody') + ->once() + ->andReturn($stream); + $response + ->shouldReceive('getHeader') + ->once() + ->with('content-type') + ->andReturn(['application/json']); + + $client = Mockery::spy(ClientInterface::class, [ + 'sendRequest' => $response, + ]); + + $provider->setHttpClient($client); + $token = $provider->getAccessToken($grant, ['code' => 'mock_authorization_code', 'scope' => ['foo', 'bar']]); + + $this->assertInstanceOf(ResourceOwnerAccessTokenInterface::class, $token); + + $this->assertSame($rawResponse['resource_owner_id'], $token->getResourceOwnerId()); + $this->assertSame($rawResponse['access_token'], $token->getToken()); + $this->assertSame($rawResponse['expires'], $token->getExpires()); + + $client + ->shouldHaveReceived('sendRequest') + ->once() + ->withArgs(fn (RequestInterface $request) => $request->getMethod() === $provider->getAccessTokenMethod() + && (string) $request->getUri() === $provider->getBaseAccessTokenUrl([])); + } + public function testGetAccessTokenWithNonJsonResponse(): void { $provider = $this->getMockProvider();