| 1441 |
ariadna |
1 |
<?php
|
|
|
2 |
namespace Aws\EndpointV2;
|
|
|
3 |
|
|
|
4 |
use Aws\Api\Operation;
|
|
|
5 |
use Aws\Api\Service;
|
|
|
6 |
use Aws\Auth\Exception\UnresolvedAuthSchemeException;
|
|
|
7 |
use Aws\CommandInterface;
|
|
|
8 |
use Aws\MetricsBuilder;
|
|
|
9 |
use Closure;
|
|
|
10 |
use GuzzleHttp\Promise\Promise;
|
|
|
11 |
use Aws\EndpointV2\Ruleset\RulesetEndpoint;
|
|
|
12 |
use function JmesPath\search;
|
|
|
13 |
|
|
|
14 |
/**
|
|
|
15 |
* Handles endpoint rule evaluation and endpoint resolution.
|
|
|
16 |
*
|
|
|
17 |
* IMPORTANT: this middleware must be added to the "build" step.
|
|
|
18 |
* Specifically, it must precede the 'builder' step.
|
|
|
19 |
*
|
|
|
20 |
* @internal
|
|
|
21 |
*/
|
|
|
22 |
class EndpointV2Middleware
|
|
|
23 |
{
|
|
|
24 |
const ACCOUNT_ID_PARAM = 'AccountId';
|
|
|
25 |
const ACCOUNT_ID_ENDPOINT_MODE_PARAM = 'AccountIdEndpointMode';
|
|
|
26 |
private static $validAuthSchemes = [
|
|
|
27 |
'sigv4' => 'v4',
|
|
|
28 |
'sigv4a' => 'v4a',
|
|
|
29 |
'none' => 'anonymous',
|
|
|
30 |
'bearer' => 'bearer',
|
|
|
31 |
'sigv4-s3express' => 'v4-s3express'
|
|
|
32 |
];
|
|
|
33 |
|
|
|
34 |
/** @var callable */
|
|
|
35 |
private $nextHandler;
|
|
|
36 |
|
|
|
37 |
/** @var EndpointProviderV2 */
|
|
|
38 |
private $endpointProvider;
|
|
|
39 |
|
|
|
40 |
/** @var Service */
|
|
|
41 |
private $api;
|
|
|
42 |
|
|
|
43 |
/** @var array */
|
|
|
44 |
private $clientArgs;
|
|
|
45 |
|
|
|
46 |
/** @var Closure */
|
|
|
47 |
private $credentialProvider;
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* Create a middleware wrapper function
|
|
|
51 |
*
|
|
|
52 |
* @param EndpointProviderV2 $endpointProvider
|
|
|
53 |
* @param Service $api
|
|
|
54 |
* @param array $args
|
|
|
55 |
* @param callable $credentialProvider
|
|
|
56 |
*
|
|
|
57 |
* @return Closure
|
|
|
58 |
*/
|
|
|
59 |
public static function wrap(
|
|
|
60 |
EndpointProviderV2 $endpointProvider,
|
|
|
61 |
Service $api,
|
|
|
62 |
array $args,
|
|
|
63 |
callable $credentialProvider
|
|
|
64 |
) : Closure
|
|
|
65 |
{
|
|
|
66 |
return function (callable $handler) use ($endpointProvider, $api, $args, $credentialProvider) {
|
|
|
67 |
return new self($handler, $endpointProvider, $api, $args, $credentialProvider);
|
|
|
68 |
};
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
/**
|
|
|
72 |
* @param callable $nextHandler
|
|
|
73 |
* @param EndpointProviderV2 $endpointProvider
|
|
|
74 |
* @param Service $api
|
|
|
75 |
* @param array $args
|
|
|
76 |
*/
|
|
|
77 |
public function __construct(
|
|
|
78 |
callable $nextHandler,
|
|
|
79 |
EndpointProviderV2 $endpointProvider,
|
|
|
80 |
Service $api,
|
|
|
81 |
array $args,
|
|
|
82 |
?callable $credentialProvider = null
|
|
|
83 |
)
|
|
|
84 |
{
|
|
|
85 |
$this->nextHandler = $nextHandler;
|
|
|
86 |
$this->endpointProvider = $endpointProvider;
|
|
|
87 |
$this->api = $api;
|
|
|
88 |
$this->clientArgs = $args;
|
|
|
89 |
$this->credentialProvider = $credentialProvider;
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
/**
|
|
|
93 |
* @param CommandInterface $command
|
|
|
94 |
*
|
|
|
95 |
* @return Promise
|
|
|
96 |
*/
|
|
|
97 |
public function __invoke(CommandInterface $command)
|
|
|
98 |
{
|
|
|
99 |
$nextHandler = $this->nextHandler;
|
|
|
100 |
$operation = $this->api->getOperation($command->getName());
|
|
|
101 |
$commandArgs = $command->toArray();
|
|
|
102 |
$providerArgs = $this->resolveArgs($commandArgs, $operation);
|
|
|
103 |
|
|
|
104 |
$endpoint = $this->endpointProvider->resolveEndpoint($providerArgs);
|
|
|
105 |
|
|
|
106 |
$this->appendEndpointMetrics($providerArgs, $endpoint, $command);
|
|
|
107 |
|
|
|
108 |
if (!empty($authSchemes = $endpoint->getProperty('authSchemes'))) {
|
|
|
109 |
$this->applyAuthScheme(
|
|
|
110 |
$authSchemes,
|
|
|
111 |
$command
|
|
|
112 |
);
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
return $nextHandler($command, $endpoint);
|
|
|
116 |
}
|
|
|
117 |
|
|
|
118 |
/**
|
|
|
119 |
* Resolves client, context params, static context params and endpoint provider
|
|
|
120 |
* arguments provided at the command level.
|
|
|
121 |
*
|
|
|
122 |
* @param array $commandArgs
|
|
|
123 |
* @param Operation $operation
|
|
|
124 |
*
|
|
|
125 |
* @return array
|
|
|
126 |
*/
|
|
|
127 |
private function resolveArgs(array $commandArgs, Operation $operation): array
|
|
|
128 |
{
|
|
|
129 |
$rulesetParams = $this->endpointProvider->getRuleset()->getParameters();
|
|
|
130 |
|
|
|
131 |
if (isset($rulesetParams[self::ACCOUNT_ID_PARAM])
|
|
|
132 |
&& isset($rulesetParams[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM])) {
|
|
|
133 |
$this->clientArgs[self::ACCOUNT_ID_PARAM] = $this->resolveAccountId();
|
|
|
134 |
}
|
|
|
135 |
|
|
|
136 |
$endpointCommandArgs = $this->filterEndpointCommandArgs(
|
|
|
137 |
$rulesetParams,
|
|
|
138 |
$commandArgs
|
|
|
139 |
);
|
|
|
140 |
$staticContextParams = $this->bindStaticContextParams(
|
|
|
141 |
$operation->getStaticContextParams()
|
|
|
142 |
);
|
|
|
143 |
$contextParams = $this->bindContextParams(
|
|
|
144 |
$commandArgs, $operation->getContextParams()
|
|
|
145 |
);
|
|
|
146 |
$operationContextParams = $this->bindOperationContextParams(
|
|
|
147 |
$commandArgs,
|
|
|
148 |
$operation->getOperationContextParams()
|
|
|
149 |
);
|
|
|
150 |
|
|
|
151 |
return array_merge(
|
|
|
152 |
$this->clientArgs,
|
|
|
153 |
$operationContextParams,
|
|
|
154 |
$contextParams,
|
|
|
155 |
$staticContextParams,
|
|
|
156 |
$endpointCommandArgs
|
|
|
157 |
);
|
|
|
158 |
}
|
|
|
159 |
|
|
|
160 |
/**
|
|
|
161 |
* Compares Ruleset parameters against Command arguments
|
|
|
162 |
* to create a mapping of arguments to pass into the
|
|
|
163 |
* endpoint provider for endpoint resolution.
|
|
|
164 |
*
|
|
|
165 |
* @param array $rulesetParams
|
|
|
166 |
* @param array $commandArgs
|
|
|
167 |
* @return array
|
|
|
168 |
*/
|
|
|
169 |
private function filterEndpointCommandArgs(
|
|
|
170 |
array $rulesetParams,
|
|
|
171 |
array $commandArgs
|
|
|
172 |
): array
|
|
|
173 |
{
|
|
|
174 |
$endpointMiddlewareOpts = [
|
|
|
175 |
'@use_dual_stack_endpoint' => 'UseDualStack',
|
|
|
176 |
'@use_accelerate_endpoint' => 'Accelerate',
|
|
|
177 |
'@use_path_style_endpoint' => 'ForcePathStyle'
|
|
|
178 |
];
|
|
|
179 |
|
|
|
180 |
$filteredArgs = [];
|
|
|
181 |
|
|
|
182 |
foreach($rulesetParams as $name => $value) {
|
|
|
183 |
if (isset($commandArgs[$name])) {
|
|
|
184 |
if (!empty($value->getBuiltIn())) {
|
|
|
185 |
continue;
|
|
|
186 |
}
|
|
|
187 |
$filteredArgs[$name] = $commandArgs[$name];
|
|
|
188 |
}
|
|
|
189 |
}
|
|
|
190 |
|
|
|
191 |
if ($this->api->getServiceName() === 's3') {
|
|
|
192 |
foreach($endpointMiddlewareOpts as $optionName => $newValue) {
|
|
|
193 |
if (isset($commandArgs[$optionName])) {
|
|
|
194 |
$filteredArgs[$newValue] = $commandArgs[$optionName];
|
|
|
195 |
}
|
|
|
196 |
}
|
|
|
197 |
}
|
|
|
198 |
|
|
|
199 |
return $filteredArgs;
|
|
|
200 |
}
|
|
|
201 |
|
|
|
202 |
/**
|
|
|
203 |
* Binds static context params to their corresponding values.
|
|
|
204 |
*
|
|
|
205 |
* @param $staticContextParams
|
|
|
206 |
*
|
|
|
207 |
* @return array
|
|
|
208 |
*/
|
|
|
209 |
private function bindStaticContextParams($staticContextParams): array
|
|
|
210 |
{
|
|
|
211 |
$scopedParams = [];
|
|
|
212 |
|
|
|
213 |
forEach($staticContextParams as $paramName => $paramValue) {
|
|
|
214 |
$scopedParams[$paramName] = $paramValue['value'];
|
|
|
215 |
}
|
|
|
216 |
|
|
|
217 |
return $scopedParams;
|
|
|
218 |
}
|
|
|
219 |
|
|
|
220 |
/**
|
|
|
221 |
* Binds context params to their corresponding values found in
|
|
|
222 |
* command arguments.
|
|
|
223 |
*
|
|
|
224 |
* @param array $commandArgs
|
|
|
225 |
* @param array $contextParams
|
|
|
226 |
*
|
|
|
227 |
* @return array
|
|
|
228 |
*/
|
|
|
229 |
private function bindContextParams(
|
|
|
230 |
array $commandArgs,
|
|
|
231 |
array $contextParams
|
|
|
232 |
): array
|
|
|
233 |
{
|
|
|
234 |
$scopedParams = [];
|
|
|
235 |
|
|
|
236 |
foreach($contextParams as $name => $spec) {
|
|
|
237 |
if (isset($commandArgs[$spec['shape']])) {
|
|
|
238 |
$scopedParams[$name] = $commandArgs[$spec['shape']];
|
|
|
239 |
}
|
|
|
240 |
}
|
|
|
241 |
|
|
|
242 |
return $scopedParams;
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
/**
|
|
|
246 |
* Binds context params to their corresponding values found in
|
|
|
247 |
* command arguments.
|
|
|
248 |
*
|
|
|
249 |
* @param array $commandArgs
|
|
|
250 |
* @param array $contextParams
|
|
|
251 |
*
|
|
|
252 |
* @return array
|
|
|
253 |
*/
|
|
|
254 |
private function bindOperationContextParams(
|
|
|
255 |
array $commandArgs,
|
|
|
256 |
array $operationContextParams
|
|
|
257 |
): array
|
|
|
258 |
{
|
|
|
259 |
$scopedParams = [];
|
|
|
260 |
|
|
|
261 |
foreach($operationContextParams as $name => $spec) {
|
|
|
262 |
$scopedValue = search($spec['path'], $commandArgs);
|
|
|
263 |
|
|
|
264 |
if ($scopedValue) {
|
|
|
265 |
$scopedParams[$name] = $scopedValue;
|
|
|
266 |
}
|
|
|
267 |
}
|
|
|
268 |
|
|
|
269 |
return $scopedParams;
|
|
|
270 |
}
|
|
|
271 |
|
|
|
272 |
/**
|
|
|
273 |
* Applies resolved auth schemes to the command object.
|
|
|
274 |
*
|
|
|
275 |
* @param $authSchemes
|
|
|
276 |
* @param $command
|
|
|
277 |
*
|
|
|
278 |
* @return void
|
|
|
279 |
*/
|
|
|
280 |
private function applyAuthScheme(
|
|
|
281 |
array $authSchemes,
|
|
|
282 |
CommandInterface $command
|
|
|
283 |
): void
|
|
|
284 |
{
|
|
|
285 |
$authScheme = $this->resolveAuthScheme($authSchemes);
|
|
|
286 |
|
|
|
287 |
$command['@context']['signature_version'] = $authScheme['version'];
|
|
|
288 |
|
|
|
289 |
if (isset($authScheme['name'])) {
|
|
|
290 |
$command['@context']['signing_service'] = $authScheme['name'];
|
|
|
291 |
}
|
|
|
292 |
|
|
|
293 |
if (isset($authScheme['region'])) {
|
|
|
294 |
$command['@context']['signing_region'] = $authScheme['region'];
|
|
|
295 |
} elseif (isset($authScheme['signingRegionSet'])) {
|
|
|
296 |
$command['@context']['signing_region_set'] = $authScheme['signingRegionSet'];
|
|
|
297 |
}
|
|
|
298 |
}
|
|
|
299 |
|
|
|
300 |
/**
|
|
|
301 |
* Returns the first compatible auth scheme in an endpoint object's
|
|
|
302 |
* auth schemes.
|
|
|
303 |
*
|
|
|
304 |
* @param array $authSchemes
|
|
|
305 |
*
|
|
|
306 |
* @return array
|
|
|
307 |
*/
|
|
|
308 |
private function resolveAuthScheme(array $authSchemes): array
|
|
|
309 |
{
|
|
|
310 |
$invalidAuthSchemes = [];
|
|
|
311 |
|
|
|
312 |
foreach($authSchemes as $authScheme) {
|
|
|
313 |
if ($this->isValidAuthScheme($authScheme['name'])) {
|
|
|
314 |
return $this->normalizeAuthScheme($authScheme);
|
|
|
315 |
}
|
|
|
316 |
$invalidAuthSchemes[$authScheme['name']] = false;
|
|
|
317 |
}
|
|
|
318 |
|
|
|
319 |
$invalidAuthSchemesString = '`' . implode(
|
|
|
320 |
'`, `',
|
|
|
321 |
array_keys($invalidAuthSchemes))
|
|
|
322 |
. '`';
|
|
|
323 |
$validAuthSchemesString = '`'
|
|
|
324 |
. implode('`, `', array_keys(
|
|
|
325 |
array_diff_key(self::$validAuthSchemes, $invalidAuthSchemes))
|
|
|
326 |
)
|
|
|
327 |
. '`';
|
|
|
328 |
throw new UnresolvedAuthSchemeException(
|
|
|
329 |
"This operation requests {$invalidAuthSchemesString}"
|
|
|
330 |
. " auth schemes, but the client currently supports {$validAuthSchemesString}."
|
|
|
331 |
);
|
|
|
332 |
}
|
|
|
333 |
|
|
|
334 |
/**
|
|
|
335 |
* Normalizes an auth scheme's name, signing region or signing region set
|
|
|
336 |
* to the auth keys recognized by the SDK.
|
|
|
337 |
*
|
|
|
338 |
* @param array $authScheme
|
|
|
339 |
* @return array
|
|
|
340 |
*/
|
|
|
341 |
private function normalizeAuthScheme(array $authScheme): array
|
|
|
342 |
{
|
|
|
343 |
/*
|
|
|
344 |
sigv4a will contain a regionSet property. which is guaranteed to be `*`
|
|
|
345 |
for now. The SigV4 class handles this automatically for now. It seems
|
|
|
346 |
complexity will be added here in the future.
|
|
|
347 |
*/
|
|
|
348 |
$normalizedAuthScheme = [];
|
|
|
349 |
|
|
|
350 |
if (isset($authScheme['disableDoubleEncoding'])
|
|
|
351 |
&& $authScheme['disableDoubleEncoding'] === true
|
|
|
352 |
&& $authScheme['name'] !== 'sigv4a'
|
|
|
353 |
&& $authScheme['name'] !== 'sigv4-s3express'
|
|
|
354 |
) {
|
|
|
355 |
$normalizedAuthScheme['version'] = 's3v4';
|
|
|
356 |
} else {
|
|
|
357 |
$normalizedAuthScheme['version'] = self::$validAuthSchemes[$authScheme['name']];
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
$normalizedAuthScheme['name'] = $authScheme['signingName'] ?? null;
|
|
|
361 |
$normalizedAuthScheme['region'] = $authScheme['signingRegion'] ?? null;
|
|
|
362 |
$normalizedAuthScheme['signingRegionSet'] = $authScheme['signingRegionSet'] ?? null;
|
|
|
363 |
|
|
|
364 |
return $normalizedAuthScheme;
|
|
|
365 |
}
|
|
|
366 |
|
|
|
367 |
private function isValidAuthScheme($signatureVersion): bool
|
|
|
368 |
{
|
|
|
369 |
if (isset(self::$validAuthSchemes[$signatureVersion])) {
|
|
|
370 |
if ($signatureVersion === 'sigv4a') {
|
|
|
371 |
return extension_loaded('awscrt');
|
|
|
372 |
}
|
|
|
373 |
return true;
|
|
|
374 |
}
|
|
|
375 |
|
|
|
376 |
return false;
|
|
|
377 |
}
|
|
|
378 |
|
|
|
379 |
/**
|
|
|
380 |
* This method tries to resolve an `AccountId` parameter from a resolved identity.
|
|
|
381 |
* We will just perform this operation if the parameter `AccountId` is part of the ruleset parameters and
|
|
|
382 |
* `AccountIdEndpointMode` is not disabled, otherwise, we will ignore it.
|
|
|
383 |
*
|
|
|
384 |
* @return null|string
|
|
|
385 |
*/
|
|
|
386 |
private function resolveAccountId(): ?string
|
|
|
387 |
{
|
|
|
388 |
if (isset($this->clientArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM])
|
|
|
389 |
&& $this->clientArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM] === 'disabled') {
|
|
|
390 |
return null;
|
|
|
391 |
}
|
|
|
392 |
|
|
|
393 |
if (is_null($this->credentialProvider)) {
|
|
|
394 |
return null;
|
|
|
395 |
}
|
|
|
396 |
|
|
|
397 |
$identityProviderFn = $this->credentialProvider;
|
|
|
398 |
$identity = $identityProviderFn()->wait();
|
|
|
399 |
|
|
|
400 |
return $identity->getAccountId();
|
|
|
401 |
}
|
|
|
402 |
|
|
|
403 |
private function appendEndpointMetrics(
|
|
|
404 |
array $providerArgs,
|
|
|
405 |
RulesetEndpoint $endpoint,
|
|
|
406 |
CommandInterface $command
|
|
|
407 |
): void
|
|
|
408 |
{
|
|
|
409 |
// Resolved AccountId Metric
|
|
|
410 |
if (!empty($providerArgs[self::ACCOUNT_ID_PARAM])) {
|
|
|
411 |
$command->getMetricsBuilder()->append(MetricsBuilder::RESOLVED_ACCOUNT_ID);
|
|
|
412 |
}
|
|
|
413 |
// AccountIdMode Metric
|
|
|
414 |
if(!empty($providerArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM])) {
|
|
|
415 |
$command->getMetricsBuilder()->identifyMetricByValueAndAppend(
|
|
|
416 |
'account_id_endpoint_mode',
|
|
|
417 |
$providerArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM]
|
|
|
418 |
);
|
|
|
419 |
}
|
|
|
420 |
|
|
|
421 |
// AccountId Endpoint Metric
|
|
|
422 |
$command->getMetricsBuilder()->identifyMetricByValueAndAppend(
|
|
|
423 |
'account_id_endpoint',
|
|
|
424 |
$endpoint->getUrl()
|
|
|
425 |
);
|
|
|
426 |
}
|
|
|
427 |
}
|