1: | <?php |
2: | |
3: | declare(strict_types=1); |
4: | |
5: | namespace OpenSearch\Handlers; |
6: | |
7: | use Aws\Credentials\CredentialProvider; |
8: | use Aws\Signature\SignatureV4; |
9: | use GuzzleHttp\Psr7\Request; |
10: | use GuzzleHttp\Psr7\Uri; |
11: | use GuzzleHttp\Psr7\Utils; |
12: | use OpenSearch\ClientBuilder; |
13: | use Psr\Http\Message\RequestInterface; |
14: | use RuntimeException; |
15: | |
16: | |
17: | |
18: | |
19: | class SigV4Handler |
20: | { |
21: | |
22: | |
23: | |
24: | private $signer; |
25: | |
26: | |
27: | |
28: | private $credentialProvider; |
29: | |
30: | |
31: | |
32: | private $wrappedHandler; |
33: | |
34: | |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | |
45: | |
46: | |
47: | public function __construct( |
48: | string $region, |
49: | string $service, |
50: | ?callable $credentialProvider = null, |
51: | ?callable $wrappedHandler = null |
52: | ) { |
53: | self::assertDependenciesInstalled(); |
54: | $this->signer = new SignatureV4($service, $region); |
55: | $this->wrappedHandler = $wrappedHandler |
56: | ?: ClientBuilder::defaultHandler(); |
57: | $this->credentialProvider = $credentialProvider |
58: | ?: CredentialProvider::defaultProvider(); |
59: | } |
60: | |
61: | |
62: | |
63: | |
64: | public function __invoke(array $request) |
65: | { |
66: | $creds = call_user_func($this->credentialProvider)->wait(); |
67: | |
68: | $psr7Request = $this->createPsr7Request($request); |
69: | $psr7Request = $psr7Request->withHeader('x-amz-content-sha256', Utils::hash($psr7Request->getBody(), 'sha256')); |
70: | $signedRequest = $this->signer |
71: | ->signRequest($psr7Request, $creds); |
72: | return call_user_func($this->wrappedHandler, $this->createRingRequest($signedRequest, $request)); |
73: | } |
74: | |
75: | public static function assertDependenciesInstalled(): void |
76: | { |
77: | if (!class_exists(SignatureV4::class)) { |
78: | throw new RuntimeException( |
79: | 'The AWS SDK for PHP must be installed in order to use the SigV4 signing handler' |
80: | ); |
81: | } |
82: | } |
83: | |
84: | |
85: | |
86: | |
87: | private function createPsr7Request(array $ringPhpRequest): Request |
88: | { |
89: | |
90: | |
91: | $hostKey = isset($ringPhpRequest['headers']['Host']) ? 'Host' : 'host'; |
92: | |
93: | |
94: | |
95: | $parsedUrl = parse_url($ringPhpRequest['headers'][$hostKey][0]); |
96: | if (isset($parsedUrl['host'])) { |
97: | $ringPhpRequest['headers'][$hostKey][0] = $parsedUrl['host']; |
98: | } |
99: | |
100: | |
101: | $uri = (new Uri($ringPhpRequest['uri'])) |
102: | ->withScheme($ringPhpRequest['scheme']) |
103: | ->withHost($ringPhpRequest['headers'][$hostKey][0]); |
104: | if (isset($ringPhpRequest['query_string'])) { |
105: | $uri = $uri->withQuery($ringPhpRequest['query_string']); |
106: | } |
107: | |
108: | |
109: | return new Request( |
110: | $ringPhpRequest['http_method'], |
111: | $uri, |
112: | $ringPhpRequest['headers'], |
113: | $ringPhpRequest['body'] |
114: | ); |
115: | } |
116: | |
117: | |
118: | |
119: | |
120: | |
121: | |
122: | private function createRingRequest(RequestInterface $request, array $originalRequest): array |
123: | { |
124: | $uri = $request->getUri(); |
125: | $body = (string) $request->getBody(); |
126: | |
127: | |
128: | |
129: | if (empty($body)) { |
130: | $body = null; |
131: | } |
132: | |
133: | |
134: | $client = $originalRequest['client']; |
135: | unset($client['curl'][CURLOPT_PORT]); |
136: | |
137: | $ringRequest = [ |
138: | 'http_method' => $request->getMethod(), |
139: | 'scheme' => $uri->getScheme(), |
140: | 'uri' => $uri->getPath(), |
141: | 'body' => $body, |
142: | 'headers' => $request->getHeaders(), |
143: | 'client' => $client |
144: | ]; |
145: | if ($uri->getQuery()) { |
146: | $ringRequest['query_string'] = $uri->getQuery(); |
147: | } |
148: | |
149: | return $ringRequest; |
150: | } |
151: | } |
152: | |