Project

General

Profile

force ldap authentication only (deny internal auth) possible?

Added by Iancu Stoica over 10 years ago

Hello,

I have been searching the web for half a day on this topic without too much success, so I am posting here in hopes of finding a solution.

The company I work for has been deploying redmine instances for a lot of projects. The reasoning is we provide the instance and give administrator rights to the project leader, and afterwards he/she can manage and customize the instance as they see fit.

All is working nicely but now we want to integrate all redmine instances with our central Active Directory. LDAP authentication works fine. However, when an administrator creates a new user (we want the project managers to add their users to the redmine instance), the authentication type cannot be locked down to ldap and this is a problem for us. We don't want to use the "on the fly" option and we don't want to leave the "internal" option available, thus forcing the admins to add only ldap users to their redmine instance.

Any advice would be greatly appreciated.

Environment:
-each redmine instance is deployed on it's own vm; most of them are on suse linux enterprise 11 sp1 x64, but we also have some ubuntu server 12.04, and fedora 13
-the redmine versions vary from 1.x to the latest version
-database engine is mysql on all of them

Best regards,
Iancu


Replies (3)

RE: force ldap authentication only (deny internal auth) possible? - Added by Martin Denizet (redmine.org team member) over 10 years ago

Hi Iancu,
I think the solution would be to do a plugin to wrap and break the internal authentication.
I think these changes should do the trick, in source:trunk/app/models/user.rb

  # Returns the user that matches provided login and password, or nil
  def self.try_to_login(login, password, active_only=true)
    login = login.to_s
    password = password.to_s

    # Make sure no one can sign in with an empty login or password
    return nil if login.empty? || password.empty?
    user = find_by_login(login)
    if user
      # user is already in local database
      return nil unless user.check_password?(password)
      return nil if !user.active? && active_only
    else
-      # user is not yet registered, try to authenticate with available sources
-      attrs = AuthSource.authenticate(login, password)
-      if attrs
-        user = new(attrs)
-        user.login = login
-        user.language = Setting.default_language
-        if user.save
-          user.reload
-          logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
-        end
-      end
+      return nil
    end    
    user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
    user
  rescue => text    
    raise text
  end


   # Returns true if +clear_password+ is the correct user's password, otherwise false
  def check_password?(clear_password)
    if auth_source_id.present?
      auth_source.authenticate(self.login, clear_password)
    else
-      User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
+      return false
    end
  end

Again, don't edit the source but wrap the method with a plugin. It's much cleaner.

Cheers,

RE: force ldap authentication only (deny internal auth) possible? - Added by Iancu Stoica over 10 years ago

Dear Martin,

Thank you very much for the provided solution. I have tested it, and although it is not ideal, it works and does solve our issue - an admin can create local accounts, but no local account can login.

Could something be done so that the actual action of creating internal accounts be blocked?

Thanks again,
Iancu

RE: force ldap authentication only (deny internal auth) possible? - Added by Martin Denizet (redmine.org team member) over 10 years ago

I see 2 options:

1. Wrap the controller

1.1 Remove the possibility to set manually the auth_source, in source:trunk/app/models/user.rb

safe_attributes 'status',
-    'auth_source_id',
    'generate_password',
    'must_change_passwd',
    :if => lambda {|user, current_user| current_user.admin?}

1.2 Wrap the controller, in source:trunk/app/controllers/users_controller.rb


  def create
    @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
    @user.safe_attributes = params[:user]
+    #Enforce the first auth source ID in the DB
+    @user.auth_source_id = AuthSource.first
    @user.admin = params[:user][:admin] || false
    @user.login = params[:user][:login]
-    @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
    @user.pref.attributes = params[:pref]

    if @user.save
      Mailer.account_information(@user, @user.password).deliver if params[:send_information]

      respond_to do |format|
        format.html {
          flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
          if params[:continue]
            attrs = params[:user].slice(:generate_password)
            redirect_to new_user_path(:user => attrs)
          else
            redirect_to edit_user_path(@user)
          end
        }
        format.api  { render :action => 'show', :status => :created, :location => user_url(@user) }
      end
    else
      @auth_sources = AuthSource.all
      # Clear password input
      @user.password = @user.password_confirmation = nil

      respond_to do |format|
        format.html { render :action => 'new' }
        format.api  { render_validation_errors(@user) }
      end
    end
  end

2. Wrap the controller to display an error message if the auth source is internal

Chain the create method in source:trunk/app/controllers/users_controller.rb with "limited_source", such as:

  def create_with_limited_source
    if params[:user][:auth_source_id].nil?
      flash[:error] = "We do not allow internal authentication!" 
      respond_to do |format|
        format.html { render :action => 'new' }
      end
    else
      create_without_limited_source
    end
  end

After thinking about it a little, I like the second option better. And I also realized there is a third option that sounds event better which is to have a validator in the model to prevent user.auth_source_id = nil (validate presence of auth_source_id)
Cheers,

    (1-3/3)