diff --git a/src/OAuth2/AbstractProvider.php b/src/OAuth2/AbstractProvider.php index 0ec1e2c..c32f5f7 100644 --- a/src/OAuth2/AbstractProvider.php +++ b/src/OAuth2/AbstractProvider.php @@ -18,6 +18,13 @@ abstract class AbstractProvider extends BaseProvider implements ProviderInterfac */ protected $credentialsResponseBody; + /** + * The cached user instance. + * + * @var \SocialiteProviders\Manager\OAuth2\User|null + */ + protected $user; + /** * @param string $providerName * @return string @@ -34,6 +41,10 @@ public static function serviceContainerKey($providerName) */ public function user() { + if ($this->user) { + return $this->user; + } + if ($this->hasInvalidState()) { throw new InvalidStateException(); } @@ -41,15 +52,15 @@ public function user() $response = $this->getAccessTokenResponse($this->getCode()); $this->credentialsResponseBody = $response; - $user = $this->mapUserToObject($this->getUserByToken( + $this->user = $this->mapUserToObject($this->getUserByToken( $token = $this->parseAccessToken($response) )); - if ($user instanceof User) { - $user->setAccessTokenResponseBody($this->credentialsResponseBody); + if ($this->user instanceof User) { + $this->user->setAccessTokenResponseBody($this->credentialsResponseBody); } - return $user->setToken($token) + return $this->user->setToken($token) ->setRefreshToken($this->parseRefreshToken($response)) ->setExpiresIn($this->parseExpiresIn($response)); } diff --git a/tests/OAuthTwoTest.php b/tests/OAuthTwoTest.php index 1f5ea82..a80be99 100644 --- a/tests/OAuthTwoTest.php +++ b/tests/OAuthTwoTest.php @@ -221,4 +221,59 @@ public function exceptionIsThrownIfStateIsNotSet(): void $provider = new OAuthTwoTestProviderStub($request, 'client_id', 'client_secret', 'redirect'); $provider->user(); } + + /** + * @test + */ + public function userObjectShouldBeCachedOnFirstCall(): void + { + $session = m::mock(SessionInterface::class); + $accessTokenResponseBody = '{"access_token": "access_token", "test": "test"}'; + $request = Request::create('foo', 'GET', [ + 'state' => str_repeat('A', 40), + 'code' => 'code', + ]); + $request->setSession($session); + $session + ->shouldReceive('pull') + ->once() + ->with('state') + ->andReturn(str_repeat('A', 40)); + $provider = new OAuthTwoTestProviderStub($request, 'client_id', 'client_secret', 'redirect_uri'); + + $provider->http = m::mock(stdClass::class); + $provider->http + ->shouldReceive('post') + ->once() + ->with('http://token.url', [ + 'headers' => [ + 'Accept' => 'application/json', + ], + 'form_params' => [ + 'grant_type' => 'authorization_code', + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'code' => 'code', + 'redirect_uri' => 'redirect_uri', + ], + ]) + ->andReturn($response = m::mock(stdClass::class)); + $response + ->shouldReceive('getBody') + ->andReturn($accessTokenResponseBody); + + $reflection = new \ReflectionClass($provider); + $reflectionProperty = $reflection->getProperty('user'); + $reflectionProperty->setAccessible(true); + + $this->assertNull($reflectionProperty->getValue($provider)); + + $firstCall = $provider->user(); + + $this->assertInstanceOf(SocialiteOAuth2User::class, $reflectionProperty->getValue($provider)); + + $secondCall = $provider->user(); + + $this->assertSame($firstCall, $secondCall); + } }