EndpointV2Middleware.php 9.1 KB

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