Changeset View
Changeset View
Standalone View
Standalone View
src/auth/PhutilAuthAdapterOAuthGoogle.php
<?php | <?php | ||||
/** | /** | ||||
* Authentication adapter for Google OAuth2. | * Authentication adapter for Google OAuth2. | ||||
*/ | */ | ||||
final class PhutilAuthAdapterOAuthGoogle extends PhutilAuthAdapterOAuth { | final class PhutilAuthAdapterOAuthGoogle extends PhutilAuthAdapterOAuth { | ||||
public function getAdapterType() { | public function getAdapterType() { | ||||
return 'google'; | return 'google'; | ||||
} | } | ||||
public function getAdapterDomain() { | public function getAdapterDomain() { | ||||
return 'google.com'; | return 'google.com'; | ||||
} | } | ||||
public function getAccountID() { | 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() { | public function getAccountEmail() { | ||||
return $this->getOAuthAccountData('email'); | return $this->getAccountID(); | ||||
} | } | ||||
public function getAccountName() { | public function getAccountName() { | ||||
// Guess account name from email address, this is just a hint anyway. | // Guess account name from email address, this is just a hint anyway. | ||||
$email = $this->getAccountEmail(); | $email = $this->getAccountEmail(); | ||||
$email = explode('@', $email); | $email = explode('@', $email); | ||||
$email = head($email); | $email = head($email); | ||||
return $email; | return $email; | ||||
} | } | ||||
public function getAccountImageURI() { | public function getAccountImageURI() { | ||||
return $this->getOAuthAccountData('picture'); | return $this->getOAuthAccountData('picture'); | ||||
} | } | ||||
public function getAccountURI() { | public function getAccountURI() { | ||||
return 'https://plus.google.com/'.$this->getOAuthAccountData('id'); | return 'https://plus.google.com/'.$this->getOAuthAccountData('id'); | ||||
} | } | ||||
public function getAccountRealName() { | 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() { | protected function getAuthenticateBaseURI() { | ||||
return 'https://accounts.google.com/o/oauth2/auth'; | return 'https://accounts.google.com/o/oauth2/auth'; | ||||
} | } | ||||
protected function getTokenBaseURI() { | protected function getTokenBaseURI() { | ||||
return 'https://accounts.google.com/o/oauth2/token'; | return 'https://accounts.google.com/o/oauth2/token'; | ||||
} | } | ||||
public function getScope() { | public function getScope() { | ||||
$scopes = array( | $scopes = array( | ||||
'https://www.googleapis.com/auth/userinfo.email', | 'email', | ||||
'https://www.googleapis.com/auth/userinfo.profile', | 'profile', | ||||
); | ); | ||||
return implode(' ', $scopes); | return implode(' ', $scopes); | ||||
} | } | ||||
public function getExtraAuthenticateParameters() { | public function getExtraAuthenticateParameters() { | ||||
return array( | return array( | ||||
'response_type' => 'code', | 'response_type' => 'code', | ||||
); | ); | ||||
} | } | ||||
public function getExtraTokenParameters() { | public function getExtraTokenParameters() { | ||||
return array( | return array( | ||||
'grant_type' => 'authorization_code', | 'grant_type' => 'authorization_code', | ||||
); | ); | ||||
} | } | ||||
protected function loadOAuthAccountData() { | 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()); | $uri->setQueryParam('access_token', $this->getAccessToken()); | ||||
$future = new HTTPSFuture($uri); | $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); | $data = json_decode($body, true); | ||||
if (!is_array($data)) { | if (!is_array($data)) { | ||||
throw new Exception( | throw new Exception( | ||||
"Expected valid JSON response from Google account data request, ". | "Expected valid JSON response from Google account data request, ". | ||||
"got: ".$body); | "got: ".$body); | ||||
} | } | ||||
return $data; | 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)); | |||||
} | |||||
} | |||||
} | } |