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()->__toString(), true)); } return []; } /** * Get the string used to separate scopes. * * @return string */ protected function getScopeSeparator() { return ' '; } /** * Change response mode when scope requires it * * @param array $options * * @return array */ protected function getAuthorizationParameters(array $options) { $options = parent::getAuthorizationParameters($options); if (strpos($options['scope'], 'name') !== false || strpos($options['scope'], 'email') !== false) { $options['response_mode'] = 'form_post'; } return $options; } /** * @param AccessToken $token * * @return mixed */ protected function fetchResourceOwnerDetails(AccessToken $token) { return json_decode(array_key_exists('user', $_GET) ? $_GET['user'] : (array_key_exists('user', $_POST) ? $_POST['user'] : '[]'), true) ?: []; } /** * Get authorization url to begin OAuth flow * * @return string */ public function getBaseAuthorizationUrl() { return 'https://appleid.apple.com/auth/authorize'; } /** * Get access token url to retrieve token * * @return string */ public function getBaseAccessTokenUrl(array $params) { return 'https://appleid.apple.com/auth/token'; } /** * Get revoke token url to revoke token * * @return string */ public function getBaseRevokeTokenUrl(array $params) { return 'https://appleid.apple.com/auth/revoke'; } /** * Get provider url to fetch user details * * @param AccessToken $token * * @return string * @throws Exception */ public function getResourceOwnerDetailsUrl(AccessToken $token) { 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 */ protected function getDefaultScopes() { return $this->defaultScopes; } /** * Check a provider response for errors. * * @param ResponseInterface $response * @param array $data Parsed response data * @return void * @throws AppleAccessDeniedException */ protected function checkResponse(ResponseInterface $response, $data) { if ($response->getStatusCode() >= 400) { throw new AppleAccessDeniedException( array_key_exists('error', $data) ? $data['error'] : $response->getReasonPhrase(), array_key_exists('code', $data) ? $data['code'] : $response->getStatusCode(), $response ); } } /** * 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) { return new AppleResourceOwner( array_merge( ['sub' => $token->getResourceOwnerId()], $response, [ 'email' => isset($token->getValues()['email']) ? $token->getValues()['email'] : (isset($response['email']) ? $response['email'] : null), 'isPrivateEmail' => $token instanceof AppleAccessToken ? $token->isPrivateEmail() : null ] ), $token->getResourceOwnerId() ); } /** * {@inheritDoc} */ public function getAccessToken($grant, array $options = []) { $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 = $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()); $options += [ 'client_secret' => $token->toString() ]; return parent::getAccessToken($grant, $options); } /** * Revokes an access or refresh token using a specified token. * * @param string $token * @param string|null $tokenTypeHint * @return \PShowSsoScoped\Psr\Http\Message\RequestInterface */ public function revokeAccessToken($token, $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')); $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()); $params = [ 'client_id' => $this->clientId, 'client_secret' => $clientSecret->toString(), 'token' => $token ]; if ($tokenTypeHint !== null) { $params += [ 'token_type_hint' => $tokenTypeHint ]; } $method = $this->getAccessTokenMethod(); $url = $this->getBaseRevokeTokenUrl($params); if (property_exists($this, 'optionProvider')) { $options = $this->optionProvider->getAccessTokenOptions(self::METHOD_POST, $params); } else { $options = $this->getAccessTokenOptions($params); } $request = $this->getRequest($method, $url, $options); return $this->getParsedResponse($request); } /** * @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 InMemory::file($this->keyFilePath); } }