diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -107,6 +107,7 @@ 'PhutilAuthAdapterOAuthTwitch' => 'auth/PhutilAuthAdapterOAuthTwitch.php', 'PhutilAuthAdapterOAuthTwitter' => 'auth/PhutilAuthAdapterOAuthTwitter.php', 'PhutilAuthAdapterPersona' => 'auth/PhutilAuthAdapterPersona.php', + 'PhutilAuthConfigurationException' => 'auth/exception/PhutilAuthConfigurationException.php', 'PhutilAuthCredentialException' => 'auth/exception/PhutilAuthCredentialException.php', 'PhutilAuthException' => 'auth/exception/PhutilAuthException.php', 'PhutilAuthUserAbortedException' => 'auth/exception/PhutilAuthUserAbortedException.php', @@ -357,6 +358,7 @@ 'nonempty' => 'utils/utils.php', 'phlog' => 'error/phlog.php', 'pht' => 'internationalization/pht.php', + 'phutil_censor_credentials' => 'utils/utils.php', 'phutil_console_confirm' => 'console/format.php', 'phutil_console_format' => 'console/format.php', 'phutil_console_get_terminal_width' => 'console/format.php', @@ -520,6 +522,7 @@ 'PhutilAuthAdapterOAuthTwitch' => 'PhutilAuthAdapterOAuth', 'PhutilAuthAdapterOAuthTwitter' => 'PhutilAuthAdapterOAuth1', 'PhutilAuthAdapterPersona' => 'PhutilAuthAdapter', + 'PhutilAuthConfigurationException' => 'PhutilAuthException', 'PhutilAuthCredentialException' => 'PhutilAuthException', 'PhutilAuthException' => 'Exception', 'PhutilAuthUserAbortedException' => 'PhutilAuthException', diff --git a/src/auth/PhutilAuthAdapterOAuthGoogle.php b/src/auth/PhutilAuthAdapterOAuthGoogle.php --- a/src/auth/PhutilAuthAdapterOAuthGoogle.php +++ b/src/auth/PhutilAuthAdapterOAuthGoogle.php @@ -14,11 +14,21 @@ } public function getAccountID() { - return $this->getOAuthAccountData('email'); + $emails = $this->getOAuthAccountData('emails', array()); + foreach ($emails as $email) { + if (idx($email, 'type') == 'account') { + return idx($email, 'value'); + } + } + + throw new Exception( + pht( + 'Expected to retrieve an "account" email from Google Plus API call ', + 'to identify account, but failed.')); } public function getAccountEmail() { - return $this->getOAuthAccountData('email'); + return $this->getAccountID(); } public function getAccountName() { @@ -38,7 +48,20 @@ } public function getAccountRealName() { - return $this->getOAuthAccountData('name'); + $name = $this->getOAuthAccountData('name', array()); + + // TODO: This could probably be made cleaner by looking up the API, but + // this should work to unbreak logins. + + $parts = array(); + $parts[] = idx($name, 'givenName'); + unset($name['givenName']); + $parts[] = idx($name, 'familyName'); + unset($name['familyName']); + $parts = array_merge($parts, $name); + $parts = array_filter($parts); + + return implode(' ', $parts); } protected function getAuthenticateBaseURI() { @@ -51,8 +74,8 @@ public function getScope() { $scopes = array( - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile', + 'email', + 'profile', ); return implode(' ', $scopes); @@ -71,11 +94,16 @@ } protected function loadOAuthAccountData() { - $uri = new PhutilURI('https://www.googleapis.com/oauth2/v1/userinfo'); + $uri = new PhutilURI('https://www.googleapis.com/plus/v1/people/me'); $uri->setQueryParam('access_token', $this->getAccessToken()); $future = new HTTPSFuture($uri); - list($body) = $future->resolvex(); + list($status, $body) = $future->resolve(); + + if ($status->isError()) { + $this->tryToThrowSpecializedError($status, $body); + throw $status; + } $data = json_decode($body, true); if (!is_array($data)) { @@ -87,4 +115,44 @@ return $data; } + private function tryToThrowSpecializedError($status, $raw_body) { + if (!($status instanceof HTTPFutureResponseStatusHTTP)) { + return; + } + + if ($status->getStatusCode() != 403) { + return; + } + + $body = phutil_json_decode($raw_body); + if (!$body) { + return; + } + + if (empty($body['error']['errors'][0])) { + return; + } + + $error = $body['error']['errors'][0]; + $domain = idx($error, 'domain'); + $reason = idx($error, 'reason'); + + if ($domain == 'usageLimits' && $reason == 'accessNotConfigured') { + throw new PhutilAuthConfigurationException( + pht( + 'Google returned an "accessNotConfigured" error. This usually means '. + 'you need to enable the "Google+ API" in your Google Cloud Console, '. + 'under "APIs".'. + "\n\n". + 'Around March 2014, Google made some API changes which require this '. + 'configuration adjustment. (If you are unable to log into '. + 'Phabricator, use "bin/auth recover" to recover access to an '. + 'administrator account.)'. + "\n\n". + 'Full HTTP Response'. + "\n\n%s", + $raw_body)); + } + } + } diff --git a/src/auth/exception/PhutilAuthConfigurationException.php b/src/auth/exception/PhutilAuthConfigurationException.php new file mode 100644 --- /dev/null +++ b/src/auth/exception/PhutilAuthConfigurationException.php @@ -0,0 +1,8 @@ +