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