PluginProbe
WP Database Backup – Unlimited Database & Files Backup by Backup for WP / 7.7
WP Database Backup – Unlimited Database & Files Backup by Backup for WP v7.7
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.7, at includes/admin/Destination/Google/google-api-php-client/src/auth/Google_OAuth2.php

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