FEATURE: add global rate limiter for admin api 60 per minute

Also move configuration of admin and user api rate limiting into global
settings. This is not intended to be configurable per site
This commit is contained in:
Sam 2017-12-11 11:07:22 +11:00
parent 394abbe26b
commit 68d3c2c74f
5 changed files with 102 additions and 59 deletions

View File

@ -168,3 +168,10 @@ s3_access_key_id =
s3_secret_access_key =
s3_use_iam_profile = false
s3_cdn_url =
### rate limits apply to all sites
max_user_api_reqs_per_minute = 20
max_user_api_reqs_per_day = 2880
max_admin_api_reqs_per_key_per_minute = 60

View File

@ -1562,8 +1562,6 @@ en:
retain_web_hook_events_period_days: "Number of days to retain web hook event records."
max_user_api_reqs_per_day: "Maximum number of user API requests per key per day"
max_user_api_reqs_per_minute: "Maximum number of user API requests per key per minute"
allow_user_api_keys: "Allow generation of user API keys"
allow_user_api_key_scopes: "List of scopes allowed for user API keys"
max_api_keys_per_user: "Maximum number of user API keys per user"

View File

@ -1448,10 +1448,6 @@ api:
default: 30
user_api:
max_user_api_reqs_per_day:
default: 2880
max_user_api_reqs_per_minute:
default: 20
allow_user_api_keys:
default: true
allow_user_api_key_scopes:

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require_dependency "auth/current_user_provider"
require_dependency "rate_limiter"
@ -79,13 +81,16 @@ class Auth::DefaultCurrentUserProvider
raise Discourse::InvalidAccess.new(I18n.t('invalid_api_credentials'), nil, custom_message: "invalid_api_credentials") unless current_user
raise Discourse::InvalidAccess if current_user.suspended? || !current_user.active
@env[API_KEY_ENV] = true
limiter_min = RateLimiter.new(nil, "admin_api_min_#{api_key}", GlobalSetting.max_admin_api_reqs_per_key_per_minute, 60)
limiter_min.performed!
end
# user api key handling
if user_api_key
limiter_min = RateLimiter.new(nil, "user_api_min_#{user_api_key}", SiteSetting.max_user_api_reqs_per_minute, 60)
limiter_day = RateLimiter.new(nil, "user_api_day_#{user_api_key}", SiteSetting.max_user_api_reqs_per_day, 86400)
limiter_min = RateLimiter.new(nil, "user_api_min_#{user_api_key}", GlobalSetting.max_user_api_reqs_per_minute, 60)
limiter_day = RateLimiter.new(nil, "user_api_day_#{user_api_key}", GlobalSetting.max_user_api_reqs_per_day, 86400)
unless limiter_day.can_perform?
limiter_day.performed!

View File

@ -16,6 +16,8 @@ describe Auth::DefaultCurrentUserProvider do
TestProvider.new(env)
end
context "server api" do
it "raises errors for incorrect api_key" do
expect {
provider("/?api_key=INCORRECT").current_user
@ -81,6 +83,41 @@ describe Auth::DefaultCurrentUserProvider do
expect(provider("/?api_key=hello&api_username=#{user.username.downcase}").current_user.id).to eq(user.id)
end
context "rate limiting" do
before do
RateLimiter.enable
end
after do
RateLimiter.disable
end
it "rate limits api requests per api key" do
global_setting :max_admin_api_reqs_per_key_per_minute, 3
user = Fabricate(:user)
key = SecureRandom.hex
api_key = ApiKey.create!(key: key, created_by_id: -1)
provider("/?api_key=#{key}&api_username=#{user.username.downcase}").current_user
provider("/?api_key=#{key}&api_username=system").current_user
provider("/?api_key=#{key}&api_username=#{user.username.downcase}").current_user
expect do
provider("/?api_key=#{key}&api_username=system").current_user
end.to raise_error(RateLimiter::LimitExceeded)
# should not rake limit a random key
api_key.destroy
key = SecureRandom.hex
ApiKey.create!(key: key, created_by_id: -1)
provider("/?api_key=#{key}&api_username=#{user.username.downcase}").current_user
end
end
end
it "should not update last seen for ajax calls without Discourse-Visible header" do
expect(provider("/topic/anything/goes",
:method => "POST",
@ -320,8 +357,8 @@ describe Auth::DefaultCurrentUserProvider do
limiter1.clear!
limiter2.clear!
SiteSetting.max_user_api_reqs_per_day = 3
SiteSetting.max_user_api_reqs_per_minute = 4
global_setting :max_user_api_reqs_per_day, 3
global_setting :max_user_api_reqs_per_minute, 4
params = {
"REQUEST_METHOD" => "GET",
@ -336,8 +373,8 @@ describe Auth::DefaultCurrentUserProvider do
provider("/", params).current_user
}.to raise_error(RateLimiter::LimitExceeded)
SiteSetting.max_user_api_reqs_per_day = 4
SiteSetting.max_user_api_reqs_per_minute = 3
global_setting :max_user_api_reqs_per_day, 4
global_setting :max_user_api_reqs_per_minute, 3
limiter1.clear!
limiter2.clear!