Skip to content

Commit 852e32c

Browse files
committed
Support the new API GetObjectAttributes
Issue: CLDSRV-817
1 parent c8f4e95 commit 852e32c

File tree

7 files changed

+1065
-0
lines changed

7 files changed

+1065
-0
lines changed

constants.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,14 @@ const constants = {
279279
rateLimitDefaultConfigCacheTTL: 30000, // 30 seconds
280280
rateLimitDefaultBurstCapacity: 1,
281281
rateLimitCleanupInterval: 10000, // 10 seconds
282+
// Metadata allowed to be returned by getObjectAttributes API
283+
allowedObjectAttributes: new Set([
284+
'StorageClass',
285+
'ObjectSize',
286+
'ObjectParts',
287+
'Checksum',
288+
'ETag',
289+
]),
282290
};
283291

284292
module.exports = constants;

lib/api/api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const { objectDelete } = require('./objectDelete');
5656
const objectDeleteTagging = require('./objectDeleteTagging');
5757
const objectGet = require('./objectGet');
5858
const objectGetACL = require('./objectGetACL');
59+
const objectGetAttributes = require('./objectGetAttributes.js');
5960
const objectGetLegalHold = require('./objectGetLegalHold');
6061
const objectGetRetention = require('./objectGetRetention');
6162
const objectGetTagging = require('./objectGetTagging');
@@ -471,6 +472,7 @@ const api = {
471472
objectDeleteTagging,
472473
objectGet,
473474
objectGetACL,
475+
objectGetAttributes,
474476
objectGetLegalHold,
475477
objectGetRetention,
476478
objectGetTagging,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { errorInstances } = require('arsenal');
2+
const { allowedObjectAttributes } = require('../../../../constants');
3+
4+
function parseAttributesHeaders(headers) {
5+
const raw = headers['x-amz-object-attributes'] || '';
6+
7+
const attributes = raw
8+
.split(',')
9+
.map(s => s.trim())
10+
.filter(s => s !== '');
11+
12+
if (attributes.length === 0) {
13+
return errorInstances.InvalidRequest.customizeDescription(
14+
'The x-amz-object-attributes header specifying the attributes to be retrieved is either missing or empty',
15+
);
16+
}
17+
18+
const invalids = attributes.filter(s => !allowedObjectAttributes.has(s));
19+
if (invalids.length > 0) {
20+
return errorInstances.InvalidArgument.customizeDescription('Invalid attribute name specified.');
21+
}
22+
23+
return attributes;
24+
}
25+
26+
module.exports = parseAttributesHeaders;

lib/api/objectGetAttributes.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
const { waterfall } = require('async');
2+
const xml2js = require('xml2js');
3+
const { errors } = require('arsenal');
4+
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
5+
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
6+
const parseAttributesHeaders = require('./apiUtils/object/parseAttributesHeader');
7+
const { decodeVersionId, getVersionIdResHeader } = require('./apiUtils/object/versioning');
8+
const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner');
9+
const { pushMetric } = require('../utapi/utilities');
10+
11+
const OBJECT_GET_ATTRIBUTES = 'objectGetAttributes';
12+
13+
/**
14+
* buildXmlResponse - Build XML response for GetObjectAttributes
15+
* @param {object} objMD - object metadata
16+
* @param {array} attributes - requested attributes
17+
* @returns {string} XML response
18+
*/
19+
function buildXmlResponse(objMD, attributes) {
20+
const attrResp = {};
21+
22+
if (attributes.includes('ETag')) {
23+
attrResp.ETag = objMD['content-md5'];
24+
}
25+
26+
if (attributes.includes('Checksum')) {
27+
attrResp.Checksum = {};
28+
}
29+
30+
if (attributes.includes('ObjectParts')) {
31+
attrResp.ObjectParts = {};
32+
}
33+
34+
if (attributes.includes('StorageClass')) {
35+
attrResp.StorageClass = objMD['x-amz-storage-class'];
36+
}
37+
38+
if (attributes.includes('ObjectSize')) {
39+
attrResp.ObjectSize = objMD['content-length'];
40+
}
41+
42+
const builder = new xml2js.Builder();
43+
return builder.buildObject({ GetObjectAttributesResponse: attrResp });
44+
}
45+
46+
/**
47+
* objectGetAttributes - Retrieves all metadata from an object without returning the object itself
48+
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
49+
* @param {object} request - http request object
50+
* @param {object} log - Werelogs logger
51+
* @param {function} callback - callback to server
52+
* @return {undefined}
53+
*/
54+
function objectGetAttributes(authInfo, request, log, callback) {
55+
log.debug('processing request', { method: OBJECT_GET_ATTRIBUTES });
56+
const { bucketName, objectKey, headers, actionImplicitDenies } = request;
57+
58+
const decodedVersionId = decodeVersionId(request.query);
59+
if (decodedVersionId instanceof Error) {
60+
log.trace('invalid versionId query', {
61+
versionId: request.query.versionId,
62+
error: decodedVersionId,
63+
});
64+
return callback(decodedVersionId);
65+
}
66+
const versionId = decodedVersionId;
67+
68+
const metadataValParams = {
69+
authInfo,
70+
bucketName,
71+
objectKey,
72+
versionId,
73+
getDeleteMarker: true,
74+
requestType: request.apiMethods || OBJECT_GET_ATTRIBUTES,
75+
request,
76+
};
77+
78+
let bucket = null;
79+
let objMD = null;
80+
let responseHeaders = null;
81+
let xml = null;
82+
83+
return waterfall(
84+
[
85+
next =>
86+
standardMetadataValidateBucketAndObj(metadataValParams, actionImplicitDenies, log, (err, b, o) => {
87+
bucket = b;
88+
objMD = o;
89+
return next(err);
90+
}),
91+
next => checkExpectedBucketOwner(headers, bucket, log, next),
92+
next => {
93+
responseHeaders = collectCorsHeaders(headers.origin, request.method, bucket);
94+
if (objMD) {
95+
responseHeaders['x-amz-version-id'] = getVersionIdResHeader(bucket.getVersioningConfiguration(), objMD);
96+
responseHeaders['Last-Modified'] = objMD['last-modified'] && new Date(objMD['last-modified']).toUTCString();
97+
}
98+
99+
return next();
100+
},
101+
next => {
102+
if (!objMD) {
103+
const e = versionId ? errors.NoSuchVersion : errors.NoSuchKey;
104+
return next(e);
105+
}
106+
107+
if (objMD.isDeleteMarker) {
108+
responseHeaders['x-amz-delete-marker'] = true;
109+
return next(errors.MethodNotAllowed);
110+
}
111+
112+
const attributes = parseAttributesHeaders(headers);
113+
if (attributes instanceof Error) {
114+
log.trace('invalid x-amz-object-attributes header', {
115+
headers,
116+
error: attributes,
117+
});
118+
return next(attributes);
119+
}
120+
121+
xml = buildXmlResponse(objMD, attributes);
122+
return next();
123+
},
124+
],
125+
err => {
126+
if (err) {
127+
log.debug('error processing request', {
128+
error: err,
129+
method: OBJECT_GET_ATTRIBUTES,
130+
});
131+
return callback(err, '', responseHeaders);
132+
}
133+
134+
pushMetric(OBJECT_GET_ATTRIBUTES, log, {
135+
authInfo,
136+
bucket: bucketName,
137+
keys: [objectKey],
138+
versionId: objMD?.versionId,
139+
location: objMD?.dataStoreName,
140+
});
141+
return callback(err, xml, responseHeaders);
142+
},
143+
);
144+
}
145+
146+
module.exports = objectGetAttributes;

0 commit comments

Comments
 (0)