|
|
@@ -0,0 +1,204 @@ |
|
|
|
<?php |
|
|
|
|
|
|
|
namespace OC\Files\ObjectStore; |
|
|
|
|
|
|
|
use Aws\Credentials\CredentialsInterface; |
|
|
|
use Aws\S3\S3Client; |
|
|
|
use Aws\S3\S3UriParser; |
|
|
|
use Aws\Signature\SignatureInterface; |
|
|
|
use GuzzleHttp\Psr7; |
|
|
|
use Psr\Http\Message\RequestInterface; |
|
|
|
|
|
|
|
/** |
|
|
|
* Legacy Amazon S3 signature implementation |
|
|
|
*/ |
|
|
|
class S3Signature implements SignatureInterface |
|
|
|
{ |
|
|
|
/** @var array Query string values that must be signed */ |
|
|
|
private $signableQueryString = [ |
|
|
|
'acl', 'cors', 'delete', 'lifecycle', 'location', 'logging', |
|
|
|
'notification', 'partNumber', 'policy', 'requestPayment', |
|
|
|
'response-cache-control', 'response-content-disposition', |
|
|
|
'response-content-encoding', 'response-content-language', |
|
|
|
'response-content-type', 'response-expires', 'restore', 'tagging', |
|
|
|
'torrent', 'uploadId', 'uploads', 'versionId', 'versioning', |
|
|
|
'versions', 'website' |
|
|
|
]; |
|
|
|
|
|
|
|
/** @var array Sorted headers that must be signed */ |
|
|
|
private $signableHeaders = ['Content-MD5', 'Content-Type']; |
|
|
|
|
|
|
|
/** @var \Aws\S3\S3UriParser S3 URI parser */ |
|
|
|
private $parser; |
|
|
|
|
|
|
|
public function __construct() |
|
|
|
{ |
|
|
|
$this->parser = new S3UriParser(); |
|
|
|
// Ensure that the signable query string parameters are sorted |
|
|
|
sort($this->signableQueryString); |
|
|
|
} |
|
|
|
|
|
|
|
public function signRequest( |
|
|
|
RequestInterface $request, |
|
|
|
CredentialsInterface $credentials |
|
|
|
) { |
|
|
|
$request = $this->prepareRequest($request, $credentials); |
|
|
|
$stringToSign = $this->createCanonicalizedString($request); |
|
|
|
$auth = 'AWS ' |
|
|
|
. $credentials->getAccessKeyId() . ':' |
|
|
|
. $this->signString($stringToSign, $credentials); |
|
|
|
|
|
|
|
return $request->withHeader('Authorization', $auth); |
|
|
|
} |
|
|
|
|
|
|
|
public function presign( |
|
|
|
RequestInterface $request, |
|
|
|
CredentialsInterface $credentials, |
|
|
|
$expires |
|
|
|
) { |
|
|
|
$query = []; |
|
|
|
// URL encoding already occurs in the URI template expansion. Undo that |
|
|
|
// and encode using the same encoding as GET object, PUT object, etc. |
|
|
|
$uri = $request->getUri(); |
|
|
|
$path = S3Client::encodeKey(rawurldecode($uri->getPath())); |
|
|
|
$request = $request->withUri($uri->withPath($path)); |
|
|
|
|
|
|
|
// Make sure to handle temporary credentials |
|
|
|
if ($token = $credentials->getSecurityToken()) { |
|
|
|
$request = $request->withHeader('X-Amz-Security-Token', $token); |
|
|
|
$query['X-Amz-Security-Token'] = $token; |
|
|
|
} |
|
|
|
|
|
|
|
if ($expires instanceof \DateTime) { |
|
|
|
$expires = $expires->getTimestamp(); |
|
|
|
} elseif (!is_numeric($expires)) { |
|
|
|
$expires = strtotime($expires); |
|
|
|
} |
|
|
|
|
|
|
|
// Set query params required for pre-signed URLs |
|
|
|
$query['AWSAccessKeyId'] = $credentials->getAccessKeyId(); |
|
|
|
$query['Expires'] = $expires; |
|
|
|
$query['Signature'] = $this->signString( |
|
|
|
$this->createCanonicalizedString($request, $expires), |
|
|
|
$credentials |
|
|
|
); |
|
|
|
|
|
|
|
// Move X-Amz-* headers to the query string |
|
|
|
foreach ($request->getHeaders() as $name => $header) { |
|
|
|
$name = strtolower($name); |
|
|
|
if (strpos($name, 'x-amz-') === 0) { |
|
|
|
$query[$name] = implode(',', $header); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
$queryString = http_build_query($query, null, '&', PHP_QUERY_RFC3986); |
|
|
|
|
|
|
|
return $request->withUri($request->getUri()->withQuery($queryString)); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @param RequestInterface $request |
|
|
|
* @param CredentialsInterface $creds |
|
|
|
* |
|
|
|
* @return RequestInterface |
|
|
|
*/ |
|
|
|
private function prepareRequest( |
|
|
|
RequestInterface $request, |
|
|
|
CredentialsInterface $creds |
|
|
|
) { |
|
|
|
$modify = [ |
|
|
|
'remove_headers' => ['X-Amz-Date'], |
|
|
|
'set_headers' => ['Date' => gmdate(\DateTime::RFC2822)] |
|
|
|
]; |
|
|
|
|
|
|
|
// Add the security token header if one is being used by the credentials |
|
|
|
if ($token = $creds->getSecurityToken()) { |
|
|
|
$modify['set_headers']['X-Amz-Security-Token'] = $token; |
|
|
|
} |
|
|
|
|
|
|
|
return Psr7\modify_request($request, $modify); |
|
|
|
} |
|
|
|
|
|
|
|
private function signString($string, CredentialsInterface $credentials) |
|
|
|
{ |
|
|
|
return base64_encode( |
|
|
|
hash_hmac('sha1', $string, $credentials->getSecretKey(), true) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
private function createCanonicalizedString( |
|
|
|
RequestInterface $request, |
|
|
|
$expires = null |
|
|
|
) { |
|
|
|
$buffer = $request->getMethod() . "\n"; |
|
|
|
|
|
|
|
// Add the interesting headers |
|
|
|
foreach ($this->signableHeaders as $header) { |
|
|
|
$buffer .= $request->getHeaderLine($header) . "\n"; |
|
|
|
} |
|
|
|
|
|
|
|
$date = $expires ?: $request->getHeaderLine('date'); |
|
|
|
$buffer .= "{$date}\n" |
|
|
|
. $this->createCanonicalizedAmzHeaders($request) |
|
|
|
. $this->createCanonicalizedResource($request); |
|
|
|
|
|
|
|
return $buffer; |
|
|
|
} |
|
|
|
|
|
|
|
private function createCanonicalizedAmzHeaders(RequestInterface $request) |
|
|
|
{ |
|
|
|
$headers = []; |
|
|
|
foreach ($request->getHeaders() as $name => $header) { |
|
|
|
$name = strtolower($name); |
|
|
|
if (strpos($name, 'x-amz-') === 0) { |
|
|
|
$value = implode(',', $header); |
|
|
|
if (strlen($value) > 0) { |
|
|
|
$headers[$name] = $name . ':' . $value; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!$headers) { |
|
|
|
return ''; |
|
|
|
} |
|
|
|
|
|
|
|
ksort($headers); |
|
|
|
|
|
|
|
return implode("\n", $headers) . "\n"; |
|
|
|
} |
|
|
|
|
|
|
|
private function createCanonicalizedResource(RequestInterface $request) |
|
|
|
{ |
|
|
|
$data = $this->parser->parse($request->getUri()); |
|
|
|
$buffer = '/'; |
|
|
|
|
|
|
|
if ($data['bucket']) { |
|
|
|
$buffer .= $data['bucket']; |
|
|
|
if (!empty($data['key']) || !$data['path_style']) { |
|
|
|
$buffer .= '/' . $data['key']; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Add sub resource parameters if present. |
|
|
|
$query = $request->getUri()->getQuery(); |
|
|
|
|
|
|
|
if ($query) { |
|
|
|
$params = Psr7\parse_query($query); |
|
|
|
$first = true; |
|
|
|
foreach ($this->signableQueryString as $key) { |
|
|
|
if (array_key_exists($key, $params)) { |
|
|
|
$value = $params[$key]; |
|
|
|
$buffer .= $first ? '?' : '&'; |
|
|
|
$first = false; |
|
|
|
$buffer .= $key; |
|
|
|
// Don't add values for empty sub-resources |
|
|
|
if (strlen($value)) { |
|
|
|
$buffer .= "={$value}"; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return $buffer; |
|
|
|
} |
|
|
|
} |