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