FetchAuthTokenCache.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <?php
  2. /*
  3. * Copyright 2010 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. namespace Google\Auth;
  18. use Psr\Cache\CacheItemPoolInterface;
  19. /**
  20. * A class to implement caching for any object implementing
  21. * FetchAuthTokenInterface
  22. */
  23. class FetchAuthTokenCache implements
  24. FetchAuthTokenInterface,
  25. GetQuotaProjectInterface,
  26. SignBlobInterface,
  27. ProjectIdProviderInterface,
  28. UpdateMetadataInterface
  29. {
  30. use CacheTrait;
  31. /**
  32. * @var FetchAuthTokenInterface
  33. */
  34. private $fetcher;
  35. /**
  36. * @param FetchAuthTokenInterface $fetcher A credentials fetcher
  37. * @param array<mixed> $cacheConfig Configuration for the cache
  38. * @param CacheItemPoolInterface $cache
  39. */
  40. public function __construct(
  41. FetchAuthTokenInterface $fetcher,
  42. array $cacheConfig = null,
  43. CacheItemPoolInterface $cache
  44. ) {
  45. $this->fetcher = $fetcher;
  46. $this->cache = $cache;
  47. $this->cacheConfig = array_merge([
  48. 'lifetime' => 1500,
  49. 'prefix' => '',
  50. ], (array) $cacheConfig);
  51. }
  52. /**
  53. * Implements FetchAuthTokenInterface#fetchAuthToken.
  54. *
  55. * Checks the cache for a valid auth token and fetches the auth tokens
  56. * from the supplied fetcher.
  57. *
  58. * @param callable $httpHandler callback which delivers psr7 request
  59. * @return array<mixed> the response
  60. * @throws \Exception
  61. */
  62. public function fetchAuthToken(callable $httpHandler = null)
  63. {
  64. if ($cached = $this->fetchAuthTokenFromCache()) {
  65. return $cached;
  66. }
  67. $auth_token = $this->fetcher->fetchAuthToken($httpHandler);
  68. $this->saveAuthTokenInCache($auth_token);
  69. return $auth_token;
  70. }
  71. /**
  72. * @return string
  73. */
  74. public function getCacheKey()
  75. {
  76. return $this->getFullCacheKey($this->fetcher->getCacheKey());
  77. }
  78. /**
  79. * @return array<mixed>|null
  80. */
  81. public function getLastReceivedToken()
  82. {
  83. return $this->fetcher->getLastReceivedToken();
  84. }
  85. /**
  86. * Get the client name from the fetcher.
  87. *
  88. * @param callable $httpHandler An HTTP handler to deliver PSR7 requests.
  89. * @return string
  90. */
  91. public function getClientName(callable $httpHandler = null)
  92. {
  93. if (!$this->fetcher instanceof SignBlobInterface) {
  94. throw new \RuntimeException(
  95. 'Credentials fetcher does not implement ' .
  96. 'Google\Auth\SignBlobInterface'
  97. );
  98. }
  99. return $this->fetcher->getClientName($httpHandler);
  100. }
  101. /**
  102. * Sign a blob using the fetcher.
  103. *
  104. * @param string $stringToSign The string to sign.
  105. * @param bool $forceOpenSsl Require use of OpenSSL for local signing. Does
  106. * not apply to signing done using external services. **Defaults to**
  107. * `false`.
  108. * @return string The resulting signature.
  109. * @throws \RuntimeException If the fetcher does not implement
  110. * `Google\Auth\SignBlobInterface`.
  111. */
  112. public function signBlob($stringToSign, $forceOpenSsl = false)
  113. {
  114. if (!$this->fetcher instanceof SignBlobInterface) {
  115. throw new \RuntimeException(
  116. 'Credentials fetcher does not implement ' .
  117. 'Google\Auth\SignBlobInterface'
  118. );
  119. }
  120. // Pass the access token from cache to GCECredentials for signing a blob.
  121. // This saves a call to the metadata server when a cached token exists.
  122. if ($this->fetcher instanceof Credentials\GCECredentials) {
  123. $cached = $this->fetchAuthTokenFromCache();
  124. $accessToken = isset($cached['access_token']) ? $cached['access_token'] : null;
  125. return $this->fetcher->signBlob($stringToSign, $forceOpenSsl, $accessToken);
  126. }
  127. return $this->fetcher->signBlob($stringToSign, $forceOpenSsl);
  128. }
  129. /**
  130. * Get the quota project used for this API request from the credentials
  131. * fetcher.
  132. *
  133. * @return string|null
  134. */
  135. public function getQuotaProject()
  136. {
  137. if ($this->fetcher instanceof GetQuotaProjectInterface) {
  138. return $this->fetcher->getQuotaProject();
  139. }
  140. return null;
  141. }
  142. /*
  143. * Get the Project ID from the fetcher.
  144. *
  145. * @param callable $httpHandler Callback which delivers psr7 request
  146. * @return string|null
  147. * @throws \RuntimeException If the fetcher does not implement
  148. * `Google\Auth\ProvidesProjectIdInterface`.
  149. */
  150. public function getProjectId(callable $httpHandler = null)
  151. {
  152. if (!$this->fetcher instanceof ProjectIdProviderInterface) {
  153. throw new \RuntimeException(
  154. 'Credentials fetcher does not implement ' .
  155. 'Google\Auth\ProvidesProjectIdInterface'
  156. );
  157. }
  158. return $this->fetcher->getProjectId($httpHandler);
  159. }
  160. /**
  161. * Updates metadata with the authorization token.
  162. *
  163. * @param array<mixed> $metadata metadata hashmap
  164. * @param string $authUri optional auth uri
  165. * @param callable $httpHandler callback which delivers psr7 request
  166. * @return array<mixed> updated metadata hashmap
  167. * @throws \RuntimeException If the fetcher does not implement
  168. * `Google\Auth\UpdateMetadataInterface`.
  169. */
  170. public function updateMetadata(
  171. $metadata,
  172. $authUri = null,
  173. callable $httpHandler = null
  174. ) {
  175. if (!$this->fetcher instanceof UpdateMetadataInterface) {
  176. throw new \RuntimeException(
  177. 'Credentials fetcher does not implement ' .
  178. 'Google\Auth\UpdateMetadataInterface'
  179. );
  180. }
  181. $cached = $this->fetchAuthTokenFromCache($authUri);
  182. if ($cached) {
  183. // Set the access token in the `Authorization` metadata header so
  184. // the downstream call to updateMetadata know they don't need to
  185. // fetch another token.
  186. if (isset($cached['access_token'])) {
  187. $metadata[self::AUTH_METADATA_KEY] = [
  188. 'Bearer ' . $cached['access_token']
  189. ];
  190. }
  191. }
  192. $newMetadata = $this->fetcher->updateMetadata(
  193. $metadata,
  194. $authUri,
  195. $httpHandler
  196. );
  197. if (!$cached && $token = $this->fetcher->getLastReceivedToken()) {
  198. $this->saveAuthTokenInCache($token, $authUri);
  199. }
  200. return $newMetadata;
  201. }
  202. /**
  203. * @param string|null $authUri
  204. * @return array<mixed>|null
  205. */
  206. private function fetchAuthTokenFromCache($authUri = null)
  207. {
  208. // Use the cached value if its available.
  209. //
  210. // TODO: correct caching; update the call to setCachedValue to set the expiry
  211. // to the value returned with the auth token.
  212. //
  213. // TODO: correct caching; enable the cache to be cleared.
  214. // if $authUri is set, use it as the cache key
  215. $cacheKey = $authUri
  216. ? $this->getFullCacheKey($authUri)
  217. : $this->fetcher->getCacheKey();
  218. $cached = $this->getCachedValue($cacheKey);
  219. if (is_array($cached)) {
  220. if (empty($cached['expires_at'])) {
  221. // If there is no expiration data, assume token is not expired.
  222. // (for JwtAccess and ID tokens)
  223. return $cached;
  224. }
  225. if (time() < $cached['expires_at']) {
  226. // access token is not expired
  227. return $cached;
  228. }
  229. }
  230. return null;
  231. }
  232. /**
  233. * @param array<mixed> $authToken
  234. * @param string|null $authUri
  235. * @return void
  236. */
  237. private function saveAuthTokenInCache($authToken, $authUri = null)
  238. {
  239. if (isset($authToken['access_token']) ||
  240. isset($authToken['id_token'])) {
  241. // if $authUri is set, use it as the cache key
  242. $cacheKey = $authUri
  243. ? $this->getFullCacheKey($authUri)
  244. : $this->fetcher->getCacheKey();
  245. $this->setCachedValue($cacheKey, $authToken);
  246. }
  247. }
  248. }