PluginProbe
Gmail SMTP / trunk
Gmail SMTP vtrunk
1.2.3.21 1.2.3.20 trunk 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.9 1.2.0 1.2.3.14 1.2.3.15 1.2.3.16 1.2.3.18 1.2.3.5
gmail-smtp / google-api-php-client / vendor / google / auth / src / CredentialsLoader.php

CredentialsLoader.php in Gmail SMTP trunk, at google-api-php-client/vendor/google/auth/src/CredentialsLoader.php

337 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\ExternalAccountAuthorizedUserCredentials;
21 use Google\Auth\Credentials\ExternalAccountCredentials;
22 use Google\Auth\Credentials\ImpersonatedServiceAccountCredentials;
23 use Google\Auth\Credentials\InsecureCredentials;
24 use Google\Auth\Credentials\ServiceAccountCredentials;
25 use Google\Auth\Credentials\UserRefreshCredentials;
26 use RuntimeException;
27 use UnexpectedValueException;
28
29 /**
30 * CredentialsLoader contains the behaviour used to locate and find default
31 * credentials files on the file system.
32 */
33 abstract class CredentialsLoader implements
34 GetUniverseDomainInterface,
35 FetchAuthTokenInterface,
36 UpdateMetadataInterface
37 {
38 use UpdateMetadataTrait;
39
40 const TOKEN_CREDENTIAL_URI = 'https://oauth2.googleapis.com/token';
41 const ENV_VAR = 'GOOGLE_APPLICATION_CREDENTIALS';
42 const QUOTA_PROJECT_ENV_VAR = 'GOOGLE_CLOUD_QUOTA_PROJECT';
43 const WELL_KNOWN_PATH = 'gcloud/application_default_credentials.json';
44 const NON_WINDOWS_WELL_KNOWN_PATH_BASE = '.config';
45 const MTLS_WELL_KNOWN_PATH = '.secureConnect/context_aware_metadata.json';
46 const MTLS_CERT_ENV_VAR = 'GOOGLE_API_USE_CLIENT_CERTIFICATE';
47
48 /**
49 * @param string $cause
50 * @return string
51 */
52 private static function unableToReadEnv($cause)
53 {
54 $msg = 'Unable to read the credential file specified by ';
55 $msg .= ' GOOGLE_APPLICATION_CREDENTIALS: ';
56 $msg .= $cause;
57
58 return $msg;
59 }
60
61 /**
62 * @return bool
63 */
64 private static function isOnWindows()
65 {
66 return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
67 }
68
69 /**
70 * Load a JSON key from the path specified in the environment.
71 *
72 * Load a JSON key from the path specified in the environment
73 * variable GOOGLE_APPLICATION_CREDENTIALS. Return null if
74 * GOOGLE_APPLICATION_CREDENTIALS is not specified.
75 *
76 * @return array<mixed>|null JSON key | null
77 */
78 public static function fromEnv()
79 {
80 $path = self::getEnv(self::ENV_VAR);
81 if (empty($path)) {
82 return null;
83 }
84 if (!file_exists($path)) {
85 $cause = 'file ' . $path . ' does not exist';
86 throw new \DomainException(self::unableToReadEnv($cause));
87 }
88 $jsonKey = file_get_contents($path);
89
90 return json_decode((string) $jsonKey, true);
91 }
92
93 /**
94 * Load a JSON key from a well known path.
95 *
96 * The well known path is OS dependent:
97 *
98 * * windows: %APPDATA%/gcloud/application_default_credentials.json
99 * * others: $HOME/.config/gcloud/application_default_credentials.json
100 *
101 * If the file does not exist, this returns null.
102 *
103 * @return array<mixed>|null JSON key | null
104 */
105 public static function fromWellKnownFile()
106 {
107 $rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME';
108 $path = [self::getEnv($rootEnv)];
109 if (!self::isOnWindows()) {
110 $path[] = self::NON_WINDOWS_WELL_KNOWN_PATH_BASE;
111 }
112 $path[] = self::WELL_KNOWN_PATH;
113 $path = implode(DIRECTORY_SEPARATOR, $path);
114 if (!file_exists($path)) {
115 return null;
116 }
117 $jsonKey = file_get_contents($path);
118 return json_decode((string) $jsonKey, true);
119 }
120
121 /**
122 * Create a new Credentials instance.
123 *
124 * @deprecated This method is being deprecated because of a potential security risk.
125 *
126 * This method does not validate the credential configuration. The security
127 * risk occurs when a credential configuration is accepted from a source
128 * that is not under your control and used without validation on your side.
129 *
130 * If you know that you will be loading credential configurations of a
131 * specific type, it is recommended to use a credential-type-specific
132 * method.
133 * This will ensure that an unexpected credential type with potential for
134 * malicious intent is not loaded unintentionally. You might still have to do
135 * validation for certain credential types. Please follow the recommendation
136 * for that method. For example, if you want to load only service accounts,
137 * you can create the {@see ServiceAccountCredentials} explicitly:
138 *
139 * ```
140 * use Google\Auth\Credentials\ServiceAccountCredentials;
141 * $creds = new ServiceAccountCredentials($scopes, $json);
142 * ```
143 *
144 * If you are loading your credential configuration from an untrusted source and have
145 * not mitigated the risks (e.g. by validating the configuration yourself), make
146 * these changes as soon as possible to prevent security risks to your environment.
147 *
148 * Regardless of the method used, it is always your responsibility to validate
149 * configurations received from external sources.
150 *
151 * @see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
152 *
153 * @param string|string[] $scope
154 * @param array<mixed> $jsonKey
155 * @param string|string[] $defaultScope
156 * @param bool $enableRegionalAccessBoundary Lookup and include the regional access boundary header.
157 * @return ServiceAccountCredentials|UserRefreshCredentials|ImpersonatedServiceAccountCredentials|ExternalAccountCredentials|ExternalAccountAuthorizedUserCredentials
158 */
159 public static function makeCredentials(
160 $scope,
161 array $jsonKey,
162 $defaultScope = null,
163 bool $enableRegionalAccessBoundary = false
164 ) {
165 if (!array_key_exists('type', $jsonKey)) {
166 throw new \InvalidArgumentException('json key is missing the type field');
167 }
168
169 if ($jsonKey['type'] == 'service_account') {
170 // Do not pass $defaultScope to ServiceAccountCredentials
171 return new ServiceAccountCredentials($scope, $jsonKey, enableRegionalAccessBoundary: $enableRegionalAccessBoundary);
172 }
173
174 if ($jsonKey['type'] == 'authorized_user') {
175 $anyScope = $scope ?: $defaultScope;
176 return new UserRefreshCredentials($anyScope, $jsonKey);
177 }
178
179 if ($jsonKey['type'] == 'impersonated_service_account') {
180 return new ImpersonatedServiceAccountCredentials(
181 $scope,
182 $jsonKey,
183 defaultScope: $defaultScope,
184 enableRegionalAccessBoundary: $enableRegionalAccessBoundary
185 );
186 }
187
188 if ($jsonKey['type'] == 'external_account') {
189 $anyScope = $scope ?: $defaultScope;
190 return new ExternalAccountCredentials($anyScope, $jsonKey, $enableRegionalAccessBoundary);
191 }
192
193 if ($jsonKey['type'] == 'external_account_authorized_user') {
194 $anyScope = $scope ?: $defaultScope;
195 return new ExternalAccountAuthorizedUserCredentials($anyScope, $jsonKey);
196 }
197
198 if ($jsonKey['type'] == 'external_account_authorized_user') {
199 $anyScope = $scope ?: $defaultScope;
200 return new ExternalAccountAuthorizedUserCredentials($anyScope, $jsonKey);
201 }
202
203 throw new \InvalidArgumentException('invalid value in the type field');
204 }
205
206 /**
207 * Create an authorized HTTP Client from an instance of FetchAuthTokenInterface.
208 *
209 * @param FetchAuthTokenInterface $fetcher is used to fetch the auth token
210 * @param array<mixed> $httpClientOptions (optional) Array of request options to apply.
211 * @param callable|null $httpHandler (optional) http client to fetch the token.
212 * @param callable|null $tokenCallback (optional) function to be called when a new token is fetched.
213 * @return \GuzzleHttp\Client
214 */
215 public static function makeHttpClient(
216 FetchAuthTokenInterface $fetcher,
217 array $httpClientOptions = [],
218 ?callable $httpHandler = null,
219 ?callable $tokenCallback = null
220 ) {
221 $middleware = new Middleware\AuthTokenMiddleware(
222 $fetcher,
223 $httpHandler,
224 $tokenCallback
225 );
226 $stack = \GuzzleHttp\HandlerStack::create();
227 $stack->push($middleware);
228
229 return new \GuzzleHttp\Client([
230 'handler' => $stack,
231 'auth' => 'google_auth',
232 ] + $httpClientOptions);
233 }
234
235 /**
236 * Create a new instance of InsecureCredentials.
237 *
238 * @return InsecureCredentials
239 */
240 public static function makeInsecureCredentials()
241 {
242 return new InsecureCredentials();
243 }
244
245 /**
246 * Fetch a quota project from the environment variable
247 * GOOGLE_CLOUD_QUOTA_PROJECT. Return null if
248 * GOOGLE_CLOUD_QUOTA_PROJECT is not specified.
249 *
250 * @return string|null
251 */
252 public static function quotaProjectFromEnv()
253 {
254 return self::getEnv(self::QUOTA_PROJECT_ENV_VAR) ?: null;
255 }
256
257 /**
258 * Gets a callable which returns the default device certification.
259 *
260 * @throws UnexpectedValueException
261 * @return callable|null
262 */
263 public static function getDefaultClientCertSource()
264 {
265 if (!$clientCertSourceJson = self::loadDefaultClientCertSourceFile()) {
266 return null;
267 }
268 $clientCertSourceCmd = $clientCertSourceJson['cert_provider_command'];
269
270 return function () use ($clientCertSourceCmd) {
271 $cmd = array_map('escapeshellarg', $clientCertSourceCmd);
272 exec(implode(' ', $cmd), $output, $returnVar);
273
274 if (0 === $returnVar) {
275 return implode(PHP_EOL, $output);
276 }
277 throw new RuntimeException(
278 '"cert_provider_command" failed with a nonzero exit code'
279 );
280 };
281 }
282
283 /**
284 * Determines whether or not the default device certificate should be loaded.
285 *
286 * @return bool
287 */
288 public static function shouldLoadClientCertSource()
289 {
290 return filter_var(self::getEnv(self::MTLS_CERT_ENV_VAR), FILTER_VALIDATE_BOOLEAN);
291 }
292
293 /**
294 * @return array{cert_provider_command:string[]}|null
295 */
296 private static function loadDefaultClientCertSourceFile()
297 {
298 $rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME';
299 $path = sprintf('%s/%s', self::getEnv($rootEnv), self::MTLS_WELL_KNOWN_PATH);
300 if (!file_exists($path)) {
301 return null;
302 }
303 $jsonKey = file_get_contents($path);
304 $clientCertSourceJson = json_decode((string) $jsonKey, true);
305 if (!$clientCertSourceJson) {
306 throw new UnexpectedValueException('Invalid client cert source JSON');
307 }
308 if (!isset($clientCertSourceJson['cert_provider_command'])) {
309 throw new UnexpectedValueException(
310 'cert source requires "cert_provider_command"'
311 );
312 }
313 if (!is_array($clientCertSourceJson['cert_provider_command'])) {
314 throw new UnexpectedValueException(
315 'cert source expects "cert_provider_command" to be an array'
316 );
317 }
318 return $clientCertSourceJson;
319 }
320
321 /**
322 * Get the universe domain from the credential. Defaults to "googleapis.com"
323 * for all credential types which do not support universe domain.
324 *
325 * @return string
326 */
327 public function getUniverseDomain(): string
328 {
329 return self::DEFAULT_UNIVERSE_DOMAIN;
330 }
331
332 private static function getEnv(string $env): mixed
333 {
334 return getenv($env) ?: $_ENV[$env] ?? null;
335 }
336 }
337