From 9ade42bf59ecae2e468a88c81ff6486199076994 Mon Sep 17 00:00:00 2001 From: xuty <xty50337@hotmail.com> Date: Tue, 31 Mar 2020 21:13:37 +0800 Subject: [PATCH] finish presignedPostPolicy --- CHANGELOG.md | 4 +++ README.md | 25 +++++++------- lib/src/minio.dart | 54 ++++++++++++++++++++++++++++++ lib/src/minio_models.dart | 69 +++++++++++++++++++++++++++++++++++++++ lib/src/minio_sign.dart | 11 +++++++ lib/src/utils.dart | 4 +++ pubspec.yaml | 2 +- 7 files changed, 156 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d46b93..f1155cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.4 + +- support presignedPostPolicy + ## 0.1.3 - support presignedGetObject and presignedPutObject diff --git a/README.md b/README.md index cc0c02b..9454ad9 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ This is the _unofficial_ MinIO Dart Client SDK that provides simple APIs to acce ## API -| Bucket operations | Object operations | Presigned operations | Bucket Policy & Notification operations | -|-------------------------|-------------------------|-----------------------|-------------------------------------------| -| [makeBucket] | [getObject] | [presignedUrl] | getBucketNotification | -| [listBuckets] | [getPartialObject] | [presignedGetObject] | setBucketNotification | -| [bucketExists] | [fGetObject] | [presignedPutObject] | removeAllBucketNotification | -| [removeBucket] | [putObject] | presignedPostPolicy | getBucketPolicy | -| [listObjects] | [fPutObject] | | setBucketPolicy | -| [listObjectsV2] | [copyObject] | | listenBucketNotification | -| [listIncompleteUploads] | [statObject] | | | -| | [removeObject] | | | -| | [removeObjects] | | | -| | [removeIncompleteUpload]| | | +| Bucket operations | Object operations | Presigned operations | Bucket Policy & Notification operations | +|-------------------------|--------------------------|----------------------|-----------------------------------------| +| [makeBucket] | [getObject] | [presignedUrl] | getBucketNotification | +| [listBuckets] | [getPartialObject] | [presignedGetObject] | setBucketNotification | +| [bucketExists] | [fGetObject] | [presignedPutObject] | removeAllBucketNotification | +| [removeBucket] | [putObject] | [presignedPostPolicy]| listenBucketNotification | +| [listObjects] | [fPutObject] | | getBucketPolicy | +| [listObjectsV2] | [copyObject] | | setBucketPolicy | +| [listIncompleteUploads] | [statObject] | | | +| | [removeObject] | | | +| | [removeObjects] | | | +| | [removeIncompleteUpload] | | | ## Usage @@ -95,4 +95,5 @@ MIT [presignedUrl]: https://pub.dev/documentation/minio/latest/minio/Minio/presignedUrl.html [presignedGetObject]: https://pub.dev/documentation/minio/latest/minio/Minio/presignedGetObject.html [presignedPutObject]: https://pub.dev/documentation/minio/latest/minio/Minio/presignedPutObject.html +[presignedPostPolicy]: https://pub.dev/documentation/minio/latest/minio/Minio/presignedPostPolicy.html diff --git a/lib/src/minio.dart b/lib/src/minio.dart index b2baeaa..0772893 100644 --- a/lib/src/minio.dart +++ b/lib/src/minio.dart @@ -641,6 +641,60 @@ class Minio { ); } + /// presignedPostPolicy can be used in situations where we want more control on the upload than what + /// presignedPutObject() provides. i.e Using presignedPostPolicy we will be able to put policy restrictions + /// on the object's `name` `bucket` `expiry` `Content-Type` + Future presignedPostPolicy(PostPolicy postPolicy) async { + if (_client.anonymous) { + throw MinioAnonymousRequestError( + 'Presigned POST policy cannot be generated for anonymous requests'); + } + + final region = await getBucketRegion(postPolicy.formData['bucket']); + var date = DateTime.now().toUtc(); + var dateStr = makeDateLong(date); + + if (postPolicy.policy['expiration'] == null) { + // 'expiration' is mandatory field for S3. + // Set default expiration date of 7 days. + var expires = DateTime.now().toUtc(); + expires.add(Duration(days: 7)); + postPolicy.setExpires(expires); + } + + postPolicy.policy['conditions'].push(['eq', r'$x-amz-date', dateStr]); + postPolicy.formData['x-amz-date'] = dateStr; + + postPolicy.policy['conditions'] + .push(['eq', r'$x-amz-algorithm', 'AWS4-HMAC-SHA256']); + postPolicy.formData['x-amz-algorithm'] = 'AWS4-HMAC-SHA256'; + + postPolicy.policy['conditions'].push( + ['eq', r'$x-amz-credential', accessKey + '/' + getScope(region, date)]); + postPolicy.formData['x-amz-credential'] = + accessKey + '/' + getScope(region, date); + + if (sessionToken != null) { + postPolicy.policy['conditions'] + .push(['eq', r'$x-amz-security-token', sessionToken]); + } + + final policyBase64 = jsonBase64(postPolicy.policy); + postPolicy.formData['policy'] = policyBase64; + + final signature = + postPresignSignatureV4(region, date, secretKey, policyBase64); + + postPolicy.formData['x-amz-signature'] = signature; + final url = _client + .getBaseRequest('POST', postPolicy.formData['bucket'], null, region, + null, null, null) + .url; + var portStr = (port == 80 || port == 443 || port == null) ? '' : ':$port'; + var urlStr = '${url.scheme}://${url.host}${portStr}${url.path}'; + return PostPolicyResult(postURL: urlStr, formData: postPolicy.formData); + } + /// Generate a presigned URL for PUT. /// Using this URL, the browser can upload to S3 only with the specified object name. /// diff --git a/lib/src/minio_models.dart b/lib/src/minio_models.dart index a1e27ad..382e6da 100644 --- a/lib/src/minio_models.dart +++ b/lib/src/minio_models.dart @@ -1,4 +1,5 @@ import 'package:minio/models.dart'; +import 'package:minio/src/minio_errors.dart'; import 'package:xml/xml.dart'; class ListObjectsChunk { @@ -113,3 +114,71 @@ class StatObjectResult { final DateTime lastModified; final Map<String, String> metaData; } + +/// Build PostPolicy object that can be signed by presignedPostPolicy +class PostPolicy { + final policy = <String, dynamic>{ + 'conditions': [], + }; + + final formData = <String, String>{}; + + /// set expiration date + void setExpires(DateTime date) { + if (date == null) { + throw MinioInvalidDateError('Invalid date : cannot be null'); + } + policy['expiration'] = date.toIso8601String(); + } + + /// set object name + void setKey(String object) { + MinioInvalidObjectNameError.check(object); + policy['conditions'].add(['eq', r'$key', object]); + formData['key'] = object; + } + + /// set object name prefix, i.e policy allows any keys with this prefix + void setKeyStartsWith(String prefix) { + MinioInvalidPrefixError.check(prefix); + policy['conditions'].push(['starts-with', r'$key', prefix]); + formData['key'] = prefix; + } + + /// set bucket name + void setBucket(bucket) { + MinioInvalidBucketNameError.check(bucket); + policy['conditions'].push(['eq', r'$bucket', bucket]); + formData['bucket'] = bucket; + } + + /// set Content-Type + void setContentType(String type) { + if (type == null) { + throw MinioError('content-type cannot be null'); + } + policy['conditions'].push(['eq', r'$Content-Type', type]); + formData['Content-Type'] = type; + } + + /// set minimum/maximum length of what Content-Length can be. + void setContentLengthRange(int min, int max) { + if (min > max) { + throw MinioError('min cannot be more than max'); + } + if (min < 0) { + throw MinioError('min should be > 0'); + } + if (max < 0) { + throw MinioError('max should be > 0'); + } + policy['conditions'].push(['content-length-range', min, max]); + } +} + +class PostPolicyResult { + PostPolicyResult({this.postURL, this.formData}); + + final String postURL; + final Map<String, String> formData; +} diff --git a/lib/src/minio_sign.dart b/lib/src/minio_sign.dart index c2313d6..fee400c 100644 --- a/lib/src/minio_sign.dart +++ b/lib/src/minio_sign.dart @@ -146,3 +146,14 @@ String presignSignatureV4( return presignedUrl; } + +// calculate the signature of the POST policy +String postPresignSignatureV4( + String region, + DateTime date, + String secretKey, + String policyBase64, +) { + final signingKey = getSigningKey(date, region, secretKey); + return sha256HmacHex(policyBase64, signingKey); +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 8b586e3..f52f794 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -31,6 +31,10 @@ String md5Base64(String source) { return base64.encode(md5digest); } +String jsonBase64(Map<String, dynamic> jsonObject) { + return base64.encode(utf8.encode(json.encode(jsonObject))); +} + XmlElement getNodeProp(XmlElement xml, String name) { final result = xml.findElements(name); return result.isNotEmpty ? result.first : null; diff --git a/pubspec.yaml b/pubspec.yaml index a7b8128..1d2accc 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.3 +version: 0.1.4 homepage: https://github.com/xtyxtyx/minio-dart issue_tracker: https://github.com/xtyxtyx/minio-dart/issues -- GitLab