19 Commits

Author SHA1 Message Date
AUDUL
344c96aeaf Merge branch 'main' into update-code-php8 2026-02-25 14:11:20 +01:00
loic
db5789d4d2 fix private email 2026-02-25 14:09:00 +01:00
AUDUL
f4ee42b17c Update code php8 (#4)
* update code to php 8
* remove lcobucci/jwt
2026-02-19 15:48:58 +01:00
loic
6d17ab2e2d update code to php 8
remove lcobucci/jwt
2026-02-19 15:46:54 +01:00
loic
31ece38879 update code to php 8
remove lcobucci/jwt
2026-02-19 15:45:56 +01:00
loic
7a03f29cc5 update code to php 8
remove lcobucci/jwt
2026-02-19 15:45:47 +01:00
loic
23a643494f update code to php 8
remove lcobucci/jwt
2026-02-19 15:41:16 +01:00
loic
e645a8afa2 update code to php 8
remove lcobucci/jwt
2026-02-19 15:40:07 +01:00
loic
fdcae826ed update code to php 8
remove lcobucci/jwt
2026-02-19 15:37:55 +01:00
loic
50fe6079e3 update code to php 8
remove lcobucci/jwt
2026-02-19 15:33:03 +01:00
loic
4b913d776d update code to php 8
remove lcobucci/jwt
2026-02-19 15:32:25 +01:00
loic
6952cbec6d update code to php 8
remove lcobucci/jwt
2026-02-19 15:30:16 +01:00
loic
ba795803bb update code to php 8
remove lcobucci/jwt
2026-02-19 15:29:43 +01:00
loic
a72cfeba83 update code to php 8
remove lcobucci/jwt
2026-02-19 15:29:01 +01:00
loic
831c8e0e25 update code to php 8
remove lcobucci/jwt
2026-02-19 15:27:56 +01:00
loic
942a875dc6 update code to php 8
remove lcobucci/jwt
2026-02-19 15:20:18 +01:00
AUDUL
e6626c4575 change naming (#2) 2026-02-19 11:58:15 +01:00
AUDUL
3f0f659741 fix firebase/php-jwt version (#1)
* fix firebase/php-jwt version

* add test for php8.5

* remove compatibility for some PHP versions
2026-02-19 11:48:28 +01:00
Matthew Grasmick
badae8f295 Add error_description to exception message (#57)
* Include error_description in exception message
2024-09-17 19:07:23 +02:00
18 changed files with 222 additions and 432 deletions

View File

@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
php-version: '8.1'
coverage: none
ini-values: memory_limit=-1
tools: composer:v2
@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
php-version: '8.1'
coverage: none
ini-values: memory_limit=-1
tools: composer:v2
@@ -48,16 +48,10 @@ jobs:
fail-fast: false
matrix:
php-version:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
- '8.2'
- '8.4'
- '8.5'
dependencies:
- lowest
- highest
@@ -81,6 +75,4 @@ jobs:
dependency-versions: '${{ matrix.dependencies }}'
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@v4
run: ./vendor/bin/phpunit

View File

@@ -1,7 +1,7 @@
# Changelog
All Notable changes to `oauth2-apple` will be documented in this file
## 0.4.0 - 202X-XX-XX
## 0.5.0 - 202X-XX-XX
### Added
- Nothing
@@ -18,6 +18,15 @@ All Notable changes to `oauth2-apple` will be documented in this file
### Security
- Nothing
## 0.4.0 - 2026-02-19
### Added
- Allow firebase/php-jwt ^7.0 [#1](https://github.com/code-rhapsodie/oauth2-apple/pull/1)
- Tests for php 8.5 [#1](https://github.com/code-rhapsodie/oauth2-apple/pull/1)
### Removed
- Compatibility with PHP 5.6 - 7.0 - 7.1 - 7.2 - 7.3 - 7.4
## 0.3.0 - 2024-05-18
### Added

View File

@@ -2,7 +2,7 @@
Contributions are **welcome** and will be fully **credited**.
We accept contributions via Pull Requests on [Github](https://github.com/patrickbussmann/oauth2-apple).
We accept contributions via Pull Requests on [Github](https://github.com/code-rhapsodie/oauth2-apple).
## Pull Requests

View File

@@ -1,14 +1,12 @@
# 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/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)
[![Latest Version](https://img.shields.io/github/release/code-rhapsodie/oauth2-apple.svg?style=flat-square)](https://github.com/code-rhapsodie/oauth2-apple/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Total Downloads](https://img.shields.io/packagist/dt/code-rhapsodie/oauth2-apple.svg?style=flat-square)](https://packagist.org/packages/code-rhapsodie/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).
This package is a fork of [patrickbussmann/oauth2-apple](https://github.com/patrickbussmann/oauth2-apple)
## Before You Begin
Here you can find the official Apple documentation:
@@ -23,7 +21,7 @@ Maybe Apple changes this sometime.
To install, use composer:
```
composer require patrickbussmann/oauth2-apple
composer require code-rhapsodie/oauth2-apple
```
## Usage
@@ -158,15 +156,15 @@ $ ./vendor/bin/phpunit
## Contributing
Please see [CONTRIBUTING](https://github.com/patrickbussmann/oauth2-apple/blob/main/CONTRIBUTING.md) for details.
Please see [CONTRIBUTING](https://github.com/code-rhapsodie/oauth2-apple/blob/main/CONTRIBUTING.md) for details.
## Credits
- [All Contributors](https://github.com/patrickbussmann/oauth2-apple/contributors)
- [All Contributors](https://github.com/code-rhapsodie/oauth2-apple/contributors)
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/main/LICENSE) for more information.
The MIT License (MIT). Please see [License File](https://github.com/code-rhapsodie/oauth2-apple/blob/main/LICENSE) for more information.

View File

@@ -1,29 +0,0 @@
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

View File

@@ -1,5 +1,5 @@
{
"name": "patrickbussmann/oauth2-apple",
"name": "code-rhapsodie/oauth2-apple",
"description": "Sign in with Apple OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"license": "MIT",
"authors": [
@@ -9,6 +9,9 @@
"homepage": "https://github.com/patrickbussmann"
}
],
"replace": {
"patrickbussmann/oauth2-apple": "*"
},
"keywords": [
"oauth",
"oauth2",
@@ -19,16 +22,16 @@
"sign-in-with-apple"
],
"require": {
"php": ">=8.1",
"ext-json": "*",
"league/oauth2-client": "^2.0",
"firebase/php-jwt": "^5.2 || ^6.0",
"lcobucci/jwt": "^3.4 || ^4.0 || ^5.0"
"firebase/php-jwt": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.0 || ^9.3",
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
"mockery/mockery": "^1.3",
"php-parallel-lint/php-parallel-lint": "^1.3",
"squizlabs/php_codesniffer": "^2.3 || ^3.0",
"squizlabs/php_codesniffer": "^3.0 || ^4.0",
"composer/semver": "^3.0"
},
"autoload": {

View File

@@ -1,23 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<exclude>./test/ext/</exclude>
</testsuite>
</testsuites>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false"
convertDeprecationsToExceptions="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true"
convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false"
stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd">
<logging/>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./test/</directory>
<exclude>./test/src/Provider/TestApple.php</exclude>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -1,20 +1,21 @@
<?php
declare(strict_types=1);
namespace League\OAuth2\Client\Provider;
use Exception;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use InvalidArgumentException;
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;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Token\AccessTokenInterface;
use League\OAuth2\Client\Token\AppleAccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class Apple extends AbstractProvider
@@ -24,30 +25,27 @@ class Apple extends AbstractProvider
/**
* Default scopes
*
* @var array
* @var array<string>
*/
public $defaultScopes = ['name', 'email'];
public array $defaultScopes = ['name', 'email'];
/**
* @var string the team id
* the team id
*/
protected $teamId;
protected string $teamId;
/**
* @var string the key file id
* the key file id
*/
protected $keyFileId;
protected string $keyFileId;
/**
* @var string the key file path
* the key file path
*/
protected $keyFilePath;
protected string $keyFilePath;
/**
* Constructs Apple's OAuth 2.0 service provider.
*
* @param array $options
* @param array $collaborators
* @inheritDoc
*/
public function __construct(array $options = [], array $collaborators = [])
{
@@ -67,28 +65,21 @@ class Apple extends AbstractProvider
}
/**
* Creates an access token from a response.
*
* The grant that was used to fetch the response can be used to provide
* additional context.
*
* @param array $response
* @param AbstractGrant $grant
* @return AccessTokenInterface
* @inheritDoc
*/
protected function createAccessToken(array $response, AbstractGrant $grant)
protected function createAccessToken(array $response, AbstractGrant $grant): AccessTokenInterface
{
return new AppleAccessToken($this->getAppleKeys(), $response);
}
/**
* @return string[] Apple's JSON Web Keys
* @return array<string, Key> Apple's JSON Web Keys
*/
private function getAppleKeys()
private function getAppleKeys(): array
{
$response = $this->httpClient->request('GET', 'https://appleid.apple.com/auth/keys');
if ($response && $response->getStatusCode() === 200) {
if ($response->getStatusCode() === 200) {
return JWK::parseKeySet(json_decode($response->getBody()->__toString(), true));
}
@@ -96,35 +87,27 @@ class Apple extends AbstractProvider
}
/**
* Get the string used to separate scopes.
*
* @return string
* @inheritDoc
*/
protected function getScopeSeparator()
protected function getScopeSeparator(): string
{
return ' ';
}
/**
* Change response mode when scope requires it
*
* @param array $options
*
* @return array
* @inheritDoc
*/
protected function getAuthorizationParameters(array $options)
protected function getAuthorizationParameters(array $options): array
{
$options = parent::getAuthorizationParameters($options);
if (strpos($options['scope'], 'name') !== false || strpos($options['scope'], 'email') !== false) {
if (str_contains($options['scope'], 'name') || str_contains($options['scope'], 'email')) {
$options['response_mode'] = 'form_post';
}
return $options;
}
/**
* @param AccessToken $token
*
* @return mixed
* @inheritDoc
*/
protected function fetchResourceOwnerDetails(AccessToken $token)
{
@@ -133,31 +116,25 @@ class Apple extends AbstractProvider
}
/**
* Get authorization url to begin OAuth flow
*
* @return string
* @inheritDoc
*/
public function getBaseAuthorizationUrl()
public function getBaseAuthorizationUrl(): string
{
return 'https://appleid.apple.com/auth/authorize';
}
/**
* Get access token url to retrieve token
*
* @return string
* @inheritDoc
*/
public function getBaseAccessTokenUrl(array $params)
public function getBaseAccessTokenUrl(array $params): string
{
return 'https://appleid.apple.com/auth/token';
}
/**
* Get revoke token url to revoke token
*
* @return string
*/
public function getBaseRevokeTokenUrl(array $params)
public function getBaseRevokeTokenUrl(): string
{
return 'https://appleid.apple.com/auth/revoke';
}
@@ -165,25 +142,17 @@ class Apple extends AbstractProvider
/**
* Get provider url to fetch user details
*
* @param AccessToken $token
*
* @return string
* @throws Exception
* @inheritDoc
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
public function getResourceOwnerDetailsUrl(AccessToken $token): string
{
throw new Exception('No Apple ID REST API available yet!');
}
/**
* Get the default scopes used by this provider.
*
* This should not be a complete list of all scopes, but the minimum
* required for the provider user interface!
*
* @return array
* @inheritDoc
*/
protected function getDefaultScopes()
protected function getDefaultScopes(): array
{
return $this->defaultScopes;
}
@@ -191,16 +160,21 @@ class Apple extends AbstractProvider
/**
* Check a provider response for errors.
*
* @param ResponseInterface $response
* @param array $data Parsed response data
* @return void
* @inheritDoc
* @throws AppleAccessDeniedException
*/
protected function checkResponse(ResponseInterface $response, $data)
protected function checkResponse(ResponseInterface $response, $data): void
{
if ($response->getStatusCode() >= 400) {
$message = $response->getReasonPhrase();
if (array_key_exists('error', $data)) {
$message = $data['error'];
}
if (array_key_exists('error_description', $data)) {
$message .= ': ' . $data['error_description'];
}
throw new AppleAccessDeniedException(
array_key_exists('error', $data) ? $data['error'] : $response->getReasonPhrase(),
$message,
array_key_exists('code', $data) ? $data['code'] : $response->getStatusCode(),
$response
);
@@ -209,20 +183,15 @@ class Apple extends AbstractProvider
/**
* Generate a user object from a successful user details request.
*
* @param array $response
* @param AccessToken $token
* @return AppleResourceOwner
*/
protected function createResourceOwner(array $response, AccessToken $token)
protected function createResourceOwner(array $response, AccessToken $token): AppleResourceOwner
{
return new AppleResourceOwner(
array_merge(
['sub' => $token->getResourceOwnerId()],
$response,
[
'email' => isset($token->getValues()['email'])
? $token->getValues()['email'] : (isset($response['email']) ? $response['email'] : null),
'email' => $token->getValues()['email'] ?? ($response['email'] ?? null),
'isPrivateEmail' => $token instanceof AppleAccessToken ? $token->isPrivateEmail() : null
]
),
@@ -233,26 +202,31 @@ class Apple extends AbstractProvider
/**
* {@inheritDoc}
*/
public function getAccessToken($grant, array $options = [])
public function getAccessToken($grant, array $options = []): AccessTokenInterface
{
$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'));
$time = time();
$token = $configuration->builder()
->issuedBy($this->teamId)
->permittedFor('https://appleid.apple.com')
->issuedAt($time)
->expiresAt($expiresAt)
->relatedTo($this->clientId)
->withHeader('alg', 'ES256')
->withHeader('kid', $this->keyFileId)
->getToken($configuration->signer(), $configuration->signingKey());
$payload = [
'iss' => $this->teamId,
'iat' => $time,
'exp' => $time + 3600,
'aud' => 'https://appleid.apple.com',
'sub' => $this->clientId,
];
$jwt = JWT::encode(
$payload,
$this->getLocalKey(),
'ES256',
$this->keyFileId,
[
'kid' => $this->keyFileId,
'alg' => 'ES256',
]
);
$options += [
'client_secret' => $token->toString()
'client_secret' => $jwt
];
return parent::getAccessToken($grant, $options);
@@ -260,42 +234,44 @@ class Apple extends AbstractProvider
/**
* Revokes an access or refresh token using a specified token.
*
* @param string $token
* @param string|null $tokenTypeHint
* @return \Psr\Http\Message\RequestInterface
*/
public function revokeAccessToken($token, $tokenTypeHint = null)
public function revokeAccessToken(string $token, ?string $tokenTypeHint = null)
{
$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'));
$time = time();
$clientSecret = $configuration->builder()
->issuedBy($this->teamId)
->permittedFor('https://appleid.apple.com')
->issuedAt($time)
->expiresAt($expiresAt)
->relatedTo($this->clientId)
->withHeader('alg', 'ES256')
->withHeader('kid', $this->keyFileId)
->getToken($configuration->signer(), $configuration->signingKey());
$payload = [
'iss' => $this->teamId,
'iat' => $time,
'exp' => $time + 3600,
'aud' => 'https://appleid.apple.com',
'sub' => $this->clientId,
];
$clientSecret = JWT::encode(
$payload,
$this->getLocalKey(),
'ES256',
$this->keyFileId,
[
'kid' => $this->keyFileId,
'alg' => 'ES256',
]
);
$params = [
'client_id' => $this->clientId,
'client_secret' => $clientSecret->toString(),
'token' => $token
'client_id' => $this->clientId,
'client_secret' => $clientSecret,
'token' => $token
];
if ($tokenTypeHint !== null) {
$params += [
'token_type_hint' => $tokenTypeHint
];
}
$method = $this->getAccessTokenMethod();
$url = $this->getBaseRevokeTokenUrl($params);
$method = $this->getAccessTokenMethod();
$url = $this->getBaseRevokeTokenUrl();
if (property_exists($this, 'optionProvider')) {
$options = $this->optionProvider->getAccessTokenOptions(self::METHOD_POST, $params);
} else {
@@ -306,29 +282,12 @@ class Apple extends AbstractProvider
return $this->getParsedResponse($request);
}
/**
* @return Configuration
*/
public function getConfiguration()
public function getLocalKey(): string
{
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()
);
if (!file_exists($this->keyFilePath)) {
throw new \InvalidArgumentException('Could not read key file');
}
}
/**
* @return Key
*/
public function getLocalKey()
{
return InMemory::file($this->keyFilePath);
return file_get_contents($this->keyFilePath);
}
}

View File

@@ -1,106 +1,72 @@
<?php namespace League\OAuth2\Client\Provider;
<?php
declare(strict_types=1);
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
/**
* @property array $response
* @property string $uid
*/
class AppleResourceOwner extends GenericResourceOwner
{
use ArrayAccessorTrait;
/**
* Raw response
*
* @var array
*/
protected $response = [];
/**
* @var string|null
*/
private $email;
private ?string $email;
/**
* @var boolean true when its private relay from apple else the user mail address
* true when it's a private relay from apple else the user mail address
*/
private $isPrivateEmail;
private bool $isPrivateEmail;
/**
* Gets resource owner attribute by key. The key supports dot notation.
*
* @param string $key
*
* @return mixed
*/
public function getAttribute($key)
public function getAttribute(string $key): mixed
{
return $this->getValueByKey($this->response, (string) $key);
return $this->getValueByKey($this->response, $key);
}
/**
* Get user first name
*
* @return string|null
*/
public function getFirstName()
public function getFirstName(): ?string
{
$name = $this->getAttribute('name');
if (isset($name)) {
if (is_array($name)) {
return $name['firstName'];
}
return null;
}
/**
* Get user user id
*
* @return string|null
* @inheritDoc
*/
public function getId()
{
return $this->resourceOwnerId;
}
/**
* Get user last name
*
* @return string|null
*/
public function getLastName()
public function getLastName(): ?string
{
$name = $this->getAttribute('name');
if (isset($name)) {
if (is_array($name)) {
return $name['lastName'];
}
return null;
}
/**
* Get user email, if available
*
* @return string|null
*/
public function getEmail()
public function getEmail(): ?string
{
return $this->getAttribute('email');
}
/**
* @return bool
*/
public function isPrivateEmail()
public function isPrivateEmail(): bool
{
return (bool) $this->getAttribute('isPrivateEmail');
}
/**
* Return all of the owner details available as an array.
*
* @return array
* @inheritDoc
*/
public function toArray()
public function toArray(): array
{
return $this->response;
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace League\OAuth2\Client\Provider\Exception;
class AppleAccessDeniedException extends IdentityProviderException

View File

@@ -8,24 +8,13 @@ use InvalidArgumentException;
class AppleAccessToken extends AccessToken
{
/**
* @var string
*/
protected $idToken;
protected string $idToken;
protected string $email;
protected ?bool $isPrivateEmail = null;
/**
* @var string
*/
protected $email;
/**
* @var boolean
*/
protected $isPrivateEmail;
/**
* 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.
@@ -46,18 +35,9 @@ class AppleAccessToken extends AccessToken
try {
try {
$decoded = JWT::decode($options['id_token'], $key);
} catch (\UnexpectedValueException $e) {
$decodeMethodReflection = new \ReflectionMethod(JWT::class, 'decode');
$decodeMethodParameters = $decodeMethodReflection->getParameters();
// Backwards compatibility for firebase/php-jwt >=5.2.0 <=5.5.1 supported by PHP 5.6
if (array_key_exists(2, $decodeMethodParameters) &&
'allowed_algs' === $decodeMethodParameters[2]->getName()
) {
$decoded = JWT::decode($options['id_token'], $key, ['RS256']);
} else {
$headers = (object) ['alg' => 'RS256'];
$decoded = JWT::decode($options['id_token'], $key, $headers);
}
} catch (\UnexpectedValueException) {
$headers = (object) ['alg' => 'RS256'];
$decoded = JWT::decode($options['id_token'], $key, $headers);
}
break;
} catch (\Exception $exception) {
@@ -66,9 +46,11 @@ class AppleAccessToken extends AccessToken
}
}
}
if (null === $decoded) {
throw new \Exception('Got no data within "id_token"!');
}
$payload = json_decode(json_encode($decoded), true);
$options['resource_owner_id'] = $payload['sub'];
@@ -85,34 +67,25 @@ class AppleAccessToken extends AccessToken
parent::__construct($options);
if (isset($options['id_token'])) {
$this->idToken = $options['id_token'];
$this->idToken = (string)$options['id_token'];
}
if (isset($options['email'])) {
$this->email = $options['email'];
$this->email = (string)$options['email'];
}
}
/**
* @return string
*/
public function getIdToken()
public function getIdToken(): string
{
return $this->idToken;
}
/**
* @return string
*/
public function getEmail()
public function getEmail(): string
{
return $this->email;
}
/**
* @return boolean
*/
public function isPrivateEmail()
public function isPrivateEmail(): ?bool
{
return $this->isPrivateEmail;
}

View File

@@ -1,29 +0,0 @@
<?php
namespace League\OAuth2\Client\Test;
use Lcobucci\JWT\Signature;
use Lcobucci\JWT\Signer;
final class KeyDumpSigner implements Signer
{
public function getAlgorithmId()
{
return 'keydump';
}
public function modifyHeader(array &$headers)
{
$headers['alg'] = $this->getAlgorithmId();
}
public function verify($expected, $payload, $key)
{
return $expected === $key->contents();
}
public function sign($payload, $key)
{
return new Signature($key->contents());
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace League\OAuth2\Client\Test;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key;
final class KeyDumpSigner implements Signer
{
public function algorithmId(): string
{
return 'keydump';
}
public function sign(string $payload, Key $key): string
{
return $key->contents();
}
public function verify(string $expected, string $payload, Key $key): bool
{
return $expected === $key->contents();
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace League\OAuth2\Client\Test;
use Composer\InstalledVersions;
use Composer\Semver\VersionParser;
if (!InstalledVersions::satisfies(new VersionParser(), 'lcobucci/jwt', '^1 || ^2 || ^3')) {
require_once __DIR__ . '/../ext/KeyDumpSigner8.php';
} else {
require_once __DIR__ . '/../ext/KeyDumpSigner5.php';
}

View File

@@ -2,19 +2,15 @@
namespace League\OAuth2\Client\Test\Provider;
use Firebase\JWT\JWT;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Response;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use League\OAuth2\Client\Provider\Apple;
use League\OAuth2\Client\Provider\AppleResourceOwner;
use League\OAuth2\Client\Test\KeyDumpSigner;
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;
use PHPUnit\Framework\TestCase;
class AppleTest extends TestCase
{
@@ -132,23 +128,27 @@ class AppleTest extends TestCase
]);
$provider = m::mock($provider);
$time = time();
$configuration = Configuration::forSymmetricSigner(
new KeyDumpSigner(),
Key\InMemory::plainText('private')
$payload = [
'iss' => 'test-team-id',
'iat' => $time,
'exp' => $time + 3600,
'aud' => 'https://appleid.apple.com',
'sub' => 'test-client',
];
$jwt = JWT::encode(
$payload,
'file://' . __DIR__ . '/../private_key.pem',
'ES256',
'test',
[
'kid' => 'test',
'alg' => 'ES256',
]
);
$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')
@@ -161,7 +161,7 @@ class AppleTest extends TestCase
'token_type' => 'Bearer',
'expires_in' => 3600,
'refresh_token' => 'r4a6e8b9c50104b78bc86b0d2649353fa.0.mrwxq.54joUj40j0cpuMANRtRjfg',
'id_token' => $token->toString()
'id_token' => $jwt
])));
$provider->setHttpClient($client);
@@ -385,12 +385,4 @@ class AppleTest extends TestCase
$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());
}
}

View File

@@ -2,10 +2,7 @@
namespace League\OAuth2\Client\Test\Provider;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use League\OAuth2\Client\Provider\Apple;
use League\OAuth2\Client\Test\KeyDumpSigner;
/**
* Class TestApple
@@ -15,21 +12,10 @@ use League\OAuth2\Client\Test\KeyDumpSigner;
class TestApple extends Apple
{
/**
* {@inheritDoc}
* @inheritDoc
*/
public function getConfiguration()
public function getLocalKey(): string
{
return Configuration::forSymmetricSigner(
new KeyDumpSigner(),
InMemory::plainText('private')
);
}
/**
* {@inheritDoc}
*/
public function getLocalKey()
{
return null;
return 'file://' . __DIR__ . '/../private_key.pem';
}
}

View File

@@ -4,15 +4,15 @@ namespace League\OAuth2\Client\Test\Token;
use Firebase\JWT\Key;
use League\OAuth2\Client\Token\AppleAccessToken;
use PHPUnit\Framework\Attributes\PreserveGlobalState;
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
use PHPUnit\Framework\TestCase;
use Mockery as m;
class AppleAccessTokenTest extends TestCase
{
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
#[PreserveGlobalState(false)]
#[RunInSeparateProcess]
public function testCreatingAccessToken()
{
$externalJWTMock = m::mock('overload:Firebase\JWT\JWT');
@@ -65,10 +65,8 @@ class AppleAccessTokenTest extends TestCase
$this->assertEquals('access_token', $refreshToken->getToken());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
#[PreserveGlobalState(false)]
#[RunInSeparateProcess]
public function testCreatingAccessTokenFailsBecauseNoDecodingIsPossible()
{
$this->expectException('\Exception');

16
test/src/private_key.pem Normal file
View File

@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJ/7C8IWBvfYj01Q
+4mldM50B8qPVHIoDEyxNsNXLcYqe3GkkHklKFRKidaxOxx6MBAQz9l2bgfh6Stt
NWkeJ3I8oZ73zju/twjqhQhzveST+xwpUP935xgkbYmZVEM9JRj6wQ52pYA2sdkx
/5wqvF7oJ7dlsGIga6PbV0/i59kdAgMBAAECgYBuCUmkHGx8irq+LkZk/aXi3tIB
FCa8Qil7kqSdJVh5pfy0RMGOYe1kVMSMI+kJhE2Mr1OXOqshxtQPJ5WGENSF2yBq
BRbFn/Xeweh1MpXT7no1dhaS0Rfn01ScvgDMc6NkuiTwshSxOhnuaFVVxYPJKFP4
aqY6Dp8vSwrISlnPbQJBAMxHtPmIU9uuDq7fYAl+5WK2S4gY5IPl2y0xJIwoN6+q
hc+3ZkMIDrcwzzqxcV82jBU7CnnV/CmZ+r99e4jmbAMCQQDIfBicV7YuuBLXlDGe
LSCSqP1+V9o2ZVtR0PM4Z4346xJN/rJ4LWz2KexY//nKUMsVIyJuYp1pJpN21K6p
8exfAkEAia9bH0TvoIV0iBEunbfVy+6qghSlEPGABLm2tHD294OrpREr78oigP54
7kpi65XMXRLqQKwlxbRu+VoORXtpGQJABxTzHZqvkcjoyXqvogHAE84qXismRyOf
bS1vWf+2cSOEmwKzNTGNlsh2U9J+9VmTQuTh03piSxOUw+7RWKl2CwJAUdoH4is3
qRdiRtRcox8QmIgtFFN5r7ZcFfJO4wjrjM5KbB3mdYoxu5jE8bkR//t5Unz5mqqu
1uo+Dq/SJyXi7w==
-----END PRIVATE KEY-----