From 2e0eebff80000b3f558dc2c3a1e8d96693728b4d Mon Sep 17 00:00:00 2001 From: Daniel Hood Date: Tue, 4 Jan 2022 15:33:50 -0600 Subject: [PATCH] Adding refresh token functionality --- src/AccessToken.php | 123 ++++++++++++++++++++++++++++++++++++++++++-- src/Client.php | 42 ++++++++++++++- src/Exception.php | 19 +++---- src/Scope.php | 47 +++-------------- 4 files changed, 175 insertions(+), 56 deletions(-) diff --git a/src/AccessToken.php b/src/AccessToken.php index 2d0497d..2a61cd6 100644 --- a/src/AccessToken.php +++ b/src/AccessToken.php @@ -44,16 +44,32 @@ class AccessToken implements \JsonSerializable */ protected $expiresAt; + /** + * + * @var string + */ + protected $refreshToken; + + /** + * @var int + */ + protected $refreshTokenExpiresAt; + /** * AccessToken constructor. * * @param string $token * @param int $expiresAt + * @param string $refreshToken + * @param int $refreshTokenExpiresAt */ - public function __construct($token = '', $expiresAt = 0) + public function __construct($token = '', $expiresAt = 0, $refreshToken = '', $refreshTokenExpiresAt = 0) { $this->setToken($token); $this->setExpiresAt($expiresAt); + + $this->setRefreshToken($refreshToken); + $this->setRefreshTokenExpiresAt($refreshTokenExpiresAt); } /** @@ -66,6 +82,16 @@ public function getToken() return $this->token; } + /** + * Get RefreshToken string + * + * @return string + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + /** * Set token string * @@ -79,6 +105,19 @@ public function setToken($token) return $this; } + /** + * Set RefreshToken string + * + * @param string $token + * + * @return AccessToken + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + return $this; + } + /** * The number of seconds remaining, from the time it was requested, before the token will expire. * @@ -89,6 +128,14 @@ public function getExpiresIn() return $this->expiresAt - time(); } + /** + * @return int seconds + */ + public function getRefreshTokenExpiresIn() + { + return $this->refreshTokenExpiresAt - time(); + } + /** * Set token expiration time * @@ -102,6 +149,18 @@ public function setExpiresIn($expiresIn) return $this; } + /** + * + * @param int $expiresIn amount of seconds before expiration + * + * @return AccessToken + */ + public function setRefreshTokenExpiresIn($expiresIn) + { + $this->refreshTokenExpiresAt = $expiresIn + time(); + return $this; + } + /** * Dynamically typecast token object into string * @@ -122,6 +181,15 @@ public function getExpiresAt() return $this->expiresAt; } + /** + * + * @return int + */ + public function getRefreshTokenExpiresAt() + { + return $this->refreshTokenExpiresAt; + } + /** * Set Unix epoch time when token will expire * @@ -135,6 +203,35 @@ public function setExpiresAt($expiresAt) return $this; } + /** + * @param int $expiresAt seconds, unix time + * + * @return AccessToken + */ + public function setRefreshTokenExpiresAt($expiresAt) + { + $this->refreshTokenExpiresAt = $expiresAt; + return $this; + } + + /** + * + * @return boolean + */ + public function isExpired() + { + return $this->expiresAt < time() + 60 * 5; + } + + /** + * + * @return boolean + */ + public function isRefreshTokenExpired() + { + return !$this->refreshTokenExpiresAt || $this->refreshTokenExpiresAt < time() + 60 * 5; + } + /** * Convert API response into AccessToken * @@ -173,9 +270,23 @@ public static function fromResponseArray($responseArray) 'Access token expiration date is not specified' ); } + + if (!isset($responseArray['refresh_token'])) { + throw new \InvalidArgumentException( + 'Refresh token is not available' + ); + } + if (!isset($responseArray['refresh_token_expires_in'])) { + throw new \InvalidArgumentException( + 'Refresh token expiration date is not specified' + ); + } + return new static( $responseArray['access_token'], - $responseArray['expires_in'] + time() + $responseArray['expires_in'] + time(), + $responseArray['refresh_token'], + $responseArray['refresh_token_expires_in'] + time() ); } @@ -185,8 +296,10 @@ public static function fromResponseArray($responseArray) public function jsonSerialize() { return [ - 'token' => $this->getToken(), - 'expiresAt' => $this->getExpiresAt(), + 'token' => $this->getToken(), + 'expiresAt' => $this->getExpiresAt(), + 'refreshToken' => $this->getRefreshToken(), + 'refreshTokenExpiresAt' => $this->getRefreshTokenExpiresAt(), ]; } -} +} \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index 169b58d..0f5b135 100644 --- a/src/Client.php +++ b/src/Client.php @@ -36,6 +36,11 @@ class Client */ const OAUTH2_GRANT_TYPE = 'authorization_code'; + /** + * Grant type + */ + const REFRESH_TOKEN_GRANT_TYPE = 'refresh_token'; + /** * Response type */ @@ -292,6 +297,7 @@ public function getAccessToken($code = '') } catch (RequestException $exception) { throw Exception::fromRequestException($exception); } + $this->setAccessToken( AccessToken::fromResponse($response) ); @@ -299,6 +305,40 @@ public function getAccessToken($code = '') return $this->accessToken; } + /** + * + * @param AccessToken $token + * @return AccessToken + */ + public function refreshAccessToken(AccessToken $token) + { + $uri = $this->buildUrl('accessToken', []); + $guzzle = new GuzzleClient([ + 'headers' => [ + 'Content-Type' => 'application/json', + 'x-li-format' => 'json', + 'Connection' => 'Keep-Alive' + ] + ]); + + try { + $response = $guzzle->post($uri, ['form_params' => [ + 'grant_type' => self::REFRESH_TOKEN_GRANT_TYPE, + 'refresh_token' => $token->getRefreshToken(), + 'client_id' => $this->getClientId(), + 'client_secret' => $this->getClientSecret(), + ]]); + } catch (RequestException $exception) { + throw Exception::fromRequestException($exception); + } + + $this->setAccessToken( + AccessToken::fromResponse($response) + ); + + return $this->accessToken; + } + /** * Convert API response into Array * @@ -597,4 +637,4 @@ protected function prepareOptions(array $params, $method) } return $options; } -} +} \ No newline at end of file diff --git a/src/Exception.php b/src/Exception.php index 91873bc..48b3f72 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -80,18 +80,15 @@ public static function fromRequestException($exception) */ private static function extractErrorDescription($exception) { - $response = $exception->getResponse(); - if (!$response) { - return null; - } - - $json = Client::responseToArray($response); + $json = Client::responseToArray( + $exception->getResponse() + ); if (isset($json['error_description'])) { - return $json['error_description']; - } - if (isset($json['message'])) { - return $json['message']; + return $json['error_description']; + } elseif (isset($json['message'])) { + return $json['message']; + } else { + return null; } - return null; } } diff --git a/src/Scope.php b/src/Scope.php index 5145671..f774ef1 100644 --- a/src/Scope.php +++ b/src/Scope.php @@ -27,54 +27,23 @@ class Scope extends AbstractEnum * Allows to read basic information about profile, such as name */ const READ_BASIC_PROFILE = 'r_basicprofile'; - - /** - * Request a minimum information about the user - * Use this scope when implementing "Sign In with LI" - */ - const READ_LITE_PROFILE = 'r_liteprofile'; - - const READ_FULL_PROFILE = 'r_fullprofile'; /** * Enables access to email address field */ const READ_EMAIL_ADDRESS = 'r_emailaddress'; - - /** - * Manage and delete your data including your profile, posts, invitations, and messages - */ - const COMPLIANCE = 'w_compliance'; - /** - * Enables managing business company - */ - const MANAGE_COMPANY = 'rw_organization_admin'; - /** - * Post, comment and like posts on behalf of an organization. - */ - const SHARE_AS_ORGANIZATION = 'w_organization_social'; - - /** - * Retrieve organizations' posts, comments, and likes. - */ - const READ_ORGANIZATION_SHARES = 'r_organization_social'; - + /** - * Post, comment and like posts on behalf of an authenticated member. + * Enables to manage business company, retrieve analytics */ - const SHARE_AS_USER = 'w_member_social'; - + const MANAGE_COMPANY = 'rw_company_admin'; + /** - * Restricted API! + * Enables ability to share content on LinkedIn */ - const READ_USER_CONTENT = 'r_member_social'; - + const SHARING = 'w_share'; /** - * Read and write access to ads. + * Manage and delete your data including your profile, posts, invitations, and messages */ - const ADS_MANAGEMENT = 'rw_ads'; - const READ_ADS = 'r_ads'; - const READ_LEADS = 'r_ads_leadgen_automation'; - const READ_ADS_REPORTING = 'r_ads_reporting'; - const READ_WRITE_DMP_SEGMENTS = 'rw_dmp_segments'; + const COMPLIANCE = 'w_compliance'; }