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 / EndpointV2 / EndpointV2Middleware.php

EndpointV2Middleware.php in Media Cloud Sync 1.4.1, at includes/sdk/s3/Aws/EndpointV2/EndpointV2Middleware.php

300 lines 11.7 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\EndpointV2;
4
5 use Dudlewebs\WPMCS\s3\Aws\Api\Operation;
6 use Dudlewebs\WPMCS\s3\Aws\Api\Service;
7 use Dudlewebs\WPMCS\s3\Aws\Auth\Exception\UnresolvedAuthSchemeException;
8 use Dudlewebs\WPMCS\s3\Aws\CommandInterface;
9 use Dudlewebs\WPMCS\s3\Aws\MetricsBuilder;
10 use Closure;
11 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\Promise;
12 use Dudlewebs\WPMCS\s3\Aws\EndpointV2\Ruleset\RulesetEndpoint;
13 use function Dudlewebs\WPMCS\s3\JmesPath\search;
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 = ['sigv4' => 'v4', 'sigv4a' => 'v4a', 'none' => 'anonymous', 'bearer' => 'bearer', 'sigv4-s3express' => 'v4-s3express'];
27 /** @var callable */
28 private $nextHandler;
29 /** @var EndpointProviderV2 */
30 private $endpointProvider;
31 /** @var Service */
32 private $api;
33 /** @var array */
34 private $clientArgs;
35 /** @var Closure */
36 private $credentialProvider;
37 /**
38 * Create a middleware wrapper function
39 *
40 * @param EndpointProviderV2 $endpointProvider
41 * @param Service $api
42 * @param array $args
43 * @param callable $credentialProvider
44 *
45 * @return Closure
46 */
47 public static function wrap(EndpointProviderV2 $endpointProvider, Service $api, array $args, callable $credentialProvider) : Closure
48 {
49 return function (callable $handler) use($endpointProvider, $api, $args, $credentialProvider) {
50 return new self($handler, $endpointProvider, $api, $args, $credentialProvider);
51 };
52 }
53 /**
54 * @param callable $nextHandler
55 * @param EndpointProviderV2 $endpointProvider
56 * @param Service $api
57 * @param array $args
58 */
59 public function __construct(callable $nextHandler, EndpointProviderV2 $endpointProvider, Service $api, array $args, ?callable $credentialProvider = null)
60 {
61 $this->nextHandler = $nextHandler;
62 $this->endpointProvider = $endpointProvider;
63 $this->api = $api;
64 $this->clientArgs = $args;
65 $this->credentialProvider = $credentialProvider;
66 }
67 /**
68 * @param CommandInterface $command
69 *
70 * @return Promise
71 */
72 public function __invoke(CommandInterface $command)
73 {
74 $nextHandler = $this->nextHandler;
75 $operation = $this->api->getOperation($command->getName());
76 $commandArgs = $command->toArray();
77 $providerArgs = $this->resolveArgs($commandArgs, $operation);
78 $endpoint = $this->endpointProvider->resolveEndpoint($providerArgs);
79 $this->appendEndpointMetrics($providerArgs, $endpoint, $command);
80 if (!empty($authSchemes = $endpoint->getProperty('authSchemes'))) {
81 $this->applyAuthScheme($authSchemes, $command);
82 }
83 return $nextHandler($command, $endpoint);
84 }
85 /**
86 * Resolves client, context params, static context params and endpoint provider
87 * arguments provided at the command level.
88 *
89 * @param array $commandArgs
90 * @param Operation $operation
91 *
92 * @return array
93 */
94 private function resolveArgs(array $commandArgs, Operation $operation) : array
95 {
96 $rulesetParams = $this->endpointProvider->getRuleset()->getParameters();
97 if (isset($rulesetParams[self::ACCOUNT_ID_PARAM]) && isset($rulesetParams[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM])) {
98 $this->clientArgs[self::ACCOUNT_ID_PARAM] = $this->resolveAccountId();
99 }
100 $endpointCommandArgs = $this->filterEndpointCommandArgs($rulesetParams, $commandArgs);
101 $staticContextParams = $this->bindStaticContextParams($operation->getStaticContextParams());
102 $contextParams = $this->bindContextParams($commandArgs, $operation->getContextParams());
103 $operationContextParams = $this->bindOperationContextParams($commandArgs, $operation->getOperationContextParams());
104 return \array_merge($this->clientArgs, $operationContextParams, $contextParams, $staticContextParams, $endpointCommandArgs);
105 }
106 /**
107 * Compares Ruleset parameters against Command arguments
108 * to create a mapping of arguments to pass into the
109 * endpoint provider for endpoint resolution.
110 *
111 * @param array $rulesetParams
112 * @param array $commandArgs
113 * @return array
114 */
115 private function filterEndpointCommandArgs(array $rulesetParams, array $commandArgs) : array
116 {
117 $endpointMiddlewareOpts = ['@use_dual_stack_endpoint' => 'UseDualStack', '@use_accelerate_endpoint' => 'Accelerate', '@use_path_style_endpoint' => 'ForcePathStyle'];
118 $filteredArgs = [];
119 foreach ($rulesetParams as $name => $value) {
120 if (isset($commandArgs[$name])) {
121 if (!empty($value->getBuiltIn())) {
122 continue;
123 }
124 $filteredArgs[$name] = $commandArgs[$name];
125 }
126 }
127 if ($this->api->getServiceName() === 's3') {
128 foreach ($endpointMiddlewareOpts as $optionName => $newValue) {
129 if (isset($commandArgs[$optionName])) {
130 $filteredArgs[$newValue] = $commandArgs[$optionName];
131 }
132 }
133 }
134 return $filteredArgs;
135 }
136 /**
137 * Binds static context params to their corresponding values.
138 *
139 * @param $staticContextParams
140 *
141 * @return array
142 */
143 private function bindStaticContextParams($staticContextParams) : array
144 {
145 $scopedParams = [];
146 foreach ($staticContextParams as $paramName => $paramValue) {
147 $scopedParams[$paramName] = $paramValue['value'];
148 }
149 return $scopedParams;
150 }
151 /**
152 * Binds context params to their corresponding values found in
153 * command arguments.
154 *
155 * @param array $commandArgs
156 * @param array $contextParams
157 *
158 * @return array
159 */
160 private function bindContextParams(array $commandArgs, array $contextParams) : array
161 {
162 $scopedParams = [];
163 foreach ($contextParams as $name => $spec) {
164 if (isset($commandArgs[$spec['shape']])) {
165 $scopedParams[$name] = $commandArgs[$spec['shape']];
166 }
167 }
168 return $scopedParams;
169 }
170 /**
171 * Binds context params to their corresponding values found in
172 * command arguments.
173 *
174 * @param array $commandArgs
175 * @param array $contextParams
176 *
177 * @return array
178 */
179 private function bindOperationContextParams(array $commandArgs, array $operationContextParams) : array
180 {
181 $scopedParams = [];
182 foreach ($operationContextParams as $name => $spec) {
183 $scopedValue = search($spec['path'], $commandArgs);
184 if ($scopedValue) {
185 $scopedParams[$name] = $scopedValue;
186 }
187 }
188 return $scopedParams;
189 }
190 /**
191 * Applies resolved auth schemes to the command object.
192 *
193 * @param $authSchemes
194 * @param $command
195 *
196 * @return void
197 */
198 private function applyAuthScheme(array $authSchemes, CommandInterface $command) : void
199 {
200 $authScheme = $this->resolveAuthScheme($authSchemes);
201 $command['@context']['signature_version'] = $authScheme['version'];
202 if (isset($authScheme['name'])) {
203 $command['@context']['signing_service'] = $authScheme['name'];
204 }
205 if (isset($authScheme['region'])) {
206 $command['@context']['signing_region'] = $authScheme['region'];
207 } elseif (isset($authScheme['signingRegionSet'])) {
208 $command['@context']['signing_region_set'] = $authScheme['signingRegionSet'];
209 }
210 }
211 /**
212 * Returns the first compatible auth scheme in an endpoint object's
213 * auth schemes.
214 *
215 * @param array $authSchemes
216 *
217 * @return array
218 */
219 private function resolveAuthScheme(array $authSchemes) : array
220 {
221 $invalidAuthSchemes = [];
222 foreach ($authSchemes as $authScheme) {
223 if ($this->isValidAuthScheme($authScheme['name'])) {
224 return $this->normalizeAuthScheme($authScheme);
225 }
226 $invalidAuthSchemes[$authScheme['name']] = \false;
227 }
228 $invalidAuthSchemesString = '`' . \implode('`, `', \array_keys($invalidAuthSchemes)) . '`';
229 $validAuthSchemesString = '`' . \implode('`, `', \array_keys(\array_diff_key(self::$validAuthSchemes, $invalidAuthSchemes))) . '`';
230 throw new UnresolvedAuthSchemeException("This operation requests {$invalidAuthSchemesString}" . " auth schemes, but the client currently supports {$validAuthSchemesString}.");
231 }
232 /**
233 * Normalizes an auth scheme's name, signing region or signing region set
234 * to the auth keys recognized by the SDK.
235 *
236 * @param array $authScheme
237 * @return array
238 */
239 private function normalizeAuthScheme(array $authScheme) : array
240 {
241 /*
242 sigv4a will contain a regionSet property. which is guaranteed to be `*`
243 for now. The SigV4 class handles this automatically for now. It seems
244 complexity will be added here in the future.
245 */
246 $normalizedAuthScheme = [];
247 if (isset($authScheme['disableDoubleEncoding']) && $authScheme['disableDoubleEncoding'] === \true && $authScheme['name'] !== 'sigv4a' && $authScheme['name'] !== 'sigv4-s3express') {
248 $normalizedAuthScheme['version'] = 's3v4';
249 } else {
250 $normalizedAuthScheme['version'] = self::$validAuthSchemes[$authScheme['name']];
251 }
252 $normalizedAuthScheme['name'] = $authScheme['signingName'] ?? null;
253 $normalizedAuthScheme['region'] = $authScheme['signingRegion'] ?? null;
254 $normalizedAuthScheme['signingRegionSet'] = $authScheme['signingRegionSet'] ?? null;
255 return $normalizedAuthScheme;
256 }
257 private function isValidAuthScheme($signatureVersion) : bool
258 {
259 if (isset(self::$validAuthSchemes[$signatureVersion])) {
260 if ($signatureVersion === 'sigv4a') {
261 return \extension_loaded('awscrt');
262 }
263 return \true;
264 }
265 return \false;
266 }
267 /**
268 * This method tries to resolve an `AccountId` parameter from a resolved identity.
269 * We will just perform this operation if the parameter `AccountId` is part of the ruleset parameters and
270 * `AccountIdEndpointMode` is not disabled, otherwise, we will ignore it.
271 *
272 * @return null|string
273 */
274 private function resolveAccountId() : ?string
275 {
276 if (isset($this->clientArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM]) && $this->clientArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM] === 'disabled') {
277 return null;
278 }
279 if (\is_null($this->credentialProvider)) {
280 return null;
281 }
282 $identityProviderFn = $this->credentialProvider;
283 $identity = $identityProviderFn()->wait();
284 return $identity->getAccountId();
285 }
286 private function appendEndpointMetrics(array $providerArgs, RulesetEndpoint $endpoint, CommandInterface $command) : void
287 {
288 // Resolved AccountId Metric
289 if (!empty($providerArgs[self::ACCOUNT_ID_PARAM])) {
290 $command->getMetricsBuilder()->append(MetricsBuilder::RESOLVED_ACCOUNT_ID);
291 }
292 // AccountIdMode Metric
293 if (!empty($providerArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM])) {
294 $command->getMetricsBuilder()->identifyMetricByValueAndAppend('account_id_endpoint_mode', $providerArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM]);
295 }
296 // AccountId Endpoint Metric
297 $command->getMetricsBuilder()->identifyMetricByValueAndAppend('account_id_endpoint', $endpoint->getUrl());
298 }
299 }
300