Skip to content

Commit

Permalink
feat: support PostObject policy v4 signature and restore archive obje…
Browse files Browse the repository at this point in the history
…ct set days (#1340)

Co-authored-by: zhengzuoyu.zzy <[email protected]>
  • Loading branch information
YunZZY and zhengzuoyu.zzy authored Nov 26, 2024
1 parent ac500d3 commit ca8c027
Show file tree
Hide file tree
Showing 17 changed files with 539 additions and 188 deletions.
110 changes: 97 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ Node.js >= 8.0.0 required. You can use 4.x in Node.js < 8.

### QA

You can join DingDing Talk Group, [Group Link](https://qr.dingtalk.com/action/joingroup?code=v1,k1,E60EuCmxajfilkaR/kknRcGR9UissskPEXu/1td36z0=)

<img src="task/dingding.jpg" height="400" title="dingding" width="300">
Please log in to the official website and contact technical support.

## License

Expand Down Expand Up @@ -162,6 +160,7 @@ All operation use es7 async/await to implement. All api is async function.
- [.listUploads(query[, options])](#listuploadsquery-options)
- [.abortMultipartUpload(name, uploadId[, options])](#abortmultipartuploadname-uploadid-options)
- [.calculatePostSignature(policy)](#calculatePostSignaturepolicy)
- [.signPostObjectPolicyV4(policy, date)](#signpostobjectpolicyv4policy-date)
- [.getObjectTagging(name, [, options])](#getObjectTaggingname-options)
- [.putObjectTagging(name, tag[, options])](#putObjectTaggingname-tag-options)
- [.deleteObjectTagging(name, [, options])](#deleteObjectTaggingname-options)
Expand Down Expand Up @@ -2450,7 +2449,7 @@ Success will return objects list on `objects` properties.
- storageClass {String} storage class type, e.g.: `Standard`
- owner {Object} object owner, including `id` and `displayName`
- restoreInfo {Object|undefined} The restoration status of the object
- ongoingRequest {Boolean} Whether the restoration is complete
- ongoingRequest {Boolean} Whether the restoration is ongoing
- expireDate {Date|undefined} The time before which the restored object can be read
- prefixes {Array<String>} prefix list
- isTruncated {Boolean} truncate or not
Expand Down Expand Up @@ -2519,7 +2518,7 @@ Success will return objects list on `objects` properties.
- storageClass {String} storage class type, e.g.: `Standard`
- owner {Object|null} object owner, including `id` and `displayName`
- restoreInfo {Object|undefined} The restoration status of the object
- ongoingRequest {Boolean} Whether the restoration is complete
- ongoingRequest {Boolean} Whether the restoration is ongoing
- expireDate {Date|undefined} The time before which the restored object can be read
- prefixes {Array<String>} prefix list
- isTruncated {Boolean} truncate or not
Expand Down Expand Up @@ -2602,7 +2601,7 @@ Success will return objects list on `objects` properties.
- storageClass {String} storage class type, e.g.: `Standard`
- owner {Object} object owner, including `id` and `displayName`
- restoreInfo {Object|undefined} The restoration status of the object
- ongoingRequest {Boolean} Whether the restoration is complete
- ongoingRequest {Boolean} Whether the restoration is ongoing
- expireDate {Date|undefined} The time before which the restored object can be read
- deleteMarker {Array<ObjectDeleteMarker>} object delete marker info list
Each `ObjectDeleteMarker`
Expand Down Expand Up @@ -2963,6 +2962,8 @@ parameters:
- [timeout] {Number} the operation timeout
- [versionId] {String} the version id of history object
- [type] {String} the default type is Archive
- [Days] {number} The duration within which the object remains in the restored state. The default value is 2.
- [JobParameters] {string} The container that stores the restoration priority. This parameter is valid only when you restore Cold Archive or Deep Cold Archive objects. The default value is Standard.
Success will return:
Expand All @@ -2974,48 +2975,48 @@ Success will return:
example:
- Restore an object with Archive type
- Restore an Archive object
```js
const result = await store.restore('ossdemo.txt');
console.log(result.status);
```
- Restore an object with ColdArchive type
- Restore a Cold Archive object
```js
const result = await store.restore('ossdemo.txt', { type: 'ColdArchive' });
console.log(result.status);
```
- Days for unfreezing Specifies the days for unfreezing
- Restore a Cold Archive object with Days
```js
const result = await store.restore('ossdemo.txt', { type: 'ColdArchive', Days: 2 });
console.log(result.status);
```
- JobParameters for unfreezing Specifies the JobParameters for unfreezing
- Restore a Cold Archive object with Days and JobParameters
```js
const result = await store.restore('ossdemo.txt', { type: 'ColdArchive', Days: 2, JobParameters: 'Standard' });
console.log(result.status);
```
- Restore an object with DeepColdArchive type
- Restore a Deep Cold Archive object
```js
const result = await store.restore('ossdemo.txt', { type: 'DeepColdArchive' });
console.log(result.status);
```
- Days for unfreezing Specifies the days for unfreezing
- Restore a Deep Cold Archive object with Days
```js
const result = await store.restore('ossdemo.txt', { type: 'DeepColdArchive', Days: 2 });
console.log(result.status);
```
- JobParameters for unfreezing Specifies the JobParameters for unfreezing
- Restore a Deep Cold Archive object with Days and JobParameters
```js
const result = await store.restore('ossdemo.txt', { type: 'DeepColdArchive', Days: 2, JobParameters: 'Standard' });
Expand Down Expand Up @@ -3852,6 +3853,89 @@ Object:
- Signature {String}
- policy {Object} response info
### .signPostObjectPolicyV4(policy, date)
Get a V4 signature of the PostObject request.
parameters:
- policy {string | Object} The policy form field in a PostObject request is used to specify the expiration time and conditions of the PostObject request that you initiate to upload an object by using an HTML form. The value of the policy form field is a JSON string or an object.
- date {Date} The time when the request was initiated.
Success will return a V4 signature of the PostObject request.
example:
```js
const axios = require('axios');
const dateFormat = require('dateformat');
const FormData = require('form-data');
const { getCredential } = require('ali-oss/lib/common/signUtils');
const { getStandardRegion } = require('ali-oss/lib/common/utils/getStandardRegion');
const { policy2Str } = require('ali-oss/lib/common/utils/policy2Str');
const OSS = require('ali-oss');
const client = new OSS({
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
stsToken: 'yourSecurityToken',
bucket: 'yourBucket',
region: 'oss-cn-hangzhou'
});
const name = 'yourObjectName';
const formData = new FormData();
formData.append('key', name);
formData.append('Content-Type', 'yourObjectContentType');
// your object cache control
formData.append('Cache-Control', 'max-age=30');
const url = client.generateObjectUrl(name).replace(name, '');
const date = new Date();
// The expiration parameter specifies the expiration time of the request.
const expirationDate = new Date(date);
expirationDate.setMinutes(date.getMinutes() + 1);
// The time must follow the ISO 8601 standard
const formattedDate = dateFormat(date, "UTC:yyyymmdd'T'HHMMss'Z'");
const credential = getCredential(formattedDate.split('T')[0], getStandardRegion(client.options.region), client.options.accessKeyId);
formData.append('x-oss-date', formattedDate);
formData.append('x-oss-credential', credential);
formData.append('x-oss-signature-version', 'OSS4-HMAC-SHA256');
const policy = {
expiration: expirationDate.toISOString(),
conditions: [
{ bucket: client.options.bucket },
{'x-oss-credential': credential},
{'x-oss-date': formattedDate},
{'x-oss-signature-version': 'OSS4-HMAC-SHA256'},
['content-length-range', 1, 10],
['eq', '$success_action_status', '200'],
['starts-with', '$key', 'yourObjectName'],
['in', '$content-type', ['image/jpg', 'text/plain']],
['not-in', '$cache-control', ['no-cache']]
]
};
if (client.options.stsToken) {
policy.conditions.push({'x-oss-security-token': client.options.stsToken});
formData.append('x-oss-security-token', client.options.stsToken);
}
const signature = client.signPostObjectPolicyV4(policy, date);
formData.append('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64'));
formData.append('x-oss-signature', signature);
formData.append('success_action_status', '200');
formData.append('file', 'yourFileContent');
axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((result) => {
console.log(result.status);
}).catch((e) => {
console.log(e);
});
```
### .getObjectTagging(name[, options])
Obtains the tags of an object.
Expand Down
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = function (config) {
concurrency: 1,
client: {
mocha: {
timeout: 6000
timeout: 10000
}
}
});
Expand Down
25 changes: 14 additions & 11 deletions lib/browser/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ merge(proto, require('../common/object/generateObjectUrl'));
merge(proto, require('../common/object/signatureUrl'));
merge(proto, require('../common/object/asyncSignatureUrl'));
merge(proto, require('../common/object/signatureUrlV4'));
merge(proto, require('../common/object/signPostObjectPolicyV4'));

proto.putMeta = async function putMeta(name, meta, options) {
const copyResult = await this.copy(name, name, {
Expand Down Expand Up @@ -301,20 +302,22 @@ proto.restore = async function restore(name, options = { type: 'Archive' }) {
options.subres.versionId = options.versionId;
}
const params = this._objectRequestParams('POST', name, options);
const paramsXMLObj = {
RestoreRequest: {
Days: options.Days ? options.Days : 2
}
};

if (options.type === 'ColdArchive' || options.type === 'DeepColdArchive') {
const paramsXMLObj = {
RestoreRequest: {
Days: options.Days ? options.Days : 2,
JobParameters: {
Tier: options.JobParameters ? options.JobParameters : 'Standard'
}
}
paramsXMLObj.RestoreRequest.JobParameters = {
Tier: options.JobParameters ? options.JobParameters : 'Standard'
};
params.content = obj2xml(paramsXMLObj, {
headers: true
});
params.mime = 'xml';
}

params.content = obj2xml(paramsXMLObj, {
headers: true
});
params.mime = 'xml';
params.successStatuses = [202];

const result = await this.request(params);
Expand Down
1 change: 1 addition & 0 deletions lib/common/object/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ merge(proto, require('./getObjectUrl'));
merge(proto, require('./signatureUrl'));
merge(proto, require('./asyncSignatureUrl'));
merge(proto, require('./signatureUrlV4'));
merge(proto, require('./signPostObjectPolicyV4'));
1 change: 1 addition & 0 deletions lib/common/object/signPostObjectPolicyV4.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare function signPostObjectPolicyV4(this: any, policy: string | object, date: Date): string;
27 changes: 27 additions & 0 deletions lib/common/object/signPostObjectPolicyV4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

const __importDefault =
(this && this.__importDefault) ||
function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, '__esModule', { value: true });
exports.signPostObjectPolicyV4 = void 0;
const dateformat_1 = __importDefault(require('dateformat'));
const getStandardRegion_1 = require('../utils/getStandardRegion');
const policy2Str_1 = require('../utils/policy2Str');
const signUtils_1 = require('../signUtils');

function signPostObjectPolicyV4(policy, date) {
const policyStr = Buffer.from(policy2Str_1.policy2Str(policy), 'utf8').toString('base64');
const formattedDate = dateformat_1.default(date, "UTC:yyyymmdd'T'HHMMss'Z'");
const onlyDate = formattedDate.split('T')[0];
const signature = signUtils_1.getSignatureV4(
this.options.accessKeySecret,
onlyDate,
getStandardRegion_1.getStandardRegion(this.options.region),
policyStr
);
return signature;
}
exports.signPostObjectPolicyV4 = signPostObjectPolicyV4;
20 changes: 20 additions & 0 deletions lib/common/object/signPostObjectPolicyV4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import dateFormat from 'dateformat';

import { getStandardRegion } from '../utils/getStandardRegion';
import { policy2Str } from '../utils/policy2Str';
import { getSignatureV4 } from '../signUtils';

export function signPostObjectPolicyV4(this: any, policy: string | object, date: Date): string {
const policyStr = Buffer.from(policy2Str(policy), 'utf8').toString('base64');
const formattedDate = dateFormat(date, "UTC:yyyymmdd'T'HHMMss'Z'");
const onlyDate = formattedDate.split('T')[0];

const signature = getSignatureV4(
this.options.accessKeySecret,
onlyDate,
getStandardRegion(this.options.region),
policyStr
);

return signature;
}
2 changes: 1 addition & 1 deletion lib/common/object/signatureUrlV4.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ proto.signatureUrlV4 = async function signatureUrlV4(method, expires, request, o
if (fixedAdditionalHeaders.length > 0) {
queries['x-oss-additional-headers'] = fixedAdditionalHeaders.join(';');
}
queries['x-oss-credential'] = `${this.options.accessKeyId}/${onlyDate}/${region}/oss/aliyun_v4_request`;
queries['x-oss-credential'] = signHelper.getCredential(onlyDate, region, this.options.accessKeyId);
queries['x-oss-date'] = formattedDate;
queries['x-oss-expires'] = expires;
queries['x-oss-signature-version'] = 'OSS4-HMAC-SHA256';
Expand Down
21 changes: 19 additions & 2 deletions lib/common/signUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ exports.getCanonicalRequest = function getCanonicalRequest(method, request, buck
return signContent.join('\n');
};

/**
* @param {string} date yyyymmdd
* @param {string} region Standard region, e.g. cn-hangzhou
* @param {string} [accessKeyId] Access Key ID
* @param {string} [product] Product name, default is oss
* @returns {string}
*/
exports.getCredential = function getCredential(date, region, accessKeyId, product = 'oss') {
const tempCredential = `${date}/${region}/${product}/aliyun_v4_request`;

if (accessKeyId) {
return `${accessKeyId}/${tempCredential}`;
}

return tempCredential;
};

/**
* @param {string} region Standard region, e.g. cn-hangzhou
* @param {string} date ISO8601 UTC:yyyymmdd'T'HHMMss'Z'
Expand All @@ -190,7 +207,7 @@ exports.getStringToSign = function getStringToSign(region, date, canonicalReques
const stringToSign = [
'OSS4-HMAC-SHA256',
date, // TimeStamp
`${date.split('T')[0]}/${region}/oss/aliyun_v4_request`, // Scope
this.getCredential(date.split('T')[0], region), // Scope
crypto.createHash('sha256').update(canonicalRequest).digest('hex') // Hashed Canonical Request
];

Expand Down Expand Up @@ -261,7 +278,7 @@ exports.authorizationV4 = function authorizationV4(
const additionalHeadersValue =
fixedAdditionalHeaders.length > 0 ? `AdditionalHeaders=${fixedAdditionalHeaders.join(';')},` : '';

return `OSS4-HMAC-SHA256 Credential=${accessKeyId}/${onlyDate}/${region}/oss/aliyun_v4_request,${additionalHeadersValue}Signature=${signatureValue}`;
return `OSS4-HMAC-SHA256 Credential=${this.getCredential(onlyDate, region, accessKeyId)},${additionalHeadersValue}Signature=${signatureValue}`;
};

/**
Expand Down
10 changes: 10 additions & 0 deletions lib/common/utils/parseRestoreInfo.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
interface IRestoreInfo {
/**
* Whether the restoration is ongoing
* If a RestoreObject request is sent but the restoration is not complete, the value is true.
* If a RestoreObject request is sent and the restoration is complete, the value is false.
*/
ongoingRequest: boolean;
/**
* The time before which the restored object can be read.
* If a RestoreObject request is sent but the restoration is not complete, the value is undefined.
* If a RestoreObject request is sent and the restoration is complete, the value is Date.
*/
expiryDate?: Date;
}
export declare const parseRestoreInfo: (originalRestoreInfo?: string | undefined) => IRestoreInfo | undefined;
Expand Down
10 changes: 10 additions & 0 deletions lib/common/utils/parseRestoreInfo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
interface IRestoreInfo {
/**
* Whether the restoration is ongoing
* If a RestoreObject request is sent but the restoration is not complete, the value is true.
* If a RestoreObject request is sent and the restoration is complete, the value is false.
*/
ongoingRequest: boolean;
/**
* The time before which the restored object can be read.
* If a RestoreObject request is sent but the restoration is not complete, the value is undefined.
* If a RestoreObject request is sent and the restoration is complete, the value is Date.
*/
expiryDate?: Date;
}

Expand Down
Loading

0 comments on commit ca8c027

Please sign in to comment.