3 acts as current user container

acts_as_current_user_container is a mixin for ActionController::Base classes.

3.1 Gotcha

Note that you have to call current_user= (the setter) with self.current_user=. Otherwise, the local variable current_user will be set. We we will write self.current_user below to stress this. Note that with self.current_user we mean the object method not a class method!

3.2 Motivation

All Rails applications featuring access control must store the currently logged in user in the session. Sessions are only available from your controllers so you will either have a lot of session[:current_user_id] calls in your code or write a method to access the current user via this method.

This method is very simple and thus it can perfectly be refactored into a mixin for your controllers. We have done just that and wrote acts_as_current_user_container.

3.3 First Glance

Let have a quick look on how it works before diving into detail (you could also call this section “for the impatient”). If you want to access the current user in multiple locations then you will want to put the call to acts_as_current_user_container into your ApplicationController:

class ApplicationController < ActionController::Base
  acts_as_current_user_container
end

Afterwards, you can get access to the current user in your controllers by the methods current_user and self.current_user. For example:

class SecretController < ActionController::Base
  before_filter :protect_me

  protected

    def protect_me
      if current_user.nil? then
        session[:errors] = 'Keep out!'
        redirect_to login_url()
        return false
      end

      true
    end
end

As you would guess by the example above, the current_user returns nil if no user has previously been set. This makes for some checks whether the result is nil or is not nil. You can circumvent this problem by passing in a symbol specifying a class (or the class itself):

class AnonymousUser
  acts_as_anonymous_user
end

class ApplicationController < ActionController::Base
  acts_as_current_user_container :anonymous_user => :anonymous_user
end

Then, current_user creates an instance of AnonymousUser and returns it if no user has been set explicitely. See the documentation on acts_as_anonymous_user for more information about this mixin.

3.4 Requirements

The acts_as_current_user_container mixin works with any ActionController::Base class (in fact the only dependency is the existence of a session method returning a Hash).

You must not use the session variable :aacuc_data, i.e. session[:aacuc_data] should not be accessed from your code.

3.5 Reference

When you use the mixin in your ActionController::Base class then the two methods current_user and self.current_user are added to this controller class. You can set an object that represents the current user by using the second helper method. The first one returns the object earlier set.

If you call current_user without calling self.current_user before in the current session then the result depends on the parameters you passed to the acts_as_current_user_container method. At the moment, this method accepts but one parameter - :anonymous_user. The following are valid values for :anonymous_user

Examples:

class ApplicationController < ActionController::Base
  acts_as_current_user_container :anonymous_user => nil
end

class ApplicationController < ActionController::Base
  acts_as_current_user_container :anonymous_user => :anonymous_user
  # Results in AnonymousUser.new to be returned by current_user
  # unless something has explicitely set within this session.
end

class ApplicationController < ActionController::Base
  acts_as_current_user_container :anonymous_user => AnonymousUser
  # Results in AnonymousUser.new to be returned by current_user
  # unless something has explicitely set within this session.
end

If the value of current_user is an ActiveRecord::Base object then the id and the class will be stored in the session. If the value of current_user implements the informal PersistableInSession interface (see below) then the methods of the interface will be used to store the class and the data necessary for unpersisting in the session.

In all other cases only the class will be stored in the session and {previous current_user}.class.new will be returned on the next call to current_user. No state is preserved in this case.

3.6 The Informal Interface PersistableInSession

If an object that is to be written into the session data via current_user is not an ActiveRecord::Base object and needs to preserve its state over requests in the session then the object and its class must provide the following methods. We refer to them as the “informal PersistableInSession interface”. In a language like Java they would be collected in an interface but since Ruby does not have (and only arguably needs) interfaces it is only informal:

The information to be stored in the session should be as few as possible: For example, a numeric user id, a login name or a pair of the login name and the Windows Domain.

If your user data is comes from an LDAP server then your User class could look like the following:

File app/model/user.rb:

class User
  attr_accessor :login

  def persist
    { :login => self.login }
  end

  def self.unpersist(hash)
    user = User.new
    user.login = hash[:login]

    return user
  end
end

3.7 Security Implications

You should be careful only to persist the absolutely minimal information that is required to identify a user into the session. The name of the class of the object to unpersists and the object id for ActiveRecord objects is a good example of this.

If you store a password hash then this information will be written into the session file - if you use file based sessons - in a temporary directory.

Storing information like permissions or information about related ActiveRecord::Base objects could lead to incoherent data: The user object you store in the session has permissions and roles that have been revoked from the given user from the database.

Other problem scenarious could be inexplicable crashes because your user object tries to reference no longer existing articles.

3.8 Testing Gotcha

Note that calling @controller.current_user will not work in your tests before you called either post or get. You can set the current user in @controller in tests by using the Test Helpers described below.

3.9 Test Helpers

ActiveRbac comes with a helper for your controller tests. You can use it by including active_rbac/test_helper in your tests.

The helper adds the method set_current_user to Test::Unit::Test. This method works exactly as the current_user= method in your acts_as_current_user controllers.

3.9.1 Example

File app/controllers/application.rb:

  class ApplicationController < ActionController::Base
    acts_as_current_user_container :anonymous_user => AnonymousUser
  end

File app/controller/test_controller.rb:

  class TestController < ApplicationController
    def action_that_requires_login
      render :text => current_user.inspect
    end
  end

File app/test/functional/test_helper.rb:

# ...
require 'active_rbac/test_helper'
# ...

File app/test/functional/test_controller_test.rb:

# ...
def test_action_that_requires_login
  set_current_user(users(:first))

  get :action_that_requires_login
end
# ...