From 668118a665bf7eccc3f538cc495cdc10e81a48aa Mon Sep 17 00:00:00 2001 From: Zoly Date: Sat, 20 Jul 2024 09:03:27 +0300 Subject: [PATCH 1/2] Update GithubOAuth.php Retrieve the user's email addresses regardless if their status is set public or not --- src/Libraries/GithubOAuth.php | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Libraries/GithubOAuth.php b/src/Libraries/GithubOAuth.php index f317be7..fa03bbe 100644 --- a/src/Libraries/GithubOAuth.php +++ b/src/Libraries/GithubOAuth.php @@ -89,7 +89,44 @@ protected function fetchUserInfoWithToken(): object exit($e->getMessage()); } - return json_decode($response->getBody()); + $userInfo = json_decode($response->getBody(), false); + + /** + * The /user API returns only the publicly visible email address or null if none is set. + */ + if (empty($userInfo->email)) { + + /** + * The /user/emails API returns the user's email addresses regardless if their status is set public or not. + * @see https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-a-user + */ + try { + $response = $this->client->request('GET', self::$API_USER_INFO_URL . '/emails', [ + 'headers' => [ + 'User-Agent' => self::$APPLICATION_NAME . '/1.0', + 'Accept' => 'application/vnd.github+json', + 'Authorization' => 'Bearer ' . $this->getToken(), + ], + 'http_errors' => false, + ]); + } catch (Exception $e) { + exit($e->getMessage()); + } + + $emailAddresses = json_decode($response->getBody(), false); + + if (empty($emailAddresses)) { + throw new Exception('No email addresses found for the user'); + } + + /** + * If multiple email addresses are returned try to get the one marked as primary, otherwise grab the first one + */ + $primaryEmail = array_filter($emailAddresses, static fn($eMail) => $eMail->primary); + $userInfo->email = !empty($primaryEmail) ? array_shift($primaryEmail)->email : array_shift($emailAddresses)->email; + } + + return $userInfo; } protected function setColumnsName(string $nameOfProcess, $userInfo): array From 646980d90711f5eb9bc8ea387f1de41d9bf1f54f Mon Sep 17 00:00:00 2001 From: Zoly Date: Mon, 22 Jul 2024 05:15:51 +0300 Subject: [PATCH 2/2] Retrieve mandatory email address from Github The /user API returns only the data the user explicitly set as public and null for those he didn't. If the user did not set the email address public (me for example), the email address being mandatory for the process, the authentication fails returning an error message. By using the /user/emails API in conjunction, it is possible to retrieve all the email addresses the user set in Github, regardless if they are set public or not, and select one of those (ex: primary one), for the login process to complete successfully. --- src/Libraries/GithubOAuth.php | 72 +++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Libraries/GithubOAuth.php b/src/Libraries/GithubOAuth.php index fa03bbe..c54fcbf 100644 --- a/src/Libraries/GithubOAuth.php +++ b/src/Libraries/GithubOAuth.php @@ -21,10 +21,11 @@ class GithubOAuth extends AbstractOAuth { - public static string $API_CODE_URL = 'https://github.com/login/oauth/authorize'; - public static string $API_TOKEN_URL = 'https://github.com/login/oauth/access_token'; - public static string $API_USER_INFO_URL = 'https://api.github.com/user'; - private static string $APPLICATION_NAME = 'ShieldOAuth'; + public static string $API_CODE_URL = 'https://github.com/login/oauth/authorize'; + public static string $API_TOKEN_URL = 'https://github.com/login/oauth/access_token'; + public static string $API_USER_INFO_URL = 'https://api.github.com/user'; // The /user API returns the user's publicly visible data or null for those that are not set + public static string $API_USER_EMAILS_URL = 'https://api.github.com/user/emails'; // The /user/emails API returns all email addresses for the user, including those that are not set public + private static string $APPLICATION_NAME = 'ShieldOAuth'; protected string $token; protected CURLRequest $client; protected ShieldOAuthConfig $config; @@ -91,44 +92,43 @@ protected function fetchUserInfoWithToken(): object $userInfo = json_decode($response->getBody(), false); - /** - * The /user API returns only the publicly visible email address or null if none is set. - */ + // the email address is mandatory if (empty($userInfo->email)) { - - /** - * The /user/emails API returns the user's email addresses regardless if their status is set public or not. - * @see https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-a-user - */ - try { - $response = $this->client->request('GET', self::$API_USER_INFO_URL . '/emails', [ - 'headers' => [ - 'User-Agent' => self::$APPLICATION_NAME . '/1.0', - 'Accept' => 'application/vnd.github+json', - 'Authorization' => 'Bearer ' . $this->getToken(), - ], - 'http_errors' => false, - ]); - } catch (Exception $e) { - exit($e->getMessage()); - } - - $emailAddresses = json_decode($response->getBody(), false); - - if (empty($emailAddresses)) { - throw new Exception('No email addresses found for the user'); - } - - /** - * If multiple email addresses are returned try to get the one marked as primary, otherwise grab the first one - */ - $primaryEmail = array_filter($emailAddresses, static fn($eMail) => $eMail->primary); - $userInfo->email = !empty($primaryEmail) ? array_shift($primaryEmail)->email : array_shift($emailAddresses)->email; + $userInfo->email = $this->getUserPrimaryEmail($this->fetchUserEmailsWithToken()); } return $userInfo; } + protected function fetchUserEmailsWithToken() + { + // send request to API URL + try { + $response = $this->client->request('GET', self::$API_USER_EMAILS_URL, [ + 'headers' => [ + 'User-Agent' => self::$APPLICATION_NAME . '/1.0', + 'Accept' => 'application/vnd.github+json', + 'Authorization' => 'Bearer ' . $this->getToken(), + ], + 'http_errors' => false, + ]); + } catch (Exception $e) { + exit($e->getMessage()); + } + + return json_decode($response->getBody(), false); + } + + protected function getUserPrimaryEmail(array $emailAddresses): string + { + // try to get the one marked as primary, otherwise grab the first one + if (! empty($emailAddresses)) { + $primaryEmail = array_filter($emailAddresses, static fn($eMail) => $eMail->primary); + $userEmail = ! empty($primaryEmail) ? array_shift($primaryEmail)->email : array_shift($emailAddresses)->email; + } + return $userEmail ?? ''; + } + protected function setColumnsName(string $nameOfProcess, $userInfo): array { if ($nameOfProcess === 'syncingUserInfo') {