diff --git a/lib/metadata/metadataUtils.js b/lib/metadata/metadataUtils.js index cf0ef7ce36..2170dd57ff 100644 --- a/lib/metadata/metadataUtils.js +++ b/lib/metadata/metadataUtils.js @@ -11,10 +11,13 @@ const { onlyOwnerAllowed } = require('../../constants'); const { actionNeedQuotaCheck, actionWithDataDeletion } = require('arsenal/build/lib/policyEvaluator/RequestContext'); const { processBytesToWrite, validateQuotas } = require('../api/apiUtils/quotas/quotaUtils'); const { config } = require('../Config'); +const cache = require('../api/apiUtils/rateLimit/cache'); const { - extractAndCacheRateLimitConfig, - checkRateLimitWithConfig, rateLimitApiActions, + requestNeedsRateCheck, + extractRateLimitConfigFromRequest, + buildRateChecksFromConfig, + checkRateLimitsForRequest, } = require('../api/apiUtils/rateLimit/helpers'); /** @@ -242,67 +245,68 @@ function validateBucket(bucket, params, log, actionImplicitDenies = {}) { * Extracts rate limit config from bucket metadata, caches it, and enforces limit. * Calls callback with error if rate limited, null if allowed or no rate limiting. * - * @param {object} bucket - Bucket metadata object - * @param {string} bucketName - Bucket name - * @param {object} request - Request object with rateLimitAlreadyChecked tracker + * @param {object} request - Request object + * @param {AuthInfo} authInfo - AuthInfo class instance, requester's info + * @param {BucketInfo} bucketMD - Bucket metadata * @param {object} log - Logger instance * @param {function} callback - Callback(err) - err if rate limited, null if allowed * @returns {undefined} */ -function checkRateLimitIfNeeded(bucket, bucketName, request, log, callback) { +function checkRateLimitIfNeeded(request, authInfo, bucketMD, log, callback) { // Skip if already checked or not enabled - if (request.rateLimitAlreadyChecked - || !config.rateLimiting?.enabled - || rateLimitApiActions.includes(request.apiMethod) - || request.isInternalServiceRequest) { + if (!requestNeedsRateCheck(request)) { return process.nextTick(callback, null); } // Extract rate limit config from bucket metadata and cache it - const rateLimitConfig = extractAndCacheRateLimitConfig(bucket, bucketName, log); + const checks = []; + const rateLimitConfig = extractRateLimitConfigFromRequest(request, authInfo, bucketMD, log); - // No rate limiting configured - if (!rateLimitConfig) { + if (!request.rateLimitBucketAlreadyChecked && rateLimitConfig.bucket !== undefined) { + cache.setCachedConfig( + cache.namespace.bucket, + bucketMD.getName(), + rateLimitConfig.bucket, + config.rateLimiting.bucket.configCacheTTL + ); + cache.setCachedBucketOwner(bucketMD.getName(), bucketMD.getOwner(), config.rateLimiting.bucket.configCacheTTL); + checks.push(...buildRateChecksFromConfig('bucket', bucketMD.getName(), rateLimitConfig.bucket)); // eslint-disable-next-line no-param-reassign - request.rateLimitAlreadyChecked = true; - return process.nextTick(callback, null); + request.rateLimitBucketAlreadyChecked = true; } - // Check rate limit with GCRA - return checkRateLimitWithConfig( - bucketName, - rateLimitConfig, - log, - (rateLimitErr, rateLimited) => { - if (rateLimitErr) { - log.error('Rate limit check error in metadata validation', { - error: rateLimitErr, - }); - } + if (!request.rateLimitAccountAlreadyChecked && rateLimitConfig.account !== undefined) { + cache.setCachedConfig( + cache.namespace.account, + authInfo.getCanonicalID(), + rateLimitConfig.account, + config.rateLimiting.account.configCacheTTL + ); + checks.push(...buildRateChecksFromConfig('account', authInfo.getCanonicalID(), rateLimitConfig.account)); + // eslint-disable-next-line no-param-reassign + request.rateLimitAccountAlreadyChecked = true; + } - if (rateLimited) { - log.addDefaultFields({ - rateLimited: true, - rateLimitSource: rateLimitConfig.source, - }); - // Add to server access log - /* eslint-disable no-param-reassign */ - if (request.serverAccessLog) { - request.serverAccessLog.rateLimited = true; - request.serverAccessLog.rateLimitSource = rateLimitConfig.source; - } - request.rateLimitAlreadyChecked = true; - /* eslint-enable no-param-reassign */ - return callback(config.rateLimiting.error); - } + const { allowed, rateLimitSource } = checkRateLimitsForRequest(checks, log); + if (!allowed) { + log.addDefaultFields({ + rateLimited: true, + rateLimitSource, + }); - // Allowed - set tracker and continue - // eslint-disable-next-line no-param-reassign - request.rateLimitAlreadyChecked = true; - return callback(null); + if (request.serverAccessLog) { + /* eslint-disable no-param-reassign */ + request.serverAccessLog.rateLimited = true; + request.serverAccessLog.rateLimitSource = rateLimitSource; + /* eslint-enable no-param-reassign */ } - ); + + return process.nextTick(callback, config.rateLimiting.error); + } + + return process.nextTick(callback, null); } + /** standardMetadataValidateBucketAndObj - retrieve bucket and object md from metadata * and check if user is authorized to access them. * @param {object} params - function parameters @@ -362,7 +366,7 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log, } // Rate limiting check if not already done in api.js - return checkRateLimitIfNeeded(bucket, bucketName, request, log, err => { + return checkRateLimitIfNeeded(request, authInfo, bucket, log, err => { if (err) { return next(err, bucket); } @@ -456,7 +460,7 @@ function standardMetadataValidateBucket(params, actionImplicitDenies, log, callb } // Rate limiting check if not already done in api.js - return checkRateLimitIfNeeded(bucket, bucketName, request, log, err => { + return checkRateLimitIfNeeded(request, params.authInfo, bucket, log, err => { if (err) { return callback(err); }