1
0
Fork 0
mirror of synced 2024-06-29 11:40:45 +12:00
appwrite/src/Appwrite/Auth/OAuth2/Apple.php

276 lines
7.3 KiB
PHP
Raw Normal View History

2019-10-05 03:29:16 +13:00
<?php
namespace Appwrite\Auth\OAuth2;
2019-10-05 03:29:16 +13:00
use Appwrite\Auth\OAuth2;
use Exception;
2019-10-05 03:29:16 +13:00
// Reference Material
// https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
2020-02-17 00:41:03 +13:00
class Apple extends OAuth2
2019-10-05 03:29:16 +13:00
{
/**
* @var array
*/
2022-05-13 03:56:20 +12:00
protected array $user = [];
2022-02-01 23:42:11 +13:00
/**
* @var array
*/
2022-05-13 03:56:20 +12:00
protected array $tokens = [];
2019-10-05 03:29:16 +13:00
2020-01-18 17:45:54 +13:00
/**
* @var array
*/
2022-05-13 03:56:20 +12:00
protected array $scopes = [
2020-06-25 09:05:16 +12:00
"name",
2020-01-19 09:12:41 +13:00
"email"
];
2020-01-18 17:45:54 +13:00
/**
* @var array
*/
2022-05-13 03:56:20 +12:00
protected array $claims = [];
2019-10-05 03:29:16 +13:00
/**
* @return string
*/
public function getName(): string
{
return 'apple';
}
2022-05-13 03:56:20 +12:00
2019-10-05 03:29:16 +13:00
/**
* @return string
*/
public function getLoginURL(): string
{
2022-05-13 03:56:20 +12:00
return 'https://appleid.apple.com/auth/authorize?' . \http_build_query([
2020-01-18 17:53:41 +13:00
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
2020-01-18 17:45:54 +13:00
'response_type' => 'code',
'response_mode' => 'form_post',
'scope' => \implode(' ', $this->getScopes())
2020-01-18 17:45:54 +13:00
]);
2019-10-05 03:29:16 +13:00
}
/**
* @param string $code
*
2022-02-01 09:20:17 +13:00
* @return array
2019-10-05 03:29:16 +13:00
*/
2022-02-02 05:47:19 +13:00
protected function getTokens(string $code): array
2019-10-05 03:29:16 +13:00
{
2022-05-13 03:56:20 +12:00
if (empty($this->tokens)) {
2022-02-01 23:42:11 +13:00
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://appleid.apple.com/auth/token',
$headers,
\http_build_query([
'grant_type' => 'authorization_code',
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getAppSecret(),
'redirect_uri' => $this->callback,
])
), true);
2022-02-01 09:39:14 +13:00
2022-02-01 23:42:11 +13:00
$this->claims = (isset($this->tokens['id_token'])) ? \explode('.', $this->tokens['id_token']) : [0 => '', 1 => ''];
$this->claims = (isset($this->claims[1])) ? \json_decode(\base64_decode($this->claims[1]), true) : [];
}
2019-10-05 03:29:16 +13:00
2022-02-01 23:42:11 +13:00
return $this->tokens;
2019-10-05 03:29:16 +13:00
}
2022-02-02 04:54:20 +13:00
/**
* @param string $refreshToken
*
* @return array
*/
2022-05-13 03:56:20 +12:00
public function refreshTokens(string $refreshToken): array
2022-02-02 04:54:20 +13:00
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://appleid.apple.com/auth/token',
$headers,
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getAppSecret(),
])
), true);
2022-05-13 03:56:20 +12:00
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
$this->claims = (isset($this->tokens['id_token'])) ? \explode('.', $this->tokens['id_token']) : [0 => '', 1 => ''];
$this->claims = (isset($this->claims[1])) ? \json_decode(\base64_decode($this->claims[1]), true) : [];
2022-02-02 04:54:20 +13:00
return $this->tokens;
}
2019-10-05 03:29:16 +13:00
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
2022-05-13 03:56:20 +12:00
return $this->claims['sub'] ?? '';
2019-10-05 03:29:16 +13:00
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
2022-05-13 03:56:20 +12:00
return $this->claims['email'] ?? '';
2019-10-05 03:29:16 +13:00
}
/**
* Check if the OAuth email is verified
2022-05-24 02:54:50 +12:00
*
2022-04-29 22:08:22 +12:00
* @link https://developer.apple.com/forums/thread/121411
2022-05-24 02:54:50 +12:00
*
2022-05-13 03:56:20 +12:00
* @param string $accessToken
2022-05-24 02:54:50 +12:00
*
* @return bool
*/
2022-04-28 08:27:21 +12:00
public function isEmailVerified(string $accessToken): bool
{
2022-05-13 03:56:20 +12:00
if ($this->claims['email_verified'] ?? false) {
2022-04-29 22:08:22 +12:00
return true;
}
return false;
}
2019-10-05 03:29:16 +13:00
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
2022-05-13 03:56:20 +12:00
if (
isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
2022-05-13 03:56:20 +12:00
$this->claims['email_verified'] === 'true'
) {
return $this->claims['email'];
2019-10-05 03:29:16 +13:00
}
return '';
}
2022-05-13 03:56:20 +12:00
protected function getAppSecret(): string
{
try {
$secret = \json_decode($this->appSecret, true);
} catch (\Throwable $th) {
throw new Exception('Invalid secret');
}
$keyfile = (isset($secret['p8'])) ? $secret['p8'] : ''; // Your p8 Key file
$keyID = (isset($secret['keyID'])) ? $secret['keyID'] : ''; // Your Key ID
$teamID = (isset($secret['teamID'])) ? $secret['teamID'] : ''; // Your Team ID (see Developer Portal)
$bundleID = $this->appID; // Your Bundle ID
$headers = [
'alg' => 'ES256',
'kid' => $keyID,
];
2022-05-13 03:56:20 +12:00
$claims = [
'iss' => $teamID,
'iat' => \time(),
2022-05-13 03:56:20 +12:00
'exp' => \time() + 86400 * 180,
'aud' => 'https://appleid.apple.com',
'sub' => $bundleID,
];
$pkey = \openssl_pkey_get_private($keyfile);
2022-05-13 03:56:20 +12:00
$payload = $this->encode(\json_encode($headers)) . '.' . $this->encode(\json_encode($claims));
$signature = '';
$success = \openssl_sign($payload, $signature, $pkey, OPENSSL_ALGO_SHA256);
2020-06-25 09:02:27 +12:00
if (!$success) {
return '';
}
2022-05-13 03:56:20 +12:00
return $payload . '.' . $this->encode($this->fromDER($signature, 64));
}
2019-10-05 03:29:16 +13:00
/**
* @param string $data
*
* @return string
*/
protected function encode($data): string
{
return \str_replace(['+', '/', '='], ['-', '_', ''], \base64_encode($data));
}
/**
* @param string $data
2019-10-05 03:29:16 +13:00
*/
protected function retrievePositiveInteger(string $data): string
2019-10-05 03:29:16 +13:00
{
while ('00' === \mb_substr($data, 0, 2, '8bit') && \mb_substr($data, 2, 2, '8bit') > '7f') {
$data = \mb_substr($data, 2, null, '8bit');
2019-10-05 03:29:16 +13:00
}
return $data;
2019-10-05 03:29:16 +13:00
}
2020-05-27 23:54:27 +12:00
/**
* @param string $der
* @param int $partLength
*/
2022-05-13 03:56:20 +12:00
protected function fromDER(string $der, int $partLength): string
2020-05-27 23:54:27 +12:00
{
$hex = \unpack('H*', $der)[1];
2022-05-13 03:56:20 +12:00
if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new \RuntimeException();
2020-05-27 23:54:27 +12:00
}
if ('81' === \mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
$hex = \mb_substr($hex, 6, null, '8bit');
2020-06-25 09:02:27 +12:00
} else {
$hex = \mb_substr($hex, 4, null, '8bit');
}
if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
2020-05-27 23:54:27 +12:00
$Rl = \hexdec(\mb_substr($hex, 2, 2, '8bit'));
$R = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Rl * 2, '8bit'));
$R = \str_pad($R, $partLength, '0', STR_PAD_LEFT);
2020-05-27 23:54:27 +12:00
$hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit');
2022-05-13 03:56:20 +12:00
if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
2020-05-27 23:54:27 +12:00
$Sl = \hexdec(\mb_substr($hex, 2, 2, '8bit'));
$S = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = \str_pad($S, $partLength, '0', STR_PAD_LEFT);
2020-05-27 23:54:27 +12:00
2022-05-13 03:56:20 +12:00
return \pack('H*', $R . $S);
2020-05-27 23:54:27 +12:00
}
2019-10-05 03:29:16 +13:00
}