BearerTokenAuthorization.php
11 months ago
ParsesIniTrait.php
11 months ago
RefreshableTokenProviderInterface.php
11 months ago
SsoToken.php
11 months ago
SsoTokenProvider.php
11 months ago
Token.php
11 months ago
TokenAuthorization.php
11 months ago
TokenInterface.php
11 months ago
TokenProvider.php
11 months ago
TokenProvider.php
269 lines
| 1 | <?php |
| 2 | namespace Aws\Token; |
| 3 | |
| 4 | use Aws; |
| 5 | use Aws\Api\DateTimeResult; |
| 6 | use Aws\CacheInterface; |
| 7 | use Aws\Exception\TokenException; |
| 8 | use GuzzleHttp\Promise; |
| 9 | |
| 10 | /** |
| 11 | * Token providers are functions that accept no arguments and return a |
| 12 | * promise that is fulfilled with an {@see \Aws\Token\TokenInterface} |
| 13 | * or rejected with an {@see \Aws\Exception\TokenException}. |
| 14 | * |
| 15 | * <code> |
| 16 | * use Aws\Token\TokenProvider; |
| 17 | * $provider = TokenProvider::defaultProvider(); |
| 18 | * // Returns a TokenInterface or throws. |
| 19 | * $token = $provider()->wait(); |
| 20 | * </code> |
| 21 | * |
| 22 | * Token providers can be composed to create a token using conditional |
| 23 | * logic that can create different tokens in different environments. You |
| 24 | * can compose multiple providers into a single provider using |
| 25 | * {@see Aws\Token\TokenProvider::chain}. This function accepts |
| 26 | * providers as variadic arguments and returns a new function that will invoke |
| 27 | * each provider until a token is successfully returned. |
| 28 | */ |
| 29 | class TokenProvider |
| 30 | { |
| 31 | use ParsesIniTrait; |
| 32 | const ENV_PROFILE = 'AWS_PROFILE'; |
| 33 | |
| 34 | /** |
| 35 | * Create a default token provider tha checks for cached a SSO token from |
| 36 | * the CLI |
| 37 | * |
| 38 | * This provider is automatically wrapped in a memoize function that caches |
| 39 | * previously provided tokens. |
| 40 | * |
| 41 | * @param array $config Optional array of token provider options. |
| 42 | * |
| 43 | * @return callable |
| 44 | */ |
| 45 | public static function defaultProvider(array $config = []) |
| 46 | { |
| 47 | |
| 48 | $cacheable = [ |
| 49 | 'sso', |
| 50 | ]; |
| 51 | |
| 52 | $defaultChain = []; |
| 53 | |
| 54 | if ( |
| 55 | !isset($config['use_aws_shared_config_files']) |
| 56 | || $config['use_aws_shared_config_files'] !== false |
| 57 | ) { |
| 58 | $profileName = getenv(self::ENV_PROFILE) ?: 'default'; |
| 59 | $defaultChain['sso'] = self::sso( |
| 60 | $profileName, |
| 61 | self::getHomeDir() . '/.aws/config', |
| 62 | $config |
| 63 | ); |
| 64 | } |
| 65 | |
| 66 | if (isset($config['token']) |
| 67 | && $config['token'] instanceof CacheInterface |
| 68 | ) { |
| 69 | foreach ($cacheable as $provider) { |
| 70 | if (isset($defaultChain[$provider])) { |
| 71 | $defaultChain[$provider] = self::cache( |
| 72 | $defaultChain[$provider], |
| 73 | $config['token'], |
| 74 | 'aws_cached_' . $provider . '_token' |
| 75 | ); |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | return self::memoize( |
| 81 | call_user_func_array( |
| 82 | [TokenProvider::class, 'chain'], |
| 83 | array_values($defaultChain) |
| 84 | ) |
| 85 | ); |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * Create a token provider function from a static token. |
| 90 | * |
| 91 | * @param TokenInterface $token |
| 92 | * |
| 93 | * @return callable |
| 94 | */ |
| 95 | public static function fromToken(TokenInterface $token) |
| 96 | { |
| 97 | $promise = Promise\Create::promiseFor($token); |
| 98 | |
| 99 | return function () use ($promise) { |
| 100 | return $promise; |
| 101 | }; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Creates an aggregate token provider that invokes the provided |
| 106 | * variadic providers one after the other until a provider returns |
| 107 | * a token. |
| 108 | * |
| 109 | * @return callable |
| 110 | */ |
| 111 | public static function chain() |
| 112 | { |
| 113 | $links = func_get_args(); |
| 114 | //Common use case for when aws_shared_config_files is false |
| 115 | if (empty($links)) { |
| 116 | return function () { |
| 117 | return Promise\Create::promiseFor(false); |
| 118 | }; |
| 119 | } |
| 120 | |
| 121 | return function () use ($links) { |
| 122 | /** @var callable $parent */ |
| 123 | $parent = array_shift($links); |
| 124 | $promise = $parent(); |
| 125 | while ($next = array_shift($links)) { |
| 126 | $promise = $promise->otherwise($next); |
| 127 | } |
| 128 | return $promise; |
| 129 | }; |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Wraps a token provider and caches a previously provided token. |
| 134 | * Ensures that cached tokens are refreshed when they expire. |
| 135 | * |
| 136 | * @param callable $provider Token provider function to wrap. |
| 137 | * @return callable |
| 138 | */ |
| 139 | public static function memoize(callable $provider) |
| 140 | { |
| 141 | return function () use ($provider) { |
| 142 | static $result; |
| 143 | static $isConstant; |
| 144 | |
| 145 | // Constant tokens will be returned constantly. |
| 146 | if ($isConstant) { |
| 147 | return $result; |
| 148 | } |
| 149 | |
| 150 | // Create the initial promise that will be used as the cached value |
| 151 | // until it expires. |
| 152 | if (null === $result) { |
| 153 | $result = $provider(); |
| 154 | } |
| 155 | |
| 156 | // Return a token that could expire and refresh when needed. |
| 157 | return $result |
| 158 | ->then(function (TokenInterface $token) use ($provider, &$isConstant, &$result) { |
| 159 | // Determine if the token is constant. |
| 160 | if (!$token->getExpiration()) { |
| 161 | $isConstant = true; |
| 162 | return $token; |
| 163 | } |
| 164 | |
| 165 | if (!$token->isExpired()) { |
| 166 | return $token; |
| 167 | } |
| 168 | return $result = $provider(); |
| 169 | }) |
| 170 | ->otherwise(function($reason) use (&$result) { |
| 171 | // Cleanup rejected promise. |
| 172 | $result = null; |
| 173 | return Promise\Create::promiseFor(null); |
| 174 | }); |
| 175 | }; |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Wraps a token provider and saves provided token in an |
| 180 | * instance of Aws\CacheInterface. Forwards calls when no token found |
| 181 | * in cache and updates cache with the results. |
| 182 | * |
| 183 | * @param callable $provider Token provider function to wrap |
| 184 | * @param CacheInterface $cache Cache to store the token |
| 185 | * @param string|null $cacheKey (optional) Cache key to use |
| 186 | * |
| 187 | * @return callable |
| 188 | */ |
| 189 | public static function cache( |
| 190 | callable $provider, |
| 191 | CacheInterface $cache, |
| 192 | $cacheKey = null |
| 193 | ) { |
| 194 | $cacheKey = $cacheKey ?: 'aws_cached_token'; |
| 195 | |
| 196 | return function () use ($provider, $cache, $cacheKey) { |
| 197 | $found = $cache->get($cacheKey); |
| 198 | if (is_array($found) && isset($found['token'])) { |
| 199 | if (isset($found['token']) && $found['token'] instanceof TokenInterface) { |
| 200 | $foundToken = $found['token']; |
| 201 | if (!$foundToken->isExpired()) { |
| 202 | return Promise\Create::promiseFor($foundToken); |
| 203 | } |
| 204 | if (isset($found['refreshMethod']) && is_callable($found['refreshMethod'])) { |
| 205 | return Promise\Create::promiseFor($found['refreshMethod']()); |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | return $provider() |
| 211 | ->then(function (TokenInterface $token) use ( |
| 212 | $cache, |
| 213 | $cacheKey |
| 214 | ) { |
| 215 | $cache->set( |
| 216 | $cacheKey, |
| 217 | $token, |
| 218 | null === $token->getExpiration() ? |
| 219 | 0 : $token->getExpiration() - time() |
| 220 | ); |
| 221 | |
| 222 | return $token; |
| 223 | }); |
| 224 | }; |
| 225 | } |
| 226 | |
| 227 | /** |
| 228 | * Gets profiles from the ~/.aws/config ini file |
| 229 | */ |
| 230 | private static function loadDefaultProfiles() { |
| 231 | $profiles = []; |
| 232 | $configFile = self::getHomeDir() . '/.aws/config'; |
| 233 | |
| 234 | if (file_exists($configFile)) { |
| 235 | $configProfileData = \Aws\parse_ini_file($configFile, true, INI_SCANNER_RAW); |
| 236 | foreach ($configProfileData as $name => $profile) { |
| 237 | // standardize config profile names |
| 238 | $name = str_replace('profile ', '', $name); |
| 239 | if (!isset($profiles[$name])) { |
| 240 | $profiles[$name] = $profile; |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | return $profiles; |
| 246 | } |
| 247 | |
| 248 | private static function reject($msg) |
| 249 | { |
| 250 | return new Promise\RejectedPromise(new TokenException($msg)); |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * Token provider that creates a token from cached sso credentials |
| 255 | * |
| 256 | * @param string $ssoProfileName the name of the ini profile name |
| 257 | * @param string $filename the location of the ini file |
| 258 | * @param array $config configuration options |
| 259 | * |
| 260 | * @return SsoToken |
| 261 | * @see Aws\Token\SsoToken for $config details. |
| 262 | */ |
| 263 | public static function sso($profileName, $filename, $config = []) |
| 264 | { |
| 265 | return new SsoTokenProvider($profileName, $filename, $config); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 |