Skip to content

Commit c128c7c

Browse files
committed
CLDSRV-755: Update API handlers
1 parent b548426 commit c128c7c

File tree

3 files changed

+63
-39
lines changed

3 files changed

+63
-39
lines changed

lib/api/bucketDeleteRateLimit.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const { errors } = require('arsenal');
22

33
const metadata = require('../metadata/wrapper');
4-
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
54
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
65
const { isRateLimitServiceUser } = require('./apiUtils/authorization/serviceUser');
76

@@ -16,7 +15,7 @@ const { isRateLimitServiceUser } = require('./apiUtils/authorization/serviceUser
1615
function bucketDeleteRateLimit(authInfo, request, log, callback) {
1716
log.debug('processing request', { method: 'bucketDeleteRateLimit' });
1817

19-
if (!isRateLimitServiceUser(authInfo)) {
18+
if (!isRateLimitServiceUser(authInfo, log)) {
2019
return callback(errors.AccessDenied);
2120
}
2221

@@ -36,15 +35,15 @@ function bucketDeleteRateLimit(authInfo, request, log, callback) {
3635
});
3736
return callback(err, corsHeaders);
3837
}
39-
if (!bucket.getRateLimitConfig()) {
38+
if (!bucket.getRateLimitConfiguration()) {
4039
log.trace('no existing bucket rate limit configuration', {
4140
method: 'bucketDeleteRateLimit',
4241
});
4342
// TODO: implement Utapi metric support
4443
return callback(null, corsHeaders);
4544
}
4645
log.trace('deleting bucket rate limit configuration in metadata');
47-
bucket.setRateLimitConfig(null);
46+
bucket.setRateLimitConfiguration(null);
4847
return metadata.updateBucket(bucketName, bucket, log, err => {
4948
if (err) {
5049
return callback(err, corsHeaders);

lib/api/bucketGetRateLimit.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,11 @@ const { isRateLimitServiceUser } = require('./apiUtils/authorization/serviceUser
1515
function bucketGetRateLimit(authInfo, request, log, callback) {
1616
log.debug('processing request', { method: 'bucketGetRateLimit' });
1717

18-
if (!isRateLimitServiceUser(authInfo)) {
18+
if (!isRateLimitServiceUser(authInfo, log)) {
1919
return callback(errors.AccessDenied);
2020
}
2121

2222
const { bucketName, headers, method } = request;
23-
const metadataValParams = {
24-
authInfo,
25-
bucketName,
26-
requestType: request.apiMethods || 'bucketGetRateLimit',
27-
request,
28-
};
2923

3024
return standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
3125
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
@@ -37,17 +31,24 @@ function bucketGetRateLimit(authInfo, request, log, callback) {
3731
return callback(err, null, corsHeaders);
3832
}
3933

40-
const rateLimitConfig = bucket.getRateLimitConfig();
41-
if (!rateLimitConfig) {
34+
const rateLimitConfig = bucket.getRateLimitConfiguration();
35+
const limit = rateLimitConfig?.getRequestsPerSecondLimit();
36+
37+
if (!rateLimitConfig || limit === undefined) {
4238
log.debug('error processing request', {
43-
error: errors.NoSuchBucketRateLimit,
39+
error: errors.NoSuchRateLimitConfiguration,
4440
method: 'bucketGetRateLimit',
4541
});
46-
return callback(errors.NoSuchBucketRateLimit, null,
42+
return callback(errors.NoSuchRateLimitConfiguration, null,
4743
corsHeaders);
4844
}
4945

50-
return callback(null, JSON.stringify(rateLimitConfig), corsHeaders);
46+
// Return flattened structure matching API spec: {"RequestsPerSecond": 1000}
47+
const response = {
48+
RequestsPerSecond: limit,
49+
};
50+
51+
return callback(null, JSON.stringify(response), corsHeaders);
5152
});
5253
}
5354

lib/api/bucketPutRateLimit.js

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
const async = require('async');
22
const { parseString } = require('xml2js');
3-
const { errorInstances, errors } = require('arsenal');
3+
const { errorInstances, errors, models } = require('arsenal');
44

55
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
66
const metadata = require('../metadata/wrapper');
7-
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
87
const { isRateLimitServiceUser } = require('./apiUtils/authorization/serviceUser');
8+
const { config } = require('../Config');
9+
10+
const RateLimitConfiguration = models.RateLimitConfiguration;
911

1012
function parseRequestBody(requestBody, callback) {
13+
// Try JSON first
14+
let jsonData;
1115
try {
12-
const jsonData = JSON.parse(requestBody);
16+
jsonData = JSON.parse(requestBody);
1317
if (typeof jsonData !== 'object') {
14-
throw new Error('Invalid JSON');
18+
throw new Error('Invalid JSON - not an object');
1519
}
20+
// JSON succeeded - return immediately, do NOT try XML
1621
return callback(null, jsonData);
17-
} catch {
18-
return parseString(requestBody, (xmlError, xmlData) => {
22+
} catch (jsonError) {
23+
// JSON failed - try XML
24+
parseString(requestBody, (xmlError, xmlData) => {
1925
if (xmlError) {
2026
return callback(errorInstances.InvalidArgument
2127
.customizeDescription('Request body must be a JSON object'));
@@ -25,15 +31,30 @@ function parseRequestBody(requestBody, callback) {
2531
}
2632
}
2733

28-
function validateRateLimitConfig(config, callback) {
29-
const limit = parseInt(config.RequestsPerSecond, 10);
34+
function validateRateLimitConfig(requestConfig, callback) {
35+
const limit = parseInt(requestConfig.RequestsPerSecond, 10);
36+
37+
// Validate positive integer
3038
if (Number.isNaN(limit) || !Number.isInteger(limit) || limit <= 0) {
3139
return callback(errorInstances.InvalidArgument
3240
.customizeDescription('RequestsPerSecond must be a positive integer'));
3341
}
34-
return callback(null, {
35-
RequestsPerSecond: limit,
42+
43+
// Validate minimum rate limit (must be >= number of nodes)
44+
const nodeCount = config.rateLimiting?.nodeCount || 1;
45+
if (limit < nodeCount) {
46+
return callback(errorInstances.InvalidArgument
47+
.customizeDescription(
48+
`RequestsPerSecond must be >= ${nodeCount} (number of CloudServer nodes)`
49+
));
50+
}
51+
52+
// Create RateLimitConfiguration model with flattened structure
53+
const rateLimitConfig = new RateLimitConfiguration({
54+
RequestsPerSecond: limit,
3655
});
56+
57+
return callback(null, rateLimitConfig);
3758
}
3859

3960
/**
@@ -47,20 +68,25 @@ function validateRateLimitConfig(config, callback) {
4768
function bucketPutRateLimit(authInfo, request, log, callback) {
4869
log.debug('processing request', { method: 'bucketPutRateLimit' });
4970

50-
if (!isRateLimitServiceUser(authInfo)) {
71+
const requestArn = authInfo.getArn();
72+
const configArn = config.rateLimiting?.serviceUserArn;
73+
log.debug('ARN comparison for rate limit authorization', {
74+
requestArn,
75+
configArn,
76+
match: requestArn === configArn,
77+
});
78+
79+
if (!isRateLimitServiceUser(authInfo, log)) {
80+
log.warn('Access denied - ARN mismatch', { requestArn, configArn });
5181
return callback(errors.AccessDenied);
5282
}
5383

5484
const { bucketName } = request;
55-
const metadataValParams = {
56-
authInfo,
57-
bucketName,
58-
requestType: request.apiMethods || 'bucketPutRateLimit',
59-
request,
60-
};
6185

6286
return async.waterfall([
63-
next => parseRequestBody(request.post, next),
87+
next => {
88+
parseRequestBody(request.post, next);
89+
},
6490
(requestBody, next) => validateRateLimitConfig(requestBody, next),
6591
(limitConfig, next) => standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
6692
(err, bucket) => {
@@ -69,11 +95,9 @@ function bucketPutRateLimit(authInfo, request, log, callback) {
6995
}
7096
return next(null, bucket, limitConfig);
7197
}),
72-
(bucket, limitConfig, next) => {
73-
bucket.setRateLimitConfig(limitConfig);
74-
metadata.updateBucket(bucket.getName(), bucket, log,
75-
err => next(err, bucket));
76-
},
98+
(bucket, limitConfig, next) => bucket.setRateLimitConfiguration(limitConfig)
99+
.then(() => metadata.updateBucket(bucket.getName(), bucket, log,
100+
err => next(err, bucket))),
77101
], (err, bucket) => {
78102
const corsHeaders = collectCorsHeaders(request.headers.origin,
79103
request.method, bucket);

0 commit comments

Comments
 (0)