23 Commits

Author SHA1 Message Date
Patrick Bußmann 51c96fba61 Updated changelog for new version 2022-04-29 23:04:42 +02:00
Patrick Bußmann a317e52085 Made older PHP Versions working 2022-04-29 22:57:56 +02:00
Daniel 415e29753b Support for firebase/php-jwt Version 6 (#32)
* wip

* wip

* wip
2022-04-29 22:57:02 +02:00
Patrick Bußmann 61e5f98312 Moved to GitHub Actions and increased test coverage 2021-08-25 21:47:57 +02:00
Thijs-jan Veldhuizen 5f20997c99 Moved fetching Apple JWT keys from token to provider
Since AccessToken is serialized in specific set-ups, existence of the httpClient leads to problems, since closures are not serializable. Also, it's not necessary to keep the httpClient there, since it's only used in the constructor.

Fixes #26, #28
2021-08-03 11:05:27 +02:00
Patrick Bußmann f6fe8c0b1b Updated CHANGELOG 2021-03-14 23:14:36 +01:00
Patrick Bußmann 113f72fa62 Merge pull request #25 from tjveldhuizen/fix-bc-break-lcobucci-jwt
Fix BC-break for combination of PHP 7.4 and lcobucci/jwt 3.4
2021-03-08 09:13:31 +01:00
Thijs-jan Veldhuizen eb4d697dda Fix BC-break for combination of PHP 7.4 and lcobucci/jwt 3.4
Fixes #24
2021-02-17 23:47:58 +01:00
Patrick Bußmann 95cc38cd0c Removing microseconds so that Sign in with Apple works again 2021-01-17 23:19:45 +01:00
Patrick Bußmann a08ded7b3a Added Codecov and updated changelog 2021-01-17 17:46:58 +01:00
Patrick Bußmann 31c61d334b Increased library compatibility and fixed all tests 2021-01-17 17:30:28 +01:00
Patrick Bußmann d649df8313 Merge pull request #20 from zhangv/master
support Lcobucci-jwt v3.4+
2021-01-15 12:11:42 +01:00
Patrick Bußmann 6702931e2e Merge branch 'master' into master 2021-01-15 12:11:32 +01:00
Patrick Bußmann 5598a94a36 Added leeway usage to README 2021-01-05 19:33:41 +01:00
Patrick Bußmann e191fd0988 Merge remote-tracking branch 'jmalinens/use_guzzle'
# Conflicts:
#	src/Token/AppleAccessToken.php
2021-01-05 12:53:11 +01:00
Patrick Bußmann 8c49a59451 Merge remote-tracking branch 'NgSekLong/patch-1' 2021-01-05 08:53:45 +01:00
Patrick Bußmann 45903a3f90 Merge remote-tracking branch 'giganti-consultoria/master' 2021-01-05 08:53:39 +01:00
zhangv 06ef0a50d1 support Lcobucci-jwt v3.4+
and compatible to the old versions
2020-12-11 14:45:06 +08:00
Juris Malinens a6b002a062 use guzzle instance from AbstractProvider to get apple auth keys instead of file_get_contents 2020-11-27 17:05:39 +02:00
NgSekLong 1b47b8e9d3 Update README include no scope instruction
Add mention for how to disable scope "Don't need email and name", and how to modify the sample code to get back `code`

i.e. Change all `$_POST` to `$_GET`
2020-09-10 11:07:06 +08:00
William Martins 3f23b1d156 Code Formatter 2020-07-31 16:50:28 -03:00
William Martins 81164c4b8a Use guzzle http client instead of file_get_contents
Many servers have allow_fopen_url disabled so they cant use file_get_contents to load apple keys.
2020-07-31 16:38:39 -03:00
Bogdan Dovgopol a45b7cdc9b Check if name scope is set before trying to return firstName or lastName 2020-07-23 12:06:08 +10:00
14 changed files with 529 additions and 302 deletions
@@ -0,0 +1,80 @@
name: 'CI'
on:
pull_request:
push:
branches:
- 'main'
env:
COMPOSER_ROOT_VERSION: '1.99.99'
jobs:
lint:
name: 'Lint'
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2'
- uses: 'shivammathur/setup-php@v2'
with:
php-version: '7.4'
coverage: 'none'
ini-values: 'memory_limit=-1'
tools: 'composer:v2'
- uses: 'ramsey/composer-install@v1'
- name: 'Lint the PHP source code'
run: './vendor/bin/parallel-lint src test'
coding-standards:
name: 'Coding Standards'
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2'
- uses: 'shivammathur/setup-php@v2'
with:
php-version: '7.4'
coverage: 'none'
ini-values: 'memory_limit=-1'
tools: 'composer:v2'
- uses: 'ramsey/composer-install@v1'
- name: 'Check coding standards'
run: './vendor/bin/phpcs src --standard=psr2 -sp --colors'
unit-tests:
name: 'Unit Tests'
runs-on: 'ubuntu-latest'
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
php-version:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
- '7.3'
- '7.4'
- '8.0'
experimental:
- false
include:
- php-version: '8.1'
experimental: true
composer-options: '--ignore-platform-reqs'
steps:
- uses: 'actions/checkout@v2'
- uses: 'shivammathur/setup-php@v2'
with:
php-version: '${{ matrix.php-version }}'
coverage: 'pcov'
ini-values: 'memory_limit=-1'
tools: 'composer:v2'
- name: 'Prepare for tests'
run: 'mkdir -p build/logs'
- uses: 'ramsey/composer-install@v1'
with:
composer-options: '${{ matrix.composer-options }}'
- name: 'Run unit tests'
run: './vendor/bin/phpunit --colors=always --coverage-clover build/logs/clover.xml'
- name: 'Publish coverage report to Codecov'
uses: 'codecov/codecov-action@v1'
+1
View File
@@ -4,3 +4,4 @@
composer.phar
composer.lock
.DS_Store
.phpunit.result.cache
-51
View File
@@ -1,51 +0,0 @@
language: php
matrix:
include:
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2
- php: 7.3
- php: nightly
- php: hhvm-3.6
sudo: required
dist: trusty
group: edge
- php: hhvm-3.9
sudo: required
dist: trusty
group: edge
- php: hhvm-3.12
sudo: required
dist: trusty
group: edge
- php: hhvm-3.15
sudo: required
dist: trusty
group: edge
- php: hhvm-nightly
sudo: required
dist: trusty
group: edge
fast_finish: true
allow_failures:
- php: nightly
- php: hhvm-3.6
- php: hhvm-3.9
- php: hhvm-3.12
- php: hhvm-3.15
- php: hhvm-nightly
before_script:
- travis_retry composer self-update
- travis_retry composer install --no-interaction --prefer-source --dev
- travis_retry phpenv rehash
script:
- ./vendor/bin/phpcs --standard=psr2 src/
- ./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
+39
View File
@@ -18,6 +18,45 @@ All Notable changes to `oauth2-apple` will be documented in this file
### Security
- Nothing
## 0.2.7 - 2022-04-29
### Added
- Support for firebase/php-jwt v6 [#31](https://github.com/patrickbussmann/oauth2-apple/pull/31) (thanks to [bashgeek](https://github.com/bashgeek))
## 0.2.6 - 2021-08-25
### Added
- GitHub Actions CI
### Removed
- Travis CI
### Fixed
- Fixed bug with serialization of AppleAccessToken [#29](https://github.com/patrickbussmann/oauth2-apple/pull/29) (thanks to [tjveldhuizen](https://github.com/tjveldhuizen))
## 0.2.5 - 2021-03-10
### Fixed
- Fix BC-break for combination of PHP 7.4 and lcobucci/jwt 3.4 [#25](https://github.com/patrickbussmann/oauth2-apple/pull/25) (thanks to [tjveldhuizen](https://github.com/tjveldhuizen))
## 0.2.4 - 2021-01-17
### Added
- Codecov for Code Coverage
### Fixed
- Few compatibility issues with PHP 8 and PHP 5.6 (Read [#16](https://github.com/patrickbussmann/oauth2-apple/pull/16) for more details)
## 0.2.3 - 2021-01-05
### Added
- Using guzzle http instead of file_get_contents [#14](https://github.com/patrickbussmann/oauth2-apple/pull/14)/[#17](https://github.com/patrickbussmann/oauth2-apple/pull/17) (thanks to [jmalinens](https://github.com/jmalinens) and [williamxsp](https://github.com/williamxsp))
- README no scope instruction [#15](https://github.com/patrickbussmann/oauth2-apple/pull/15) (thanks to [NgSekLong](https://github.com/NgSekLong))
- README leeway usage [#18](https://github.com/patrickbussmann/oauth2-apple/issues/18) (thanks to [lukequinnell](https://github.com/lukequinnell))
### Fixed
- Fixed getting first and last name issues [#13](https://github.com/patrickbussmann/oauth2-apple/pull/13) (thanks to [bogdandovgopol](https://github.com/bogdandovgopol))
## 0.2.1 - 2020-02-13
### Added
+10 -5
View File
@@ -1,9 +1,10 @@
# Sign in with Apple ID Provider for OAuth 2.0 Client
[![Latest Version](https://img.shields.io/github/release/patrickbussmann/oauth2-apple.svg?style=flat-square)](https://github.com/patrickbussmann/oauth2-apple/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Build Status](https://img.shields.io/travis/patrickbussmann/oauth2-apple/master.svg?style=flat-square)](https://travis-ci.org/patrickbussmann/oauth2-apple)
[![Build Status](https://img.shields.io/travis/patrickbussmann/oauth2-apple/main.svg?style=flat-square)](https://travis-ci.org/patrickbussmann/oauth2-apple)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/patrickbussmann/oauth2-apple.svg?style=flat-square)](https://scrutinizer-ci.com/g/patrickbussmann/oauth2-apple/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/patrickbussmann/oauth2-apple.svg?style=flat-square)](https://scrutinizer-ci.com/g/patrickbussmann/oauth2-apple)
[![codecov](https://codecov.io/gh/patrickbussmann/oauth2-apple/branch/main/graph/badge.svg?token=TN3ZNVHUXV)](https://codecov.io/gh/patrickbussmann/oauth2-apple)
[![Total Downloads](https://img.shields.io/packagist/dt/patrickbussmann/oauth2-apple.svg?style=flat-square)](https://packagist.org/packages/patrickbussmann/oauth2-apple)
This package provides Apple ID OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).
@@ -32,11 +33,14 @@ Usage is the same as The League's OAuth client, using `\League\OAuth2\Client\Pro
### Authorization Code Flow
```php
// $leeway is needed for clock skew
Firebase\JWT\JWT::$leeway = 60;
$provider = new League\OAuth2\Client\Provider\Apple([
'clientId' => '{apple-client-id}',
'teamId' => '{apple-team-id}', // 1A234BFK46 https://developer.apple.com/account/#/membership/ (Team ID)
'keyFileId' => '{apple-key-file-id}', // 1ABC6523AA https://developer.apple.com/account/resources/authkeys/list (Key ID)
'keyFilePath' => '{apple-key-file-path}', // __DIR__ . '/AuthKey_1ABC6523AA.p8' -> Download key above
'keyFilePath' => '{apple-key-file-path}', // __DIR__ . '/AuthKey_1ABC6523AA.p8' -> Download key above
'redirectUri' => 'https://example.com/callback-url',
]);
@@ -107,6 +111,7 @@ At the time of authoring this documentation, the following scopes are available.
Please note that you will get this informations only at the first log in of the user!
In the following log ins you'll get only the user id!
If you only want to get the user id, you can set the `scope` as ` `, then change all the `$_POST` to `$_GET`.
### Refresh Tokens
@@ -125,15 +130,15 @@ $ ./vendor/bin/phpunit
## Contributing
Please see [CONTRIBUTING](https://github.com/patrickbussmann/oauth2-apple/blob/master/CONTRIBUTING.md) for details.
Please see [CONTRIBUTING](https://github.com/patrickbussmann/oauth2-apple/blob/main/CONTRIBUTING.md) for details.
## Credits
- [All Contributors](https://github.com/patrickbussmann/oauth2-apple/contributors)
Template for this repository was the [LinkedIn](https://github.com/thephpleague/oauth2-linkedin).
Template for this repository was the [LinkedIn](https://github.com/thephpleague/oauth2-linkedin).
## License
The MIT License (MIT). Please see [License File](https://github.com/patrickbussmann/oauth2-apple/blob/master/LICENSE) for more information.
The MIT License (MIT). Please see [License File](https://github.com/patrickbussmann/oauth2-apple/blob/main/LICENSE) for more information.
+29
View File
@@ -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
+7 -7
View File
@@ -19,16 +19,16 @@
"sign-in-with-apple"
],
"require": {
"league/oauth2-client": "^2.0",
"ext-json": "*",
"firebase/php-jwt": "^5.2",
"lcobucci/jwt": "^3.3"
"league/oauth2-client": "^2.0",
"firebase/php-jwt": "^5.2 || ^6.0",
"lcobucci/jwt": "^3.4 || ^4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0",
"mockery/mockery": "~0.9",
"squizlabs/php_codesniffer": "~2.0",
"ext-json": "*"
"phpunit/phpunit": "^5.7 || ^6.0 || ^9.3",
"mockery/mockery": "^1.3",
"php-parallel-lint/php-parallel-lint": "^1.3",
"squizlabs/php_codesniffer": "^2.3 || ^3.0"
},
"autoload": {
"psr-4": {
+20 -35
View File
@@ -1,37 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<logging>
<log type="coverage-html"
target="./build/coverage/html"
charset="UTF-8"
highlight="false"
lowUpperBound="35"
highLowerBound="70"/>
<log type="coverage-clover"
target="./build/coverage/log/coverage.xml"/>
</logging>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./</directory>
<exclude>
<directory suffix=".php">./vendor</directory>
<directory suffix=".php">./test</directory>
</exclude>
</whitelist>
</filter>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">./</directory>
</include>
<exclude>
<directory suffix=".php">./vendor</directory>
<directory suffix=".php">./test</directory>
</exclude>
<report>
<clover outputFile="./build/coverage/log/coverage.xml"/>
<html outputDirectory="./build/coverage/html" lowUpperBound="35" highLowerBound="70"/>
</report>
</coverage>
<logging/>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./test/</directory>
</testsuite>
</testsuites>
</phpunit>
+47 -11
View File
@@ -3,9 +3,11 @@
namespace League\OAuth2\Client\Provider;
use Exception;
use Firebase\JWT\JWK;
use InvalidArgumentException;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Provider\Exception\AppleAccessDeniedException;
@@ -76,7 +78,21 @@ class Apple extends AbstractProvider
*/
protected function createAccessToken(array $response, AbstractGrant $grant)
{
return new AppleAccessToken($response);
return new AppleAccessToken($this->getAppleKeys(), $response);
}
/**
* @return string[] Apple's JSON Web Keys
*/
private function getAppleKeys()
{
$response = $this->httpClient->request('GET', 'https://appleid.apple.com/auth/keys');
if ($response && $response->getStatusCode() === 200) {
return JWK::parseKeySet(json_decode($response->getBody()->getContents(), true));
}
return [];
}
/**
@@ -208,32 +224,52 @@ class Apple extends AbstractProvider
*/
public function getAccessToken($grant, array $options = [])
{
$signer = new Sha256();
$time = time();
$configuration = $this->getConfiguration();
$time = new \DateTimeImmutable();
$time = $time->setTime($time->format('H'), $time->format('i'), $time->format('s'));
$expiresAt = $time->modify('+1 Hour');
$expiresAt = $expiresAt->setTime($expiresAt->format('H'), $expiresAt->format('i'), $expiresAt->format('s'));
$token = (new Builder())
$token = $configuration->builder()
->issuedBy($this->teamId)
->permittedFor('https://appleid.apple.com')
->issuedAt($time)
->expiresAt($time + 600)
->expiresAt($expiresAt)
->relatedTo($this->clientId)
->withClaim('sub', $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);
}
/**
* @return Configuration
*/
public function getConfiguration()
{
if (method_exists(Signer\Ecdsa\Sha256::class, 'create')) {
return Configuration::forSymmetricSigner(
Signer\Ecdsa\Sha256::create(),
$this->getLocalKey()
);
} else {
return Configuration::forSymmetricSigner(
new Signer\Ecdsa\Sha256(),
$this->getLocalKey()
);
}
}
/**
* @return Key
*/
public function getLocalKey()
{
return new Key('file://' . $this->keyFilePath);
return InMemory::file($this->keyFilePath);
}
}
+10 -2
View File
@@ -46,7 +46,11 @@ class AppleResourceOwner extends GenericResourceOwner
*/
public function getFirstName()
{
return $this->getAttribute('name')['firstName'];
$name = $this->getAttribute('name');
if (isset($name)) {
return $name['firstName'];
}
return null;
}
/**
@@ -66,7 +70,11 @@ class AppleResourceOwner extends GenericResourceOwner
*/
public function getLastName()
{
return $this->getAttribute('name')['lastName'];
$name = $this->getAttribute('name');
if (isset($name)) {
return $name['lastName'];
}
return null;
}
/**
+4 -12
View File
@@ -2,8 +2,8 @@
namespace League\OAuth2\Client\Token;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use InvalidArgumentException;
class AppleAccessToken extends AccessToken
@@ -26,13 +26,14 @@ class AppleAccessToken extends AccessToken
/**
* Constructs an access token.
*
* @param Key[] $keys Valid Apple JWT keys
* @param array $options An array of options returned by the service provider
* in the access token request. The `access_token` option is required.
* @throws InvalidArgumentException if `access_token` is not provided in `$options`.
*
* @throws \Exception
*/
public function __construct(array $options = [])
public function __construct(array $keys, array $options = [])
{
if (array_key_exists('refresh_token', $options)) {
if (empty($options['id_token'])) {
@@ -40,11 +41,10 @@ class AppleAccessToken extends AccessToken
}
$decoded = null;
$keys = $this->getAppleKey();
$last = end($keys);
foreach ($keys as $key) {
try {
$decoded = JWT::decode($options['id_token'], $key, ['RS256']);
$decoded = JWT::decode($options['id_token'], $key);
break;
} catch (\Exception $exception) {
if ($last === $key) {
@@ -79,14 +79,6 @@ class AppleAccessToken extends AccessToken
}
}
/**
* @return array Apple's JSON Web Key
*/
protected function getAppleKey()
{
return JWK::parseKeySet(json_decode(file_get_contents('https://appleid.apple.com/auth/keys'), true));
}
/**
* @return string
*/
+223 -163
View File
@@ -2,28 +2,28 @@
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 Lcobucci\JWT\Signer\Key;
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\Token\AppleAccessToken;
use League\OAuth2\Client\Tool\QueryBuilderTrait;
use PHPUnit\Framework\TestCase;
use Mockery as m;
class AppleTest extends \PHPUnit_Framework_TestCase
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',
@@ -32,62 +32,49 @@ class AppleTest extends \PHPUnit_Framework_TestCase
]);
}
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'
]);
}
public function testMissingKeyFileIdDuringInstantiationThrowsException()
{
$this->expectException('InvalidArgumentException');
new Apple([
'clientId' => 'mock.example',
'teamId' => 'mock.team.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'
]);
}
public function testMissingKeyFilePathDuringInstantiationThrowsException()
{
$this->expectException('InvalidArgumentException');
new Apple([
'clientId' => 'mock.example',
'teamId' => 'mock.team.id',
'keyFileId' => 'mock.file.id',
'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 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);
@@ -97,22 +84,24 @@ class AppleTest extends \PHPUnit_Framework_TestCase
$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']);
@@ -120,135 +109,206 @@ class AppleTest extends \PHPUnit_Framework_TestCase
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 = time();
$token = (new Builder())
->issuedBy('test-team-id')
->permittedFor('https://appleid.apple.com')
->issuedAt($time)
->expiresAt($time + 600)
->relatedTo('test-client')
->withClaim('sub', 'test')
->withHeader('alg', 'RS256')
->withHeader('kid', 'test')
->getToken();
$client = m::mock('GuzzleHttp\ClientInterface');
$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);
$configuration = Configuration::forUnsecuredSigner();
$provider->getAccessToken('authorization_code', [
'code' => 'hello-world'
]);
$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'
]);
}
public function testFetchingOwnerDetails()
{
$class = new \ReflectionClass($this->provider);
$method = $class->getMethod('fetchResourceOwnerDetails');
$method->setAccessible(true);
public function testGetAccessTokenFailedBecauseAppleHasError()
{
$this->expectException('Exception');
$this->expectExceptionMessage('Got no data within "id_token"!');
$arr = [
'name' => 'John Doe'
];
$_POST['user'] = json_encode($arr);
$data = $method->invokeArgs($this->provider, [new AccessToken(['access_token' => 'hello'])]);
$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);
$this->assertEquals($arr, $data);
}
$client = m::mock(ClientInterface::class);
$client->shouldReceive('request')
->times(1)
->andReturn(new Response(500, [], 'Internal Server Error'));
$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' => 'abc'
])));
$provider->setHttpClient($client);
$provider->getAccessToken('authorization_code', [
'code' => 'hello-world'
]);
}
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']));
}
public function testCheckResponse()
{
$this->setExpectedException(AppleAccessDeniedException::class, 'invalid_client', 400);
public function testCheckResponse()
{
$this->expectException('\League\OAuth2\Client\Provider\Exception\AppleAccessDeniedException');
$this->expectExceptionMessage('invalid_client');
$provider = $this->getProvider();
$class = new \ReflectionClass($provider);
$method = $class->getMethod('checkResponse');
$method->setAccessible(true);
$class = new \ReflectionClass($this->provider);
$method = $class->getMethod('checkResponse');
$method->setAccessible(true);
$method->invokeArgs($provider, [new Response(400, []), [
'error' => 'invalid_client',
'code' => 400
]]);
}
$method->invokeArgs($this->provider, [new Response(400, []), [
'error' => 'invalid_client',
'code' => 400
]]);
}
public function testCreationOfResourceOwnerWithName()
{
$provider = $this->getProvider();
$class = new \ReflectionClass($provider);
$method = $class->getMethod('createResourceOwner');
$method->setAccessible(true);
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());
/** @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());
}
}
public function testCreationOfResourceOwnerWithoutName()
{
$provider = $this->getProvider();
$class = new \ReflectionClass($provider);
$method = $class->getMethod('createResourceOwner');
$method->setAccessible(true);
/** @var AppleResourceOwner $data */
$data = $method->invokeArgs($provider, [
[],
new AccessToken([
'access_token' => 'hello',
'email' => 'john@doe.de',
'resource_owner_id' => '123.4.567'
])
]);
$this->assertEquals('john@doe.de', $data->getEmail());
$this->assertNull($data->getLastName());
$this->assertNull($data->getFirstName());
}
public function testGetConfiguration()
{
$provider = m::mock(Apple::class)->makePartial();
$provider->shouldReceive('getLocalKey')->andReturn(m::mock(Key::class));
$this->assertInstanceOf(Configuration::class, $provider->getConfiguration());
}
}
+10 -1
View File
@@ -2,6 +2,7 @@
namespace League\OAuth2\Client\Test\Provider;
use Lcobucci\JWT\Configuration;
use League\OAuth2\Client\Provider\Apple;
/**
@@ -12,7 +13,15 @@ use League\OAuth2\Client\Provider\Apple;
class TestApple extends Apple
{
/**
* @return \Lcobucci\JWT\Signer\Key|null
* {@inheritDoc}
*/
public function getConfiguration()
{
return Configuration::forUnsecuredSigner();
}
/**
* {@inheritDoc}
*/
public function getLocalKey()
{
+49 -15
View File
@@ -2,18 +2,13 @@
namespace League\OAuth2\Client\Test\Token;
use Firebase\JWT\Key;
use League\OAuth2\Client\Token\AppleAccessToken;
use PHPUnit\Framework\TestCase;
use Mockery as m;
class AppleAccessTokenTest extends TestCase
{
public function tearDown()
{
m::close();
parent::tearDown();
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
@@ -22,18 +17,16 @@ class AppleAccessTokenTest extends TestCase
{
$externalJWTMock = m::mock('overload:Firebase\JWT\JWT');
$externalJWTMock->shouldReceive('decode')
->with('something', 'examplekey', ['RS256'])
->with('something', 'examplekey')
->once()
->andReturn([
'sub' => '123.abc.123'
'sub' => '123.abc.123',
'email_verified' => true,
'email' => 'john@doe.com',
'is_private_email' => true
]);
$externalJWKMock = m::mock('overload:Firebase\JWT\JWK');
$externalJWKMock->shouldReceive('parseKeySet')
->once()
->andReturn(['examplekey']);
$accessToken = new AppleAccessToken([
$accessToken = new AppleAccessToken(['examplekey'], [
'access_token' => 'access_token',
'token_type' => 'Bearer',
'expires_in' => 3600,
@@ -43,15 +36,56 @@ class AppleAccessTokenTest extends TestCase
$this->assertEquals('something', $accessToken->getIdToken());
$this->assertEquals('123.abc.123', $accessToken->getResourceOwnerId());
$this->assertEquals('access_token', $accessToken->getToken());
$this->assertEquals('john@doe.com', $accessToken->getEmail());
$this->assertTrue($accessToken->isPrivateEmail());
$this->assertTrue(true);
}
public function testCreateFailsBecauseNoIdTokenIsSet()
{
$this->expectException('\InvalidArgumentException');
$this->expectExceptionMessage('Required option not passed: "id_token"');
new AppleAccessToken(['examplekey'], [
'access_token' => 'access_token',
'token_type' => 'Bearer',
'expires_in' => 3600,
'refresh_token' => 'abc.0.def'
]);
}
public function testCreatingRefreshToken()
{
$refreshToken = new AppleAccessToken([
$refreshToken = new AppleAccessToken([], [
'access_token' => 'access_token',
'token_type' => 'Bearer',
'expires_in' => 3600
]);
$this->assertEquals('access_token', $refreshToken->getToken());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testCreatingAccessTokenFailsBecauseNoDecodingIsPossible()
{
$this->expectException('\Exception');
$this->expectExceptionMessage('Got no data within "id_token"!');
$externalJWTMock = m::mock('overload:Firebase\JWT\JWT');
$externalJWTMock->shouldReceive('decode')
->with('something', 'examplekey')
->once()
->andReturnNull();
new AppleAccessToken(['examplekey'], [
'access_token' => 'access_token',
'token_type' => 'Bearer',
'expires_in' => 3600,
'refresh_token' => 'abc.0.def',
'id_token' => 'something'
]);
}
}