v2: Updates

* Simplifies & beautifies everything
* Introduces a new Class system.
* Errors are defaulted to AWS's handler.
* New function names & more efficient handling.
* Should fix a majority of the errors.

Please read the README for more!
This commit is contained in:
Devang Srivastava 2020-09-28 15:32:51 +05:30
commit e6d7753dc8
1095 changed files with 45088 additions and 2911 deletions

View file

@ -19,7 +19,8 @@ class AnonymousSignature implements SignatureInterface
public function presign(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
$expires,
array $options = []
) {
return $request;
}

View file

@ -59,6 +59,10 @@ class S3SignatureV4 extends SignatureV4
*/
protected function createCanonicalizedPath($path)
{
return '/' . ltrim($path, '/');
// Only remove one slash in case of keys that have a preceding slash
if (substr($path, 0, 1) === '/') {
$path = substr($path, 1);
}
return '/' . $path;
}
}

View file

@ -39,6 +39,7 @@ interface SignatureInterface
public function presign(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
$expires,
array $options = []
);
}

View file

@ -40,6 +40,11 @@ use Aws\Exception\UnresolvedSignatureException;
*/
class SignatureProvider
{
private static $s3v4SignedServices = [
's3' => true,
's3control' => true,
];
/**
* Resolves and signature provider and ensures a non-null return value.
*
@ -111,7 +116,7 @@ class SignatureProvider
switch ($version) {
case 's3v4':
case 'v4':
return $service === 's3'
return !empty(self::$s3v4SignedServices[$service])
? new S3SignatureV4($service, $region)
: new SignatureV4($service, $region);
case 'v4-unsigned-body':

View file

@ -15,6 +15,7 @@ class SignatureV4 implements SignatureInterface
use SignatureTrait;
const ISO8601_BASIC = 'Ymd\THis\Z';
const UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD';
const AMZ_CONTENT_SHA256_HEADER = 'X-Amz-Content-Sha256';
/** @var string */
private $service;
@ -25,6 +26,41 @@ class SignatureV4 implements SignatureInterface
/** @var bool */
private $unsigned;
/**
* The following headers are not signed because signing these headers
* would potentially cause a signature mismatch when sending a request
* through a proxy or if modified at the HTTP client level.
*
* @return array
*/
private function getHeaderBlacklist()
{
return [
'cache-control' => true,
'content-type' => true,
'content-length' => true,
'expect' => true,
'max-forwards' => true,
'pragma' => true,
'range' => true,
'te' => true,
'if-match' => true,
'if-none-match' => true,
'if-modified-since' => true,
'if-unmodified-since' => true,
'if-range' => true,
'accept' => true,
'authorization' => true,
'proxy-authorization' => true,
'from' => true,
'referer' => true,
'user-agent' => true,
'x-amzn-trace-id' => true,
'aws-sdk-invocation-id' => true,
'aws-sdk-retry' => true,
];
}
/**
* @param string $service Service name to use when signing
* @param string $region Region name to use when signing
@ -55,7 +91,7 @@ class SignatureV4 implements SignatureInterface
$payload = $this->getPayload($request);
if ($payload == self::UNSIGNED_PAYLOAD) {
$parsed['headers']['X-Amz-Content-Sha256'] = [$payload];
$parsed['headers'][self::AMZ_CONTENT_SHA256_HEADER] = [$payload];
}
$context = $this->createContext($parsed, $payload);
@ -76,6 +112,28 @@ class SignatureV4 implements SignatureInterface
return $this->buildRequest($parsed);
}
/**
* Get the headers that were used to pre-sign the request.
* Used for the X-Amz-SignedHeaders header.
*
* @param array $headers
* @return array
*/
private function getPresignHeaders(array $headers)
{
$presignHeaders = [];
$blacklist = $this->getHeaderBlacklist();
foreach ($headers as $name => $value) {
$lName = strtolower($name);
if (!isset($blacklist[$lName])
&& $name !== self::AMZ_CONTENT_SHA256_HEADER
) {
$presignHeaders[] = $lName;
}
}
return $presignHeaders;
}
public function presign(
RequestInterface $request,
CredentialsInterface $credentials,
@ -86,7 +144,7 @@ class SignatureV4 implements SignatureInterface
$startTimestamp = isset($options['start_time'])
? $this->convertToTimestamp($options['start_time'], null)
: time();
$expiresTimestamp = $this->convertToTimestamp($expires, $startTimestamp);
$parsed = $this->createPresignedRequest($request, $credentials);
@ -95,10 +153,13 @@ class SignatureV4 implements SignatureInterface
$shortDate = substr($httpDate, 0, 8);
$scope = $this->createScope($shortDate, $this->region, $this->service);
$credential = $credentials->getAccessKeyId() . '/' . $scope;
if ($credentials->getSecurityToken()) {
unset($parsed['headers']['X-Amz-Security-Token']);
}
$parsed['query']['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
$parsed['query']['X-Amz-Credential'] = $credential;
$parsed['query']['X-Amz-Date'] = gmdate('Ymd\THis\Z', $startTimestamp);
$parsed['query']['X-Amz-SignedHeaders'] = 'host';
$parsed['query']['X-Amz-SignedHeaders'] = implode(';', $this->getPresignHeaders($parsed['headers']));
$parsed['query']['X-Amz-Expires'] = $this->convertExpires($expiresTimestamp, $startTimestamp);
$context = $this->createContext($parsed, $payload);
$stringToSign = $this->createStringToSign($httpDate, $scope, $context['creq']);
@ -151,9 +212,9 @@ class SignatureV4 implements SignatureInterface
return self::UNSIGNED_PAYLOAD;
}
// Calculate the request signature payload
if ($request->hasHeader('X-Amz-Content-Sha256')) {
if ($request->hasHeader(self::AMZ_CONTENT_SHA256_HEADER)) {
// Handle streaming operations (e.g. Glacier.UploadArchive)
return $request->getHeaderLine('X-Amz-Content-Sha256');
return $request->getHeaderLine(self::AMZ_CONTENT_SHA256_HEADER);
}
if (!$request->getBody()->isSeekable()) {
@ -207,31 +268,7 @@ class SignatureV4 implements SignatureInterface
*/
private function createContext(array $parsedRequest, $payload)
{
// The following headers are not signed because signing these headers
// would potentially cause a signature mismatch when sending a request
// through a proxy or if modified at the HTTP client level.
static $blacklist = [
'cache-control' => true,
'content-type' => true,
'content-length' => true,
'expect' => true,
'max-forwards' => true,
'pragma' => true,
'range' => true,
'te' => true,
'if-match' => true,
'if-none-match' => true,
'if-modified-since' => true,
'if-unmodified-since' => true,
'if-range' => true,
'accept' => true,
'authorization' => true,
'proxy-authorization' => true,
'from' => true,
'referer' => true,
'user-agent' => true,
'x-amzn-trace-id' => true
];
$blacklist = $this->getHeaderBlacklist();
// Normalize the path as required by SigV4
$canon = $parsedRequest['method'] . "\n"
@ -292,7 +329,7 @@ class SignatureV4 implements SignatureInterface
private function convertToTimestamp($dateValue, $relativeTimeBase = null)
{
if ($dateValue instanceof \DateTime) {
if ($dateValue instanceof \DateTimeInterface) {
$timestamp = $dateValue->getTimestamp();
} elseif (!is_numeric($dateValue)) {
$timestamp = strtotime($dateValue,
@ -326,7 +363,10 @@ class SignatureV4 implements SignatureInterface
if (substr($lname, 0, 5) == 'x-amz') {
$parsedRequest['query'][$name] = $header;
}
if ($lname !== 'host') {
$blacklist = $this->getHeaderBlacklist();
if (isset($blacklist[$lname])
|| $lname === strtolower(self::AMZ_CONTENT_SHA256_HEADER)
) {
unset($parsedRequest['headers'][$name]);
}
}