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

454 lines 15.0 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 if (!$code && isset($_GET['code'])) {
92 $code = $_GET['code'];
93 }
94
95 if ($code) {
96 // We got here from the redirect from a successful authorization grant, fetch the access token
97 $request = Google_Client::$io->makeRequest(new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), array(
98 'code' => $code,
99 'grant_type' => 'authorization_code',
100 'redirect_uri' => $this->redirectUri,
101 'client_id' => $this->client_id,
102 'client_secret' => $this->client_secret
103 )));
104
105 if ($request->getResponseHttpCode() == 200) {
106 $this->setAccessToken($request->getResponseBody());
107 $this->token['created'] = time();
108 return $this->getAccessToken();
109 } else {
110 $response = $request->getResponseBody();
111 $decodedResponse = json_decode($response, true);
112 if ($decodedResponse != null && $decodedResponse['error']) {
113 $response = $decodedResponse['error'];
114 }
115 throw new Google_AuthException("Error fetching OAuth2 access token, message: '$response'", $request->getResponseHttpCode());
116 }
117 }
118
119 $auth_url = $this->createAuthUrl($service['scope']);
120 header('Location: ' . $auth_url);
121 return true;
122 }
123
124 /**
125 * Create a URL to obtain user authorization.
126 * The authorization endpoint allows the user to first
127 * authenticate, and then grant/deny the access request.
128 * @param string $scope The scope is expressed as a list of space-delimited strings.
129 * @return string
130 */
131 public function createAuthUrl($scope) {
132 $params = array(
133 'response_type=code',
134 'redirect_uri=' . urlencode($this->redirectUri),
135 'client_id=' . urlencode($this->client_id),
136 'scope=' . urlencode($scope),
137 'access_type=' . urlencode($this->accessType),
138 'approval_prompt=' . urlencode($this->approvalPrompt),
139 );
140
141 // if the list of scopes contains plus.login, add request_visible_actions
142 // to auth URL
143 if(strpos($scope, 'plus.login') && count($this->requestVisibleActions) > 0) {
144 $params[] = 'request_visible_actions=' .
145 urlencode($this->requestVisibleActions);
146 }
147
148 if (isset($this->state)) {
149 $params[] = 'state=' . urlencode($this->state);
150 }
151 $params = implode('&', $params);
152 return self::OAUTH2_AUTH_URL . "?$params";
153 }
154
155 /**
156 * @param string $token
157 * @throws Google_AuthException
158 */
159 public function setAccessToken($token) {
160 $token = json_decode($token, true);
161 if ($token == null) {
162 throw new Google_AuthException('Could not json decode the token');
163 }
164 if (! isset($token['access_token'])) {
165 throw new Google_AuthException("Invalid token format");
166 }
167 $this->token = $token;
168 }
169
170 public function getAccessToken() {
171 return wp_json_encode($this->token);
172 }
173
174 public function setDeveloperKey($developerKey) {
175 $this->developerKey = $developerKey;
176 }
177
178 public function setState($state) {
179 $this->state = $state;
180 }
181
182 public function setAccessType($accessType) {
183 $this->accessType = $accessType;
184 }
185
186 public function setApprovalPrompt($approvalPrompt) {
187 $this->approvalPrompt = $approvalPrompt;
188 }
189
190 public function setAssertionCredentials(Google_AssertionCredentials $creds) {
191 $this->assertionCredentials = $creds;
192 }
193
194 /**
195 * Include an access_token in a given apiHttpRequest.
196 * @param Google_HttpRequest $request
197 * @return Google_HttpRequest
198 * @throws Google_AuthException
199 */
200 public function sign(Google_HttpRequest $request) {
201 // add the developer key to the request before signing it
202 if ($this->developerKey) {
203 $requestUrl = $request->getUrl();
204 $requestUrl .= (strpos($request->getUrl(), '?') === false) ? '?' : '&';
205 $requestUrl .= 'key=' . urlencode($this->developerKey);
206 $request->setUrl($requestUrl);
207 }
208
209 // Cannot sign the request without an OAuth access token.
210 if (null == $this->token && null == $this->assertionCredentials) {
211 return $request;
212 }
213
214 // Check if the token is set to expire in the next 30 seconds
215 // (or has already expired).
216 if ($this->isAccessTokenExpired()) {
217 if ($this->assertionCredentials) {
218 $this->refreshTokenWithAssertion();
219 } else {
220 if (! array_key_exists('refresh_token', $this->token)) {
221 throw new Google_AuthException("The OAuth 2.0 access token has expired, "
222 . "and a refresh token is not available. Refresh tokens are not "
223 . "returned for responses that were auto-approved.");
224 }
225 $this->refreshToken($this->token['refresh_token']);
226 }
227 }
228
229 // Add the OAuth2 header to the request
230 $request->setRequestHeaders(
231 array('Authorization' => 'Bearer ' . $this->token['access_token'])
232 );
233
234 return $request;
235 }
236
237 /**
238 * Fetches a fresh access token with the given refresh token.
239 * @param string $refreshToken
240 * @return void
241 */
242 public function refreshToken($refreshToken) {
243 $this->refreshTokenRequest(array(
244 'client_id' => $this->client_id,
245 'client_secret' => $this->client_secret,
246 'refresh_token' => $refreshToken,
247 'grant_type' => 'refresh_token'
248 ));
249 }
250
251 /**
252 * Fetches a fresh access token with a given assertion token.
253 * @param Google_AssertionCredentials $assertionCredentials optional.
254 * @return void
255 */
256 public function refreshTokenWithAssertion($assertionCredentials = null) {
257 if (!$assertionCredentials) {
258 $assertionCredentials = $this->assertionCredentials;
259 }
260
261 $this->refreshTokenRequest(array(
262 'grant_type' => 'assertion',
263 'assertion_type' => $assertionCredentials->assertionType,
264 'assertion' => $assertionCredentials->generateAssertion(),
265 ));
266 }
267
268 private function refreshTokenRequest($params) {
269 $http = new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), $params);
270 $request = Google_Client::$io->makeRequest($http);
271
272 $code = $request->getResponseHttpCode();
273 $body = $request->getResponseBody();
274 if (200 == $code) {
275 $token = json_decode($body, true);
276 if ($token == null) {
277 throw new Google_AuthException("Could not json decode the access token");
278 }
279
280 if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
281 throw new Google_AuthException("Invalid token format");
282 }
283
284 $this->token['access_token'] = $token['access_token'];
285 $this->token['expires_in'] = $token['expires_in'];
286 $this->token['created'] = time();
287 } else {
288 throw new Google_AuthException("Error refreshing the OAuth2 token, message: '$body'", $code);
289 }
290 }
291
292 /**
293 * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
294 * token, if a token isn't provided.
295 * @throws Google_AuthException
296 * @param string|null $token The token (access token or a refresh token) that should be revoked.
297 * @return boolean Returns True if the revocation was successful, otherwise False.
298 */
299 public function revokeToken($token = null) {
300 if (!$token) {
301 $token = $this->token['access_token'];
302 }
303 $request = new Google_HttpRequest(self::OAUTH2_REVOKE_URI, 'POST', array(), "token=$token");
304 $response = Google_Client::$io->makeRequest($request);
305 $code = $response->getResponseHttpCode();
306 if ($code == 200) {
307 $this->token = null;
308 return true;
309 }
310
311 return false;
312 }
313
314 /**
315 * Returns if the access_token is expired.
316 * @return bool Returns True if the access_token is expired.
317 */
318 public function isAccessTokenExpired() {
319 if (null == $this->token) {
320 return true;
321 }
322
323 // If the token is set to expire in the next 30 seconds.
324 $expired = ($this->token['created']
325 + ($this->token['expires_in'] - 30)) < time();
326
327 return $expired;
328 }
329
330 // Gets federated sign-on certificates to use for verifying identity tokens.
331 // Returns certs as array structure, where keys are key ids, and values
332 // are PEM encoded certificates.
333 private function getFederatedSignOnCerts() {
334 // This relies on makeRequest caching certificate responses.
335 $request = Google_Client::$io->makeRequest(new Google_HttpRequest(
336 self::OAUTH2_FEDERATED_SIGNON_CERTS_URL));
337 if ($request->getResponseHttpCode() == 200) {
338 $certs = json_decode($request->getResponseBody(), true);
339 if ($certs) {
340 return $certs;
341 }
342 }
343 throw new Google_AuthException(
344 "Failed to retrieve verification certificates: '" .
345 $request->getResponseBody() . "'.",
346 $request->getResponseHttpCode());
347 }
348
349 /**
350 * Verifies an id token and returns the authenticated apiLoginTicket.
351 * Throws an exception if the id token is not valid.
352 * The audience parameter can be used to control which id tokens are
353 * accepted. By default, the id token must have been issued to this OAuth2 client.
354 *
355 * @param $id_token
356 * @param $audience
357 * @return Google_LoginTicket
358 */
359 public function verifyIdToken($id_token = null, $audience = null) {
360 if (!$id_token) {
361 $id_token = $this->token['id_token'];
362 }
363
364 $certs = $this->getFederatedSignonCerts();
365 if (!$audience) {
366 $audience = $this->client_id;
367 }
368 return $this->verifySignedJwtWithCerts($id_token, $certs, $audience);
369 }
370
371 // Verifies the id token, returns the verified token contents.
372 // Visible for testing.
373 function verifySignedJwtWithCerts($jwt, $certs, $required_audience) {
374 $segments = explode(".", $jwt);
375 if (count($segments) != 3) {
376 throw new Google_AuthException("Wrong number of segments in token: $jwt");
377 }
378 $signed = $segments[0] . "." . $segments[1];
379 $signature = Google_Utils::urlSafeB64Decode($segments[2]);
380
381 // Parse envelope.
382 $envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
383 if (!$envelope) {
384 throw new Google_AuthException("Can't parse token envelope: " . $segments[0]);
385 }
386
387 // Parse token
388 $json_body = Google_Utils::urlSafeB64Decode($segments[1]);
389 $payload = json_decode($json_body, true);
390 if (!$payload) {
391 throw new Google_AuthException("Can't parse token payload: " . $segments[1]);
392 }
393
394 // Check signature
395 $verified = false;
396 foreach ($certs as $keyName => $pem) {
397 $public_key = new Google_PemVerifier($pem);
398 if ($public_key->verify($signed, $signature)) {
399 $verified = true;
400 break;
401 }
402 }
403
404 if (!$verified) {
405 throw new Google_AuthException("Invalid token signature: $jwt");
406 }
407
408 // Check issued-at timestamp
409 $iat = 0;
410 if (array_key_exists("iat", $payload)) {
411 $iat = $payload["iat"];
412 }
413 if (!$iat) {
414 throw new Google_AuthException("No issue time in token: $json_body");
415 }
416 $earliest = $iat - self::CLOCK_SKEW_SECS;
417
418 // Check expiration timestamp
419 $now = time();
420 $exp = 0;
421 if (array_key_exists("exp", $payload)) {
422 $exp = $payload["exp"];
423 }
424 if (!$exp) {
425 throw new Google_AuthException("No expiration time in token: $json_body");
426 }
427 if ($exp >= $now + self::MAX_TOKEN_LIFETIME_SECS) {
428 throw new Google_AuthException(
429 "Expiration time too far in future: $json_body");
430 }
431
432 $latest = $exp + self::CLOCK_SKEW_SECS;
433 if ($now < $earliest) {
434 throw new Google_AuthException(
435 "Token used too early, $now < $earliest: $json_body");
436 }
437 if ($now > $latest) {
438 throw new Google_AuthException(
439 "Token used too late, $now > $latest: $json_body");
440 }
441
442 // TODO(beaton): check issuer field?
443
444 // Check audience
445 $aud = $payload["aud"];
446 if ($aud != $required_audience) {
447 throw new Google_AuthException("Wrong recipient, $aud != $required_audience: $json_body");
448 }
449
450 // All good.
451 return new Google_LoginTicket($envelope, $payload);
452 }
453 }
454