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

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