implement api limiting

This commit is contained in:
albert
2013-03-20 16:35:35 -07:00
parent 7470d189c3
commit 2ac22d07cd
7 changed files with 73 additions and 14 deletions

View File

@@ -13,9 +13,16 @@ class ApplicationController < ActionController::Base
protected
def api_check
if CurrentUser.is_anonymous? && request.format.to_s =~ /json|xml/
render :text => "401 Not Authorized\n", :layout => false, :status => 401
return false
if request.format.to_s =~ /json|xml/
if CurrentUser.is_anonymous?
render :text => "401 Not Authorized\n", :layout => false, :status => 401
return false
end
if ApiLimiter.throttled?(request.remote_ip)
render :text => "421 User Throttled\n", :layout => false, :status => 421
return false
end
end
return true

View File

@@ -164,6 +164,10 @@ class AnonymousUser
def enable_sequential_post_navigation
true
end
def api_hourly_limit
500
end
%w(member banned privileged builder platinum contributor janitor moderator admin).each do |name|
define_method("is_#{name}?") do

View File

@@ -0,0 +1,9 @@
module ApiLimiter
def throttled?(ip_addr)
key = "#{ip_addr}:#{Time.now.hour}"
MEMCACHE.fetch(key, 1.hour, true) {0}
MEMCACHE.incr(key).to_i > CurrentUser.user.api_hourly_limit
end
module_function :throttled?
end

View File

@@ -1,15 +1,11 @@
class Cache
def self.incr(key, expiry = 0)
val = Cache.get(key, expiry)
Cache.put(key, val.to_i + 1)
def self.incr(key)
MEMCACHE.incr(key)
ActiveRecord::Base.logger.debug('MemCache Incr %s' % [key])
end
def self.decr(key, expiry = 0)
val = Cache.get(key, expiry)
if val.to_i > 0
Cache.put(key, val.to_i - 1)
end
def self.decr(key)
MEMCACHE.decr(key)
ActiveRecord::Base.logger.debug('MemCache Decr %s' % [key])
end

View File

@@ -469,6 +469,16 @@ class User < ActiveRecord::Base
20_000
end
end
def api_hourly_limit
if is_platinum?
20_000
elsif is_privileged?
10_000
else
3_000
end
end
end
module ApiMethods

View File

@@ -16,16 +16,32 @@ class PostsControllerTest < ActionController::TestCase
end
context "for api calls" do
context "passing the api limit" do
setup do
User.any_instance.stubs(:api_hourly_limit).returns(5)
end
should "work" do
CurrentUser.user.api_hourly_limit.times do
get :index, {:format => "json", :login => @user.name, :api_key => @user.bcrypt_cookie_password_hash}
assert_response :success
end
get :index, {:format => "json", :login => @user.name, :api_key => @user.bcrypt_cookie_password_hash}
assert_response 421
end
end
context "using http basic auth" do
should "succeed for password matches" do
@basic_auth_string = "Basic #{ActiveSupport::Base64.encode64("#{@user.name}:#{@user.bcrypt_cookie_password_hash}")}"
@basic_auth_string = "Basic #{::Base64.encode64("#{@user.name}:#{@user.bcrypt_cookie_password_hash}")}"
@request.env['HTTP_AUTHORIZATION'] = @basic_auth_string
get :index, {:format => "json"}
assert_response :success
end
should "fail for password mismatches" do
@basic_auth_string = "Basic #{ActiveSupport::Base64.encode64("#{@user.name}:badpassword")}"
@basic_auth_string = "Basic #{::Base64.encode64("#{@user.name}:badpassword")}"
@request.env['HTTP_AUTHORIZATION'] = @basic_auth_string
get :index, {:format => "json"}
assert_response 401

View File

@@ -53,6 +53,23 @@ class MockMemcache
def flush_all
@memory = {}
end
def fetch key, expiry = 0, raw = false
if @memory.has_key?(key)
@memory[key]
else
@memory[key] = yield
end
@memory[key]
end
def incr key
@memory[key] += 1
end
def decr key
@memory[key] -= 1
end
def set key, value, expiry = 0
@memory[key] = value
@@ -62,7 +79,7 @@ class MockMemcache
@memory[key]
end
def delete key, delay
def delete key, delay = 0
@memory.delete key
end