Skip to content

Commit

Permalink
feat: signatureUrl refactor and support callback (#408)
Browse files Browse the repository at this point in the history
* feat: es6 compatibility & add API which used for url with signature

* feat: signatureUrl support callback parameter

* test: add test case for signatureUrl with callback

* docs: update README

* fix: fix issue about review
  • Loading branch information
duan007a authored and PeterRao committed Mar 29, 2018
1 parent 02b0efd commit 343938f
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 111 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,12 @@ parameters:
- [content-disposition] {String} set the response content disposition
- [cache-control] {String} set the response cache control
- See more: https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
- [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.
Expand Down
48 changes: 12 additions & 36 deletions lib/browser/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const copy = require('copy-to');
const path = require('path');
const mime = require('mime');
const callback = require('../common/callback');
const signHelper = require('../common/signUtils');
// var assert = require('assert');


Expand Down Expand Up @@ -426,56 +427,31 @@ proto.getACL = function* getACL(name, options) {
};

proto.signatureUrl = function (name, options) {
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,
};
options = options || {};
const expires = utility.timestamp() + (options.expires || 1800);
let resource = this._getResource(params);
const query = {};
const signList = [];

if (options.response) {
Object.keys(options.response).forEach((k) => {
const key = `response-${k.toLowerCase()}`;
query[key] = options.response[k];
signList.push(`${key}=${options.response[k]}`);
});
}
if (this.options.stsToken) {
query['security-token'] = this.options.stsToken;
signList.push(`security-token=${this.options.stsToken}`);
}
if (options.process) {
const processKeyword = 'x-oss-process';
query[processKeyword] = options.process;
const item = `${processKeyword}=${options.process}`;
signList.push(item);
}

if (signList.length > 0) {
signList.sort();
resource += `?${signList.join('&')}`;
const resource = this._getResource(params);

if (this.options.stsToken) {
options['security-token'] = this.options.stsToken;
}

const stringToSign = [
options.method || 'GET',
options['content-md5'] || '', // Content-MD5
options['content-type'] || '', // Content-Type
expires,
resource,
].join('\n');
const signature = this.signature(stringToSign);
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: signature,
Signature: signRes.Signature,
};
copy(query).to(url.query);

copy(signRes.subResource).to(url.query);

return url.format();
};
Expand Down
157 changes: 118 additions & 39 deletions lib/common/signUtils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

var crypto = require('crypto');
var is = require('is-type-of');
const crypto = require('crypto');
const is = require('is-type-of');

/**
*
Expand All @@ -10,25 +9,29 @@ var is = require('is-type-of');
* @return
*/
exports.buildCanonicalizedResource = function buildCanonicalizedResource(resourcePath, parameters) {
var canonicalizedResource = '' + resourcePath;
var separatorString = '?';
let canonicalizedResource = `${resourcePath}`;
let separatorString = '?';

if (is.string(parameters) && parameters.trim() !== '') {
canonicalizedResource += separatorString + parameters;
} else if (is.array(parameters)) {
parameters.sort();
canonicalizedResource += separatorString + parameters.join('&');
} else if (parameters) {
var keys = Object.keys(parameters).sort();
for (var key in keys) {
var paramKey = keys[key];
canonicalizedResource += separatorString + paramKey;
var parameterValue = parameters[paramKey];
if (parameterValue) {
canonicalizedResource += '=' + parameterValue;
const compareFunc = (entry1, entry2) => {
if (entry1[0] >= entry2[0]) {
return 1;
}
return 0;
};
const processFunc = ([key, value]) => {
canonicalizedResource += separatorString + key;
if (value) {
canonicalizedResource += `=${value}`;
}
separatorString = '&';
}
};
Object.entries(parameters).sort(compareFunc).forEach(processFunc);
}

return canonicalizedResource;
Expand All @@ -42,42 +45,49 @@ exports.buildCanonicalizedResource = function buildCanonicalizedResource(resourc
* @return {String} canonicalString
*/
exports.buildCanonicalString = function canonicalString(method, resourcePath, request, expires) {
var HEADER_CONTENT_TYPE = 'content-type';
var HEADER_CONTENT_MD5 = 'content-md5';
var OSS_PREFIX = 'x-oss-';
request = request || {};
const headers = request.headers || {};
const HEADER_CONTENT_TYPE = 'content-type';
const HEADER_CONTENT_MD5 = 'content-md5';
const OSS_PREFIX = 'x-oss-';
const canonicalElements = [method];
const headersToSign = {};

var headers = request.headers || {};
var canonicalElements = [method];

var headersToSign = {};
for (var key in headers) {
var lowerKey = key.toLowerCase();
Object.entries(headers).forEach(([key, value]) => {
const lowerKey = key.toLowerCase();
if (lowerKey === HEADER_CONTENT_TYPE
|| lowerKey === HEADER_CONTENT_MD5
|| lowerKey.indexOf(OSS_PREFIX) === 0) {
headersToSign[lowerKey] = String(headers[key]).trim();
|| lowerKey === HEADER_CONTENT_MD5
|| lowerKey.indexOf(OSS_PREFIX) === 0) {
headersToSign[lowerKey] = String(value).trim();
}
}
});

if (!headersToSign.hasOwnProperty(HEADER_CONTENT_MD5)) {
if (!Object.prototype.hasOwnProperty.call(headersToSign, HEADER_CONTENT_MD5)) {
headersToSign.HEADER_CONTENT_MD5 = '';
}

if (!headersToSign.hasOwnProperty(HEADER_CONTENT_TYPE)) {
if (!Object.prototype.hasOwnProperty.call(headersToSign, HEADER_CONTENT_TYPE)) {
headersToSign.HEADER_CONTENT_TYPE = '';
}

var keys = Object.keys(headersToSign).sort();
for (var key in keys) {
var parameterName = keys[key];
var parameterValue = headersToSign[parameterName];
if (parameterName.indexOf(OSS_PREFIX) !== 0) {
canonicalElements.push(parameterValue);
const compareFunc = (a, b) => {
if (a[0] >= b[0]) {
return 1;
}
return 0;
};

const processFunc = ([key, value]) => {
if (key.indexOf(OSS_PREFIX) !== 0) {
canonicalElements.push(value);
} else {
canonicalElements.push(parameterName + ':' + parameterValue);
canonicalElements.push(`${key}:${value}`);
}
}
expires = expires || headersToSign["x-oss-date"];
};

Object.entries(headersToSign).sort(compareFunc).forEach(processFunc);

expires = expires || headersToSign['x-oss-date'];
canonicalElements.splice(3, 0, expires);

canonicalElements.push(this.buildCanonicalizedResource(resourcePath, request.parameters));
Expand All @@ -90,7 +100,7 @@ exports.buildCanonicalString = function canonicalString(method, resourcePath, re
* @param {String} canonicalString
*/
exports.computeSignature = function computeSignature(accessKeySecret, canonicalString) {
var signature = crypto.createHmac('sha1', accessKeySecret);
const signature = crypto.createHmac('sha1', accessKeySecret);
return signature.update(new Buffer(canonicalString, 'utf8')).digest('base64');
};

Expand All @@ -100,5 +110,74 @@ exports.computeSignature = function computeSignature(accessKeySecret, canonicalS
* @param {String} canonicalString
*/
exports.authorization = function authorization(accessKeyId, accessKeySecret, canonicalString) {
return 'OSS ' + accessKeyId + ':' + this.computeSignature(accessKeySecret, canonicalString);
return `OSS ${accessKeyId}:${this.computeSignature(accessKeySecret, canonicalString)}`;
};

/**
*
* @param {String} accessKeySecret
* @param {Object} options
* @param {String} resource
* @param {Number} expires
*/
exports._signatureForURL = function _signatureForURL(accessKeySecret, options, resource, expires) {
const headers = {};
const subResource = {};

if (options.process) {
const processKeyword = 'x-oss-process';
subResource[processKeyword] = options.process;
}

if (options.response) {
Object.entries(options.response).forEach(([k, value]) => {
const key = `response-${k.toLowerCase()}`;
subResource[key] = value;
});
}

Object.entries(options).forEach(([key, value]) => {
const lowerKey = key.toLowerCase();
if (lowerKey.indexOf('x-oss-') === 0) {
headers[lowerKey] = value;
} else if (lowerKey !== 'expires' && lowerKey !== 'response' && lowerKey !== 'process' && lowerKey !== 'method') {
subResource[lowerKey] = value;
}
});

if (Object.prototype.hasOwnProperty.call(options, 'security-token')) {
subResource['security-token'] = options['security-token'];
}

if (Object.prototype.hasOwnProperty.call(options, 'callback')) {
const json = {
callbackUrl: encodeURI(options.callback.url),
callbackBody: options.callback.body,
};
if (options.callback.host) {
json.callbackHost = options.callback.host;
}
if (options.callback.contentType) {
json.callbackBodyType = options.callback.contentType;
}
subResource.callback = new Buffer(JSON.stringify(json)).toString('base64');

if (options.callback.customValue) {
const callbackVar = {};
Object.keys(options.callback.customValue).forEach((key) => {
callbackVar[`x:${key}`] = options.callback.customValue[key];
});
subResource['callback-var'] = new Buffer(JSON.stringify(callbackVar)).toString('base64');
}
}

const canonicalString = this.buildCanonicalString(options.method, resource, {
headers,
parameters: subResource,
}, expires.toString());

return {
Signature: this.computeSignature(accessKeySecret, canonicalString),
subResource,
};
};
48 changes: 12 additions & 36 deletions lib/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const copy = require('copy-to');
const path = require('path');
const mime = require('mime');
const callback = require('./common/callback');
const signHelper = require('./common/signUtils');


const proto = exports;
Expand Down Expand Up @@ -430,56 +431,31 @@ proto.getACL = function* getACL(name, options) {
};

proto.signatureUrl = function (name, options) {
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,
};
options = options || {};
const expires = utility.timestamp() + (options.expires || 1800);
let resource = this._getResource(params);
const query = {};
const signList = [];

if (options.response) {
Object.keys(options.response).forEach((k) => {
const key = `response-${k.toLowerCase()}`;
query[key] = options.response[k];
signList.push(`${key}=${options.response[k]}`);
});
}
if (this.options.stsToken) {
query['security-token'] = this.options.stsToken;
signList.push(`security-token=${this.options.stsToken}`);
}
if (options.process) {
const processKeyword = 'x-oss-process';
query[processKeyword] = options.process;
const item = `${processKeyword}=${options.process}`;
signList.push(item);
}

if (signList.length > 0) {
signList.sort();
resource += `?${signList.join('&')}`;
const resource = this._getResource(params);

if (this.options.stsToken) {
options['security-token'] = this.options.stsToken;
}

const stringToSign = [
options.method || 'GET',
options['content-md5'] || '', // Content-MD5
options['content-type'] || '', // Content-Type
expires,
resource,
].join('\n');
const signature = this.signature(stringToSign);
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: signature,
Signature: signRes.Signature,
};
copy(query).to(url.query);

copy(signRes.subResource).to(url.query);

return url.format();
};
Expand Down
Loading

0 comments on commit 343938f

Please sign in to comment.