Google_Auth_OAuth2

Authentication class that deals with the OAuth 2 web-server authentication flow.

Defined (1)

The class is defined in the following location(s).

/lib/vendor/google/apiclient/src/Google/Auth/OAuth2.php  
  1. class Google_Auth_OAuth2 extends Google_Auth_Abstract 
  2. const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'; 
  3. const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'; 
  4. const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth'; 
  5. const CLOCK_SKEW_SECS = 300; // five minutes in seconds 
  6. const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds 
  7. const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds 
  8. const OAUTH2_ISSUER = 'accounts.google.com'; 
  9.  
  10. /** @var Google_Auth_AssertionCredentials $assertionCredentials */ 
  11. private $assertionCredentials; 
  12.  
  13. /** 
  14. * @var string The state parameters for CSRF and other forgery protection. 
  15. */ 
  16. private $state; 
  17.  
  18. /** 
  19. * @var array The token bundle. 
  20. */ 
  21. private $token = array(); 
  22.  
  23. /** 
  24. * @var Google_Client the base client 
  25. */ 
  26. private $client; 
  27.  
  28. /** 
  29. * Instantiates the class, but does not initiate the login flow, leaving it 
  30. * to the discretion of the caller. 
  31. */ 
  32. public function __construct(Google_Client $client) 
  33. $this->client = $client; 
  34.  
  35. /** 
  36. * Perform an authenticated / signed apiHttpRequest. 
  37. * This function takes the apiHttpRequest, calls apiAuth->sign on it 
  38. * (which can modify the request in what ever way fits the auth mechanism) 
  39. * and then calls apiCurlIO::makeRequest on the signed request 
  40. * @param Google_Http_Request $request 
  41. * @return Google_Http_Request The resulting HTTP response including the 
  42. * responseHttpCode, responseHeaders and responseBody. 
  43. */ 
  44. public function authenticatedRequest(Google_Http_Request $request) 
  45. $request = $this->sign($request); 
  46. return $this->client->getIo()->makeRequest($request); 
  47.  
  48. /** 
  49. * @param string $code 
  50. * @throws Google_Auth_Exception 
  51. * @return string 
  52. */ 
  53. public function authenticate($code) 
  54. if (strlen($code) == 0) { 
  55. throw new Google_Auth_Exception("Invalid code"); 
  56.  
  57. // We got here from the redirect from a successful authorization grant,  
  58. // fetch the access token 
  59. $request = new Google_Http_Request( 
  60. self::OAUTH2_TOKEN_URI,  
  61. 'POST',  
  62. array(),  
  63. array( 
  64. 'code' => $code,  
  65. 'grant_type' => 'authorization_code',  
  66. 'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),  
  67. 'client_id' => $this->client->getClassConfig($this, 'client_id'),  
  68. 'client_secret' => $this->client->getClassConfig($this, 'client_secret') 
  69. ); 
  70. $request->disableGzip(); 
  71. $response = $this->client->getIo()->makeRequest($request); 
  72.  
  73. if ($response->getResponseHttpCode() == 200) { 
  74. $this->setAccessToken($response->getResponseBody()); 
  75. $this->token['created'] = time(); 
  76. return $this->getAccessToken(); 
  77. } else { 
  78. $decodedResponse = json_decode($response->getResponseBody(), true); 
  79. if ($decodedResponse != null && $decodedResponse['error']) { 
  80. $decodedResponse = $decodedResponse['error']; 
  81. if (isset($decodedResponse['error_description'])) { 
  82. $decodedResponse .= ": " . $decodedResponse['error_description']; 
  83. throw new Google_Auth_Exception( 
  84. sprintf( 
  85. "Error fetching OAuth2 access token, message: '%s'",  
  86. $decodedResponse 
  87. ),  
  88. $response->getResponseHttpCode() 
  89. ); 
  90.  
  91. /** 
  92. * Create a URL to obtain user authorization. 
  93. * The authorization endpoint allows the user to first 
  94. * authenticate, and then grant/deny the access request. 
  95. * @param string $scope The scope is expressed as a list of space-delimited strings. 
  96. * @return string 
  97. */ 
  98. public function createAuthUrl($scope) 
  99. $params = array( 
  100. 'response_type' => 'code',  
  101. 'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),  
  102. 'client_id' => $this->client->getClassConfig($this, 'client_id'),  
  103. 'scope' => $scope,  
  104. 'access_type' => $this->client->getClassConfig($this, 'access_type'),  
  105. ); 
  106.  
  107. $params = $this->maybeAddParam($params, 'approval_prompt'); 
  108. $params = $this->maybeAddParam($params, 'login_hint'); 
  109. $params = $this->maybeAddParam($params, 'hd'); 
  110. $params = $this->maybeAddParam($params, 'openid.realm'); 
  111. $params = $this->maybeAddParam($params, 'prompt'); 
  112. $params = $this->maybeAddParam($params, 'include_granted_scopes'); 
  113.  
  114. // If the list of scopes contains plus.login, add request_visible_actions 
  115. // to auth URL. 
  116. $rva = $this->client->getClassConfig($this, 'request_visible_actions'); 
  117. if (strpos($scope, 'plus.login') && strlen($rva) > 0) { 
  118. $params['request_visible_actions'] = $rva; 
  119.  
  120. if (isset($this->state)) { 
  121. $params['state'] = $this->state; 
  122.  
  123. return self::OAUTH2_AUTH_URL . "?" . http_build_query($params, '', '&'); 
  124.  
  125. /** 
  126. * @param string $token 
  127. * @throws Google_Auth_Exception 
  128. */ 
  129. public function setAccessToken($token) 
  130. $token = json_decode($token, true); 
  131. if ($token == null) { 
  132. throw new Google_Auth_Exception('Could not json decode the token'); 
  133. if (! isset($token['access_token'])) { 
  134. throw new Google_Auth_Exception("Invalid token format"); 
  135. $this->token = $token; 
  136.  
  137. public function getAccessToken() 
  138. return json_encode($this->token); 
  139.  
  140. public function getRefreshToken() 
  141. if (array_key_exists('refresh_token', $this->token)) { 
  142. return $this->token['refresh_token']; 
  143. } else { 
  144. return null; 
  145.  
  146. public function setState($state) 
  147. $this->state = $state; 
  148.  
  149. public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds) 
  150. $this->assertionCredentials = $creds; 
  151.  
  152. /** 
  153. * Include an accessToken in a given apiHttpRequest. 
  154. * @param Google_Http_Request $request 
  155. * @return Google_Http_Request 
  156. * @throws Google_Auth_Exception 
  157. */ 
  158. public function sign(Google_Http_Request $request) 
  159. // add the developer key to the request before signing it 
  160. if ($this->client->getClassConfig($this, 'developer_key')) { 
  161. $request->setQueryParam('key', $this->client->getClassConfig($this, 'developer_key')); 
  162.  
  163. // Cannot sign the request without an OAuth access token. 
  164. if (null == $this->token && null == $this->assertionCredentials) { 
  165. return $request; 
  166.  
  167. // Check if the token is set to expire in the next 30 seconds 
  168. // (or has already expired). 
  169. if ($this->isAccessTokenExpired()) { 
  170. if ($this->assertionCredentials) { 
  171. $this->refreshTokenWithAssertion(); 
  172. } else { 
  173. if (! array_key_exists('refresh_token', $this->token)) { 
  174. throw new Google_Auth_Exception( 
  175. "The OAuth 2.0 access token has expired, " 
  176. ." and a refresh token is not available. Refresh tokens" 
  177. ." are not returned for responses that were auto-approved." 
  178. ); 
  179. $this->refreshToken($this->token['refresh_token']); 
  180.  
  181. // Add the OAuth2 header to the request 
  182. $request->setRequestHeaders( 
  183. array('Authorization' => 'Bearer ' . $this->token['access_token']) 
  184. ); 
  185.  
  186. return $request; 
  187.  
  188. /** 
  189. * Fetches a fresh access token with the given refresh token. 
  190. * @param string $refreshToken 
  191. * @return void 
  192. */ 
  193. public function refreshToken($refreshToken) 
  194. $this->refreshTokenRequest( 
  195. array( 
  196. 'client_id' => $this->client->getClassConfig($this, 'client_id'),  
  197. 'client_secret' => $this->client->getClassConfig($this, 'client_secret'),  
  198. 'refresh_token' => $refreshToken,  
  199. 'grant_type' => 'refresh_token' 
  200. ); 
  201.  
  202. /** 
  203. * Fetches a fresh access token with a given assertion token. 
  204. * @param Google_Auth_AssertionCredentials $assertionCredentials optional. 
  205. * @return void 
  206. */ 
  207. public function refreshTokenWithAssertion($assertionCredentials = null) 
  208. if (!$assertionCredentials) { 
  209. $assertionCredentials = $this->assertionCredentials; 
  210.  
  211. $cacheKey = $assertionCredentials->getCacheKey(); 
  212.  
  213. if ($cacheKey) { 
  214. // We can check whether we have a token available in the 
  215. // cache. If it is expired, we can retrieve a new one from 
  216. // the assertion. 
  217. $token = $this->client->getCache()->get($cacheKey); 
  218. if ($token) { 
  219. $this->setAccessToken($token); 
  220. if (!$this->isAccessTokenExpired()) { 
  221. return; 
  222.  
  223. $this->refreshTokenRequest( 
  224. array( 
  225. 'grant_type' => 'assertion',  
  226. 'assertion_type' => $assertionCredentials->assertionType,  
  227. 'assertion' => $assertionCredentials->generateAssertion(),  
  228. ); 
  229.  
  230. if ($cacheKey) { 
  231. // Attempt to cache the token. 
  232. $this->client->getCache()->set( 
  233. $cacheKey,  
  234. $this->getAccessToken() 
  235. ); 
  236.  
  237. private function refreshTokenRequest($params) 
  238. $http = new Google_Http_Request( 
  239. self::OAUTH2_TOKEN_URI,  
  240. 'POST',  
  241. array(),  
  242. $params 
  243. ); 
  244. $http->disableGzip(); 
  245. $request = $this->client->getIo()->makeRequest($http); 
  246.  
  247. $code = $request->getResponseHttpCode(); 
  248. $body = $request->getResponseBody(); 
  249. if (200 == $code) { 
  250. $token = json_decode($body, true); 
  251. if ($token == null) { 
  252. throw new Google_Auth_Exception("Could not json decode the access token"); 
  253.  
  254. if (! isset($token['access_token']) || ! isset($token['expires_in'])) { 
  255. throw new Google_Auth_Exception("Invalid token format"); 
  256.  
  257. if (isset($token['id_token'])) { 
  258. $this->token['id_token'] = $token['id_token']; 
  259. $this->token['access_token'] = $token['access_token']; 
  260. $this->token['expires_in'] = $token['expires_in']; 
  261. $this->token['created'] = time(); 
  262. } else { 
  263. throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code); 
  264.  
  265. /** 
  266. * Revoke an OAuth2 access token or refresh token. This method will revoke the current access 
  267. * token, if a token isn't provided. 
  268. * @throws Google_Auth_Exception 
  269. * @param string|null $token The token (access token or a refresh token) that should be revoked. 
  270. * @return boolean Returns True if the revocation was successful, otherwise False. 
  271. */ 
  272. public function revokeToken($token = null) 
  273. if (!$token) { 
  274. if (!$this->token) { 
  275. // Not initialized, no token to actually revoke 
  276. return false; 
  277. } elseif (array_key_exists('refresh_token', $this->token)) { 
  278. $token = $this->token['refresh_token']; 
  279. } else { 
  280. $token = $this->token['access_token']; 
  281. $request = new Google_Http_Request( 
  282. self::OAUTH2_REVOKE_URI,  
  283. 'POST',  
  284. array(),  
  285. "token=$token" 
  286. ); 
  287. $request->disableGzip(); 
  288. $response = $this->client->getIo()->makeRequest($request); 
  289. $code = $response->getResponseHttpCode(); 
  290. if ($code == 200) { 
  291. $this->token = null; 
  292. return true; 
  293.  
  294. return false; 
  295.  
  296. /** 
  297. * Returns if the access_token is expired. 
  298. * @return bool Returns True if the access_token is expired. 
  299. */ 
  300. public function isAccessTokenExpired() 
  301. if (!$this->token || !isset($this->token['created'])) { 
  302. return true; 
  303.  
  304. // If the token is set to expire in the next 30 seconds. 
  305. $expired = ($this->token['created'] 
  306. + ($this->token['expires_in'] - 30)) < time(); 
  307.  
  308. return $expired; 
  309.  
  310. // Gets federated sign-on certificates to use for verifying identity tokens. 
  311. // Returns certs as array structure, where keys are key ids, and values 
  312. // are PEM encoded certificates. 
  313. private function getFederatedSignOnCerts() 
  314. return $this->retrieveCertsFromLocation( 
  315. $this->client->getClassConfig($this, 'federated_signon_certs_url') 
  316. ); 
  317.  
  318. /** 
  319. * Retrieve and cache a certificates file. 
  320. * @param $url location 
  321. * @return array certificates 
  322. */ 
  323. public function retrieveCertsFromLocation($url) 
  324. // If we're retrieving a local file, just grab it. 
  325. if ("http" != substr($url, 0, 4)) { 
  326. $file = file_get_contents($url); 
  327. if ($file) { 
  328. return json_decode($file, true); 
  329. } else { 
  330. throw new Google_Auth_Exception( 
  331. "Failed to retrieve verification certificates: '" . 
  332. $url . "'." 
  333. ); 
  334.  
  335. // This relies on makeRequest caching certificate responses. 
  336. $request = $this->client->getIo()->makeRequest( 
  337. new Google_Http_Request( 
  338. $url 
  339. ); 
  340. if ($request->getResponseHttpCode() == 200) { 
  341. $certs = json_decode($request->getResponseBody(), true); 
  342. if ($certs) { 
  343. return $certs; 
  344. throw new Google_Auth_Exception( 
  345. "Failed to retrieve verification certificates: '" . 
  346. $request->getResponseBody() . "'.",  
  347. $request->getResponseHttpCode() 
  348. ); 
  349.  
  350. /** 
  351. * Verifies an id token and returns the authenticated apiLoginTicket. 
  352. * Throws an exception if the id token is not valid. 
  353. * The audience parameter can be used to control which id tokens are 
  354. * accepted. By default, the id token must have been issued to this OAuth2 client. 
  355. * @param $id_token 
  356. * @param $audience 
  357. * @return Google_Auth_LoginTicket 
  358. */ 
  359. public function verifyIdToken($id_token = null, $audience = null) 
  360. if (!$id_token) { 
  361. $id_token = $this->token['id_token']; 
  362. $certs = $this->getFederatedSignonCerts(); 
  363. if (!$audience) { 
  364. $audience = $this->client->getClassConfig($this, 'client_id'); 
  365.  
  366. return $this->verifySignedJwtWithCerts($id_token, $certs, $audience, self::OAUTH2_ISSUER); 
  367.  
  368. /** 
  369. * Verifies the id token, returns the verified token contents. 
  370. * @param $jwt the token 
  371. * @param $certs array of certificates 
  372. * @param $required_audience the expected consumer of the token 
  373. * @param [$issuer] the expected issues, defaults to Google 
  374. * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS 
  375. * @return token information if valid, false if not 
  376. */ 
  377. public function verifySignedJwtWithCerts( 
  378. $jwt,  
  379. $certs,  
  380. $required_audience,  
  381. $issuer = null,  
  382. $max_expiry = null 
  383. ) { 
  384. if (!$max_expiry) { 
  385. // Set the maximum time we will accept a token for. 
  386. $max_expiry = self::MAX_TOKEN_LIFETIME_SECS; 
  387.  
  388. $segments = explode(".", $jwt); 
  389. if (count($segments) != 3) { 
  390. throw new Google_Auth_Exception("Wrong number of segments in token: $jwt"); 
  391. $signed = $segments[0] . "." . $segments[1]; 
  392. $signature = Google_Utils::urlSafeB64Decode($segments[2]); 
  393.  
  394. // Parse envelope. 
  395. $envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true); 
  396. if (!$envelope) { 
  397. throw new Google_Auth_Exception("Can't parse token envelope: " . $segments[0]); 
  398.  
  399. // Parse token 
  400. $json_body = Google_Utils::urlSafeB64Decode($segments[1]); 
  401. $payload = json_decode($json_body, true); 
  402. if (!$payload) { 
  403. throw new Google_Auth_Exception("Can't parse token payload: " . $segments[1]); 
  404.  
  405. // Check signature 
  406. $verified = false; 
  407. foreach ($certs as $keyName => $pem) { 
  408. $public_key = new Google_Verifier_Pem($pem); 
  409. if ($public_key->verify($signed, $signature)) { 
  410. $verified = true; 
  411. break; 
  412.  
  413. if (!$verified) { 
  414. throw new Google_Auth_Exception("Invalid token signature: $jwt"); 
  415.  
  416. // Check issued-at timestamp 
  417. $iat = 0; 
  418. if (array_key_exists("iat", $payload)) { 
  419. $iat = $payload["iat"]; 
  420. if (!$iat) { 
  421. throw new Google_Auth_Exception("No issue time in token: $json_body"); 
  422. $earliest = $iat - self::CLOCK_SKEW_SECS; 
  423.  
  424. // Check expiration timestamp 
  425. $now = time(); 
  426. $exp = 0; 
  427. if (array_key_exists("exp", $payload)) { 
  428. $exp = $payload["exp"]; 
  429. if (!$exp) { 
  430. throw new Google_Auth_Exception("No expiration time in token: $json_body"); 
  431. if ($exp >= $now + $max_expiry) { 
  432. throw new Google_Auth_Exception( 
  433. sprintf("Expiration time too far in future: %s", $json_body) 
  434. ); 
  435.  
  436. $latest = $exp + self::CLOCK_SKEW_SECS; 
  437. if ($now < $earliest) { 
  438. throw new Google_Auth_Exception( 
  439. sprintf( 
  440. "Token used too early, %s < %s: %s",  
  441. $now,  
  442. $earliest,  
  443. $json_body 
  444. ); 
  445. if ($now > $latest) { 
  446. throw new Google_Auth_Exception( 
  447. sprintf( 
  448. "Token used too late, %s > %s: %s",  
  449. $now,  
  450. $latest,  
  451. $json_body 
  452. ); 
  453.  
  454. $iss = $payload['iss']; 
  455. if ($issuer && $iss != $issuer) { 
  456. throw new Google_Auth_Exception( 
  457. sprintf( 
  458. "Invalid issuer, %s != %s: %s",  
  459. $iss,  
  460. $issuer,  
  461. $json_body 
  462. ); 
  463.  
  464. // Check audience 
  465. $aud = $payload["aud"]; 
  466. if ($aud != $required_audience) { 
  467. throw new Google_Auth_Exception( 
  468. sprintf( 
  469. "Wrong recipient, %s != %s:",  
  470. $aud,  
  471. $required_audience,  
  472. $json_body 
  473. ); 
  474.  
  475. // All good. 
  476. return new Google_Auth_LoginTicket($envelope, $payload); 
  477.  
  478. /** 
  479. * Add a parameter to the auth params if not empty string. 
  480. */ 
  481. private function maybeAddParam($params, $name) 
  482. $param = $this->client->getClassConfig($this, $name); 
  483. if ($param != '') { 
  484. $params[$name] = $param; 
  485. return $params;