Amending current user logic in Discourse

(Sam Saffron) #1

I just added a couple of classes that allow you to override the way we lookup the current user. This is particularly important to people looking to integrate using cookie auth from a top level domain.

Essentially you can elect a provider for the current user functionality, to do so:

Implement a class that inherits off:

class Auth::CurrentUserProvider

  # do all current user initialization here
  def initialize(env)
    raise NotImplementedError

  # our current user, return nil if none is found
  def current_user
    raise NotImplementedError

  # log on a user and set cookies and session etc.
  def log_on_user(user,session,cookies)
    raise NotImplementedError

  # api has special rights return true if api was detected
  def is_api?
    raise NotImplementedError

  # 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

  def log_off_user(session, cookies)
    raise NotImplementedError

If you just feel like extending the current behavior, inherit off Auth::DefaultCurrentUserProvider , this allow you to run special code when a user is logged on or off and so on.

To wire your own provider, run

Discourse.current_user_provider = MyCurrentUserProvider

This is way cleaner and more predictable than monkey patching.

MediaWiki + Discourse integration
Login disappear
Monkey patching a controller from a plugin
A few API problems
Is there an endpoint to check if a user is logged in
Arrow icons are pointing in the wrong direction for inbound links
Embedding comments on private sites
(Michael - #2

Just found out that this also fixes the first problem described here so I’m a happy camper. Thanks :smile:

(pctr) #3

I’m not too familiar with Discourse and Ruby. What is the best way to configure Discourse.current_user_provider so Discourse will pick it up on the next restart?

(Kiran Patil) #5

Can you please share your code ?

I think that should be common to all sites who wants to integrate Discourse with main site using cookie auth.

(Michael - #6

I don’t think I understand. There is no code. My post was talking about how @sam 's code solved a few API issues we were encountering, and he mentioned that those new classes made it possible to do cookie based integration. But that was not our problem, or something we solved.

(Roberto_Pezzali) #7

I think that he’s asking a sample code to integrate Discourse with a rails app using the new classes added by Sam.

(Vikhyat Korrapati) #8

The default current user provider’s current_user method has the following code in it:

if current_user && current_user.suspended?
  current_user = nil

if current_user

# possible we have an api call, impersonate
unless current_user
  if api_key_value = request["api_key"]
    api_key = ApiKey.where(key: api_key_value).includes(:user).first
    if api_key.present?
      @env[API_KEY] = true
      api_username = request["api_username"]

      if api_key.user.present?
        raise if api_username && (api_key.user.username_lower != api_username.downcase)
        current_user = api_key.user
      elsif api_username
        current_user = User.where(username_lower: api_username.downcase).first


Pretty much every custom user provider implementation is going to have to duplicate that logic, so wouldn’t it make sense to do it by default, somewhere outside of the current_user method?

(Sam Saffron) #9

Sure, care to attempt a refactor?

(Alessandro Maccagnan) #10

@sam, what about the ‘impersonate’ function available to admins? If we write our ‘current_user’ implementation, what we need to care of to not broke it?

(Sam Saffron) #11

I think @shivermetimbers solved the impersonation thing, care to link to the solution here ?

(Shiv Kumar) #12

The impersonate feature, as it stands, is actually more like a become feature. When you “impersonate”, what you’re actually doing is fully logging on as that user, hence “become”. All you need to do to ensure this still works is to make sure that the log_on_user method is still able to log on the user you are trying to impersonate. As long as it does that, everything should work fine.

I’d share how it works in our plugin, but what we’ve done is pretty unique due to the way our authentication system works. If you want to share your code, I’d be happy to take a look and provide some guidance.