PluginProbe ʕ •ᴥ•ʔ
Transferito: WP Migration / trunk
Transferito: WP Migration vtrunk
trunk 11.4.0 12.0.0 13.1.0 14.0.0 14.0.11 14.0.7 14.1.0 14.1.1 14.1.2 14.1.3 14.1.4
transferito / vendor / aws / aws-sdk-php / src / S3Control / EndpointArnMiddleware.php
transferito / vendor / aws / aws-sdk-php / src / S3Control Last commit date
Exception 10 months ago EndpointArnMiddleware.php 10 months ago S3ControlClient.php 10 months ago
EndpointArnMiddleware.php
361 lines
1 <?php
2 namespace Aws\S3Control;
3
4 use Aws\Api\Service;
5 use Aws\Arn\AccessPointArnInterface;
6 use Aws\Arn\ArnInterface;
7 use Aws\Arn\ArnParser;
8 use Aws\Arn\Exception\InvalidArnException;
9 use Aws\Arn\S3\BucketArnInterface;
10 use Aws\Arn\S3\OutpostsArnInterface;
11 use Aws\CommandInterface;
12 use Aws\Endpoint\PartitionEndpointProvider;
13 use Aws\Exception\InvalidRegionException;
14 use Aws\Exception\UnresolvedEndpointException;
15 use Aws\S3\EndpointRegionHelperTrait;
16 use GuzzleHttp\Psr7;
17 use Psr\Http\Message\RequestInterface;
18
19 /**
20 * Checks for access point ARN in members targeting BucketName, modifying
21 * endpoint as appropriate
22 *
23 * @internal
24 */
25 class EndpointArnMiddleware
26 {
27 use EndpointRegionHelperTrait;
28
29 /**
30 * Commands which do not do ARN expansion for a specific given shape name
31 * @var array
32 */
33 private static $selectiveNonArnableCmds = [
34 'AccessPointName' => [
35 'CreateAccessPoint',
36 ],
37 'BucketName' => [],
38 ];
39
40 /**
41 * Commands which do not do ARN expansion at all for relevant members
42 * @var array
43 */
44 private static $nonArnableCmds = [
45 'CreateBucket',
46 'ListRegionalBuckets',
47 ];
48
49 /**
50 * Commands which trigger endpoint and signer redirection based on presence
51 * of OutpostId
52 * @var array
53 */
54 private static $outpostIdRedirectCmds = [
55 'CreateBucket',
56 'ListRegionalBuckets',
57 ];
58
59 /** @var callable */
60 private $nextHandler;
61
62 /** @var boolean */
63 private $isUseEndpointV2;
64
65 /**
66 * Create a middleware wrapper function.
67 *
68 * @param Service $service
69 * @param $region
70 * @param array $config
71 * @return callable
72 */
73 public static function wrap(
74 Service $service,
75 $region,
76 array $config,
77 $isUseEndpointV2
78 )
79 {
80 return function (callable $handler) use ($service, $region, $config, $isUseEndpointV2) {
81 return new self($handler, $service, $region, $config, $isUseEndpointV2);
82 };
83 }
84
85 public function __construct(
86 callable $nextHandler,
87 Service $service,
88 $region,
89 array $config = [],
90 $isUseEndpointV2 = false
91 )
92 {
93 $this->partitionProvider = PartitionEndpointProvider::defaultProvider();
94 $this->region = $region;
95 $this->service = $service;
96 $this->config = $config;
97 $this->nextHandler = $nextHandler;
98 $this->isUseEndpointV2 = $isUseEndpointV2;
99 }
100
101 public function __invoke(CommandInterface $cmd, RequestInterface $req)
102 {
103 $nextHandler = $this->nextHandler;
104
105 $op = $this->service->getOperation($cmd->getName())->toArray();
106 if (!empty($op['input']['shape'])
107 && !in_array($cmd->getName(), self::$nonArnableCmds)
108 ) {
109 $service = $this->service->toArray();
110 if (!empty($input = $service['shapes'][$op['input']['shape']])) {
111
112 // Stores member name that targets 'BucketName' shape
113 $bucketNameMember = null;
114
115 // Stores member name that targets 'AccessPointName' shape
116 $accesspointNameMember = null;
117
118 foreach ($input['members'] as $key => $member) {
119 if ($member['shape'] === 'BucketName') {
120 $bucketNameMember = $key;
121 }
122 if ($member['shape'] === 'AccessPointName') {
123 $accesspointNameMember = $key;
124 }
125 }
126
127 // Determine if appropriate member contains ARN value and is
128 // eligible for ARN expansion
129 if (!is_null($bucketNameMember)
130 && !empty($cmd[$bucketNameMember])
131 && !in_array($cmd->getName(), self::$selectiveNonArnableCmds['BucketName'])
132 && ArnParser::isArn($cmd[$bucketNameMember])
133 ) {
134 $arn = ArnParser::parse($cmd[$bucketNameMember]);
135 !$this->isUseEndpointV2 && $partition = $this->validateBucketArn($arn);
136 } elseif (!is_null($accesspointNameMember)
137 && !empty($cmd[$accesspointNameMember])
138 && !in_array($cmd->getName(), self::$selectiveNonArnableCmds['AccessPointName'])
139 && ArnParser::isArn($cmd[$accesspointNameMember])
140 ) {
141 $arn = ArnParser::parse($cmd[$accesspointNameMember]);
142 !$this->isUseEndpointV2 && $partition = $this->validateAccessPointArn($arn);
143 }
144
145 // Process only if an appropriate member contains an ARN value
146 // and is an Outposts ARN
147 if (!empty($arn) && $arn instanceof OutpostsArnInterface) {
148 if (!$this->isUseEndpointV2) {
149 // Generate host based on ARN
150 $host = $this->generateOutpostsArnHost($arn, $req);
151 $req = $req->withHeader('x-amz-outpost-id', $arn->getOutpostId());
152 }
153
154 // ARN replacement
155 $path = $req->getUri()->getPath();
156 if ($arn instanceof AccessPointArnInterface) {
157 // Replace ARN with access point name
158 $path = str_replace(
159 urlencode($cmd[$accesspointNameMember]),
160 $arn->getAccesspointName(),
161 $path
162 );
163
164 // Replace ARN in the payload
165 $req->getBody()->seek(0);
166 $body = Psr7\Utils::streamFor(str_replace(
167 $cmd[$accesspointNameMember],
168 $arn->getAccesspointName(),
169 $req->getBody()->getContents()
170 ));
171
172 // Replace ARN in the command
173 $cmd[$accesspointNameMember] = $arn->getAccesspointName();
174 } elseif ($arn instanceof BucketArnInterface) {
175
176 // Replace ARN in the path
177 $path = str_replace(
178 urlencode($cmd[$bucketNameMember]),
179 $arn->getBucketName(),
180 $path
181 );
182
183 // Replace ARN in the payload
184 $req->getBody()->seek(0);
185 $newBody = str_replace(
186 $cmd[$bucketNameMember],
187 $arn->getBucketName(),
188 $req->getBody()->getContents()
189 );
190 $body = Psr7\Utils::streamFor($newBody);
191
192 // Replace ARN in the command
193 $cmd[$bucketNameMember] = $arn->getBucketName();
194 }
195
196 // Validate or set account ID in command
197 if (isset($cmd['AccountId'])) {
198 if ($cmd['AccountId'] !== $arn->getAccountId()) {
199 throw new \InvalidArgumentException("The account ID"
200 . " supplied in the command ({$cmd['AccountId']})"
201 . " does not match the account ID supplied in the"
202 . " ARN (" . $arn->getAccountId() . ").");
203 }
204 } else {
205 $cmd['AccountId'] = $arn->getAccountId();
206 }
207
208 // Set modified request
209 if (isset($body)) {
210 $req = $req->withBody($body);
211 }
212 if ($this->isUseEndpointV2) {
213 $req = $req->withUri($req->getUri()->withPath($path));
214 goto next;
215 }
216 $req = $req
217 ->withUri($req->getUri()->withHost($host)->withPath($path))
218 ->withHeader('x-amz-account-id', $arn->getAccountId());
219
220 // Update signing region based on ARN data if configured to do so
221 if ($this->config['use_arn_region']->isUseArnRegion()) {
222 $region = $arn->getRegion();
223 } else {
224 $region = $this->region;
225 }
226 $endpointData = $partition([
227 'region' => $region,
228 'service' => $arn->getService()
229 ]);
230 $cmd['@context']['signing_region'] = $endpointData['signingRegion'];
231
232 // Update signing service for Outposts ARNs
233 if ($arn instanceof OutpostsArnInterface) {
234 $cmd['@context']['signing_service'] = $arn->getService();
235 }
236
237
238 }
239 }
240 }
241 if ($this->isUseEndpointV2) {
242 goto next;
243 }
244 // For operations that redirect endpoint & signing service based on
245 // presence of OutpostId member. These operations will likely not
246 // overlap with operations that perform ARN expansion.
247 if (in_array($cmd->getName(), self::$outpostIdRedirectCmds)
248 && !empty($cmd['OutpostId'])
249 ) {
250 $req = $req->withUri(
251 $req->getUri()->withHost($this->generateOutpostIdHost())
252 );
253 $cmd['@context']['signing_service'] = 's3-outposts';
254 }
255
256 next:
257 return $nextHandler($cmd, $req);
258 }
259
260 private function generateOutpostsArnHost(
261 OutpostsArnInterface $arn,
262 RequestInterface $req
263 ) {
264 if (!empty($this->config['use_arn_region']->isUseArnRegion())) {
265 $region = $arn->getRegion();
266 } else {
267 $region = $this->region;
268 }
269 $fipsString = $this->config['use_fips_endpoint']->isUseFipsEndpoint()
270 ? "-fips"
271 : "";
272 $suffix = $this->getPartitionSuffix($arn, $this->partitionProvider);
273 return "s3-outposts{$fipsString}.{$region}.{$suffix}";
274 }
275
276 private function generateOutpostIdHost()
277 {
278 $partition = $this->partitionProvider->getPartition(
279 $this->region,
280 $this->service->getEndpointPrefix()
281 );
282 $suffix = $partition->getDnsSuffix();
283 return "s3-outposts.{$this->region}.{$suffix}";
284 }
285
286 private function validateBucketArn(ArnInterface $arn)
287 {
288 if ($arn instanceof BucketArnInterface) {
289 return $this->validateArn($arn);
290 }
291
292 throw new InvalidArnException('Provided ARN was not a valid S3 bucket'
293 . ' ARN.');
294 }
295
296 private function validateAccessPointArn(ArnInterface $arn)
297 {
298 if ($arn instanceof AccessPointArnInterface) {
299 return $this->validateArn($arn);
300 }
301
302 throw new InvalidArnException('Provided ARN was not a valid S3 access'
303 . ' point ARN.');
304 }
305
306 /**
307 * Validates an ARN, returning a partition object corresponding to the ARN
308 * if successful
309 *
310 * @param $arn
311 * @return \Aws\Endpoint\Partition
312 */
313 private function validateArn(ArnInterface $arn)
314 {
315 // Dualstack is not supported with Outposts ARNs
316 if ($arn instanceof OutpostsArnInterface
317 && !empty($this->config['dual_stack'])
318 ) {
319 throw new UnresolvedEndpointException(
320 'Dualstack is currently not supported with S3 Outposts ARNs.'
321 . ' Please disable dualstack or do not supply an Outposts ARN.');
322 }
323
324 // Get partitions for ARN and client region
325 $arnPart = $this->partitionProvider->getPartitionByName(
326 $arn->getPartition()
327 );
328 $clientPart = $this->partitionProvider->getPartition(
329 $this->region,
330 's3'
331 );
332
333 // If client partition not found, try removing pseudo-region qualifiers
334 if (!($clientPart->isRegionMatch($this->region, 's3'))) {
335 $clientPart = $this->partitionProvider->getPartition(
336 \Aws\strip_fips_pseudo_regions($this->region),
337 's3'
338 );
339 }
340
341 // Verify that the partition matches for supplied partition and region
342 if ($arn->getPartition() !== $clientPart->getName()) {
343 throw new InvalidRegionException('The supplied ARN partition'
344 . " does not match the client's partition.");
345 }
346 if ($clientPart->getName() !== $arnPart->getName()) {
347 throw new InvalidRegionException('The corresponding partition'
348 . ' for the supplied ARN region does not match the'
349 . " client's partition.");
350 }
351
352 // Ensure ARN region matches client region unless
353 // configured for using ARN region over client region
354 $this->validateMatchingRegion($arn);
355
356 // Ensure it is not resolved to fips pseudo-region for S3 Outposts
357 $this->validateFipsConfigurations($arn);
358
359 return $arnPart;
360 }
361 }