CASify Redmine

Redmine is a project management web application. It’s written in Ruby using the Rails framework.

There is a CAS client available written in Ruby: RubyCAS-Client.

So, I’ve made some modifications on Redmine code in order to use the CAS service to authenticate Redmine users. Moreover, user information is updated from a LDAP every time the user login in the Redmine website (using Net::LDAP). And the fields to modify this data will be disabled on My account page.

Summarizing, I’ve uploaded a patch which provides this specific behaviour (this patch is done against the revision 2824 of Redmine). I know that it’s hardcoded, but it could be a base for further steps and I hope that it could be useful for someone else (or even for me in the future ;-) ). In the next paragraphs I’ll go into more technical details.

Instructions

Once you’ve installed the last version of Redmine (I’m using trunk revision 2824), you’ll need to install RubyCAS-Client (again from trunk because of some functions are not available at last stable 2.0.1):

./script/plugin install http://rubycas-client.googlecode.com/svn/trunk/rubycas-client

First of all, you should add the basic configuration for CAS at config/environment.rb:

CASClient::Frameworks::Rails::Filter.configure(
  :cas_base_url => 'https://localhost/cas/'
)

Moreover, it’s needed to add the next line to the class AccountController at app/controllers/account_controller.rb:

  before_filter CASClient::Frameworks::Rails::Filter

Then, in order to change the login behaviour you should modify the login action of the account controller. Change the method login at app/controllers/account_controller.rb:

require 'casclient'
require 'casclient/frameworks/rails/filter'

  def login
    if session[:cas_user].empty?
      # Logout user
      self.logged_user = nil
      CASClient::Frameworks::Rails::Filter::redirect_to_cas_for_authentication(self)
    else
      cas_authentication(session[:cas_user])
    end
  end

Now, it’s necessary to add the new method cas_authentication, which register an user if it’s the first time that makes login on the Redmine website:

  def cas_authentication(cas_user)
    user = User.find_or_initialize_by_login(cas_user)

    if user.new_record?
      # Create on the fly
      user.login = cas_user

      user = update_user_data_from_ldap(user)

      user.status = User::STATUS_REGISTERED

      register_automatically(user) do
        onthefly_creation_failed(user)
      end
    else
      if user.active?
        user = update_user_data_from_ldap(user, true)
        successful_authentication(user)
      else
        account_pending
      end
    end
  end

The method update_user_data_from_ldap gets the user data from LDAP, and update user data on database if needed:

  def update_user_data_from_ldap (user, save = false)
    ldap = Net::LDAP::new
    ldap.host = 'localhost'
    ldap.port = '389'

    treebase = 'dc=example,dc=com'

    filter = Net::LDAP::Filter.eq('uid', user.login)

    entry = ldap.search( :base => treebase, :filter => filter, :attributes => ['cn', 'sn', 'mail'] ).first

    user.firstname = entry.cn.first
    user.lastname = entry.sn.first
    user.mail = entry.mail.first

    user.save if save

    return user
  end

Following, you should modify the logout behaviour, changing the method logout of the same controller:

  def logout
    cookies.delete :autologin
    Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged?
    self.logged_user = nil
    CASClient::Frameworks::Rails::Filter::logout(self)
  end

The last step is to disable the fields gotten from LDAP at My account page. Modify the app/views/my/account.rhtml file adding :disabled => true option:

<p><%= f.text_field :firstname, :required => true, :disabled => true %></p>
<p><%= f.text_field :lastname, :required => true, :disabled => true %></p>
<p><%= f.text_field :mail, :required => true, :disabled => true %></p>

Finally, you can get all this stuff from a single patch. Any comment or suggestion are, as usual, welcomed.

  1. Neoh59 says:

    Hello,

    I use Redmine 0.8.4 (latest stable release)

    CASifying Redmine will be great. But why do you write a function to connect to LDAP although LDAP configuration can be made in the Redmine interface ?

    I’m dreaming of a SSO plugin for Redmine. But I’m not be able to do it. :-(
    Do you ? ;-)

    Regards,
    Neoh

    PS: Sorry for my poor english, I’m french.

  2. Manuel Rego Casasnovas says:

    I’m just using LDAP for a specific requirement. I need that the first time the user login Redmine, the information from LDAP about this use was stored on the database (morover every time the user login again I should update the data if that has changed on LDAP). Anyway, I could have a look at LDAP plugin, maybe it fulfils my requirements ;-)

    About the SSO plugin, maybe this patch could be useful as example for that plugin. But for the moment it’s enough for my needs.

  3. rubiojr says:

    Awesome mate. Your patch saved us hours of work!!!

  4. lnomine says:

    Hi,

    Do you plan to use the 0.9.x version of Redmine? I’d love to see your patch working for the current version.

  5. Manuel Rego Casasnovas says:

    Maybe in the future we could update to this version, but not in the short term. Sorry.

    I’ll keep you informed if we update the patch.

  1. There are no trackbacks for this post yet.

Leave a Reply

*

Bad Behavior has blocked 184 access attempts in the last 7 days.