PluginProbe
ManageWP Worker / 3.9.23
ManageWP Worker v3.9.23
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / lib / google-api-client / auth / Google_OAuth2.php

Google_OAuth2.php in ManageWP Worker 3.9.23, at lib/google-api-client/auth/Google_OAuth2.php

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