From cbfda8fa6c969e07a26e743a9f2da12680d60fa2 Mon Sep 17 00:00:00 2001 From: xuty <xty50337@hotmail.com> Date: Tue, 31 Mar 2020 10:52:25 +0800 Subject: [PATCH] finish presignedUrl --- CHANGELOG.md | 4 +++ README.md | 5 +++- example/minio_example.dart | 4 +++ lib/src/minio.dart | 33 +++++++++++++++++++++ lib/src/minio_client.dart | 46 ++++++++++++++++++++++------- lib/src/minio_sign.dart | 59 ++++++++++++++++++++++++++++++++++++-- lib/src/utils.dart | 6 ++++ pubspec.yaml | 2 +- 8 files changed, 144 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c36d42..3ce396a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +- support presignedUrl + ## 0.1.1 - update dependency diff --git a/README.md b/README.md index 492b757..0d2e8ee 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is the _unofficial_ MinIO Dart Client SDK that provides simple APIs to acce | Bucket operations | Object operations | Presigned operations | Bucket Policy & Notification operations | |-------------------------|-------------------------|-----------------------|-------------------------------------------| -| [makeBucket] | [getObject] | presignedUrl | getBucketNotification | +| [makeBucket] | [getObject] | [presignedUrl] | getBucketNotification | | [listBuckets] | [getPartialObject] | presignedGetObject | setBucketNotification | | [bucketExists] | [fGetObject] | presignedPutObject | removeAllBucketNotification | | [removeBucket] | [putObject] | presignedPostPolicy | getBucketPolicy | @@ -91,3 +91,6 @@ MIT [fGetObject]: https://pub.dev/documentation/minio/latest/io/MinioX/fGetObject.html [fPutObject]: https://pub.dev/documentation/minio/latest/io/MinioX/fPutObject.html + +[presignedUrl]: https://pub.dev/documentation/minio/latest/minio/Minio/presignedUrl.html + diff --git a/example/minio_example.dart b/example/minio_example.dart index 345e8ae..8849e3f 100644 --- a/example/minio_example.dart +++ b/example/minio_example.dart @@ -30,6 +30,10 @@ void main() async { print('--- etag:'); print(etag); + final url = await minio.presignedUrl('GET', bucket, object, expires: 1000); + print('--- presigned url:'); + print(url); + final copyResult1 = await minio.copyObject(bucket, copy1, '$bucket/$object'); final copyResult2 = await minio.copyObject(bucket, copy2, '$bucket/$object'); print('--- copy1 etag:'); diff --git a/lib/src/minio.dart b/lib/src/minio.dart index 6726870..328d3a5 100644 --- a/lib/src/minio.dart +++ b/lib/src/minio.dart @@ -3,6 +3,7 @@ import 'package:minio/models.dart'; import 'package:minio/src/minio_client.dart'; import 'package:minio/src/minio_errors.dart'; import 'package:minio/src/minio_helpers.dart'; +import 'package:minio/src/minio_sign.dart'; import 'package:minio/src/minio_uploader.dart'; import 'package:minio/src/utils.dart'; import 'package:xml/xml.dart' as xml; @@ -613,6 +614,38 @@ class Minio { return resp.body; } + /// Generate a generic presigned URL which can be + /// used for HTTP methods GET, PUT, HEAD and DELETE + /// + /// - [method]: name of the HTTP method + /// - [bucketName]: name of the bucket + /// - [objectName]: name of the object + /// - [expiry]: expiry in seconds (optional, default 7 days) + /// - [reqParams]: request parameters (optional) + /// - [requestDate]: A date object, the url will be issued at (optional) + Future<String> presignedUrl( + String method, + String bucket, + String object, { + int expires, + String resource, + Map<String, String> reqParams, + DateTime requestDate, + }) async { + MinioInvalidBucketNameError.check(bucket); + MinioInvalidObjectNameError.check(object); + + assert(expires == null || expires >= 0); + + expires ??= expires = 24 * 60 * 60 * 7; // 7 days in seconds + reqParams ??= {}; + requestDate ??= DateTime.now().toUtc(); + + final region = await getBucketRegion(bucket); + final request = _client.getBaseRequest(method, bucket, object, region, resource, reqParams, {}); + return presignSignatureV4(this, request, region, requestDate, expires); + } + /// Uploads the object. Future<String> putObject( String bucket, diff --git a/lib/src/minio_client.dart b/lib/src/minio_client.dart index ee84cf8..2aebd12 100644 --- a/lib/src/minio_client.dart +++ b/lib/src/minio_client.dart @@ -26,6 +26,18 @@ class MinioRequest extends BaseRequest { } throw UnsupportedError('unsupported body type: ${body.runtimeType}'); } + + MinioRequest replace({ + String method, + Uri url, + Map<String, String> headers, + body, + }) { + final result = MinioRequest(method ?? this.method, url ?? this.url); + result.body = body ?? this.body; + result.headers.addAll(headers ?? this.headers); + return result; + } } class MinioClient { @@ -52,26 +64,20 @@ class MinioClient { Map<String, String> queries, Map<String, String> headers, }) async { - final url = getRequestUrl(bucket, object, resource, queries); - final request = MinioRequest(method, url); - final date = DateTime.now().toUtc(); - final sha256sum = enableSHA256 ? sha256Hex(payload) : 'UNSIGNED-PAYLOAD'; - region ??= await minio.getBucketRegion(bucket); + final request = getBaseRequest( + method, bucket, object, region, resource, queries, headers); request.body = payload; + final date = DateTime.now().toUtc(); + final sha256sum = enableSHA256 ? sha256Hex(payload) : 'UNSIGNED-PAYLOAD'; request.headers.addAll({ - 'host': url.host, 'user-agent': userAgent, 'x-amz-date': makeDateLong(date), 'x-amz-content-sha256': sha256sum, }); - if (headers != null) { - request.headers.addAll(headers); - } - final authorization = signV4(minio, request, date, 'us-east-1'); request.headers['authorization'] = authorization; @@ -132,6 +138,26 @@ class MinioClient { return response; } + MinioRequest getBaseRequest( + String method, + String bucket, + String object, + String region, + String resource, + Map<String, String> queries, + Map<String, String> headers, + ) { + final url = getRequestUrl(bucket, object, resource, queries); + final request = MinioRequest(method, url); + request.headers['host'] = url.host; + + if (headers != null) { + request.headers.addAll(headers); + } + + return request; + } + Uri getRequestUrl( String bucket, String object, diff --git a/lib/src/minio_sign.dart b/lib/src/minio_sign.dart index f79fd7c..c2313d6 100644 --- a/lib/src/minio_sign.dart +++ b/lib/src/minio_sign.dart @@ -2,6 +2,7 @@ import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; import 'package:minio/minio.dart'; import 'package:minio/src/minio_client.dart'; +import 'package:minio/src/minio_errors.dart'; import 'package:minio/src/minio_helpers.dart'; import 'package:minio/src/utils.dart'; @@ -14,7 +15,9 @@ String signV4( String region, ) { final signedHeaders = getSignedHeaders(request.headers.keys); - final canonicalRequest = getCanonicalRequest(request, signedHeaders); + final hashedPayload = request.headers['x-amz-content-sha256']; + final canonicalRequest = + getCanonicalRequest(request, signedHeaders, hashedPayload); final stringToSign = getStringToSign(canonicalRequest, requestDate, region); final signingKey = getSigningKey(requestDate, region, minio.secretKey); final credential = getCredential(minio.accessKey, region, requestDate); @@ -36,8 +39,11 @@ List<String> getSignedHeaders(Iterable<String> headers) { return result; } -String getCanonicalRequest(MinioRequest request, List<String> signedHeaders) { - final hashedPayload = request.headers['x-amz-content-sha256']; +String getCanonicalRequest( + MinioRequest request, + List<String> signedHeaders, + String hashedPayload, +) { final requestResource = request.url.path; final headers = signedHeaders.map( (header) => '${header.toLowerCase()}:${request.headers[header]}', @@ -93,3 +99,50 @@ List<int> getSigningKey(DateTime date, String region, String secretKey) { String getCredential(String accessKey, String region, DateTime requestDate) { return '$accessKey/${getScope(region, requestDate)}'; } + +// returns a presigned URL string +String presignSignatureV4( + Minio minio, + MinioRequest request, + String region, + DateTime requestDate, + int expires, +) { + if (expires < 1) { + throw MinioExpiresParamError('expires param cannot be less than 1 seconds'); + } + if (expires > 604800) { + throw MinioExpiresParamError('expires param cannot be greater than 7 days'); + } + + final iso8601Date = makeDateLong(requestDate); + final signedHeaders = getSignedHeaders(request.headers.keys); + final credential = getCredential(minio.accessKey, region, requestDate); + + final requestQuery = <String, String>{}; + requestQuery['X-Amz-Algorithm'] = signV4Algorithm; + requestQuery['X-Amz-Credential'] = credential; + requestQuery['X-Amz-Date'] = iso8601Date; + requestQuery['X-Amz-Expires'] = expires.toString(); + requestQuery['X-Amz-SignedHeaders'] = signedHeaders.join(';').toLowerCase(); + if (minio.sessionToken != null) { + requestQuery['X-Amz-Security-Token'] = minio.sessionToken; + } + + request = request.replace( + url: request.url.replace(queryParameters: { + ...request.url.queryParameters, + ...requestQuery, + }), + ); + + final canonicalRequest = + getCanonicalRequest(request, signedHeaders, 'UNSIGNED-PAYLOAD'); + + final stringToSign = getStringToSign(canonicalRequest, requestDate, region); + final signingKey = getSigningKey(requestDate, region, minio.secretKey); + final signature = sha256HmacHex(stringToSign, signingKey); + final presignedUrl = request.url.toString() + '&X-Amz-Signature=${signature}'; + + return presignedUrl; +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index d62d422..8b586e3 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -20,6 +20,12 @@ String sha256Hex(Object data) { return hex.encode(sha256.convert(data).bytes); } +String sha256HmacHex(String data, List<int> signingKey) { + return hex + .encode(Hmac(sha256, signingKey).convert(utf8.encode(data)).bytes) + .toLowerCase(); +} + String md5Base64(String source) { final md5digest = md5.convert(utf8.encode(source)).bytes; return base64.encode(md5digest); diff --git a/pubspec.yaml b/pubspec.yaml index ac5e578..3f8b0b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: minio description: Unofficial MinIO Dart Client SDK that provides simple APIs to access any Amazon S3 compatible object storage server. -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/xtyxtyx/minio-dart issue_tracker: https://github.com/xtyxtyx/minio-dart/issues -- GitLab