@@ -9,6 +9,11 @@ const bucketShield = require('../api/apiUtils/bucket/bucketShield');
99const { onlyOwnerAllowed } = require ( '../../constants' ) ;
1010const { actionNeedQuotaCheck, actionWithDataDeletion } = require ( 'arsenal/build/lib/policyEvaluator/RequestContext' ) ;
1111const { processBytesToWrite, validateQuotas } = require ( '../api/apiUtils/quotas/quotaUtils' ) ;
12+ const { config } = require ( '../Config' ) ;
13+ const {
14+ extractAndCacheRateLimitConfig,
15+ checkRateLimitWithConfig,
16+ } = require ( '../api/apiUtils/rateLimit/helpers' ) ;
1217
1318/** getNullVersionFromMaster - retrieves the null version
1419 * metadata via retrieving the master key
@@ -169,6 +174,65 @@ function validateBucket(bucket, params, log, actionImplicitDenies = {}) {
169174 }
170175 return null ;
171176}
177+
178+ /**
179+ * Check rate limiting if not already checked
180+ *
181+ * Extracts rate limit config from bucket metadata, caches it, and enforces limit.
182+ * Calls callback with error if rate limited, null if allowed or no rate limiting.
183+ *
184+ * @param {object } bucket - Bucket metadata object
185+ * @param {string } bucketName - Bucket name
186+ * @param {object } request - Request object with rateLimitAlreadyChecked tracker
187+ * @param {object } log - Logger instance
188+ * @param {function } callback - Callback(err) - err if rate limited, null if allowed
189+ * @returns {undefined }
190+ */
191+ function checkRateLimitIfNeeded ( bucket , bucketName , request , log , callback ) {
192+ // Skip if already checked or not enabled
193+ if ( request . rateLimitAlreadyChecked || ! config . rateLimiting ?. enabled ) {
194+ return process . nextTick ( callback , null ) ;
195+ }
196+
197+ // Extract rate limit config from bucket metadata and cache it
198+ const rateLimitConfig = extractAndCacheRateLimitConfig ( bucket , bucketName , log ) ;
199+
200+ // No rate limiting configured
201+ if ( ! rateLimitConfig ) {
202+ // eslint-disable-next-line no-param-reassign
203+ request . rateLimitAlreadyChecked = true ;
204+ return process . nextTick ( callback , null ) ;
205+ }
206+
207+ // Check rate limit with GCRA
208+ return checkRateLimitWithConfig (
209+ bucketName ,
210+ rateLimitConfig ,
211+ log ,
212+ ( rateLimitErr , rateLimited ) => {
213+ if ( rateLimitErr ) {
214+ log . error ( 'Rate limit check error in metadata validation' , {
215+ error : rateLimitErr ,
216+ } ) ;
217+ }
218+
219+ if ( rateLimited ) {
220+ log . addDefaultFields ( {
221+ rateLimited : true ,
222+ rateLimitSource : rateLimitConfig . source ,
223+ } ) ;
224+ // eslint-disable-next-line no-param-reassign
225+ request . rateLimitAlreadyChecked = true ;
226+ return callback ( config . rateLimiting . error ) ;
227+ }
228+
229+ // Allowed - set tracker and continue
230+ // eslint-disable-next-line no-param-reassign
231+ request . rateLimitAlreadyChecked = true ;
232+ return callback ( null ) ;
233+ }
234+ ) ;
235+ }
172236/** standardMetadataValidateBucketAndObj - retrieve bucket and object md from metadata
173237 * and check if user is authorized to access them.
174238 * @param {object } params - function parameters
@@ -222,12 +286,21 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log,
222286 if ( validationError ) {
223287 return next ( validationError , bucket ) ;
224288 }
225- const objMD = getResult . obj ? JSON . parse ( getResult . obj ) : undefined ;
226- if ( ! objMD && versionId === 'null' ) {
227- return getNullVersionFromMaster ( bucketName , objectKey , log ,
228- ( err , nullVer ) => next ( err , bucket , nullVer ) ) ;
229- }
230- return next ( null , bucket , objMD ) ;
289+
290+ // Rate limiting check if not already done in api.js
291+ return checkRateLimitIfNeeded ( bucket , bucketName , request , log , err => {
292+ if ( err ) {
293+ return next ( err , bucket ) ;
294+ }
295+
296+ // Continue with object metadata processing
297+ const objMD = getResult . obj ? JSON . parse ( getResult . obj ) : undefined ;
298+ if ( ! objMD && versionId === 'null' ) {
299+ return getNullVersionFromMaster ( bucketName , objectKey , log ,
300+ ( err , nullVer ) => next ( err , bucket , nullVer ) ) ;
301+ }
302+ return next ( null , bucket , objMD ) ;
303+ } ) ;
231304 } ,
232305 ( bucket , objMD , next ) => {
233306 const objMetadata = objMD ;
@@ -294,7 +367,7 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log,
294367 * @return {undefined } - and call callback with params err, bucket md
295368 */
296369function standardMetadataValidateBucket ( params , actionImplicitDenies , log , callback ) {
297- const { bucketName } = params ;
370+ const { bucketName, request } = params ;
298371 return metadata . getBucket ( bucketName , log , ( err , bucket ) => {
299372 if ( err ) {
300373 // if some implicit actionImplicitDenies, return AccessDenied before
@@ -305,8 +378,17 @@ function standardMetadataValidateBucket(params, actionImplicitDenies, log, callb
305378 log . debug ( 'metadata getbucket failed' , { error : err } ) ;
306379 return callback ( err ) ;
307380 }
308- const validationError = validateBucket ( bucket , params , log , actionImplicitDenies ) ;
309- return callback ( validationError , bucket ) ;
381+
382+ // Rate limiting check if not already done in api.js
383+ return checkRateLimitIfNeeded ( bucket , bucketName , request , log , err => {
384+ if ( err ) {
385+ return callback ( err ) ;
386+ }
387+
388+ // Continue with validation
389+ const validationError = validateBucket ( bucket , params , log , actionImplicitDenies ) ;
390+ return callback ( validationError , bucket ) ;
391+ } ) ;
310392 } ) ;
311393}
312394
0 commit comments