add current_user_provider so people can override current_user bevior cleanly, see

http://meta.discourse.org/t/amending-current-user-logic-in-discourse/10278
This commit is contained in:
Sam 2013-10-09 15:10:37 +11:00
parent 8e6ae0e278
commit 7993845bfa
15 changed files with 178 additions and 84 deletions

View File

@ -1,13 +1,12 @@
require 'current_user'
require 'canonical_url'
require_dependency 'current_user'
require_dependency 'canonical_url'
require_dependency 'discourse'
require_dependency 'custom_renderer'
require 'archetype'
require_dependency 'archetype'
require_dependency 'rate_limiter'
class ApplicationController < ActionController::Base
include CurrentUser
include CanonicalURL::ControllerExtensions
serialization_scope :guardian

View File

@ -68,7 +68,7 @@ class SessionController < ApplicationController
def destroy
reset_session
cookies[:_t] = nil
log_off_user
render nothing: true
end

View File

@ -139,7 +139,7 @@ class UsersController < ApplicationController
register_nickname(user)
if user.save
activator = UserActivator.new(user, session, cookies)
activator = UserActivator.new(user, request, session, cookies)
message = activator.activation_message
create_third_party_auth_records(user, auth)

View File

@ -3,8 +3,8 @@ require_dependency 'current_user'
class AdminConstraint
def matches?(request)
return false unless request.session[:current_user_id].present?
User.admins.where(id: request.session[:current_user_id].to_i).exists?
provider = Discourse.current_user_provider.new(request.env)
provider.current_user && provider.current_user.admin?
end
end

View File

@ -0,0 +1,34 @@
module Auth; end
class Auth::CurrentUserProvider
# do all current user initialization here
def initialize(env)
raise NotImplementedError
end
# our current user, return nil if none is found
def current_user
raise NotImplementedError
end
# log on a user and set cookies and session etc.
def log_on_user(user,session,cookies)
raise NotImplementedError
end
# api has special rights return true if api was detected
def is_api?
raise NotImplementedError
end
# we may need to know very early on in the middleware if an auth token
# exists, to optimise caching
def has_auth_cookie?
raise NotImplementedError
end
def log_off_user(session, cookies)
raise NotImplementedError
end
end

View File

@ -0,0 +1,79 @@
require_dependency "auth/current_user_provider"
class Auth::DefaultCurrentUserProvider
CURRENT_USER_KEY = "_DISCOURSE_CURRENT_USER"
API_KEY = "_DISCOURSE_API"
TOKEN_COOKIE = "_t"
# do all current user initialization here
def initialize(env)
@env = env
@request = Rack::Request.new(env)
end
# our current user, return nil if none is found
def current_user
return @env[CURRENT_USER_KEY] if @env.key?(CURRENT_USER_KEY)
request = Rack::Request.new(@env)
auth_token = request.cookies[TOKEN_COOKIE]
current_user = nil
if auth_token && auth_token.length == 32
current_user = User.where(auth_token: auth_token).first
end
if current_user && current_user.is_banned?
current_user = nil
end
if current_user
current_user.update_last_seen!
current_user.update_ip_address!(request.ip)
end
# possible we have an api call, impersonate
unless current_user
if api_key = request["api_key"]
if api_username = request["api_username"]
if SiteSetting.api_key_valid?(api_key)
@env[API_KEY] = true
current_user = User.where(username_lower: api_username.downcase).first
end
end
end
end
@env[CURRENT_USER_KEY] = current_user
end
def log_on_user(user, session, cookies)
unless user.auth_token && user.auth_token.length == 32
user.auth_token = SecureRandom.hex(16)
user.save!
end
cookies.permanent[TOKEN_COOKIE] = { value: user.auth_token, httponly: true }
@env[CURRENT_USER_KEY] = user
end
def log_off_user(session, cookies)
cookies[TOKEN_COOKIE] = nil
end
# api has special rights return true if api was detected
def is_api?
current_user
@env[API_KEY]
end
def has_auth_cookie?
request = Rack::Request.new(@env)
cookie = request.cookies[CURRENT_USER_KEY]
!cookie.nil? && cookie.length == 32
end
end

View File

@ -1,90 +1,39 @@
module CurrentUser
def self.has_auth_cookie?(env)
request = Rack::Request.new(env)
cookie = request.cookies["_t"]
!cookie.nil? && cookie.length == 32
Discourse.current_user_provider.new(env).has_auth_cookie?
end
def self.lookup_from_env(env)
request = Rack::Request.new(env)
lookup_from_auth_token(request.cookies["_t"])
Discourse.current_user_provider.new(env).current_user
end
def self.lookup_from_auth_token(auth_token)
if auth_token && auth_token.length == 32
User.where(auth_token: auth_token).first
end
end
# can be used to pretend current user does no exist, for CSRF attacks
def clear_current_user
@current_user = nil
@not_logged_in = true
@current_user_provider = Discourse.current_user_provider.new({})
end
def log_on_user(user)
session[:current_user_id] = user.id
unless user.auth_token && user.auth_token.length == 32
user.auth_token = SecureRandom.hex(16)
user.save!
end
set_permanent_cookie!(user)
current_user_provider.log_on_user(user,session,cookies)
end
def set_permanent_cookie!(user)
cookies.permanent["_t"] = { value: user.auth_token, httponly: true }
def log_off_user
current_user_provider.log_off_user(session,cookies)
end
def is_api?
# ensure current user has been called
# otherwise
current_user
@is_api
current_user_provider.is_api?
end
def current_user
return @current_user if @current_user || @not_logged_in
current_user_provider.current_user
end
if session[:current_user_id].blank?
# maybe we have a cookie?
@current_user = CurrentUser.lookup_from_auth_token(cookies["_t"])
session[:current_user_id] = @current_user.id if @current_user
else
@current_user ||= User.where(id: session[:current_user_id]).first
private
# I have flip flopped on this (sam), if our permanent cookie
# conflicts with our current session assume session is bust
# kill it
if @current_user && cookies["_t"] != @current_user.auth_token
@current_user = nil
end
end
if @current_user && @current_user.is_banned?
@current_user = nil
end
@not_logged_in = session[:current_user_id].blank?
if @current_user
@current_user.update_last_seen!
@current_user.update_ip_address!(request.remote_ip)
end
# possible we have an api call, impersonate
unless @current_user
if api_key = request["api_key"]
if api_username = request["api_username"]
if SiteSetting.api_key_valid?(api_key)
@is_api = true
@current_user = User.where(username_lower: api_username.downcase).first
end
end
end
end
@current_user
def current_user_provider
@current_user_provider ||= Discourse.current_user_provider.new(request.env)
end
end

View File

@ -1,5 +1,6 @@
require 'cache'
require_dependency 'plugin/instance'
require_dependency 'auth/default_current_user_provider'
module Discourse
@ -148,6 +149,14 @@ module Discourse
end
end
def self.current_user_provider
@current_user_provider || Auth::DefaultCurrentUserProvider
end
def self.current_user_provider=(val)
@current_user_provider = val
end
private
def self.maintenance_mode_key

View File

@ -4,7 +4,8 @@ class HomePageConstraint
end
def matches?(request)
homepage = request.session[:current_user_id].present? ? SiteSetting.homepage : SiteSetting.anonymous_homepage
provider = Discourse.current_user_provider.new(request.env)
homepage = provider.current_user ? SiteSetting.homepage : SiteSetting.anonymous_homepage
homepage == @filter
end
end
end

View File

@ -4,9 +4,17 @@ require_dependency 'rate_limiter/on_create_record'
# A redis backed rate limiter.
class RateLimiter
def self.disable
@disabled = true
end
def self.enable
@disabled = false
end
# We don't observe rate limits in test mode
def self.disabled?
Rails.env.test?
@disabled || Rails.env.test?
end
def initialize(user, key, max, secs)

View File

@ -3,8 +3,8 @@ require_dependency 'current_user'
class StaffConstraint
def matches?(request)
return false unless request.session[:current_user_id].present?
User.staff.where(id: request.session[:current_user_id].to_i).exists?
provider = Discourse.current_user_provider.new(request.env)
provider.current_user && provider.current_user.staff?
end
end

View File

@ -1,10 +1,12 @@
# Responsible for dealing with different activation processes when a user is created
class UserActivator
attr_reader :user, :session, :cookies
def initialize(user, session, cookies)
attr_reader :user, :request, :session, :cookies
def initialize(user, request, session, cookies)
@user = user
@session = session
@cookies = cookies
@request = request
end
def activation_message
@ -14,7 +16,7 @@ class UserActivator
private
def activator
factory.new(user, session, cookies)
factory.new(user, request, session, cookies)
end
def factory

View File

@ -3,10 +3,8 @@ require_dependency 'current_user'
describe CurrentUser do
it "allows us to lookup a user from our environment" do
token = EmailToken.generate_token
user = Fabricate.build(:user)
User.expects(:where).returns([user])
CurrentUser.lookup_from_env("HTTP_COOKIE" => "_t=#{token};").should == user
user = Fabricate(:user, auth_token: EmailToken.generate_token)
CurrentUser.lookup_from_env("HTTP_COOKIE" => "_t=#{user.auth_token};").should == user
end
# it "allows us to lookup a user from our app" do

View File

@ -63,8 +63,22 @@ Spork.prefork do
end
end
class TestCurrentUserProvider < Auth::DefaultCurrentUserProvider
def log_on_user(user,session,cookies)
session[:current_user_id] = user.id
super
end
def log_off_user(session,cookies)
session[:current_user_id] = nil
super
end
end
config.before(:all) do
DiscoursePluginRegistry.clear
Discourse.current_user_provider = TestCurrentUserProvider
require_dependency 'site_settings/local_process_provider'
SiteSetting.provider = SiteSettings::LocalProcessProvider.new
end

View File

@ -10,7 +10,8 @@ module Helpers
end
def log_in_user(user)
session[:current_user_id] = user.id
provider = Discourse.current_user_provider.new(request.env)
provider.log_on_user(user,session,cookies)
end
def fixture_file(filename)