Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
 
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
use Firebase\JWT\ExpiredException as ExpiredExceptionV3;
20
use GuzzleHttp\Client;
21
use GuzzleHttp\ClientInterface;
22
use Psr\Cache\CacheItemPoolInterface;
23
use Google\Auth\Cache\MemoryCacheItemPool;
24
use Stash\Driver\FileSystem;
25
use Stash\Pool;
26
 
27
/**
28
 * Wrapper around Google Access Tokens which provides convenience functions
29
 *
30
 */
31
class Google_AccessToken_Verify
32
{
33
  const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
34
  const OAUTH2_ISSUER = 'accounts.google.com';
35
  const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
36
 
37
  /**
38
   * @var GuzzleHttp\ClientInterface The http client
39
   */
40
  private $http;
41
 
42
  /**
43
   * @var Psr\Cache\CacheItemPoolInterface cache class
44
   */
45
  private $cache;
46
 
47
  /**
48
   * Instantiates the class, but does not initiate the login flow, leaving it
49
   * to the discretion of the caller.
50
   */
51
  public function __construct(
52
      ClientInterface $http = null,
53
      CacheItemPoolInterface $cache = null,
54
      $jwt = null
55
  ) {
56
    if (null === $http) {
57
      $http = new Client();
58
    }
59
 
60
    if (null === $cache) {
61
      $cache = new MemoryCacheItemPool;
62
    }
63
 
64
    $this->http = $http;
65
    $this->cache = $cache;
66
    $this->jwt = $jwt ?: $this->getJwtService();
67
  }
68
 
69
  /**
70
   * Verifies an id token and returns the authenticated apiLoginTicket.
71
   * Throws an exception if the id token is not valid.
72
   * The audience parameter can be used to control which id tokens are
73
   * accepted.  By default, the id token must have been issued to this OAuth2 client.
74
   *
75
   * @param $audience
76
   * @return array the token payload, if successful
77
   */
78
  public function verifyIdToken($idToken, $audience = null)
79
  {
80
    if (empty($idToken)) {
81
      throw new LogicException('id_token cannot be null');
82
    }
83
 
84
    // set phpseclib constants if applicable
85
    $this->setPhpsecConstants();
86
 
87
    // Check signature
88
    $certs = $this->getFederatedSignOnCerts();
89
    foreach ($certs as $cert) {
90
      $bigIntClass = $this->getBigIntClass();
91
      $rsaClass = $this->getRsaClass();
92
      $modulus = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['n']), 256);
93
      $exponent = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['e']), 256);
94
 
95
      $rsa = new $rsaClass();
96
      $rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
97
 
98
      try {
99
        $payload = $this->jwt->decode(
100
            $idToken,
101
            $rsa->getPublicKey(),
102
            array('RS256')
103
        );
104
 
105
        if (property_exists($payload, 'aud')) {
106
          if ($audience && $payload->aud != $audience) {
107
            return false;
108
          }
109
        }
110
 
111
        // support HTTP and HTTPS issuers
112
        // @see https://developers.google.com/identity/sign-in/web/backend-auth
113
        $issuers = array(self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS);
114
        if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {
115
          return false;
116
        }
117
 
118
        return (array) $payload;
119
      } catch (ExpiredException $e) {
120
        return false;
121
      } catch (ExpiredExceptionV3 $e) {
122
        return false;
123
      } catch (DomainException $e) {
124
        // continue
125
      }
126
    }
127
 
128
    return false;
129
  }
130
 
131
  private function getCache()
132
  {
133
    return $this->cache;
134
  }
135
 
136
  /**
137
   * Retrieve and cache a certificates file.
138
   *
139
   * @param $url string location
140
   * @throws Google_Exception
141
   * @return array certificates
142
   */
143
  private function retrieveCertsFromLocation($url)
144
  {
145
    // If we're retrieving a local file, just grab it.
146
    if (0 !== strpos($url, 'http')) {
147
      if (!$file = file_get_contents($url)) {
148
        throw new Google_Exception(
149
            "Failed to retrieve verification certificates: '" .
150
            $url . "'."
151
        );
152
      }
153
 
154
      return json_decode($file, true);
155
    }
156
 
157
    $response = $this->http->get($url);
158
 
159
    if ($response->getStatusCode() == 200) {
160
      return json_decode((string) $response->getBody(), true);
161
    }
162
    throw new Google_Exception(
163
        sprintf(
164
            'Failed to retrieve verification certificates: "%s".',
165
            $response->getBody()->getContents()
166
        ),
167
        $response->getStatusCode()
168
    );
169
  }
170
 
171
  // Gets federated sign-on certificates to use for verifying identity tokens.
172
  // Returns certs as array structure, where keys are key ids, and values
173
  // are PEM encoded certificates.
174
  private function getFederatedSignOnCerts()
175
  {
176
    $certs = null;
177
    if ($cache = $this->getCache()) {
178
      $cacheItem = $cache->getItem('federated_signon_certs_v3', 3600);
179
      $certs = $cacheItem->get();
180
    }
181
 
182
 
183
    if (!$certs) {
184
      $certs = $this->retrieveCertsFromLocation(
185
          self::FEDERATED_SIGNON_CERT_URL
186
      );
187
 
188
      if ($cache) {
189
        $cacheItem->set($certs);
190
        $cache->save($cacheItem);
191
      }
192
    }
193
 
194
    if (!isset($certs['keys'])) {
195
      throw new InvalidArgumentException(
196
          'federated sign-on certs expects "keys" to be set'
197
      );
198
    }
199
 
200
    return $certs['keys'];
201
  }
202
 
203
  private function getJwtService()
204
  {
205
    $jwtClass = 'JWT';
206
    if (class_exists('\Firebase\JWT\JWT')) {
207
      $jwtClass = 'Firebase\JWT\JWT';
208
    }
209
 
210
    if (property_exists($jwtClass, 'leeway')) {
211
      // adds 1 second to JWT leeway
212
      // @see https://github.com/google/google-api-php-client/issues/827
213
      $jwtClass::$leeway = 1;
214
    }
215
 
216
    return new $jwtClass;
217
  }
218
 
219
  private function getRsaClass()
220
  {
221
    if (class_exists('phpseclib\Crypt\RSA')) {
222
      return 'phpseclib\Crypt\RSA';
223
    }
224
 
225
    return 'Crypt_RSA';
226
  }
227
 
228
  private function getBigIntClass()
229
  {
230
    if (class_exists('phpseclib\Math\BigInteger')) {
231
      return 'phpseclib\Math\BigInteger';
232
    }
233
 
234
    return 'Math_BigInteger';
235
  }
236
 
237
  private function getOpenSslConstant()
238
  {
239
    if (class_exists('phpseclib\Crypt\RSA')) {
240
      return 'phpseclib\Crypt\RSA::MODE_OPENSSL';
241
    }
242
 
243
    if (class_exists('Crypt_RSA')) {
244
      return 'CRYPT_RSA_MODE_OPENSSL';
245
    }
246
 
247
    throw new \Exception('Cannot find RSA class');
248
  }
249
 
250
  /**
251
   * phpseclib calls "phpinfo" by default, which requires special
252
   * whitelisting in the AppEngine VM environment. This function
253
   * sets constants to bypass the need for phpseclib to check phpinfo
254
   *
255
   * @see phpseclib/Math/BigInteger
256
   * @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85
257
   */
258
  private function setPhpsecConstants()
259
  {
260
    if (filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN)) {
261
      if (!defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
262
        define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
263
      }
264
      if (!defined('CRYPT_RSA_MODE')) {
265
        define('CRYPT_RSA_MODE', constant($this->getOpenSslConstant()));
266
      }
267
    }
268
  }
269
}