CredentialsLoader.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. /*
  3. * Copyright 2015 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 Google\Auth\Credentials\InsecureCredentials;
  19. use Google\Auth\Credentials\ServiceAccountCredentials;
  20. use Google\Auth\Credentials\UserRefreshCredentials;
  21. use RuntimeException;
  22. use UnexpectedValueException;
  23. /**
  24. * CredentialsLoader contains the behaviour used to locate and find default
  25. * credentials files on the file system.
  26. */
  27. abstract class CredentialsLoader implements
  28. FetchAuthTokenInterface,
  29. UpdateMetadataInterface
  30. {
  31. const TOKEN_CREDENTIAL_URI = 'https://oauth2.googleapis.com/token';
  32. const ENV_VAR = 'GOOGLE_APPLICATION_CREDENTIALS';
  33. const WELL_KNOWN_PATH = 'gcloud/application_default_credentials.json';
  34. const NON_WINDOWS_WELL_KNOWN_PATH_BASE = '.config';
  35. const MTLS_WELL_KNOWN_PATH = '.secureConnect/context_aware_metadata.json';
  36. const MTLS_CERT_ENV_VAR = 'GOOGLE_API_USE_CLIENT_CERTIFICATE';
  37. /**
  38. * @param string $cause
  39. * @return string
  40. */
  41. private static function unableToReadEnv($cause)
  42. {
  43. $msg = 'Unable to read the credential file specified by ';
  44. $msg .= ' GOOGLE_APPLICATION_CREDENTIALS: ';
  45. $msg .= $cause;
  46. return $msg;
  47. }
  48. /**
  49. * @return bool
  50. */
  51. private static function isOnWindows()
  52. {
  53. return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
  54. }
  55. /**
  56. * Load a JSON key from the path specified in the environment.
  57. *
  58. * Load a JSON key from the path specified in the environment
  59. * variable GOOGLE_APPLICATION_CREDENTIALS. Return null if
  60. * GOOGLE_APPLICATION_CREDENTIALS is not specified.
  61. *
  62. * @return array<mixed>|null JSON key | null
  63. */
  64. public static function fromEnv()
  65. {
  66. $path = getenv(self::ENV_VAR);
  67. if (empty($path)) {
  68. return null;
  69. }
  70. if (!file_exists($path)) {
  71. $cause = 'file ' . $path . ' does not exist';
  72. throw new \DomainException(self::unableToReadEnv($cause));
  73. }
  74. $jsonKey = file_get_contents($path);
  75. return json_decode((string) $jsonKey, true);
  76. }
  77. /**
  78. * Load a JSON key from a well known path.
  79. *
  80. * The well known path is OS dependent:
  81. *
  82. * * windows: %APPDATA%/gcloud/application_default_credentials.json
  83. * * others: $HOME/.config/gcloud/application_default_credentials.json
  84. *
  85. * If the file does not exist, this returns null.
  86. *
  87. * @return array<mixed>|null JSON key | null
  88. */
  89. public static function fromWellKnownFile()
  90. {
  91. $rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME';
  92. $path = [getenv($rootEnv)];
  93. if (!self::isOnWindows()) {
  94. $path[] = self::NON_WINDOWS_WELL_KNOWN_PATH_BASE;
  95. }
  96. $path[] = self::WELL_KNOWN_PATH;
  97. $path = implode(DIRECTORY_SEPARATOR, $path);
  98. if (!file_exists($path)) {
  99. return null;
  100. }
  101. $jsonKey = file_get_contents($path);
  102. return json_decode((string) $jsonKey, true);
  103. }
  104. /**
  105. * Create a new Credentials instance.
  106. *
  107. * @param string|string[] $scope the scope of the access request, expressed
  108. * either as an Array or as a space-delimited String.
  109. * @param array<mixed> $jsonKey the JSON credentials.
  110. * @param string|string[] $defaultScope The default scope to use if no
  111. * user-defined scopes exist, expressed either as an Array or as a
  112. * space-delimited string.
  113. *
  114. * @return ServiceAccountCredentials|UserRefreshCredentials
  115. */
  116. public static function makeCredentials(
  117. $scope,
  118. array $jsonKey,
  119. $defaultScope = null
  120. ) {
  121. if (!array_key_exists('type', $jsonKey)) {
  122. throw new \InvalidArgumentException('json key is missing the type field');
  123. }
  124. if ($jsonKey['type'] == 'service_account') {
  125. // Do not pass $defaultScope to ServiceAccountCredentials
  126. return new ServiceAccountCredentials($scope, $jsonKey);
  127. }
  128. if ($jsonKey['type'] == 'authorized_user') {
  129. $anyScope = $scope ?: $defaultScope;
  130. return new UserRefreshCredentials($anyScope, $jsonKey);
  131. }
  132. throw new \InvalidArgumentException('invalid value in the type field');
  133. }
  134. /**
  135. * Create an authorized HTTP Client from an instance of FetchAuthTokenInterface.
  136. *
  137. * @param FetchAuthTokenInterface $fetcher is used to fetch the auth token
  138. * @param array<mixed> $httpClientOptions (optional) Array of request options to apply.
  139. * @param callable $httpHandler (optional) http client to fetch the token.
  140. * @param callable $tokenCallback (optional) function to be called when a new token is fetched.
  141. * @return \GuzzleHttp\Client
  142. */
  143. public static function makeHttpClient(
  144. FetchAuthTokenInterface $fetcher,
  145. array $httpClientOptions = [],
  146. callable $httpHandler = null,
  147. callable $tokenCallback = null
  148. ) {
  149. $middleware = new Middleware\AuthTokenMiddleware(
  150. $fetcher,
  151. $httpHandler,
  152. $tokenCallback
  153. );
  154. $stack = \GuzzleHttp\HandlerStack::create();
  155. $stack->push($middleware);
  156. return new \GuzzleHttp\Client([
  157. 'handler' => $stack,
  158. 'auth' => 'google_auth',
  159. ] + $httpClientOptions);
  160. }
  161. /**
  162. * Create a new instance of InsecureCredentials.
  163. *
  164. * @return InsecureCredentials
  165. */
  166. public static function makeInsecureCredentials()
  167. {
  168. return new InsecureCredentials();
  169. }
  170. /**
  171. * export a callback function which updates runtime metadata.
  172. *
  173. * @return callable updateMetadata function
  174. * @deprecated
  175. */
  176. public function getUpdateMetadataFunc()
  177. {
  178. return array($this, 'updateMetadata');
  179. }
  180. /**
  181. * Updates metadata with the authorization token.
  182. *
  183. * @param array<mixed> $metadata metadata hashmap
  184. * @param string $authUri optional auth uri
  185. * @param callable $httpHandler callback which delivers psr7 request
  186. * @return array<mixed> updated metadata hashmap
  187. */
  188. public function updateMetadata(
  189. $metadata,
  190. $authUri = null,
  191. callable $httpHandler = null
  192. ) {
  193. if (isset($metadata[self::AUTH_METADATA_KEY])) {
  194. // Auth metadata has already been set
  195. return $metadata;
  196. }
  197. $result = $this->fetchAuthToken($httpHandler);
  198. if (!isset($result['access_token'])) {
  199. return $metadata;
  200. }
  201. $metadata_copy = $metadata;
  202. $metadata_copy[self::AUTH_METADATA_KEY] = array('Bearer ' . $result['access_token']);
  203. return $metadata_copy;
  204. }
  205. /**
  206. * Gets a callable which returns the default device certification.
  207. *
  208. * @throws UnexpectedValueException
  209. * @return callable|null
  210. */
  211. public static function getDefaultClientCertSource()
  212. {
  213. if (!$clientCertSourceJson = self::loadDefaultClientCertSourceFile()) {
  214. return null;
  215. }
  216. $clientCertSourceCmd = $clientCertSourceJson['cert_provider_command'];
  217. return function () use ($clientCertSourceCmd) {
  218. $cmd = array_map('escapeshellarg', $clientCertSourceCmd);
  219. exec(implode(' ', $cmd), $output, $returnVar);
  220. if (0 === $returnVar) {
  221. return implode(PHP_EOL, $output);
  222. }
  223. throw new RuntimeException(
  224. '"cert_provider_command" failed with a nonzero exit code'
  225. );
  226. };
  227. }
  228. /**
  229. * Determines whether or not the default device certificate should be loaded.
  230. *
  231. * @return bool
  232. */
  233. public static function shouldLoadClientCertSource()
  234. {
  235. return filter_var(getenv(self::MTLS_CERT_ENV_VAR), FILTER_VALIDATE_BOOLEAN);
  236. }
  237. /**
  238. * @return array{cert_provider_command:string[]}|null
  239. */
  240. private static function loadDefaultClientCertSourceFile()
  241. {
  242. $rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME';
  243. $path = sprintf('%s/%s', getenv($rootEnv), self::MTLS_WELL_KNOWN_PATH);
  244. if (!file_exists($path)) {
  245. return null;
  246. }
  247. $jsonKey = file_get_contents($path);
  248. $clientCertSourceJson = json_decode((string) $jsonKey, true);
  249. if (!$clientCertSourceJson) {
  250. throw new UnexpectedValueException('Invalid client cert source JSON');
  251. }
  252. if (!isset($clientCertSourceJson['cert_provider_command'])) {
  253. throw new UnexpectedValueException(
  254. 'cert source requires "cert_provider_command"'
  255. );
  256. }
  257. if (!is_array($clientCertSourceJson['cert_provider_command'])) {
  258. throw new UnexpectedValueException(
  259. 'cert source expects "cert_provider_command" to be an array'
  260. );
  261. }
  262. return $clientCertSourceJson;
  263. }
  264. }