diff --git a/.travis.yml b/.travis.yml index bf408e1..c4c043d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ matrix: - php: 7.0 - php: 7.1 - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: 8.0 - php: nightly - php: hhvm-3.6 sudo: required @@ -35,8 +38,6 @@ matrix: - php: hhvm-3.12 - php: hhvm-3.15 - php: hhvm-nightly - - php: 7.3 - - php: 7.4 before_script: - travis_retry composer self-update @@ -48,5 +49,8 @@ script: - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover after_script: - - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi - - if [ "$TRAVIS_PHP_VERSION" == "7.1" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi + - if [ "$TRAVIS_PHP_VERSION" == "7.4" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi + - if [ "$TRAVIS_PHP_VERSION" == "7.4" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..047a8a9 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,29 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + status: + project: + default: + target: auto + threshold: 0% + patch: + default: + target: auto + threshold: 0% + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: false diff --git a/composer.json b/composer.json index 52bb09f..b825476 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,12 @@ "league/oauth2-client": "^2.0", "ext-json": "*", "firebase/php-jwt": "^5.2", - "lcobucci/jwt": "^3.4.1" + "lcobucci/jwt": "^3.4 || ^4.0" }, "require-dev": { - "phpunit/phpunit": "^4.8|^7.5", - "mockery/mockery": "~1.3.3", - "squizlabs/php_codesniffer": "~2.0", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.3", + "mockery/mockery": "^1.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0", "ext-json": "*" }, "autoload": { diff --git a/phpunit.xml b/phpunit.xml index ecca7f4..1116871 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,34 +1,22 @@ - - - - - - - - ./test/ - - - - - ./ - - ./vendor - ./test - - - + + + + ./ + + + ./vendor + ./test + + + + + + + + + + ./test/ + + diff --git a/src/Provider/Apple.php b/src/Provider/Apple.php index 742a1ae..a0bd811 100644 --- a/src/Provider/Apple.php +++ b/src/Provider/Apple.php @@ -4,11 +4,10 @@ use Exception; use InvalidArgumentException; -use Lcobucci\JWT\Builder; -use Lcobucci\JWT\Signer\Ecdsa\Sha256; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Signer\Key\LocalFileReference; +use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Key; -use DateTimeImmutable; - use League\OAuth2\Client\Grant\AbstractGrant; use League\OAuth2\Client\Provider\Exception\AppleAccessDeniedException; use League\OAuth2\Client\Token\AccessToken; @@ -210,61 +209,43 @@ protected function createResourceOwner(array $response, AccessToken $token) */ public function getAccessToken($grant, array $options = []) { - if(class_exists('\Lcobucci\JWT\Configuration')){ - return $this->getAccessToken34($grant,$options); - } - - $signer = new Sha256(); + $configuration = $this->getConfiguration(); $time = new \DateTimeImmutable(); $expiresAt = $time->modify('+1 Hour'); - $token = (new Builder()) + $token = $configuration->builder() ->issuedBy($this->teamId) ->permittedFor('https://appleid.apple.com') - ->issuedAt($time->getTimestamp()) - ->expiresAt($expiresAt->getTimestamp()) + ->issuedAt($time) + ->expiresAt($expiresAt) ->relatedTo($this->clientId) ->withHeader('alg', 'ES256') ->withHeader('kid', $this->keyFileId) - ->getToken($signer, $this->getLocalKey()); + ->getToken($configuration->signer(), $configuration->signingKey()); $options += [ - 'client_secret' => (string) $token + 'client_secret' => $token->toString() ]; return parent::getAccessToken($grant, $options); } - private function getAccessToken34($grant, array $options = []) + /** + * @return Configuration + */ + public function getConfiguration() { - $signer = new Sha256(); - $now = new DateTimeImmutable(); - $key = Key\LocalFileReference::file($this->keyFilePath); - $config = \Lcobucci\JWT\Configuration::forSymmetricSigner($signer, $key); - - $token = $config->builder() - ->issuedBy($this->teamId) - ->permittedFor('https://appleid.apple.com') - ->issuedAt($now) - ->expiresAt($now->modify('+10 minute')) - ->relatedTo($this->clientId) - ->withHeader('alg', 'ES256') - ->withHeader('kid', $this->keyFileId) - ->getToken($config->signer(), $this->getLocalKey()); - - $options += [ - 'client_secret' => (string) $token - ]; - - return parent::getAccessToken($grant, $options); + return Configuration::forSymmetricSigner( + Signer\Ecdsa\Sha256::create(), + $this->getLocalKey() + ); } - /** * @return Key */ public function getLocalKey() { - return new Key('file://' . $this->keyFilePath); + return LocalFileReference::file($this->keyFilePath); } } diff --git a/test/src/Provider/AppleTest.php b/test/src/Provider/AppleTest.php index 6d12fae..702affd 100644 --- a/test/src/Provider/AppleTest.php +++ b/test/src/Provider/AppleTest.php @@ -2,15 +2,11 @@ namespace League\OAuth2\Client\Test\Provider; -use Exception; use GuzzleHttp\ClientInterface; use GuzzleHttp\Psr7\Response; -use InvalidArgumentException; -use Lcobucci\JWT\Builder; +use Lcobucci\JWT\Configuration; use League\OAuth2\Client\Provider\Apple; -use League\OAuth2\Client\Test\Provider\TestApple; use League\OAuth2\Client\Provider\AppleResourceOwner; -use League\OAuth2\Client\Provider\Exception\AppleAccessDeniedException; use League\OAuth2\Client\Token\AccessToken; use League\OAuth2\Client\Tool\QueryBuilderTrait; use PHPUnit\Framework\TestCase; @@ -20,12 +16,12 @@ class AppleTest extends TestCase { use QueryBuilderTrait; - /** @var Apple|\Mockery\MockInterface */ - protected $provider; - - protected function setUp() + /** + * @return Apple + */ + private function getProvider() { - $this->provider = new \League\OAuth2\Client\Provider\Apple([ + return new Apple([ 'clientId' => 'mock.example', 'teamId' => 'mock.team.id', 'keyFileId' => 'mock.file.id', @@ -34,62 +30,49 @@ protected function setUp() ]); } - public function tearDown() + public function testMissingTeamIdDuringInstantiationThrowsException() { - m::close(); - parent::tearDown(); + $this->expectException('InvalidArgumentException'); + new Apple([ + 'clientId' => 'mock.example', + 'keyFileId' => 'mock.file.id', + 'keyFilePath' => __DIR__ . '/p256-private-key.p8', + 'redirectUri' => 'none' + ]); } - /** - * @expectedException InvalidArgumentException - */ - public function testMissingTeamIdDuringInstantiationThrowsException() - { - new \League\OAuth2\Client\Provider\Apple([ - 'clientId' => 'mock.example', - 'keyFileId' => 'mock.file.id', - 'keyFilePath' => __DIR__ . '/p256-private-key.p8', - 'redirectUri' => 'none' - ]); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testMissingKeyFileIdDuringInstantiationThrowsException() - { - new \League\OAuth2\Client\Provider\Apple([ - 'clientId' => 'mock.example', - 'teamId' => 'mock.team.id', - 'keyFilePath' => __DIR__ . '/p256-private-key.p8', - 'redirectUri' => 'none' - ]); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testMissingKeyFilePathDuringInstantiationThrowsException() - { - new \League\OAuth2\Client\Provider\Apple([ - 'clientId' => 'mock.example', - 'teamId' => 'mock.team.id', - 'keyFileId' => 'mock.file.id', - 'redirectUri' => 'none' - ]); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testMissingKeyDuringInstantiationThrowsException() - { - $this->provider->getLocalKey(); - } + public function testMissingKeyFileIdDuringInstantiationThrowsException() + { + $this->expectException('InvalidArgumentException'); + new Apple([ + 'clientId' => 'mock.example', + 'teamId' => 'mock.team.id', + 'keyFilePath' => __DIR__ . '/p256-private-key.p8', + 'redirectUri' => 'none' + ]); + } + + public function testMissingKeyFilePathDuringInstantiationThrowsException() + { + $this->expectException('InvalidArgumentException'); + new Apple([ + 'clientId' => 'mock.example', + 'teamId' => 'mock.team.id', + 'keyFileId' => 'mock.file.id', + 'redirectUri' => 'none' + ]); + } + + public function testMissingKeyDuringInstantiationThrowsException() + { + $this->expectException('InvalidArgumentException'); + $this->getProvider()->getLocalKey(); + } public function testAuthorizationUrl() { - $url = $this->provider->getAuthorizationUrl(); + $provider = $this->getProvider(); + $url = $provider->getAuthorizationUrl(); $uri = parse_url($url); parse_str($uri['query'], $query); @@ -99,22 +82,24 @@ public function testAuthorizationUrl() $this->assertArrayHasKey('scope', $query); $this->assertArrayHasKey('response_type', $query); $this->assertArrayHasKey('response_mode', $query); - $this->assertNotNull($this->provider->getState()); + $this->assertNotNull($provider->getState()); } public function testScopes() { + $provider = $this->getProvider(); $scopeSeparator = ' '; $options = ['scope' => [uniqid(), uniqid()]]; $query = ['scope' => implode($scopeSeparator, $options['scope'])]; - $url = $this->provider->getAuthorizationUrl($options); + $url = $provider->getAuthorizationUrl($options); $encodedScope = $this->buildQueryString($query); - $this->assertContains($encodedScope, $url); + $this->assertNotFalse(strpos($url, $encodedScope)); } public function testGetAuthorizationUrl() { - $url = $this->provider->getAuthorizationUrl(); + $provider = $this->getProvider(); + $url = $provider->getAuthorizationUrl(); $uri = parse_url($url); $this->assertEquals('/auth/authorize', $uri['path']); @@ -122,140 +107,142 @@ public function testGetAuthorizationUrl() public function testGetBaseAccessTokenUrl() { + $provider = $this->getProvider(); $params = []; - $url = $this->provider->getBaseAccessTokenUrl($params); + $url = $provider->getBaseAccessTokenUrl($params); $uri = parse_url($url); $this->assertEquals('/auth/token', $uri['path']); } - /** - * @expectedException \Firebase\JWT\SignatureInvalidException - */ public function testGetAccessToken() { - $provider = new TestApple([ - 'clientId' => 'mock.example', - 'teamId' => 'mock.team.id', - 'keyFileId' => 'mock.file.id', - 'keyFilePath' => __DIR__ . '/../../resources/p256-private-key.p8', - 'redirectUri' => 'none' - ]); + $this->expectException('UnexpectedValueException'); + $provider = new TestApple([ + 'clientId' => 'mock.example', + 'teamId' => 'mock.team.id', + 'keyFileId' => 'mock.file.id', + 'keyFilePath' => __DIR__ . '/../../resources/p256-private-key.p8', + 'redirectUri' => 'none' + ]); $provider = m::mock($provider); - $time = new \DateTimeImmutable(); - $expiresAt = $time->modify('+1 Hour'); - $token = (new Builder()) - ->issuedBy('test-team-id') - ->permittedFor('https://appleid.apple.com') - ->issuedAt($time->getTimestamp()) - ->expiresAt($expiresAt->getTimestamp()) - ->relatedTo('test-client') - ->withClaim('sub', 'test') - ->withHeader('alg', 'RS256') - ->withHeader('kid', 'test') - ->getToken(); - - $client = m::mock(ClientInterface::class); - $client->shouldReceive('request') - ->times(1) - ->andReturn(new Response(200, [], file_get_contents('https://appleid.apple.com/auth/keys'))); - $client->shouldReceive('send') - ->times(1) - ->andReturn(new Response(200, [], json_encode([ - 'access_token' => 'aad897dee58fe4f66bf220c181adaf82b.0.mrwxq.hmiE0djj1vJqoNisKmF-pA', - 'token_type' => 'Bearer', - 'expires_in' => 3600, - 'refresh_token' => 'r4a6e8b9c50104b78bc86b0d2649353fa.0.mrwxq.54joUj40j0cpuMANRtRjfg', - 'id_token' => (string) $token - ]))); - $provider->setHttpClient($client); - - $provider->getAccessToken('authorization_code', [ - 'code' => 'hello-world' - ]); - } - public function testFetchingOwnerDetails() - { - $class = new \ReflectionClass($this->provider); - $method = $class->getMethod('fetchResourceOwnerDetails'); - $method->setAccessible(true); + $configuration = Configuration::forUnsecuredSigner(); - $arr = [ - 'name' => 'John Doe' - ]; - $_POST['user'] = json_encode($arr); - $data = $method->invokeArgs($this->provider, [new AccessToken(['access_token' => 'hello'])]); + $time = new \DateTimeImmutable(); + $expiresAt = $time->modify('+1 Hour'); + $token = $configuration->builder() + ->issuedBy('test-team-id') + ->permittedFor('https://appleid.apple.com') + ->issuedAt($time) + ->expiresAt($expiresAt) + ->relatedTo('test-client') + ->withHeader('alg', 'RS256') + ->withHeader('kid', 'test') + ->getToken($configuration->signer(), $configuration->signingKey()); + + $client = m::mock(ClientInterface::class); + $client->shouldReceive('request') + ->times(1) + ->andReturn(new Response(200, [], file_get_contents('https://appleid.apple.com/auth/keys'))); + $client->shouldReceive('send') + ->times(1) + ->andReturn(new Response(200, [], json_encode([ + 'access_token' => 'aad897dee58fe4f66bf220c181adaf82b.0.mrwxq.hmiE0djj1vJqoNisKmF-pA', + 'token_type' => 'Bearer', + 'expires_in' => 3600, + 'refresh_token' => 'r4a6e8b9c50104b78bc86b0d2649353fa.0.mrwxq.54joUj40j0cpuMANRtRjfg', + 'id_token' => $token->toString() + ]))); + $provider->setHttpClient($client); + + $provider->getAccessToken('authorization_code', [ + 'code' => 'hello-world' + ]); + } - $this->assertEquals($arr, $data); - } + public function testFetchingOwnerDetails() + { + $provider = $this->getProvider(); + $class = new \ReflectionClass($provider); + $method = $class->getMethod('fetchResourceOwnerDetails'); + $method->setAccessible(true); + + $arr = [ + 'name' => 'John Doe' + ]; + $_POST['user'] = json_encode($arr); + $data = $method->invokeArgs($provider, [new AccessToken(['access_token' => 'hello'])]); + + $this->assertEquals($arr, $data); + } /** * @see https://github.com/patrickbussmann/oauth2-apple/issues/12 */ - public function testFetchingOwnerDetailsIssue12() - { - $class = new \ReflectionClass($this->provider); - $method = $class->getMethod('fetchResourceOwnerDetails'); - $method->setAccessible(true); + public function testFetchingOwnerDetailsIssue12() + { + $provider = $this->getProvider(); + $class = new \ReflectionClass($provider); + $method = $class->getMethod('fetchResourceOwnerDetails'); + $method->setAccessible(true); $_POST['user'] = ''; - $data = $method->invokeArgs($this->provider, [new AccessToken(['access_token' => 'hello'])]); + $data = $method->invokeArgs($provider, [new AccessToken(['access_token' => 'hello'])]); - $this->assertEquals([], $data); - } + $this->assertEquals([], $data); + } - /** - * @expectedException Exception - */ - public function testNotImplementedGetResourceOwnerDetailsUrl() - { - $this->provider->getResourceOwnerDetailsUrl(new AccessToken(['access_token' => 'hello'])); - } + public function testNotImplementedGetResourceOwnerDetailsUrl() + { + $this->expectException('Exception'); + $provider = $this->getProvider(); + $provider->getResourceOwnerDetailsUrl(new AccessToken(['access_token' => 'hello'])); + } - /** - * @expectedException \League\OAuth2\Client\Provider\Exception\AppleAccessDeniedException - */ - public function testCheckResponse() - { - $class = new \ReflectionClass($this->provider); - $method = $class->getMethod('checkResponse'); - $method->setAccessible(true); - - $method->invokeArgs($this->provider, [new Response(400, []), [ - 'error' => 'invalid_client', - 'code' => 400 - ]]); - } - - public function testCreationOfResourceOwner() - { - $class = new \ReflectionClass($this->provider); - $method = $class->getMethod('createResourceOwner'); - $method->setAccessible(true); - - /** @var AppleResourceOwner $data */ - $data = $method->invokeArgs($this->provider, [ - [ - 'email' => 'john@doe.com',// <- Fake E-Mail from user input - 'name' => [ - 'firstName' => 'John', - 'lastName' => 'Doe' - ] - ], - new AccessToken([ - 'access_token' => 'hello', - 'email' => 'john@doe.de', - 'resource_owner_id' => '123.4.567' - ]) - ]); - $this->assertEquals('john@doe.de', $data->getEmail()); - $this->assertEquals('Doe', $data->getLastName()); - $this->assertEquals('John', $data->getFirstName()); - $this->assertEquals('123.4.567', $data->getId()); + public function testCheckResponse() + { + $this->expectException('\League\OAuth2\Client\Provider\Exception\AppleAccessDeniedException'); + $provider = $this->getProvider(); + $class = new \ReflectionClass($provider); + $method = $class->getMethod('checkResponse'); + $method->setAccessible(true); + + $method->invokeArgs($provider, [new Response(400, []), [ + 'error' => 'invalid_client', + 'code' => 400 + ]]); + } + + public function testCreationOfResourceOwner() + { + $provider = $this->getProvider(); + $class = new \ReflectionClass($provider); + $method = $class->getMethod('createResourceOwner'); + $method->setAccessible(true); + + /** @var AppleResourceOwner $data */ + $data = $method->invokeArgs($provider, [ + [ + 'email' => 'john@doe.com',// <- Fake E-Mail from user input + 'name' => [ + 'firstName' => 'John', + 'lastName' => 'Doe' + ] + ], + new AccessToken([ + 'access_token' => 'hello', + 'email' => 'john@doe.de', + 'resource_owner_id' => '123.4.567' + ]) + ]); + $this->assertEquals('john@doe.de', $data->getEmail()); + $this->assertEquals('Doe', $data->getLastName()); + $this->assertEquals('John', $data->getFirstName()); + $this->assertEquals('123.4.567', $data->getId()); $this->assertFalse($data->isPrivateEmail()); $this->assertArrayHasKey('name', $data->toArray()); - } + } } diff --git a/test/src/Provider/TestApple.php b/test/src/Provider/TestApple.php index 2796e51..d9ec3ca 100644 --- a/test/src/Provider/TestApple.php +++ b/test/src/Provider/TestApple.php @@ -2,6 +2,7 @@ namespace League\OAuth2\Client\Test\Provider; +use Lcobucci\JWT\Configuration; use League\OAuth2\Client\Provider\Apple; /** @@ -12,7 +13,15 @@ class TestApple extends Apple { /** - * @return \Lcobucci\JWT\Signer\Key|null + * {@inheritDoc} + */ + public function getConfiguration() + { + return Configuration::forUnsecuredSigner(); + } + + /** + * {@inheritDoc} */ public function getLocalKey() { diff --git a/test/src/Token/AppleAccessTokenTest.php b/test/src/Token/AppleAccessTokenTest.php index 892668b..49793bc 100644 --- a/test/src/Token/AppleAccessTokenTest.php +++ b/test/src/Token/AppleAccessTokenTest.php @@ -2,7 +2,6 @@ namespace League\OAuth2\Client\Test\Token; -use GuzzleHttp\ClientInterface; use GuzzleHttp\Psr7\Response; use League\OAuth2\Client\Token\AppleAccessToken; use PHPUnit\Framework\TestCase; @@ -10,12 +9,6 @@ class AppleAccessTokenTest extends TestCase { - public function tearDown() - { - m::close(); - parent::tearDown(); - } - /** * @runInSeparateProcess * @preserveGlobalState disabled