Revert "Revert "BUGFIX: improve error messages for invalid API keys""

This reverts commit e9afe28586.
This commit is contained in:
Sam 2014-05-23 08:13:25 +10:00
parent 43bac5467a
commit cf254000cf
6 changed files with 94 additions and 40 deletions

View File

@ -2,9 +2,11 @@ require_dependency "auth/current_user_provider"
class Auth::DefaultCurrentUserProvider class Auth::DefaultCurrentUserProvider
CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER" CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER".freeze
API_KEY ||= "_DISCOURSE_API" API_KEY ||= "api_key".freeze
TOKEN_COOKIE ||= "_t" API_KEY_ENV ||= "_DISCOURSE_API".freeze
TOKEN_COOKIE ||= "_t".freeze
PATH_INFO ||= "PATH_INFO".freeze
# do all current user initialization here # do all current user initialization here
def initialize(env) def initialize(env)
@ -12,12 +14,11 @@ class Auth::DefaultCurrentUserProvider
@request = Rack::Request.new(env) @request = Rack::Request.new(env)
end end
# our current user, return nil if none is found # our current user, return nil if none is found
def current_user def current_user
return @env[CURRENT_USER_KEY] if @env.key?(CURRENT_USER_KEY) return @env[CURRENT_USER_KEY] if @env.key?(CURRENT_USER_KEY)
request = Rack::Request.new(@env) request = @request
auth_token = request.cookies[TOKEN_COOKIE] auth_token = request.cookies[TOKEN_COOKIE]
@ -31,33 +32,19 @@ class Auth::DefaultCurrentUserProvider
current_user = nil current_user = nil
end end
if current_user if current_user && should_update_last_seen?
u = current_user u = current_user
Scheduler::Defer.later do Scheduler::Defer.later do
u.update_last_seen! u.update_last_seen!
u.update_ip_address!(request.ip) u.update_ip_address!(request.ip)
end end
end end
# possible we have an api call, impersonate # possible we have an api call, impersonate
unless current_user if api_key = request[API_KEY]
if api_key_value = request["api_key"] current_user = lookup_api_user(api_key, request)
api_key = ApiKey.where(key: api_key_value).includes(:user).first raise Discourse::InvalidAccess unless current_user
if api_key.present? @env[API_KEY_ENV] = true
@env[API_KEY] = true
api_username = request["api_username"]
if api_key.user.present?
raise Discourse::InvalidAccess.new if api_username && (api_key.user.username_lower != api_username.downcase)
current_user = api_key.user
elsif api_username
current_user = User.find_by(username_lower: api_username.downcase)
end
end
end
end end
@env[CURRENT_USER_KEY] = current_user @env[CURRENT_USER_KEY] = current_user
@ -95,8 +82,28 @@ class Auth::DefaultCurrentUserProvider
end end
def has_auth_cookie? def has_auth_cookie?
request = Rack::Request.new(@env) cookie = @request.cookies[TOKEN_COOKIE]
cookie = request.cookies[TOKEN_COOKIE]
!cookie.nil? && cookie.length == 32 !cookie.nil? && cookie.length == 32
end end
def should_update_last_seen?
!(@request.path =~ /^\/message-bus\//)
end
protected
def lookup_api_user(api_key_value, request)
api_key = ApiKey.where(key: api_key_value).includes(:user).first
if api_key
api_username = request["api_username"]
if api_key.user
api_key.user if !api_username || (api_key.user.username_lower == api_username.downcase)
elsif api_username
User.find_by(username_lower: api_username.downcase)
else
nil
end
end
end
end end

View File

@ -0,0 +1,50 @@
require 'spec_helper'
require_dependency 'auth/default_current_user_provider'
describe Auth::DefaultCurrentUserProvider do
def provider(url, opts=nil)
opts ||= {method: "GET"}
env = Rack::MockRequest.env_for(url, opts)
Auth::DefaultCurrentUserProvider.new(env)
end
it "raises errors for incorrect api_key" do
expect{
provider("/?api_key=INCORRECT").current_user
}.to raise_error(Discourse::InvalidAccess)
end
it "finds a user for a correct per-user api key" do
user = Fabricate(:user)
ApiKey.create!(key: "hello", user_id: user.id, created_by_id: -1)
provider("/?api_key=hello").current_user.id.should == user.id
end
it "raises for a user pretending" do
user = Fabricate(:user)
user2 = Fabricate(:user)
ApiKey.create!(key: "hello", user_id: user.id, created_by_id: -1)
expect{
provider("/?api_key=hello&api_username=#{user2.username.downcase}").current_user
}.to raise_error(Discourse::InvalidAccess)
end
it "finds a user for a correct system api key" do
user = Fabricate(:user)
ApiKey.create!(key: "hello", created_by_id: -1)
provider("/?api_key=hello&api_username=#{user.username.downcase}").current_user.id.should == user.id
end
it "should not update last seen for message bus" do
provider("/message-bus/anything/goes", method: "POST").should_update_last_seen?.should == false
provider("/message-bus/anything/goes", method: "GET").should_update_last_seen?.should == false
end
it "should update last seen for others" do
provider("/topic/anything/goes", method: "POST").should_update_last_seen?.should == true
provider("/topic/anything/goes", method: "GET").should_update_last_seen?.should == true
end
end

View File

@ -5,10 +5,9 @@ describe CurrentUser do
it "allows us to lookup a user from our environment" do it "allows us to lookup a user from our environment" do
user = Fabricate(:user, auth_token: EmailToken.generate_token, active: true) user = Fabricate(:user, auth_token: EmailToken.generate_token, active: true)
EmailToken.confirm(user.auth_token) EmailToken.confirm(user.auth_token)
CurrentUser.lookup_from_env("HTTP_COOKIE" => "_t=#{user.auth_token};").should == user
env = Rack::MockRequest.env_for("/test", "HTTP_COOKIE" => "_t=#{user.auth_token};")
CurrentUser.lookup_from_env(env).should == user
end end
# it "allows us to lookup a user from our app" do
# end
end end

View File

@ -163,16 +163,14 @@ describe 'api' do
it 'disallows phonies to bookmark posts' do it 'disallows phonies to bookmark posts' do
PostAction.expects(:act).with(user, post, PostActionType.types[:bookmark]).never PostAction.expects(:act).with(user, post, PostActionType.types[:bookmark]).never
lambda do
put :bookmark, bookmarked: "true", post_id: post.id, api_key: SecureRandom.hex(32), api_username: user.username, format: :json put :bookmark, bookmarked: "true", post_id: post.id, api_key: SecureRandom.hex(32), api_username: user.username, format: :json
end.should raise_error Discourse::NotLoggedIn response.code.to_i.should == 403
end end
it 'disallows blank api' do it 'disallows blank api' do
PostAction.expects(:act).with(user, post, PostActionType.types[:bookmark]).never PostAction.expects(:act).with(user, post, PostActionType.types[:bookmark]).never
lambda do
put :bookmark, bookmarked: "true", post_id: post.id, api_key: "", api_username: user.username, format: :json put :bookmark, bookmarked: "true", post_id: post.id, api_key: "", api_username: user.username, format: :json
end.should raise_error Discourse::NotLoggedIn response.code.to_i.should == 403
end end
end end
end end

View File

@ -638,7 +638,7 @@ describe TopicsController do
end end
context "when 'login required' site setting has been enabled" do context "when 'login required' site setting has been enabled" do
before { SiteSetting.stubs(:login_required?).returns(true) } before { SiteSetting.login_required = true }
context 'and the user is logged in' do context 'and the user is logged in' do
before { log_in(:coding_horror) } before { log_in(:coding_horror) }
@ -662,9 +662,9 @@ describe TopicsController do
expect(response).to be_successful expect(response).to be_successful
end end
it 'redirects to the login page if invalid key is provided' do it 'returns 403 for an invalid key' do
get :show, topic_id: topic.id, slug: topic.slug, api_key: "bad" get :show, topic_id: topic.id, slug: topic.slug, api_key: "bad"
expect(response).to redirect_to login_path expect(response.code.to_i).to be(403)
end end
end end
end end

View File

@ -59,7 +59,7 @@ describe UserBadgesController do
it 'grants badges from master api calls' do it 'grants badges from master api calls' do
api_key = Fabricate(:api_key) api_key = Fabricate(:api_key)
StaffActionLogger.any_instance.expects(:log_badge_grant).never StaffActionLogger.any_instance.expects(:log_badge_grant).never
xhr :post, :create, badge_id: badge.id, username: user.username, api_key: api_key.key xhr :post, :create, badge_id: badge.id, username: user.username, api_key: api_key.key, api_username: "system"
response.status.should == 200 response.status.should == 200
user_badge = UserBadge.find_by(user: user, badge: badge) user_badge = UserBadge.find_by(user: user, badge: badge)
user_badge.should be_present user_badge.should be_present