diff --git a/src/Social/Service/OpenIDConnectService.php b/src/Social/Service/OpenIDConnectService.php index 3cac93f..f797168 100644 --- a/src/Social/Service/OpenIDConnectService.php +++ b/src/Social/Service/OpenIDConnectService.php @@ -63,7 +63,7 @@ protected function getIdTokenKeys(): array $this->getConfig('openid.baseUrl') ); } - $client = new Client(); + $client = $this->getHttpClient(); $jwksData = $client->get($jwksUri)->getJson(); if (!$jwksData) { throw new BadRequestException( @@ -77,8 +77,13 @@ protected function getIdTokenKeys(): array public function discover(): array { $openidUrl = $this->getConfig('openid.url'); - $client = new Client(); + $client = $this->getHttpClient(); return $client->get($openidUrl)->getJson(); } + + protected function getHttpClient(): Client + { + return new Client(); + } } diff --git a/tests/TestCase/Social/Mapper/LinkedInOpenIDConnectTest.php b/tests/TestCase/Social/Mapper/LinkedInOpenIDConnectTest.php new file mode 100644 index 0000000..5e95926 --- /dev/null +++ b/tests/TestCase/Social/Mapper/LinkedInOpenIDConnectTest.php @@ -0,0 +1,81 @@ + 'test-token', + 'expires' => 1490988496, + ]); + $rawData = [ + 'sub' => '1', + 'token' => $token, + 'email' => 'test@gmail.com', + 'given_name' => 'Test', + 'family_name' => 'User', + 'industry' => 'Computer Software', + 'location' => [ + 'country' => [ + 'code' => 'es', + ], + 'name' => 'Spain', + ], + 'picture' => 'https://media.licdn.com/mpr/mprx/test.jpg', + + + 'bio' => 'The best test user in the world.', + 'publicProfileUrl' => 'https://www.linkedin.com/in/test', + ]; + $providerMapper = new LinkedInOpenIDConnect(); + $user = $providerMapper($rawData); + + $this->assertEquals([ + 'id' => '1', + 'username' => null, + 'full_name' => null, + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => 'test@gmail.com', + 'avatar' => 'https://media.licdn.com/mpr/mprx/test.jpg', + 'gender' => null, + 'link' => 'https://www.linkedin.com', + 'bio' => 'The best test user in the world.', + 'locale' => null, + 'validated' => true, + 'credentials' => [ + 'token' => 'test-token', + 'secret' => null, + 'expires' => 1490988496, + ], + 'raw' => $rawData, + ], $user); + } +} diff --git a/tests/TestCase/Social/Service/OpenIDConnectServiceTest.php b/tests/TestCase/Social/Service/OpenIDConnectServiceTest.php new file mode 100644 index 0000000..14a56d6 --- /dev/null +++ b/tests/TestCase/Social/Service/OpenIDConnectServiceTest.php @@ -0,0 +1,315 @@ +Client = $this->getMockBuilder( + \Cake\Http\Client::class + )->onlyMethods([ + 'get', + ])->getMock(); + + $this->Provider = $this->getMockBuilder( + \League\OAuth2\Client\Provider\LinkedIn::class + )->setConstructorArgs([ + [ + 'id' => '1', + 'firstName' => 'first', + 'lastName' => 'last', + ], + [], + ])->setMethods([ + 'getAccessToken', 'getState', 'getAuthorizationUrl', 'getResourceOwner', + ])->getMock(); + + $config = [ + 'service' => OpenIDConnectService::class, + 'className' => $this->Provider, + 'mapper' => \CakeDC\Auth\Social\Mapper\LinkedInOpenIDConnect::class, + 'authParams' => ['scope' => ['public_profile', 'email', 'user_birthday', 'user_gender', 'user_link']], + 'options' => [ + 'state' => '__TEST_STATE__', + ], + 'collaborators' => [], + 'signature' => null, + 'mapFields' => [], + 'path' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'prefix' => null, + ], + ]; + + $this->Service = $this->getMockBuilder( + \CakeDC\Auth\Social\Service\OpenIDConnectService::class + )->setConstructorArgs([ + $config, + ])->onlyMethods([ + 'getIdTokenKeys', + 'getHttpClient', + ])->getMock(); + + $this->Service->expects($this->any()) + ->method('getHttpClient') + ->will($this->returnValue($this->Client)); + + //new OpenIDConnectService($config); + $this->Request = ServerRequestFactory::fromGlobals(); + } + + /** + * teardown any static object changes and restore them. + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + + unset($this->Provider, $this->Service, $this->Request); + } + + /** + * Test construct + * + * @return void + */ + public function testConstruct() + { + $service = new OpenIDConnectService([ + 'className' => \League\OAuth2\Client\Provider\LinkedIn::class, + 'mapper' => \CakeDC\Auth\Social\Mapper\LinkedInOpenIDConnect::class, + 'authParams' => ['scope' => ['public_profile', 'email', 'user_birthday', 'user_gender', 'user_link']], + 'options' => [], + 'collaborators' => [], + 'signature' => null, + 'mapFields' => [], + 'path' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'prefix' => null, + ], + ]); + $this->assertInstanceOf(ServiceInterface::class, $service); + } + + /** + * Test GetUser InvalidRequest + * + * @return void + */ + public function testGetUserInvalidRequest() + { + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + + $this->expectException(BadRequestException::class, 'Invalid OAuth2 state'); + + $this->Service->getUser($this->Request); + } + + /** + * Test GetUser MisingIdToken + * + * @return void + */ + public function testGetUserMisingIdToken() + { + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $this->Provider->expects($this->once()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($token)); + + $this->expectException(BadRequestException::class, 'Missing id_token in response'); + + $this->Service->getUser($this->Request); + } + + /** + * Test GetUser InvalidIdToken + * + * @return void + */ + public function testGetUserInvalidIdToken() + { + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + 'id_token' => 'invalid-jwt', + ]); + + $this->Provider->expects($this->once()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($token)); + + $this->expectException(BadRequestException::class, 'Invalid id token. Key may not be empty'); + + $this->Service->getUser($this->Request); + } + + /** + * Test GetUser + * + * @return void + */ + public function testGetUser() + { + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $key = 'example_key'; + $alg = 'HS256'; + $payload = [ + 'iat' => 1490988496, + 'iss' => 'https://www.linkedin.com/', + ]; + $kid = 'd929668a-bab1-4c69-9598-4373149723ff'; + $jwt = JWT::encode($payload, $key, $alg, $kid); + + $token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + 'id_token' => $jwt, + ]); + + $this->Provider->expects($this->once()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($token)); + + $jwksData = [ + $kid => new Key($key, $alg), + ]; + + $this->Service->expects($this->once()) + ->method('getIdTokenKeys') + ->will($this->returnValue($jwksData)); + + $actual = $this->Service->getUser($this->Request); + + $expected = [ + 'token' => $token, + 'iat' => 1490988496, + 'iss' => 'https://www.linkedin.com/', + ]; + + $this->assertEquals($expected, $actual); + } + + /** + * Test discover + * + * @return void + */ + public function testDiscover() + { + $arrayTest = ['test' => 'test']; + $response = new \Cake\Http\Client\Response([], json_encode($arrayTest)); + + $this->Client->expects($this->once()) + ->method('get') + ->will($this->returnValue($response)); + + $actual = $this->Service->discover(); + + $this->assertEquals($arrayTest, $actual); + } +}