| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\Endpoint; |
| 4 |
|
| 5 |
use ArrayAccess; |
| 6 |
use Dudlewebs\WPMCS\s3\Aws\HasDataTrait; |
| 7 |
use Dudlewebs\WPMCS\s3\Aws\Sts\RegionalEndpoints\ConfigurationProvider; |
| 8 |
use Dudlewebs\WPMCS\s3\Aws\S3\RegionalEndpoint\ConfigurationProvider as S3ConfigurationProvider; |
| 9 |
use InvalidArgumentException as Iae; |
| 10 |
/** |
| 11 |
* Default implementation of an AWS partition. |
| 12 |
*/ |
| 13 |
final class Partition implements ArrayAccess, PartitionInterface |
| 14 |
{ |
| 15 |
use HasDataTrait; |
| 16 |
private $stsLegacyGlobalRegions = ['ap-northeast-1', 'ap-south-1', 'ap-southeast-1', 'ap-southeast-2', 'aws-global', 'ca-central-1', 'eu-central-1', 'eu-north-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2']; |
| 17 |
/** |
| 18 |
* The partition constructor accepts the following options: |
| 19 |
* |
| 20 |
* - `partition`: (string, required) The partition name as specified in an |
| 21 |
* ARN (e.g., `aws`) |
| 22 |
* - `partitionName`: (string) The human readable name of the partition |
| 23 |
* (e.g., "AWS Standard") |
| 24 |
* - `dnsSuffix`: (string, required) The DNS suffix of the partition. This |
| 25 |
* value is used to determine how endpoints in the partition are resolved. |
| 26 |
* - `regionRegex`: (string) A PCRE regular expression that specifies the |
| 27 |
* pattern that region names in the endpoint adhere to. |
| 28 |
* - `regions`: (array, required) A map of the regions in the partition. |
| 29 |
* Each key is the region as present in a hostname (e.g., `us-east-1`), |
| 30 |
* and each value is a structure containing region information. |
| 31 |
* - `defaults`: (array) A map of default key value pairs to apply to each |
| 32 |
* endpoint of the partition. Any value in an `endpoint` definition will |
| 33 |
* supersede any values specified in `defaults`. |
| 34 |
* - `services`: (array, required) A map of service endpoint prefix name |
| 35 |
* (the value found in a hostname) to information about the service. |
| 36 |
* |
| 37 |
* @param array $definition |
| 38 |
* |
| 39 |
* @throws Iae if any required options are missing |
| 40 |
*/ |
| 41 |
public function __construct(array $definition) |
| 42 |
{ |
| 43 |
foreach (['partition', 'regions', 'services', 'dnsSuffix'] as $key) { |
| 44 |
if (!isset($definition[$key])) { |
| 45 |
throw new Iae("Partition missing required {$key} field"); |
| 46 |
} |
| 47 |
} |
| 48 |
$this->data = $definition; |
| 49 |
} |
| 50 |
public function getName() |
| 51 |
{ |
| 52 |
return $this->data['partition']; |
| 53 |
} |
| 54 |
/** |
| 55 |
* @internal |
| 56 |
* @return mixed |
| 57 |
*/ |
| 58 |
public function getDnsSuffix() |
| 59 |
{ |
| 60 |
return $this->data['dnsSuffix']; |
| 61 |
} |
| 62 |
public function isRegionMatch($region, $service) |
| 63 |
{ |
| 64 |
if (isset($this->data['regions'][$region]) || isset($this->data['services'][$service]['endpoints'][$region])) { |
| 65 |
return \true; |
| 66 |
} |
| 67 |
if (isset($this->data['regionRegex'])) { |
| 68 |
return (bool) \preg_match("@{$this->data['regionRegex']}@", $region); |
| 69 |
} |
| 70 |
return \false; |
| 71 |
} |
| 72 |
public function getAvailableEndpoints($service, $allowNonRegionalEndpoints = \false) |
| 73 |
{ |
| 74 |
if ($this->isServicePartitionGlobal($service)) { |
| 75 |
return [$this->getPartitionEndpoint($service)]; |
| 76 |
} |
| 77 |
if (isset($this->data['services'][$service]['endpoints'])) { |
| 78 |
$serviceRegions = \array_keys($this->data['services'][$service]['endpoints']); |
| 79 |
return $allowNonRegionalEndpoints ? $serviceRegions : \array_intersect($serviceRegions, \array_keys($this->data['regions'])); |
| 80 |
} |
| 81 |
return []; |
| 82 |
} |
| 83 |
public function __invoke(array $args = []) |
| 84 |
{ |
| 85 |
$service = isset($args['service']) ? $args['service'] : ''; |
| 86 |
$region = isset($args['region']) ? $args['region'] : ''; |
| 87 |
$scheme = isset($args['scheme']) ? $args['scheme'] : 'https'; |
| 88 |
$options = isset($args['options']) ? $args['options'] : []; |
| 89 |
$data = $this->getEndpointData($service, $region, $options); |
| 90 |
$variant = $this->getVariant($options, $data); |
| 91 |
if (isset($variant['hostname'])) { |
| 92 |
$template = $variant['hostname']; |
| 93 |
} else { |
| 94 |
$template = isset($data['hostname']) ? $data['hostname'] : ''; |
| 95 |
} |
| 96 |
$dnsSuffix = isset($variant['dnsSuffix']) ? $variant['dnsSuffix'] : $this->data['dnsSuffix']; |
| 97 |
return ['endpoint' => "{$scheme}://" . $this->formatEndpoint($template, $service, $region, $dnsSuffix), 'signatureVersion' => $this->getSignatureVersion($data), 'signingRegion' => isset($data['credentialScope']['region']) ? $data['credentialScope']['region'] : $region, 'signingName' => isset($data['credentialScope']['service']) ? $data['credentialScope']['service'] : $service]; |
| 98 |
} |
| 99 |
private function getEndpointData($service, $region, $options) |
| 100 |
{ |
| 101 |
$defaultRegion = $this->resolveRegion($service, $region, $options); |
| 102 |
$data = isset($this->data['services'][$service]['endpoints'][$defaultRegion]) ? $this->data['services'][$service]['endpoints'][$defaultRegion] : []; |
| 103 |
$data += isset($this->data['services'][$service]['defaults']) ? $this->data['services'][$service]['defaults'] : []; |
| 104 |
$data += isset($this->data['defaults']) ? $this->data['defaults'] : []; |
| 105 |
return $data; |
| 106 |
} |
| 107 |
private function getSignatureVersion(array $data) |
| 108 |
{ |
| 109 |
static $supportedBySdk = ['s3v4', 'v4', 'anonymous']; |
| 110 |
$possibilities = \array_intersect($supportedBySdk, isset($data['signatureVersions']) ? $data['signatureVersions'] : ['v4']); |
| 111 |
return \array_shift($possibilities); |
| 112 |
} |
| 113 |
private function resolveRegion($service, $region, $options) |
| 114 |
{ |
| 115 |
if (isset($this->data['services'][$service]['endpoints'][$region]) && $this->isFipsEndpointUsed($region)) { |
| 116 |
return $region; |
| 117 |
} |
| 118 |
if ($this->isServicePartitionGlobal($service) || $this->isStsLegacyEndpointUsed($service, $region, $options) || $this->isS3LegacyEndpointUsed($service, $region, $options)) { |
| 119 |
return $this->getPartitionEndpoint($service); |
| 120 |
} |
| 121 |
return $region; |
| 122 |
} |
| 123 |
private function isServicePartitionGlobal($service) |
| 124 |
{ |
| 125 |
return isset($this->data['services'][$service]['isRegionalized']) && \false === $this->data['services'][$service]['isRegionalized'] && isset($this->data['services'][$service]['partitionEndpoint']); |
| 126 |
} |
| 127 |
/** |
| 128 |
* STS legacy endpoints used for valid regions unless option is explicitly |
| 129 |
* set to 'regional' |
| 130 |
* |
| 131 |
* @param string $service |
| 132 |
* @param string $region |
| 133 |
* @param array $options |
| 134 |
* @return bool |
| 135 |
*/ |
| 136 |
private function isStsLegacyEndpointUsed($service, $region, $options) |
| 137 |
{ |
| 138 |
return $service === 'sts' && \in_array($region, $this->stsLegacyGlobalRegions) && (empty($options['sts_regional_endpoints']) || ConfigurationProvider::unwrap($options['sts_regional_endpoints'])->getEndpointsType() !== 'regional'); |
| 139 |
} |
| 140 |
/** |
| 141 |
* S3 legacy us-east-1 endpoint used for valid regions unless option is explicitly |
| 142 |
* set to 'regional' |
| 143 |
* |
| 144 |
* @param string $service |
| 145 |
* @param string $region |
| 146 |
* @param array $options |
| 147 |
* @return bool |
| 148 |
*/ |
| 149 |
private function isS3LegacyEndpointUsed($service, $region, $options) |
| 150 |
{ |
| 151 |
return $service === 's3' && $region === 'us-east-1' && (empty($options['s3_us_east_1_regional_endpoint']) || S3ConfigurationProvider::unwrap($options['s3_us_east_1_regional_endpoint'])->getEndpointsType() !== 'regional'); |
| 152 |
} |
| 153 |
private function getPartitionEndpoint($service) |
| 154 |
{ |
| 155 |
return $this->data['services'][$service]['partitionEndpoint']; |
| 156 |
} |
| 157 |
private function formatEndpoint($template, $service, $region, $dnsSuffix) |
| 158 |
{ |
| 159 |
return \strtr($template, ['{service}' => $service, '{region}' => $region, '{dnsSuffix}' => $dnsSuffix]); |
| 160 |
} |
| 161 |
/** |
| 162 |
* @param $region |
| 163 |
* @return bool |
| 164 |
*/ |
| 165 |
private function isFipsEndpointUsed($region) |
| 166 |
{ |
| 167 |
return \strpos($region, "fips") !== \false; |
| 168 |
} |
| 169 |
/** |
| 170 |
* @param array $options |
| 171 |
* @param array $data |
| 172 |
* @return array |
| 173 |
*/ |
| 174 |
private function getVariant(array $options, array $data) |
| 175 |
{ |
| 176 |
$variantTags = []; |
| 177 |
if (isset($options['use_fips_endpoint'])) { |
| 178 |
$useFips = $options['use_fips_endpoint']; |
| 179 |
if (\is_bool($useFips)) { |
| 180 |
$useFips && ($variantTags[] = 'fips'); |
| 181 |
} elseif ($useFips->isUseFipsEndpoint()) { |
| 182 |
$variantTags[] = 'fips'; |
| 183 |
} |
| 184 |
} |
| 185 |
if (isset($options['use_dual_stack_endpoint'])) { |
| 186 |
$useDualStack = $options['use_dual_stack_endpoint']; |
| 187 |
if (\is_bool($useDualStack)) { |
| 188 |
$useDualStack && ($variantTags[] = 'dualstack'); |
| 189 |
} elseif ($useDualStack->isUseDualStackEndpoint()) { |
| 190 |
$variantTags[] = 'dualstack'; |
| 191 |
} |
| 192 |
} |
| 193 |
if (!empty($variantTags)) { |
| 194 |
if (isset($data['variants'])) { |
| 195 |
foreach ($data['variants'] as $variant) { |
| 196 |
if (\array_count_values($variant['tags']) == \array_count_values($variantTags)) { |
| 197 |
return $variant; |
| 198 |
} |
| 199 |
} |
| 200 |
} |
| 201 |
if (isset($this->data['defaults']['variants'])) { |
| 202 |
foreach ($this->data['defaults']['variants'] as $variant) { |
| 203 |
if (\array_count_values($variant['tags']) == \array_count_values($variantTags)) { |
| 204 |
return $variant; |
| 205 |
} |
| 206 |
} |
| 207 |
} |
| 208 |
} |
| 209 |
} |
| 210 |
} |
| 211 |
|