1
0
Fork 0
mirror of synced 2024-06-03 03:14:50 +12:00
appwrite/src/Appwrite/Auth/OAuth2/Apple.php

236 lines
6.2 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
*/
protected $user = [];
2020-01-18 17:45:54 +13:00
/**
* @var array
*/
2020-01-19 09:12:41 +13:00
protected $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
*/
protected $claims = [];
2019-10-05 03:29:16 +13:00
/**
* @return string
*/
public function getName(): string
{
return 'apple';
}
/**
* @return string
*/
public function getLoginURL(): string
{
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
*
* @return string
*/
public function getAccessToken(string $code): string
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
2019-10-05 03:29:16 +13:00
$accessToken = $this->request(
'POST',
'https://appleid.apple.com/auth/token',
$headers,
\http_build_query([
'grant_type' => 'authorization_code',
2020-01-18 17:53:41 +13:00
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getAppSecret(),
2020-01-18 17:53:41 +13:00
'redirect_uri' => $this->callback,
])
2019-10-05 03:29:16 +13:00
);
$accessToken = \json_decode($accessToken, true);
$this->claims = (isset($accessToken['id_token'])) ? \explode('.', $accessToken['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
if (isset($accessToken['access_token'])) {
return $accessToken['access_token'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
if (isset($this->claims['sub']) && !empty($this->claims['sub'])) {
return $this->claims['sub'];
2019-10-05 03:29:16 +13:00
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
if (isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
return $this->claims['email'];
2019-10-05 03:29:16 +13:00
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
if (isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
return $this->claims['email'];
2019-10-05 03:29:16 +13:00
}
return '';
}
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,
];
$claims = [
'iss' => $teamID,
'iat' => \time(),
'exp' => \time() + 86400*180,
'aud' => 'https://appleid.apple.com',
'sub' => $bundleID,
];
$pkey = \openssl_pkey_get_private($keyfile);
$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 '';
}
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
*/
protected function fromDER(string $der, int $partLength):string
2020-05-27 23:54:27 +12:00
{
$hex = \unpack('H*', $der)[1];
2020-05-27 23:54:27 +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');
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
return \pack('H*', $R.$S);
2020-05-27 23:54:27 +12:00
}
2019-10-05 03:29:16 +13:00
}