2012-09-27 22:07:19 +01:00
|
|
|
require 'rack/throttle'
|
|
|
|
require 'redis'
|
|
|
|
|
|
|
|
# Limit hourly API usage
|
|
|
|
# http://martinciu.com/2011/08/how-to-add-api-throttle-to-your-rails-app.html
|
|
|
|
class ApiDefender < Rack::Throttle::Hourly
|
|
|
|
|
|
|
|
def initialize(app)
|
|
|
|
options = {
|
2014-01-21 04:51:49 +00:00
|
|
|
cache: Redis.new(thread_safe: true),
|
|
|
|
key_prefix: :throttle,
|
2014-12-12 23:00:24 +00:00
|
|
|
max: 3000 # only 3000 request per hour
|
2012-09-27 22:07:19 +01:00
|
|
|
}
|
|
|
|
@app, @options = app, options
|
|
|
|
end
|
|
|
|
|
2012-12-20 18:41:10 +00:00
|
|
|
# this method checks if request needs throttling.
|
|
|
|
# If so, it increases usage counter and compare it with maximum
|
2012-09-27 22:07:19 +01:00
|
|
|
# allowed API calls. Returns true if a request can be handled.
|
|
|
|
def allowed?(request)
|
|
|
|
need_defense?(request) ? cache_incr(request) <= max_per_window : true
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(env)
|
|
|
|
status, heders, body = super
|
|
|
|
request = Rack::Request.new(env)
|
|
|
|
# just to be nice for our clients we inform them how many
|
|
|
|
# requests remaining does they have
|
|
|
|
if need_defense?(request)
|
|
|
|
heders['X-RateLimit-Limit'] = max_per_window.to_s
|
2012-12-20 18:41:10 +00:00
|
|
|
heders['X-RateLimit-Remaining'] = ([0, max_per_window - (cache_get(choice_key(request)).to_i rescue 1)].max).to_s
|
2012-09-27 22:07:19 +01:00
|
|
|
end
|
2012-12-26 16:49:28 +00:00
|
|
|
@is_authorized = @user = nil
|
2012-09-27 22:07:19 +01:00
|
|
|
[status, heders, body]
|
|
|
|
end
|
|
|
|
|
|
|
|
# key increase and key expiration
|
|
|
|
def cache_incr(request)
|
|
|
|
key = cache_key(request)
|
|
|
|
count = cache.incr(key)
|
|
|
|
cache.expire(key, 1.day) if count == 1
|
2012-12-20 18:41:10 +00:00
|
|
|
|
2012-12-26 13:50:03 +00:00
|
|
|
if @user
|
|
|
|
count = cache.incr(choice_key(request))
|
2012-12-20 18:41:10 +00:00
|
|
|
cache.expire(key, 1.day) if count == 1
|
|
|
|
end
|
2012-09-27 22:07:19 +01:00
|
|
|
count
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
2012-12-20 18:41:10 +00:00
|
|
|
# only API calls should be throttled
|
|
|
|
def need_defense?(request)
|
2013-07-04 11:51:36 +01:00
|
|
|
APP_CONFIG['allowed_addresses'].exclude?(request.ip) &&
|
|
|
|
request.env['PATH_INFO'] =~ /^\/api\/v1\// &&
|
|
|
|
!system_user?(request)
|
2012-12-20 18:41:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def authorized?(request)
|
2012-12-26 16:49:28 +00:00
|
|
|
return @is_authorized if @is_authorized
|
2012-12-20 18:41:10 +00:00
|
|
|
auth = Rack::Auth::Basic::Request.new(request.env)
|
2012-12-26 15:13:08 +00:00
|
|
|
@user = User.auth_by_token_or_login_pass(*auth.credentials) if auth.provided? and auth.basic?
|
2012-12-26 16:49:28 +00:00
|
|
|
@is_authorized = true # cache
|
2012-12-20 18:41:10 +00:00
|
|
|
end
|
2012-09-27 22:07:19 +01:00
|
|
|
|
2012-12-26 13:50:03 +00:00
|
|
|
def choice_key request
|
|
|
|
return cache_key(request) unless @user
|
2012-12-20 18:41:10 +00:00
|
|
|
[@options[:key_prefix], @user.uname, Time.now.strftime('%Y-%m-%dT%H')].join(':')
|
|
|
|
end
|
2012-12-24 14:31:59 +00:00
|
|
|
|
|
|
|
def system_user? request
|
2012-12-29 14:49:28 +00:00
|
|
|
authorized?(request) && @user.try(:system?)
|
2012-12-24 14:31:59 +00:00
|
|
|
end
|
2012-12-20 18:41:10 +00:00
|
|
|
end
|