PluginProbe
WP Database Backup – Unlimited Database & Files Backup by Backup for WP / 7.13
WP Database Backup – Unlimited Database & Files Backup by Backup for WP v7.13
7.13 7.12 trunk 1.1 2.1.1 5.9 6.0 6.1 6.10 6.11 6.12 6.12.1 6.2 6.3 6.4 6.5 6.5.1 6.6 6.7 6.8 6.9 7.0 7.0.1 7.1 7.10 All 34 releases
wp-database-backup / includes / admin / Destination / Google / google-api-php-client / src / auth / Google_OAuth2.php

Google_OAuth2.php in WP Database Backup – Unlimited Database & Files Backup by Backup for WP 7.13, at includes/admin/Destination/Google/google-api-php-client/src/auth/Google_OAuth2.php

460 lines 15.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4 }
5 //phpcs:ignoreFile -- Thirdparty code.
6 /*
7 * Copyright 2008 Google Inc.
8 *
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22 require_once "Google_Verifier.php";
23 require_once "Google_LoginTicket.php";
24 require_once "service/Google_Utils.php";
25
26 /**
27 * Authentication class that deals with the OAuth 2 web-server authentication flow
28 *
29 * @author Chris Chabot <chabotc@google.com>
30 * @author Chirag Shah <chirags@google.com>
31 *
32 */
33 class Google_OAuth2 extends Google_Auth {
34 public $client_id;
35 public $client_secret;
36 public $developerKey;
37 public $token;
38 public $redirectUri;
39 public $state;
40 public $accessType = 'offline';
41 public $approvalPrompt = 'force';
42 public $requestVisibleActions;
43
44 /** @var Google_AssertionCredentials $assertionCredentials */
45 public $assertionCredentials;
46
47 const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
48 const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
49 const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
50 const OAUTH2_FEDERATED_SIGNON_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs';
51 const CLOCK_SKEW_SECS = 300; // five minutes in seconds
52 const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
53 const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
54
55 /**
56 * Instantiates the class, but does not initiate the login flow, leaving it
57 * to the discretion of the caller (which is done by calling authenticate()).
58 */
59 public function __construct() {
60 global $apiConfig;
61
62 if (! empty($apiConfig['developer_key'])) {
63 $this->developerKey = $apiConfig['developer_key'];
64 }
65
66 if (! empty($apiConfig['oauth2_client_id'])) {
67 $this->client_id = $apiConfig['oauth2_client_id'];
68 }
69
70 if (! empty($apiConfig['oauth2_client_secret'])) {
71 $this->client_secret = $apiConfig['oauth2_client_secret'];
72 }
73
74 if (! empty($apiConfig['oauth2_redirect_uri'])) {
75 $this->redirectUri = $apiConfig['oauth2_redirect_uri'];
76 }
77
78 if (! empty($apiConfig['oauth2_access_type'])) {
79 $this->accessType = $apiConfig['oauth2_access_type'];
80 }
81
82 if (! empty($apiConfig['oauth2_approval_prompt'])) {
83 $this->approvalPrompt = $apiConfig['oauth2_approval_prompt'];
84 }
85
86 }
87
88 /**
89 * @param $service
90 * @param string|null $code
91 * @throws Google_AuthException
92 * @return string
93 */
94 public function authenticate($service, $code = null) {
95 //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
96 if (!$code && isset($_GET['code'])) {
97 //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
98 $code = $_GET['code'];
99 }
100
101 if ($code) {
102 // We got here from the redirect from a successful authorization grant, fetch the access token
103 $request = Google_Client::$io->makeRequest(new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), array(
104 'code' => $code,
105 'grant_type' => 'authorization_code',
106 'redirect_uri' => $this->redirectUri,
107 'client_id' => $this->client_id,
108 'client_secret' => $this->client_secret
109 )));
110
111 if ($request->getResponseHttpCode() == 200) {
112 $this->setAccessToken($request->getResponseBody());
113 $this->token['created'] = time();
114 return $this->getAccessToken();
115 } else {
116 $response = $request->getResponseBody();
117 $decodedResponse = json_decode($response, true);
118 if ($decodedResponse != null && $decodedResponse['error']) {
119 $response = $decodedResponse['error'];
120 }
121 throw new Google_AuthException("Error fetching OAuth2 access token, message:".esc_html($response), esc_html($request->getResponseHttpCode()));
122 }
123 }
124
125 $auth_url = $this->createAuthUrl($service['scope']);
126 header('Location: ' . $auth_url);
127 return true;
128 }
129
130 /**
131 * Create a URL to obtain user authorization.
132 * The authorization endpoint allows the user to first
133 * authenticate, and then grant/deny the access request.
134 * @param string $scope The scope is expressed as a list of space-delimited strings.
135 * @return string
136 */
137 public function createAuthUrl($scope) {
138 $params = array(
139 'response_type=code',
140 'redirect_uri=' . urlencode($this->redirectUri),
141 'client_id=' . urlencode($this->client_id),
142 'scope=' . urlencode($scope),
143 'access_type=' . urlencode($this->accessType),
144 'approval_prompt=' . urlencode($this->approvalPrompt),
145 );
146
147 // if the list of scopes contains plus.login, add request_visible_actions
148 // to auth URL
149 if(strpos($scope, 'plus.login') && count($this->requestVisibleActions) > 0) {
150 $params[] = 'request_visible_actions=' .
151 urlencode($this->requestVisibleActions);
152 }
153
154 if (isset($this->state)) {
155 $params[] = 'state=' . urlencode($this->state);
156 }
157 $params = implode('&', $params);
158 return self::OAUTH2_AUTH_URL . "?$params";
159 }
160
161 /**
162 * @param string $token
163 * @throws Google_AuthException
164 */
165 public function setAccessToken($token) {
166 $token = json_decode($token, true);
167 if ($token == null) {
168 throw new Google_AuthException('Could not json decode the token');
169 }
170 if (! isset($token['access_token'])) {
171 throw new Google_AuthException("Invalid token format");
172 }
173 $this->token = $token;
174 }
175
176 public function getAccessToken() {
177 return wp_json_encode($this->token);
178 }
179
180 public function setDeveloperKey($developerKey) {
181 $this->developerKey = $developerKey;
182 }
183
184 public function setState($state) {
185 $this->state = $state;
186 }
187
188 public function setAccessType($accessType) {
189 $this->accessType = $accessType;
190 }
191
192 public function setApprovalPrompt($approvalPrompt) {
193 $this->approvalPrompt = $approvalPrompt;
194 }
195
196 public function setAssertionCredentials(Google_AssertionCredentials $creds) {
197 $this->assertionCredentials = $creds;
198 }
199
200 /**
201 * Include an access_token in a given apiHttpRequest.
202 * @param Google_HttpRequest $request
203 * @return Google_HttpRequest
204 * @throws Google_AuthException
205 */
206 public function sign(Google_HttpRequest $request) {
207 // add the developer key to the request before signing it
208 if ($this->developerKey) {
209 $requestUrl = $request->getUrl();
210 $requestUrl .= (strpos($request->getUrl(), '?') === false) ? '?' : '&';
211 $requestUrl .= 'key=' . urlencode($this->developerKey);
212 $request->setUrl($requestUrl);
213 }
214
215 // Cannot sign the request without an OAuth access token.
216 if (null == $this->token && null == $this->assertionCredentials) {
217 return $request;
218 }
219
220 // Check if the token is set to expire in the next 30 seconds
221 // (or has already expired).
222 if ($this->isAccessTokenExpired()) {
223 if ($this->assertionCredentials) {
224 $this->refreshTokenWithAssertion();
225 } else {
226 if (! array_key_exists('refresh_token', $this->token)) {
227 throw new Google_AuthException("The OAuth 2.0 access token has expired, "
228 . "and a refresh token is not available. Refresh tokens are not "
229 . "returned for responses that were auto-approved.");
230 }
231 $this->refreshToken($this->token['refresh_token']);
232 }
233 }
234
235 // Add the OAuth2 header to the request
236 $request->setRequestHeaders(
237 array('Authorization' => 'Bearer ' . $this->token['access_token'])
238 );
239
240 return $request;
241 }
242
243 /**
244 * Fetches a fresh access token with the given refresh token.
245 * @param string $refreshToken
246 * @return void
247 */
248 public function refreshToken($refreshToken) {
249 $this->refreshTokenRequest(array(
250 'client_id' => $this->client_id,
251 'client_secret' => $this->client_secret,
252 'refresh_token' => $refreshToken,
253 'grant_type' => 'refresh_token'
254 ));
255 }
256
257 /**
258 * Fetches a fresh access token with a given assertion token.
259 * @param Google_AssertionCredentials $assertionCredentials optional.
260 * @return void
261 */
262 public function refreshTokenWithAssertion($assertionCredentials = null) {
263 if (!$assertionCredentials) {
264 $assertionCredentials = $this->assertionCredentials;
265 }
266
267 $this->refreshTokenRequest(array(
268 'grant_type' => 'assertion',
269 'assertion_type' => $assertionCredentials->assertionType,
270 'assertion' => $assertionCredentials->generateAssertion(),
271 ));
272 }
273
274 private function refreshTokenRequest($params) {
275 $http = new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), $params);
276 $request = Google_Client::$io->makeRequest($http);
277
278 $code = $request->getResponseHttpCode();
279 $body = $request->getResponseBody();
280 if (200 == $code) {
281 $token = json_decode($body, true);
282 if ($token == null) {
283 throw new Google_AuthException("Could not json decode the access token");
284 }
285
286 if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
287 throw new Google_AuthException("Invalid token format");
288 }
289
290 $this->token['access_token'] = $token['access_token'];
291 $this->token['expires_in'] = $token['expires_in'];
292 $this->token['created'] = time();
293 } else {
294 throw new Google_AuthException("Error refreshing the OAuth2 token, message: ".esc_html($body), esc_html($code));
295 }
296 }
297
298 /**
299 * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
300 * token, if a token isn't provided.
301 * @throws Google_AuthException
302 * @param string|null $token The token (access token or a refresh token) that should be revoked.
303 * @return boolean Returns True if the revocation was successful, otherwise False.
304 */
305 public function revokeToken($token = null) {
306 if (!$token) {
307 $token = $this->token['access_token'];
308 }
309 $request = new Google_HttpRequest(self::OAUTH2_REVOKE_URI, 'POST', array(), "token=$token");
310 $response = Google_Client::$io->makeRequest($request);
311 $code = $response->getResponseHttpCode();
312 if ($code == 200) {
313 $this->token = null;
314 return true;
315 }
316
317 return false;
318 }
319
320 /**
321 * Returns if the access_token is expired.
322 * @return bool Returns True if the access_token is expired.
323 */
324 public function isAccessTokenExpired() {
325 if (null == $this->token) {
326 return true;
327 }
328
329 // If the token is set to expire in the next 30 seconds.
330 $expired = ($this->token['created']
331 + ($this->token['expires_in'] - 30)) < time();
332
333 return $expired;
334 }
335
336 // Gets federated sign-on certificates to use for verifying identity tokens.
337 // Returns certs as array structure, where keys are key ids, and values
338 // are PEM encoded certificates.
339 private function getFederatedSignOnCerts() {
340 // This relies on makeRequest caching certificate responses.
341 $request = Google_Client::$io->makeRequest(new Google_HttpRequest(
342 self::OAUTH2_FEDERATED_SIGNON_CERTS_URL));
343 if ($request->getResponseHttpCode() == 200) {
344 $certs = json_decode($request->getResponseBody(), true);
345 if ($certs) {
346 return $certs;
347 }
348 }
349 throw new Google_AuthException(
350 "Failed to retrieve verification certificates: '" .
351 esc_html($request->getResponseBody()) . "'.",
352 esc_html($request->getResponseHttpCode()));
353 }
354
355 /**
356 * Verifies an id token and returns the authenticated apiLoginTicket.
357 * Throws an exception if the id token is not valid.
358 * The audience parameter can be used to control which id tokens are
359 * accepted. By default, the id token must have been issued to this OAuth2 client.
360 *
361 * @param $id_token
362 * @param $audience
363 * @return Google_LoginTicket
364 */
365 public function verifyIdToken($id_token = null, $audience = null) {
366 if (!$id_token) {
367 $id_token = $this->token['id_token'];
368 }
369
370 $certs = $this->getFederatedSignonCerts();
371 if (!$audience) {
372 $audience = $this->client_id;
373 }
374 return $this->verifySignedJwtWithCerts($id_token, $certs, $audience);
375 }
376
377 // Verifies the id token, returns the verified token contents.
378 // Visible for testing.
379 function verifySignedJwtWithCerts($jwt, $certs, $required_audience) {
380 $segments = explode(".", $jwt);
381 if (count($segments) != 3) {
382 throw new Google_AuthException("Wrong number of segments in token:". esc_html($jwt));
383 }
384 $signed = $segments[0] . "." . $segments[1];
385 $signature = Google_Utils::urlSafeB64Decode($segments[2]);
386
387 // Parse envelope.
388 $envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
389 if (!$envelope) {
390 throw new Google_AuthException("Can't parse token envelope: " . esc_html($segments[0]));
391 }
392
393 // Parse token
394 $json_body = Google_Utils::urlSafeB64Decode($segments[1]);
395 $payload = json_decode($json_body, true);
396 if (!$payload) {
397 throw new Google_AuthException("Can't parse token payload: " . esc_html($segments[1]));
398 }
399
400 // Check signature
401 $verified = false;
402 foreach ($certs as $keyName => $pem) {
403 $public_key = new Google_PemVerifier($pem);
404 if ($public_key->verify($signed, $signature)) {
405 $verified = true;
406 break;
407 }
408 }
409
410 if (!$verified) {
411 throw new Google_AuthException("Invalid token signature: ".esc_html($jwt));
412 }
413
414 // Check issued-at timestamp
415 $iat = 0;
416 if (array_key_exists("iat", $payload)) {
417 $iat = $payload["iat"];
418 }
419 if (!$iat) {
420 throw new Google_AuthException("No issue time in token: ".esc_html($json_body));
421 }
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 }
430 if (!$exp) {
431 throw new Google_AuthException("No expiration time in token:".esc_html($json_body));
432 }
433 if ($exp >= $now + self::MAX_TOKEN_LIFETIME_SECS) {
434 throw new Google_AuthException(
435 "Expiration time too far in future:".esc_html($json_body));
436 }
437
438 $latest = $exp + self::CLOCK_SKEW_SECS;
439 if ($now < $earliest) {
440 throw new Google_AuthException(
441 "Token used too early,".esc_html($now) ." < " .esc_html($earliest)." : ".esc_html($json_body));
442 }
443 if ($now > $latest) {
444 throw new Google_AuthException(
445 "Token used too late,".esc_html($now) ." > " .esc_html($earliest)." : ".esc_html($json_body));
446 }
447
448 // TODO(beaton): check issuer field?
449
450 // Check audience
451 $aud = $payload["aud"];
452 if ($aud != $required_audience) {
453 throw new Google_AuthException("Wrong recipient, ".esc_html($aud) ." != ". esc_html($required_audience).' : '.esc_html($json_body));
454 }
455
456 // All good.
457 return new Google_LoginTicket($envelope, $payload);
458 }
459 }
460