add option to use legacy v2 auth with s3
Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
parent
e550a3ddd8
commit
34ced4dd97
|
@ -53,6 +53,8 @@ class AmazonS3 extends Backend {
|
||||||
->setType(DefinitionParameter::VALUE_BOOLEAN),
|
->setType(DefinitionParameter::VALUE_BOOLEAN),
|
||||||
(new DefinitionParameter('use_path_style', $l->t('Enable Path Style')))
|
(new DefinitionParameter('use_path_style', $l->t('Enable Path Style')))
|
||||||
->setType(DefinitionParameter::VALUE_BOOLEAN),
|
->setType(DefinitionParameter::VALUE_BOOLEAN),
|
||||||
|
(new DefinitionParameter('legacy_auth', $l->t('Legacy (v2) authentication')))
|
||||||
|
->setType(DefinitionParameter::VALUE_BOOLEAN),
|
||||||
])
|
])
|
||||||
->addAuthScheme(AccessKey::SCHEME_AMAZONS3_ACCESSKEY)
|
->addAuthScheme(AccessKey::SCHEME_AMAZONS3_ACCESSKEY)
|
||||||
->setLegacyAuthMechanism($legacyAuth)
|
->setLegacyAuthMechanism($legacyAuth)
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
namespace OC\Files\ObjectStore;
|
namespace OC\Files\ObjectStore;
|
||||||
|
|
||||||
|
use Aws\ClientResolver;
|
||||||
use Aws\S3\Exception\S3Exception;
|
use Aws\S3\Exception\S3Exception;
|
||||||
use Aws\S3\S3Client;
|
use Aws\S3\S3Client;
|
||||||
|
|
||||||
|
@ -86,11 +87,15 @@ trait S3ConnectionTrait {
|
||||||
],
|
],
|
||||||
'endpoint' => $base_url,
|
'endpoint' => $base_url,
|
||||||
'region' => $this->params['region'],
|
'region' => $this->params['region'],
|
||||||
'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false
|
'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
|
||||||
|
'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider())
|
||||||
];
|
];
|
||||||
if (isset($this->params['proxy'])) {
|
if (isset($this->params['proxy'])) {
|
||||||
$options['request.options'] = ['proxy' => $this->params['proxy']];
|
$options['request.options'] = ['proxy' => $this->params['proxy']];
|
||||||
}
|
}
|
||||||
|
if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
|
||||||
|
$options['signature_version'] = 'v2';
|
||||||
|
}
|
||||||
$this->connection = new S3Client($options);
|
$this->connection = new S3Client($options);
|
||||||
|
|
||||||
if (!$this->connection->isBucketDnsCompatible($this->bucket)) {
|
if (!$this->connection->isBucketDnsCompatible($this->bucket)) {
|
||||||
|
@ -120,4 +125,14 @@ trait S3ConnectionTrait {
|
||||||
sleep($this->timeout);
|
sleep($this->timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function legacySignatureProvider($version, $service, $region) {
|
||||||
|
switch ($version) {
|
||||||
|
case 'v2':
|
||||||
|
case 's3':
|
||||||
|
return new S3Signature();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue