PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
media-cloud-sync / includes / sdk / s3 / Aws / Credentials / CredentialProvider.php

CredentialProvider.php in Media Cloud Sync 1.4.1, at includes/sdk/s3/Aws/Credentials/CredentialProvider.php

725 lines 35.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\Aws\Credentials;
4
5 use Dudlewebs\WPMCS\s3\Aws;
6 use Dudlewebs\WPMCS\s3\Aws\Api\DateTimeResult;
7 use Dudlewebs\WPMCS\s3\Aws\CacheInterface;
8 use Dudlewebs\WPMCS\s3\Aws\Exception\CredentialsException;
9 use Dudlewebs\WPMCS\s3\Aws\Sts\StsClient;
10 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise;
11 /**
12 * Credential providers are functions that accept no arguments and return a
13 * promise that is fulfilled with an {@see \Aws\Credentials\CredentialsInterface}
14 * or rejected with an {@see \Aws\Exception\CredentialsException}.
15 *
16 * <code>
17 * use Aws\Credentials\CredentialProvider;
18 * $provider = CredentialProvider::defaultProvider();
19 * // Returns a CredentialsInterface or throws.
20 * $creds = $provider()->wait();
21 * </code>
22 *
23 * Credential providers can be composed to create credentials using conditional
24 * logic that can create different credentials in different environments. You
25 * can compose multiple providers into a single provider using
26 * {@see Aws\Credentials\CredentialProvider::chain}. This function accepts
27 * providers as variadic arguments and returns a new function that will invoke
28 * each provider until a successful set of credentials is returned.
29 *
30 * <code>
31 * // First try an INI file at this location.
32 * $a = CredentialProvider::ini(null, '/path/to/file.ini');
33 * // Then try an INI file at this location.
34 * $b = CredentialProvider::ini(null, '/path/to/other-file.ini');
35 * // Then try loading from environment variables.
36 * $c = CredentialProvider::env();
37 * // Combine the three providers together.
38 * $composed = CredentialProvider::chain($a, $b, $c);
39 * // Returns a promise that is fulfilled with credentials or throws.
40 * $promise = $composed();
41 * // Wait on the credentials to resolve.
42 * $creds = $promise->wait();
43 * </code>
44 */
45 class CredentialProvider
46 {
47 const ENV_ARN = 'AWS_ROLE_ARN';
48 const ENV_KEY = 'AWS_ACCESS_KEY_ID';
49 const ENV_PROFILE = 'AWS_PROFILE';
50 const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME';
51 const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
52 const ENV_ACCOUNT_ID = 'AWS_ACCOUNT_ID';
53 const ENV_SESSION = 'AWS_SESSION_TOKEN';
54 const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
55 const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
56 public const ENV_REGION = 'AWS_REGION';
57 public const FALLBACK_REGION = 'us-east-1';
58 public const REFRESH_WINDOW = 60;
59 /**
60 * Create a default credential provider that
61 * first checks for environment variables,
62 * then checks for assumed role via web identity,
63 * then checks for cached SSO credentials from the CLI,
64 * then check for credential_process in the "default" profile in ~/.aws/credentials,
65 * then checks for the "default" profile in ~/.aws/credentials,
66 * then for credential_process in the "default profile" profile in ~/.aws/config,
67 * then checks for "profile default" profile in ~/.aws/config (which is
68 * the default profile of AWS CLI),
69 * then tries to make a GET Request to fetch credentials if ECS environment variable is presented,
70 * finally checks for EC2 instance profile credentials.
71 *
72 * This provider is automatically wrapped in a memoize function that caches
73 * previously provided credentials.
74 *
75 * @param array $config Optional array of ecs/instance profile credentials
76 * provider options.
77 *
78 * @return callable
79 */
80 public static function defaultProvider(array $config = [])
81 {
82 $cacheable = ['web_identity', 'sso', 'process_credentials', 'process_config', 'ecs', 'instance'];
83 $profileName = \getenv(self::ENV_PROFILE) ?: 'default';
84 $defaultChain = ['env' => self::env(), 'web_identity' => self::assumeRoleWithWebIdentityCredentialProvider($config)];
85 if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] !== \false) {
86 $defaultChain['sso'] = self::sso($profileName, self::getHomeDir() . '/.aws/config', $config);
87 $defaultChain['process_credentials'] = self::process();
88 $defaultChain['ini'] = self::ini(null, null, $config);
89 $defaultChain['process_config'] = self::process('profile ' . $profileName, self::getHomeDir() . '/.aws/config');
90 $defaultChain['ini_config'] = self::ini('profile ' . $profileName, self::getHomeDir() . '/.aws/config');
91 }
92 if (self::shouldUseEcs()) {
93 $defaultChain['ecs'] = self::ecsCredentials($config);
94 } else {
95 $defaultChain['instance'] = self::instanceProfile($config);
96 }
97 if (isset($config['credentials']) && $config['credentials'] instanceof CacheInterface) {
98 foreach ($cacheable as $provider) {
99 if (isset($defaultChain[$provider])) {
100 $defaultChain[$provider] = self::cache($defaultChain[$provider], $config['credentials'], 'aws_cached_' . $provider . '_credentials');
101 }
102 }
103 }
104 return self::memoize(\call_user_func_array([CredentialProvider::class, 'chain'], \array_values($defaultChain)));
105 }
106 /**
107 * Create a credential provider function from a set of static credentials.
108 *
109 * @param CredentialsInterface $creds
110 *
111 * @return callable
112 */
113 public static function fromCredentials(CredentialsInterface $creds)
114 {
115 $promise = Promise\Create::promiseFor($creds);
116 return function () use($promise) {
117 return $promise;
118 };
119 }
120 /**
121 * Creates an aggregate credentials provider that invokes the provided
122 * variadic providers one after the other until a provider returns
123 * credentials.
124 *
125 * @return callable
126 */
127 public static function chain()
128 {
129 $links = \func_get_args();
130 if (empty($links)) {
131 throw new \InvalidArgumentException('No providers in chain');
132 }
133 return function ($previousCreds = null) use($links) {
134 /** @var callable $parent */
135 $parent = \array_shift($links);
136 $promise = $parent();
137 while ($next = \array_shift($links)) {
138 if ($next instanceof InstanceProfileProvider && $previousCreds instanceof Credentials) {
139 $promise = $promise->otherwise(function () use($next, $previousCreds) {
140 return $next($previousCreds);
141 });
142 } else {
143 $promise = $promise->otherwise($next);
144 }
145 }
146 return $promise;
147 };
148 }
149 /**
150 * Wraps a credential provider and caches previously provided credentials.
151 *
152 * Ensures that cached credentials are refreshed when they expire.
153 *
154 * @param callable $provider Credentials provider function to wrap.
155 *
156 * @return callable
157 */
158 public static function memoize(callable $provider)
159 {
160 return function () use($provider) {
161 static $result;
162 static $isConstant;
163 // Constant credentials will be returned constantly.
164 if ($isConstant) {
165 return $result;
166 }
167 // Create the initial promise that will be used as the cached value
168 // until it expires.
169 if (null === $result) {
170 $result = $provider();
171 }
172 // Return credentials that could expire and refresh when needed.
173 return $result->then(function (CredentialsInterface $creds) use($provider, &$isConstant, &$result) {
174 // Determine if these are constant credentials.
175 if (!$creds->getExpiration()) {
176 $isConstant = \true;
177 return $creds;
178 }
179 // Check if credentials are expired or will expire in 1 minute
180 $needsRefresh = $creds->getExpiration() - \time() <= self::REFRESH_WINDOW;
181 // Refresh if expired or expiring soon
182 if (!$needsRefresh && !$creds->isExpired()) {
183 return $creds;
184 }
185 // Refresh the result and forward the promise.
186 return $result = $provider($creds);
187 })->otherwise(function ($reason) use(&$result) {
188 // Cleanup rejected promise.
189 $result = null;
190 return new Promise\RejectedPromise($reason);
191 });
192 };
193 }
194 /**
195 * Wraps a credential provider and saves provided credentials in an
196 * instance of Aws\CacheInterface. Forwards calls when no credentials found
197 * in cache and updates cache with the results.
198 *
199 * @param callable $provider Credentials provider function to wrap
200 * @param CacheInterface $cache Cache to store credentials
201 * @param string|null $cacheKey (optional) Cache key to use
202 *
203 * @return callable
204 */
205 public static function cache(callable $provider, CacheInterface $cache, $cacheKey = null)
206 {
207 $cacheKey = $cacheKey ?: 'aws_cached_credentials';
208 return function () use($provider, $cache, $cacheKey) {
209 $found = $cache->get($cacheKey);
210 if ($found instanceof CredentialsInterface && !$found->isExpired()) {
211 return Promise\Create::promiseFor($found);
212 }
213 return $provider()->then(function (CredentialsInterface $creds) use($cache, $cacheKey) {
214 $cache->set($cacheKey, $creds, null === $creds->getExpiration() ? 0 : $creds->getExpiration() - \time());
215 return $creds;
216 });
217 };
218 }
219 /**
220 * Provider that creates credentials from environment variables
221 * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN.
222 *
223 * @return callable
224 */
225 public static function env()
226 {
227 return function () {
228 // Use credentials from environment variables, if available
229 $key = \getenv(self::ENV_KEY);
230 $secret = \getenv(self::ENV_SECRET);
231 $accountId = \getenv(self::ENV_ACCOUNT_ID) ?: null;
232 $token = \getenv(self::ENV_SESSION) ?: null;
233 if ($key && $secret) {
234 return Promise\Create::promiseFor(new Credentials($key, $secret, $token, null, $accountId, CredentialSources::ENVIRONMENT));
235 }
236 return self::reject('Could not find environment variable ' . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET);
237 };
238 }
239 /**
240 * Credential provider that creates credentials using instance profile
241 * credentials.
242 *
243 * @param array $config Array of configuration data.
244 *
245 * @return InstanceProfileProvider
246 * @see Aws\Credentials\InstanceProfileProvider for $config details.
247 */
248 public static function instanceProfile(array $config = [])
249 {
250 return new InstanceProfileProvider($config);
251 }
252 /**
253 * Credential provider that retrieves cached SSO credentials from the CLI
254 *
255 * @return callable
256 */
257 public static function sso($ssoProfileName = 'default', $filename = null, $config = [])
258 {
259 $filename = $filename ?: self::getHomeDir() . '/.aws/config';
260 return function () use($ssoProfileName, $filename, $config) {
261 if (!@\is_readable($filename)) {
262 return self::reject("Cannot read credentials from {$filename}");
263 }
264 $profiles = self::loadProfiles($filename);
265 if (isset($profiles[$ssoProfileName])) {
266 $ssoProfile = $profiles[$ssoProfileName];
267 } elseif (isset($profiles['profile ' . $ssoProfileName])) {
268 $ssoProfileName = 'profile ' . $ssoProfileName;
269 $ssoProfile = $profiles[$ssoProfileName];
270 } else {
271 return self::reject("Profile {$ssoProfileName} does not exist in {$filename}.");
272 }
273 if (!empty($ssoProfile['sso_session'])) {
274 return CredentialProvider::getSsoCredentials($profiles, $ssoProfileName, $filename, $config);
275 } else {
276 return CredentialProvider::getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config);
277 }
278 };
279 }
280 /**
281 * Credential provider that creates credentials using
282 * ecs credentials by a GET request, whose uri is specified
283 * by environment variable
284 *
285 * @param array $config Array of configuration data.
286 *
287 * @return EcsCredentialProvider
288 * @see Aws\Credentials\EcsCredentialProvider for $config details.
289 */
290 public static function ecsCredentials(array $config = [])
291 {
292 return new EcsCredentialProvider($config);
293 }
294 /**
295 * Credential provider that creates credentials using assume role
296 *
297 * @param array $config Array of configuration data
298 * @return callable
299 * @see Aws\Credentials\AssumeRoleCredentialProvider for $config details.
300 */
301 public static function assumeRole(array $config = [])
302 {
303 return new AssumeRoleCredentialProvider($config);
304 }
305 /**
306 * Credential provider that creates credentials by assuming role from a
307 * Web Identity Token
308 *
309 * @param array $config Array of configuration data
310 * @return callable
311 * @see Aws\Credentials\AssumeRoleWithWebIdentityCredentialProvider for
312 * $config details.
313 */
314 public static function assumeRoleWithWebIdentityCredentialProvider(array $config = [])
315 {
316 return function () use($config) {
317 $arnFromEnv = \getenv(self::ENV_ARN);
318 $tokenFromEnv = \getenv(self::ENV_TOKEN_FILE);
319 $stsClient = isset($config['stsClient']) ? $config['stsClient'] : null;
320 $region = isset($config['region']) ? $config['region'] : null;
321 if ($tokenFromEnv && $arnFromEnv) {
322 $sessionName = \getenv(self::ENV_ROLE_SESSION_NAME) ? \getenv(self::ENV_ROLE_SESSION_NAME) : null;
323 $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $arnFromEnv, 'WebIdentityTokenFile' => $tokenFromEnv, 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region, 'source' => CredentialSources::ENVIRONMENT_STS_WEB_ID_TOKEN]);
324 return $provider();
325 }
326 $profileName = \getenv(self::ENV_PROFILE) ?: 'default';
327 if (isset($config['filename'])) {
328 $profiles = self::loadProfiles($config['filename']);
329 } else {
330 $profiles = self::loadDefaultProfiles();
331 }
332 if (isset($profiles[$profileName])) {
333 $profile = $profiles[$profileName];
334 if (isset($profile['region'])) {
335 $region = $profile['region'];
336 }
337 if (isset($profile['web_identity_token_file']) && isset($profile['role_arn'])) {
338 $sessionName = isset($profile['role_session_name']) ? $profile['role_session_name'] : null;
339 $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $profile['role_arn'], 'WebIdentityTokenFile' => $profile['web_identity_token_file'], 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region, 'source' => CredentialSources::PROFILE_STS_WEB_ID_TOKEN]);
340 return $provider();
341 }
342 } else {
343 return self::reject("Unknown profile: {$profileName}");
344 }
345 return self::reject("No RoleArn or WebIdentityTokenFile specified");
346 };
347 }
348 /**
349 * Credentials provider that creates credentials using an ini file stored
350 * in the current user's home directory. A source can be provided
351 * in this file for assuming a role using the credential_source config option.
352 *
353 * @param string|null $profile Profile to use. If not specified will use
354 * the "default" profile in "~/.aws/credentials".
355 * @param string|null $filename If provided, uses a custom filename rather
356 * than looking in the home directory.
357 * @param array|null $config If provided, may contain the following:
358 * preferStaticCredentials: If true, prefer static
359 * credentials to role_arn if both are present
360 * disableAssumeRole: If true, disable support for
361 * roles that assume an IAM role. If true and role profile
362 * is selected, an error is raised.
363 * stsClient: StsClient used to assume role specified in profile
364 *
365 * @return callable
366 */
367 public static function ini($profile = null, $filename = null, array $config = [])
368 {
369 $filename = self::getFileName($filename);
370 $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default');
371 return function () use($profile, $filename, $config) {
372 $preferStaticCredentials = isset($config['preferStaticCredentials']) ? $config['preferStaticCredentials'] : \false;
373 $disableAssumeRole = isset($config['disableAssumeRole']) ? $config['disableAssumeRole'] : \false;
374 $stsClient = isset($config['stsClient']) ? $config['stsClient'] : null;
375 if (!@\is_readable($filename)) {
376 return self::reject("Cannot read credentials from {$filename}");
377 }
378 $data = self::loadProfiles($filename);
379 if ($data === \false) {
380 return self::reject("Invalid credentials file: {$filename}");
381 }
382 if (!isset($data[$profile])) {
383 return self::reject("'{$profile}' not found in credentials file");
384 }
385 /*
386 In the CLI, the presence of both a role_arn and static credentials have
387 different meanings depending on how many profiles have been visited. For
388 the first profile processed, role_arn takes precedence over any static
389 credentials, but for all subsequent profiles, static credentials are
390 used if present, and only in their absence will the profile's
391 source_profile and role_arn keys be used to load another set of
392 credentials. This bool is intended to yield compatible behaviour in this
393 sdk.
394 */
395 $preferStaticCredentialsToRoleArn = $preferStaticCredentials && isset($data[$profile]['aws_access_key_id']) && isset($data[$profile]['aws_secret_access_key']);
396 if (isset($data[$profile]['role_arn']) && !$preferStaticCredentialsToRoleArn) {
397 if ($disableAssumeRole) {
398 return self::reject("Role assumption profiles are disabled. " . "Failed to load profile " . $profile);
399 }
400 return self::loadRoleProfile($data, $profile, $filename, $stsClient, $config);
401 }
402 if (!isset($data[$profile]['aws_access_key_id']) || !isset($data[$profile]['aws_secret_access_key'])) {
403 return self::reject("No credentials present in INI profile " . "'{$profile}' ({$filename})");
404 }
405 if (empty($data[$profile]['aws_session_token'])) {
406 $data[$profile]['aws_session_token'] = isset($data[$profile]['aws_security_token']) ? $data[$profile]['aws_security_token'] : null;
407 }
408 return Promise\Create::promiseFor(new Credentials($data[$profile]['aws_access_key_id'], $data[$profile]['aws_secret_access_key'], $data[$profile]['aws_session_token'], null, $data[$profile]['aws_account_id'] ?? null, CredentialSources::PROFILE));
409 };
410 }
411 /**
412 * Credentials provider that creates credentials using a process configured in
413 * ini file stored in the current user's home directory.
414 *
415 * @param string|null $profile Profile to use. If not specified will use
416 * the "default" profile in "~/.aws/credentials".
417 * @param string|null $filename If provided, uses a custom filename rather
418 * than looking in the home directory.
419 *
420 * @return callable
421 */
422 public static function process($profile = null, $filename = null)
423 {
424 $filename = self::getFileName($filename);
425 $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default');
426 return function () use($profile, $filename) {
427 if (!@\is_readable($filename)) {
428 return self::reject("Cannot read process credentials from {$filename}");
429 }
430 $data = \Dudlewebs\WPMCS\s3\Aws\parse_ini_file($filename, \true, \INI_SCANNER_RAW);
431 if ($data === \false) {
432 return self::reject("Invalid credentials file: {$filename}");
433 }
434 if (!isset($data[$profile])) {
435 return self::reject("'{$profile}' not found in credentials file");
436 }
437 if (!isset($data[$profile]['credential_process'])) {
438 return self::reject("No credential_process present in INI profile " . "'{$profile}' ({$filename})");
439 }
440 $credentialProcess = $data[$profile]['credential_process'];
441 $json = \shell_exec($credentialProcess);
442 $processData = \json_decode($json, \true);
443 // Only support version 1
444 if (isset($processData['Version'])) {
445 if ($processData['Version'] !== 1) {
446 return self::reject("credential_process does not return Version == 1");
447 }
448 }
449 if (!isset($processData['AccessKeyId']) || !isset($processData['SecretAccessKey'])) {
450 return self::reject("credential_process does not return valid credentials");
451 }
452 if (isset($processData['Expiration'])) {
453 try {
454 $expiration = new DateTimeResult($processData['Expiration']);
455 } catch (\Exception $e) {
456 return self::reject("credential_process returned invalid expiration");
457 }
458 $now = new DateTimeResult();
459 if ($expiration < $now) {
460 return self::reject("credential_process returned expired credentials");
461 }
462 $expires = $expiration->getTimestamp();
463 } else {
464 $expires = null;
465 }
466 if (empty($processData['SessionToken'])) {
467 $processData['SessionToken'] = null;
468 }
469 $accountId = null;
470 if (!empty($processData['AccountId'])) {
471 $accountId = $processData['AccountId'];
472 } elseif (!empty($data[$profile]['aws_account_id'])) {
473 $accountId = $data[$profile]['aws_account_id'];
474 }
475 return Promise\Create::promiseFor(new Credentials($processData['AccessKeyId'], $processData['SecretAccessKey'], $processData['SessionToken'], $expires, $accountId, CredentialSources::PROFILE_PROCESS));
476 };
477 }
478 /**
479 * Assumes role for profile that includes role_arn
480 *
481 * @return callable
482 */
483 private static function loadRoleProfile($profiles, $profileName, $filename, $stsClient, $config = [])
484 {
485 $roleProfile = $profiles[$profileName];
486 $roleArn = isset($roleProfile['role_arn']) ? $roleProfile['role_arn'] : '';
487 $roleSessionName = isset($roleProfile['role_session_name']) ? $roleProfile['role_session_name'] : 'aws-sdk-php-' . \round(\microtime(\true) * 1000);
488 if (empty($roleProfile['source_profile']) == empty($roleProfile['credential_source'])) {
489 return self::reject("Either source_profile or credential_source must be set " . "using profile " . $profileName . ", but not both.");
490 }
491 $sourceProfileName = "";
492 if (!empty($roleProfile['source_profile'])) {
493 $sourceProfileName = $roleProfile['source_profile'];
494 if (!isset($profiles[$sourceProfileName])) {
495 return self::reject("source_profile " . $sourceProfileName . " using profile " . $profileName . " does not exist");
496 }
497 if (isset($config['visited_profiles']) && \in_array($roleProfile['source_profile'], $config['visited_profiles'])) {
498 return self::reject("Circular source_profile reference found.");
499 }
500 $config['visited_profiles'][] = $roleProfile['source_profile'];
501 } else {
502 if (empty($roleArn)) {
503 return self::reject("A role_arn must be provided with credential_source in " . "file {$filename} under profile {$profileName} ");
504 }
505 }
506 if (empty($stsClient)) {
507 $config['preferStaticCredentials'] = \true;
508 $sourceCredentials = null;
509 if (!empty($roleProfile['source_profile'])) {
510 $sourceCredentials = \call_user_func(CredentialProvider::ini($sourceProfileName, $filename, $config))->wait();
511 } else {
512 $sourceCredentials = self::getCredentialsFromSource($profileName, $filename);
513 }
514 $region = $profiles[$sourceProfileName]['region'] ?? $config['region'] ?? \getEnv(self::ENV_REGION) ?: null;
515 $stsClient = self::createDefaultStsClient($sourceCredentials, $region);
516 }
517 $result = $stsClient->assumeRole(['RoleArn' => $roleArn, 'RoleSessionName' => $roleSessionName]);
518 $credentials = $stsClient->createCredentials($result, CredentialSources::STS_ASSUME_ROLE);
519 return Promise\Create::promiseFor($credentials);
520 }
521 /**
522 * Gets the environment's HOME directory if available.
523 *
524 * @return null|string
525 */
526 private static function getHomeDir()
527 {
528 // On Linux/Unix-like systems, use the HOME environment variable
529 if ($homeDir = \getenv('HOME')) {
530 return $homeDir;
531 }
532 // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
533 $homeDrive = \getenv('HOMEDRIVE');
534 $homePath = \getenv('HOMEPATH');
535 return $homeDrive && $homePath ? $homeDrive . $homePath : null;
536 }
537 /**
538 * Gets profiles from specified $filename, or default ini files.
539 */
540 private static function loadProfiles($filename)
541 {
542 $profileData = \Dudlewebs\WPMCS\s3\Aws\parse_ini_file($filename, \true, \INI_SCANNER_RAW);
543 // If loading .aws/credentials, also load .aws/config when AWS_SDK_LOAD_NONDEFAULT_CONFIG is set
544 if ($filename === self::getHomeDir() . '/.aws/credentials' && \getenv('AWS_SDK_LOAD_NONDEFAULT_CONFIG')) {
545 $configFilename = self::getHomeDir() . '/.aws/config';
546 $configProfileData = \Dudlewebs\WPMCS\s3\Aws\parse_ini_file($configFilename, \true, \INI_SCANNER_RAW);
547 foreach ($configProfileData as $name => $profile) {
548 // standardize config profile names
549 $name = \str_replace('profile ', '', $name);
550 if (!isset($profileData[$name])) {
551 $profileData[$name] = $profile;
552 }
553 }
554 }
555 return $profileData;
556 }
557 /**
558 * Gets profiles from ~/.aws/credentials and ~/.aws/config ini files
559 */
560 private static function loadDefaultProfiles()
561 {
562 $profiles = [];
563 $credFile = self::getHomeDir() . '/.aws/credentials';
564 $configFile = self::getHomeDir() . '/.aws/config';
565 if (\file_exists($credFile)) {
566 $profiles = \Dudlewebs\WPMCS\s3\Aws\parse_ini_file($credFile, \true, \INI_SCANNER_RAW);
567 }
568 if (\file_exists($configFile)) {
569 $configProfileData = \Dudlewebs\WPMCS\s3\Aws\parse_ini_file($configFile, \true, \INI_SCANNER_RAW);
570 foreach ($configProfileData as $name => $profile) {
571 // standardize config profile names
572 $name = \str_replace('profile ', '', $name);
573 if (!isset($profiles[$name])) {
574 $profiles[$name] = $profile;
575 }
576 }
577 }
578 return $profiles;
579 }
580 public static function getCredentialsFromSource($profileName = '', $filename = '', $config = [])
581 {
582 $data = self::loadProfiles($filename);
583 $credentialSource = !empty($data[$profileName]['credential_source']) ? $data[$profileName]['credential_source'] : null;
584 $credentialsPromise = null;
585 switch ($credentialSource) {
586 case 'Environment':
587 $credentialsPromise = self::env();
588 break;
589 case 'Ec2InstanceMetadata':
590 $credentialsPromise = self::instanceProfile($config);
591 break;
592 case 'EcsContainer':
593 $credentialsPromise = self::ecsCredentials($config);
594 break;
595 default:
596 throw new CredentialsException("Invalid credential_source found in config file: {$credentialSource}. Valid inputs " . "include Environment, Ec2InstanceMetadata, and EcsContainer.");
597 }
598 $credentialsResult = null;
599 try {
600 $credentialsResult = $credentialsPromise()->wait();
601 } catch (\Exception $reason) {
602 return self::reject("Unable to successfully retrieve credentials from the source specified in the" . " credentials file: {$credentialSource}; failure message was: " . $reason->getMessage());
603 }
604 return function () use($credentialsResult) {
605 return Promise\Create::promiseFor($credentialsResult);
606 };
607 }
608 private static function reject($msg)
609 {
610 return new Promise\RejectedPromise(new CredentialsException($msg));
611 }
612 /**
613 * @param $filename
614 * @return string
615 */
616 private static function getFileName($filename)
617 {
618 if (!isset($filename)) {
619 $filename = \getenv(self::ENV_SHARED_CREDENTIALS_FILE) ?: self::getHomeDir() . '/.aws/credentials';
620 }
621 return $filename;
622 }
623 /**
624 * @return boolean
625 */
626 public static function shouldUseEcs()
627 {
628 //Check for relative uri. if not, then full uri.
629 //fall back to server for each as getenv is not thread-safe.
630 return !empty(\getenv(EcsCredentialProvider::ENV_URI)) || !empty($_SERVER[EcsCredentialProvider::ENV_URI]) || !empty(\getenv(EcsCredentialProvider::ENV_FULL_URI)) || !empty($_SERVER[EcsCredentialProvider::ENV_FULL_URI]);
631 }
632 /**
633 * @param $profiles
634 * @param $ssoProfileName
635 * @param $filename
636 * @param $config
637 * @return Promise\PromiseInterface
638 */
639 private static function getSsoCredentials($profiles, $ssoProfileName, $filename, $config)
640 {
641 if (empty($config['ssoOidcClient'])) {
642 $ssoProfile = $profiles[$ssoProfileName];
643 $sessionName = $ssoProfile['sso_session'];
644 if (empty($profiles['sso-session ' . $sessionName])) {
645 return self::reject("Could not find sso-session {$sessionName} in {$filename}");
646 }
647 $ssoSession = $profiles['sso-session ' . $ssoProfile['sso_session']];
648 $ssoOidcClient = new Aws\SSOOIDC\SSOOIDCClient(['region' => $ssoSession['sso_region'], 'version' => '2019-06-10', 'credentials' => \false]);
649 } else {
650 $ssoOidcClient = $config['ssoClient'];
651 }
652 $tokenPromise = new Aws\Token\SsoTokenProvider($ssoProfileName, $filename, $ssoOidcClient);
653 $token = $tokenPromise()->wait();
654 $ssoCredentials = CredentialProvider::getCredentialsFromSsoService($ssoProfile, $ssoSession['sso_region'], $token->getToken(), $config);
655 //Expiration value is returned in epoch milliseconds. Conversion to seconds
656 $expiration = \intdiv($ssoCredentials['expiration'], 1000);
657 return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration, $ssoProfile['sso_account_id'], CredentialSources::PROFILE_SSO));
658 }
659 /**
660 * @param $profiles
661 * @param $ssoProfileName
662 * @param $filename
663 * @param $config
664 * @return Promise\PromiseInterface
665 */
666 private static function getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config)
667 {
668 $ssoProfile = $profiles[$ssoProfileName];
669 if (empty($ssoProfile['sso_start_url']) || empty($ssoProfile['sso_region']) || empty($ssoProfile['sso_account_id']) || empty($ssoProfile['sso_role_name'])) {
670 return self::reject("Profile {$ssoProfileName} in {$filename} must contain the following keys: " . "sso_start_url, sso_region, sso_account_id, and sso_role_name.");
671 }
672 $tokenLocation = self::getHomeDir() . '/.aws/sso/cache/' . \sha1($ssoProfile['sso_start_url']) . ".json";
673 if (!@\is_readable($tokenLocation)) {
674 return self::reject("Unable to read token file at {$tokenLocation}");
675 }
676 $tokenData = \json_decode(\file_get_contents($tokenLocation), \true);
677 if (empty($tokenData['accessToken']) || empty($tokenData['expiresAt'])) {
678 return self::reject("Token file at {$tokenLocation} must contain an access token and an expiration");
679 }
680 try {
681 $expiration = (new DateTimeResult($tokenData['expiresAt']))->getTimestamp();
682 } catch (\Exception $e) {
683 return self::reject("Cached SSO credentials returned an invalid expiration");
684 }
685 $now = \time();
686 if ($expiration < $now) {
687 return self::reject("Cached SSO credentials returned expired credentials");
688 }
689 $ssoCredentials = CredentialProvider::getCredentialsFromSsoService($ssoProfile, $ssoProfile['sso_region'], $tokenData['accessToken'], $config);
690 return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration, $ssoProfile['sso_account_id'], CredentialSources::PROFILE_SSO_LEGACY));
691 }
692 /**
693 * @param array $ssoProfile
694 * @param string $clientRegion
695 * @param string $accessToken
696 * @param array $config
697 * @return array|null
698 */
699 private static function getCredentialsFromSsoService($ssoProfile, $clientRegion, $accessToken, $config)
700 {
701 if (empty($config['ssoClient'])) {
702 $ssoClient = new Aws\SSO\SSOClient(['region' => $clientRegion, 'version' => '2019-06-10', 'credentials' => \false]);
703 } else {
704 $ssoClient = $config['ssoClient'];
705 }
706 $ssoResponse = $ssoClient->getRoleCredentials(['accessToken' => $accessToken, 'accountId' => $ssoProfile['sso_account_id'], 'roleName' => $ssoProfile['sso_role_name']]);
707 $ssoCredentials = $ssoResponse['roleCredentials'];
708 return $ssoCredentials;
709 }
710 /**
711 * @param CredentialsInterface $credentials
712 * @param string|null $region
713 *
714 * @return StsClient
715 */
716 private static function createDefaultStsClient(CredentialsInterface|callable $credentials, ?string $region) : StsClient
717 {
718 if (empty($region)) {
719 $region = self::FALLBACK_REGION;
720 \trigger_error('NOTICE: STS client created without explicit `region` configuration.' . \PHP_EOL . "Defaulting to `{$region}`. This fallback behavior may be removed." . \PHP_EOL . 'To avoid potential disruptions, configure a `region` using one of the following methods:' . \PHP_EOL . '(1) Add `region` to your source profile in ~/.aws/credentials,' . \PHP_EOL . '(2) Pass `region` in the `$config` array when calling the provider,' . \PHP_EOL . '(3) Set the `AWS_REGION` environment variable.' . \PHP_EOL . 'See: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_credentials_assume_role.html#assume-role-with-profile' . \PHP_EOL, \E_USER_NOTICE);
721 }
722 return new StsClient(['credentials' => $credentials, 'region' => $region]);
723 }
724 }
725