Skip to content

Commit 9697776

Browse files
authored
PreSign request (#341)
* Draft PreSign request * Add documentation * Implement Presign in Signers * Regenerate Inputs * Refactor signer * Fix compare aligment * Add RequestContext * Add SDK tests * Typehint with DatetimeInterface * Remove presign override in s3 * Use memory first for RewindableFallback * Add comment on internal use of RewindableStream * Close stream and fix typehint * Fix treshold size
1 parent 0ff8fa3 commit 9697776

20 files changed

+825
-98
lines changed

src/AbstractApi.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,31 @@ final public function getConfiguration(): Configuration
8080
return $this->configuration;
8181
}
8282

83+
final public function presign(Input $input, ?\DateTimeInterface $expires = null): string
84+
{
85+
$request = $input->request();
86+
$request->setEndpoint($this->getEndpoint($request->getUri(), $request->getQuery()));
87+
88+
if (null !== $credentials = $this->credentialProvider->getCredentials($this->configuration)) {
89+
$this->getSigner()->presign($request, $credentials, new RequestContext(['expirationDate' => $expires]));
90+
}
91+
92+
return $request->getEndpoint();
93+
}
94+
8395
abstract protected function getServiceCode(): string;
8496

8597
abstract protected function getSignatureVersion(): string;
8698

8799
abstract protected function getSignatureScopeName(): string;
88100

89-
final protected function getResponse(Request $request): Response
101+
final protected function getResponse(Request $request, ?RequestContext $context = null): Response
90102
{
91103
$request->setEndpoint($this->getEndpoint($request->getUri(), $request->getQuery()));
92104

93-
$this->getSigner()->sign($request, $this->credentialProvider->getCredentials($this->configuration));
105+
if (null !== $credentials = $this->credentialProvider->getCredentials($this->configuration)) {
106+
$this->getSigner()->sign($request, $credentials, $context ?? new RequestContext());
107+
}
94108

95109
$length = $request->getBody()->length();
96110
if (null !== $length && !$request->hasHeader('content-length')) {

src/Input.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace AsyncAws\Core;
4+
5+
/**
6+
* Representation of a AWS Request.
7+
*
8+
* @author Jérémy Derussé <[email protected]>
9+
*
10+
* @internal
11+
*/
12+
interface Input
13+
{
14+
public function request(): Request;
15+
}

src/Request.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use AsyncAws\Core\Stream\Stream;
77

88
/**
9-
* Representation of a AWS Request.
9+
* Representation of an HTTP Request.
1010
*
1111
* @author Jérémy Derussé <[email protected]>
1212
*
@@ -26,6 +26,8 @@ class Request
2626

2727
private $endpoint;
2828

29+
private $parsed;
30+
2931
/**
3032
* @param string[]|string[][] $headers
3133
*/
@@ -47,6 +49,11 @@ public function getMethod(): string
4749
return $this->method;
4850
}
4951

52+
public function setMethod(string $method): void
53+
{
54+
$this->method = $method;
55+
}
56+
5057
public function getUri(): string
5158
{
5259
return $this->uri;
@@ -75,6 +82,11 @@ public function getHeader(string $name)
7582
return $this->headers[strtolower($name)] ?? null;
7683
}
7784

85+
public function removeHeader(string $name): void
86+
{
87+
unset($this->headers[strtolower($name)]);
88+
}
89+
7890
public function getBody(): Stream
7991
{
8092
return $this->body;
@@ -85,13 +97,39 @@ public function setBody(Stream $body)
8597
$this->body = $body;
8698
}
8799

100+
public function hasQueryAttribute($name): bool
101+
{
102+
return \array_key_exists($name, $this->query);
103+
}
104+
105+
public function removeQueryAttribute($name): void
106+
{
107+
unset($this->query[$name]);
108+
$this->endpoint = '';
109+
}
110+
111+
public function setQueryAttribute($name, $value): void
112+
{
113+
$this->query[$name] = $value;
114+
$this->endpoint = '';
115+
}
116+
117+
public function getQueryAttribute(string $name): ?string
118+
{
119+
return $this->query[$name] ?? null;
120+
}
121+
88122
public function getQuery(): array
89123
{
90124
return $this->query;
91125
}
92126

93127
public function getEndpoint(): string
94128
{
129+
if (empty($this->endpoint)) {
130+
$this->endpoint = $this->parsed['scheme'] . '://' . $this->parsed['host'] . (isset($this->parsed['port']) ? ':' . $this->parsed['port'] : '') . $this->uri . ($this->query ? (false === \strpos($this->uri, '?') ? '?' : '&') . http_build_query($this->query) : '');
131+
}
132+
95133
return $this->endpoint;
96134
}
97135

@@ -100,6 +138,10 @@ public function setEndpoint(string $endpoint): void
100138
if (!empty($this->endpoint)) {
101139
throw new LogicException('Request::$endpoint cannot be changed after it has a value.');
102140
}
141+
103142
$this->endpoint = $endpoint;
143+
$this->parsed = \parse_url($this->endpoint);
144+
\parse_str($this->parsed['query'] ?? '', $this->query);
145+
$this->uri = $this->parsed['path'] ?? '/';
104146
}
105147
}

src/RequestContext.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace AsyncAws\Core;
4+
5+
use AsyncAws\Core\Exception\InvalidArgument;
6+
7+
/**
8+
* Contains contextual information alongside a request.
9+
*
10+
* @author Jérémy Derussé <[email protected]>
11+
*
12+
* @internal
13+
*/
14+
class RequestContext
15+
{
16+
public const AVAILABLE_OPTIONS = [
17+
'operation' => true,
18+
'expirationDate' => true,
19+
'currentDate' => true,
20+
];
21+
22+
/**
23+
* @var string|null
24+
*/
25+
private $operation;
26+
27+
/**
28+
* @var \DateTimeInterface|null
29+
*/
30+
private $expirationDate;
31+
32+
/**
33+
* @var \DateTimeInterface|null
34+
*/
35+
private $currentDate;
36+
37+
/**
38+
* @param array{
39+
* operation?: null|string
40+
* expirationDate?: null|\\DateTimeInterface
41+
* currentDate?: null|\\DateTimeInterface
42+
* }
43+
*/
44+
public function __construct(array $options = [])
45+
{
46+
if (0 < \count($invalidOptions = \array_diff_key($options, self::AVAILABLE_OPTIONS))) {
47+
throw new InvalidArgument(\sprintf('Invalid option(s) "%s" passed to "%s". ', \implode('", "', \array_keys($invalidOptions)), __METHOD__));
48+
}
49+
50+
foreach ($options as $property => $value) {
51+
$this->$property = $value;
52+
}
53+
}
54+
55+
public function getOperation(): ?string
56+
{
57+
return $this->operation;
58+
}
59+
60+
public function getExpirationDate(): ?\DateTimeInterface
61+
{
62+
return $this->expirationDate;
63+
}
64+
65+
public function getCurrentDate(): ?\DateTimeInterface
66+
{
67+
return $this->currentDate;
68+
}
69+
}

src/Signer/Signer.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use AsyncAws\Core\Credentials\Credentials;
66
use AsyncAws\Core\Request;
7+
use AsyncAws\Core\RequestContext;
78

89
/**
910
* Interface for signing a request.
@@ -12,5 +13,7 @@
1213
*/
1314
interface Signer
1415
{
15-
public function sign(Request $request, ?Credentials $credentials): void;
16+
public function sign(Request $request, Credentials $credentials, RequestContext $context): void;
17+
18+
public function presign(Request $request, Credentials $credentials, RequestContext $context): void;
1619
}

0 commit comments

Comments
 (0)