Skip to content

Commit

Permalink
add current_user_provider so people can override current_user bevior …
Browse files Browse the repository at this point in the history
  • Loading branch information
SamSaffron committed Oct 9, 2013
1 parent 8e6ae0e commit 7993845
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 84 deletions.
7 changes: 3 additions & 4 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/session_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def forgot_password

def destroy
reset_session
cookies[:_t] = nil
log_off_user
render nothing: true
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def create
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)

Expand Down
4 changes: 2 additions & 2 deletions lib/admin_constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
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
34 changes: 34 additions & 0 deletions lib/auth/current_user_provider.rb
Original file line number Diff line number Diff line change
@@ -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
79 changes: 79 additions & 0 deletions lib/auth/default_current_user_provider.rb
Original file line number Diff line number Diff line change
@@ -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
75 changes: 12 additions & 63 deletions lib/current_user.rb
Original file line number Diff line number Diff line change
@@ -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

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

# 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
current_user_provider.current_user
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
private

@current_user
def current_user_provider
@current_user_provider ||= Discourse.current_user_provider.new(request.env)
end

end
9 changes: 9 additions & 0 deletions lib/discourse.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'cache'
require_dependency 'plugin/instance'
require_dependency 'auth/default_current_user_provider'

module Discourse

Expand Down Expand Up @@ -148,6 +149,14 @@ def self.store
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
Expand Down
5 changes: 3 additions & 2 deletions lib/homepage_constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ def initialize(filter)
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
10 changes: 9 additions & 1 deletion lib/rate_limiter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@
# 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)
Expand Down
4 changes: 2 additions & 2 deletions lib/staff_constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
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
8 changes: 5 additions & 3 deletions lib/user_activator.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,7 +16,7 @@ def activation_message
private

def activator
factory.new(user, session, cookies)
factory.new(user, request, session, cookies)
end

def factory
Expand Down
6 changes: 2 additions & 4 deletions spec/components/current_user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@

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
Expand Down
14 changes: 14 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,22 @@
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
Expand Down
3 changes: 2 additions & 1 deletion spec/support/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ def log_in(fabricator=nil)
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)
Expand Down

0 comments on commit 7993845

Please sign in to comment.