From 1a05e8077b9e2ecbef0579253594ee2e67c45af4 Mon Sep 17 00:00:00 2001 From: moca_tao7 Date: Mon, 9 May 2022 14:25:47 +0800 Subject: [PATCH] feat: add asyncSignatureUrl method (#1057) * feat: add asyncSignatureUrl method * chore: add asyncSignatureUrl warn message * feat: to resolve conversation * chore: optimized test case * chore: optimized test case * chore: chore: optimized test case * chore: the tag signatureUrl will be deprecated in the next version * chore: remove not use function checkBrowserEnv Co-authored-by: Undefined --- README.md | 78 ++++++++++++++++++++++++++ lib/browser/object.js | 1 + lib/common/object/asyncSignatureUrl.js | 45 +++++++++++++++ lib/common/object/index.js | 2 +- lib/common/object/signatureUrl.js | 26 +++------ test/browser/browser.test.js | 31 ++++++++++ test/node/object.test.js | 43 -------------- test/node/sts.test.js | 27 +++++++++ 8 files changed, 190 insertions(+), 63 deletions(-) create mode 100644 lib/common/object/asyncSignatureUrl.js diff --git a/README.md b/README.md index e4e4b2e23..645504166 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ All operation use es7 async/await to implement. All api is async function. - [.putMeta(name, meta[, options])](#putmetaname-meta-options) - [.deleteMulti(names[, options])](#deletemultinames-options) - [.signatureUrl(name[, options])](#signatureurlname-options) + - [.asyncSignatureUrl(name[, options])](#signatureurlname-options) - [.putACL(name, acl[, options])](#putaclname-acl-options) - [.getACL(name[, options])](#getaclname-options) - [.restore(name[, options])](#restorename-options) @@ -2636,6 +2637,83 @@ const url = store.signatureUrl('ossdemo.png', { console.log(url); ``` +### .asyncSignatureUrl(name[, options]) + +Basically the same as signatureUrl, if refreshSTSToken is configured asyncSignatureUrl will refresh stsToken + +parameters: + +- name {String} object name store on OSS +- [options] {Object} optional parameters + - [expires] {Number} after expires seconds, the url will become invalid, default is `1800` + - [method] {String} the HTTP method, default is 'GET' + - [Content-Type] {String} set the request content type + - [process] {String} image process params, will send with `x-oss-process` + e.g.: `{process: 'image/resize,w_200'}` + - [trafficLimit] {Number} traffic limit, range: `819200`~`838860800`. + - [subResource] {Object} additional signature parameters in url. + - [response] {Object} set the response headers for download + - [content-type] {String} set the response content type + - [content-disposition] {String} set the response content disposition + - [cache-control] {String} set the response cache control + - See more: + - [callback] {Object} set the callback for the operation + - url {String} set the url for callback + - [host] {String} set the host for callback + - body {String} set the body for callback + - [contentType] {String} set the type for body + - [customValue] {Object} set the custom value for callback,eg. {var1: value1,var2:value2} + +Success will return signature url. + +example: + +- Get signature url for object + +```js +const url = await store.asyncSignatureUrl('ossdemo.txt'); +console.log(url); +// -------------------------------------------------- +const url = await store.asyncSignatureUrl('ossdemo.txt', { + expires: 3600, + method: 'PUT' +}); +console.log(url); +// put object with signatureUrl +// ------------------------------------------------- +const url = await store.asyncSignatureUrl('ossdemo.txt', { + expires: 3600, + method: 'PUT', + 'Content-Type': 'text/plain; charset=UTF-8', +}); +console.log(url); +// -------------------------------------------------- +const url = await store.asyncSignatureUrl('ossdemo.txt', { + expires: 3600, + response: { + 'content-type': 'text/custom', + 'content-disposition': 'attachment' + } +}); +console.log(url); +// put operation +``` + +- Get a signature url for a processed image + +```js +const url = await store.asyncSignatureUrl('ossdemo.png', { + process: 'image/resize,w_200' +}); +console.log(url); +// -------------------------------------------------- +const url = await store.asyncSignatureUrl('ossdemo.png', { + expires: 3600, + process: 'image/resize,w_200' +}); +console.log(url); +``` + ### .putACL(name, acl[, options]) Set object's ACL. diff --git a/lib/browser/object.js b/lib/browser/object.js index 1ad7205e2..01f76ddd0 100644 --- a/lib/browser/object.js +++ b/lib/browser/object.js @@ -163,6 +163,7 @@ merge(proto, require('../common/object/getObjectMeta')); merge(proto, require('../common/object/getObjectUrl')); merge(proto, require('../common/object/generateObjectUrl')); merge(proto, require('../common/object/signatureUrl')); +merge(proto, require('../common/object/asyncSignatureUrl')); proto.putMeta = async function putMeta(name, meta, options) { const copyResult = await this.copy(name, name, { diff --git a/lib/common/object/asyncSignatureUrl.js b/lib/common/object/asyncSignatureUrl.js new file mode 100644 index 000000000..ba1c643a3 --- /dev/null +++ b/lib/common/object/asyncSignatureUrl.js @@ -0,0 +1,45 @@ +const urlutil = require('url'); +const utility = require('utility'); +const copy = require('copy-to'); +const signHelper = require('../../common/signUtils'); +const { isIP } = require('../utils/isIP'); +const { setSTSToken } = require('../utils/setSTSToken'); +const { isFunction } = require('../utils/isFunction'); +const proto = exports; + +proto.asyncSignatureUrl = async function asyncSignatureUrl(name, options) { + if (isIP(this.options.endpoint.hostname)) { + throw new Error('can not get the object URL when endpoint is IP'); + } + options = options || {}; + name = this._objectName(name); + options.method = options.method || 'GET'; + const expires = utility.timestamp() + (options.expires || 1800); + const params = { + bucket: this.options.bucket, + object: name + }; + + const resource = this._getResource(params); + + if (this.options.stsToken && isFunction(this.options.refreshSTSToken)) { + await setSTSToken.call(this); + } + + if (this.options.stsToken) { + options['security-token'] = this.options.stsToken; + } + + const signRes = signHelper._signatureForURL(this.options.accessKeySecret, options, resource, expires); + + const url = urlutil.parse(this._getReqUrl(params)); + url.query = { + OSSAccessKeyId: this.options.accessKeyId, + Expires: expires, + Signature: signRes.Signature + }; + + copy(signRes.subResource).to(url.query); + + return url.format(); +}; diff --git a/lib/common/object/index.js b/lib/common/object/index.js index 47a88811f..755d3774f 100644 --- a/lib/common/object/index.js +++ b/lib/common/object/index.js @@ -22,4 +22,4 @@ merge(proto, require('./getAsyncFetch')); merge(proto, require('./generateObjectUrl')); merge(proto, require('./getObjectUrl')); merge(proto, require('./signatureUrl')); - +merge(proto, require('./asyncSignatureUrl')); diff --git a/lib/common/object/signatureUrl.js b/lib/common/object/signatureUrl.js index 275e73c06..be41c7f81 100644 --- a/lib/common/object/signatureUrl.js +++ b/lib/common/object/signatureUrl.js @@ -3,11 +3,15 @@ const utility = require('utility'); const copy = require('copy-to'); const signHelper = require('../../common/signUtils'); const { isIP } = require('../utils/isIP'); -const { isFunction } = require('../../common/utils/isFunction'); -const { checkCredentials } = require('../utils/setSTSToken'); -const { formatObjKey } = require('../utils/formatObjKey'); + const proto = exports; +/** + * signatureUrl + * @deprecated will be deprecated in 7.x + * @param {String} name object name + * @param {Object} options options + */ proto.signatureUrl = function signatureUrl(name, options) { if (isIP(this.options.endpoint.hostname)) { throw new Error('can not get the object URL when endpoint is IP'); @@ -23,22 +27,6 @@ proto.signatureUrl = function signatureUrl(name, options) { const resource = this._getResource(params); - if (this.options.stsToken && isFunction(this.options.refreshSTSToken)) { - const now = new Date(); - if (this.stsTokenFreshTime >= this.options.refreshSTSTokenInterval) { - this.stsTokenFreshTime = now; - this.options.refreshSTSToken().then(r => { - const credentials = formatObjKey(r, 'firstLowerCase'); - if (credentials.securityToken) { - credentials.stsToken = credentials.securityToken; - } - checkCredentials(credentials); - Object.assign(this.options, credentials); - }); - } else { - this.stsTokenFreshTime = now; - } - } if (this.options.stsToken) { options['security-token'] = this.options.stsToken; } diff --git a/test/browser/browser.test.js b/test/browser/browser.test.js index 6cc4c24ca..ce747e438 100644 --- a/test/browser/browser.test.js +++ b/test/browser/browser.test.js @@ -22,6 +22,12 @@ const timemachine = require('timemachine'); timemachine.reset(); +function sleep(time) { + return new Promise(resolve => { + setTimeout(resolve, time); + }); +} + const cleanBucket = async store => { let result = await store.list({ 'max-keys': 1000 @@ -1079,6 +1085,31 @@ describe('browser', () => { // http://www.aliyun.com/darwin-v4.4.2/ali-sdk/oss/get-meta.js?OSSAccessKeyId= assert.equal(url.indexOf('http://www.aliyun.com/'), 0); }); + + it('signatureUrl will should use refreshSTSToken', async () => { + let flag = false; + + store = oss({ + region: ossConfig.region, + accessKeyId: ossConfig.accessKeyId, + accessKeySecret: ossConfig.accessKeySecret, + stsToken: ossConfig.stsToken, + refreshSTSToken: () => { + flag = true; + return { + accessKeyId: 'b', + accessKeySecret: 'b', + stsToken: 'b' + }; + }, + bucket: ossConfig.bucket, + refreshSTSTokenInterval: 1000 + }); + + await sleep(2000); + await store.asyncSignatureUrl('test.txt'); + assert(flag); + }); }); describe('multipart', () => { diff --git a/test/node/object.test.js b/test/node/object.test.js index bab05cb7d..9a6f2f31d 100644 --- a/test/node/object.test.js +++ b/test/node/object.test.js @@ -1048,49 +1048,6 @@ describe('test/object.test.js', () => { assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); }); - it('should signature use setSTSToken', async () => { - const stsClient = sts(stsConfig); - const policy = { - Statement: [ - { - Action: ['oss:*'], - Effect: 'Allow', - Resource: ['acs:oss:*:*:*'] - } - ], - Version: '1' - }; - const response = await stsClient.assumeRole(stsConfig.roleArn, policy); - - const tempStore = oss({ - bucket: stsConfig.bucket, - accessKeyId: response.credentials.AccessKeyId, - accessKeySecret: response.credentials.AccessKeySecret, - region: config.region, - stsToken: response.credentials.SecurityToken, - refreshSTSToken: async () => { - const r = await stsClient.assumeRole(stsConfig.roleArn, policy); - return { - accessKeyId: r.credentials.AccessKeyId, - accessKeySecret: r.credentials.AccessKeySecret, - stsToken: r.credentials.SecurityToken - }; - }, - refreshSTSTokenInterval: 2000 - }); - const content = 'setSTSToken test'; - await tempStore.put(name, Buffer.from(content)); - const beforeUrl = tempStore.signatureUrl(name); - const urlRes = await urllib.request(beforeUrl); - assert.equal(urlRes.data.toString(), content); - const beforeTime = tempStore.stsTokenFreshTime; - await utils.sleep(ms(5000)); - const afterUrl = tempStore.signatureUrl(name); - const afeterRes = await urllib.request(afterUrl); - assert.equal(afeterRes.data.toString(), content); - assert.notEqual(beforeTime, tempStore.stsTokenFreshTime); - }); - it('should signature url get object ok', async () => { try { const result = await store.get(name); diff --git a/test/node/sts.test.js b/test/node/sts.test.js index 1eae8e130..8f3ffe96d 100644 --- a/test/node/sts.test.js +++ b/test/node/sts.test.js @@ -181,6 +181,7 @@ describe('test/sts.test.js', () => { }; store = new OSS(testRefreshSTSTokenConf); }); + it('should refresh sts token when token is expired', async () => { try { store.options.refreshSTSToken = async () => { @@ -198,5 +199,31 @@ describe('test/sts.test.js', () => { assert(false, error); } }); + + it('asyncSignatureUrl will should use refreshSTSToken', async () => { + const { credentials } = await stsClient.assumeRole(stsConfig.roleArn); + let flag = false; + + store = new OSS({ + region: config.region, + accessKeyId: credentials.AccessKeyId, + accessKeySecret: credentials.AccessKeySecret, + stsToken: credentials.SecurityToken, + refreshSTSToken: () => { + flag = true; + return { + accessKeyId: 'b', + accessKeySecret: 'b', + stsToken: 'b' + }; + }, + bucket: stsConfig.bucket, + refreshSTSTokenInterval: 1000 + }); + await utils.sleep(2000); + await store.asyncSignatureUrl('test.txt'); + + assert(flag); + }); }); });