Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support PostObject policy v4 signature and restore archive object set days #1340

Merged
merged 1 commit into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading