Index: app/models/auth_source_ldap.rb =================================================================== --- app/models/auth_source_ldap.rb (revision 2700) +++ app/models/auth_source_ldap.rb (working copy) @@ -15,84 +15,130 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'net/ldap' +# Modified by Daniel Marczisovszky (marczi@dev-labs.com) +# to allow dereferencing aliases, START_TLS + +require 'ldap' require 'iconv' class AuthSourceLdap < AuthSource validates_presence_of :host, :port, :attr_login validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true - validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true + validates_length_of :account, :base_dn, :filter, :maximum => 255, :allow_nil => true validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true - validates_numericality_of :port, :only_integer => true - + validates_numericality_of :port, :protocol_version, :only_integer => true + before_validation :strip_ldap_attributes - + def after_initialize self.port = 389 if self.port == 0 + self.protocol_version = 3 if self.protocol_version == 0 end - + def authenticate(login, password) return nil if login.blank? || password.blank? attrs = [] # get user's DN - ldap_con = initialize_ldap_con(self.account, self.account_password) - login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) - object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) + + # Ticket #1913 by Adi Kriegisch (adi@cg.tuwien.ac.at) + if self.account.include? "$login" then + logger.debug "LDAP-Auth with User login" if logger && logger.debug? + ldap_con = initialize_ldap_con(self.account.sub("$login", encode(login)), password) + else + logger.debug "LDAP-Auth with Admin User" if logger && logger.debug? + ldap_con = initialize_ldap_con(self.account, self.account_password) + end + + if self.filter.empty? + filter = self.attr_login + "=" + encode(login) + else + filter = self.filter.gsub("$login", encode(login)) + end dn = String.new - ldap_con.search( :base => self.base_dn, - :filter => object_filter & login_filter, - # only ask for the DN if on-the-fly registration is disabled - :attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry| + logger.debug "Search in DN: #{self.base_dn} with filter: #{filter}" if logger && logger.debug? + ldap_con.search( self.base_dn, LDAP::LDAP_SCOPE_SUBTREE, filter, + (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) { |entry| dn = entry.dn attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), :mail => AuthSourceLdap.get_attr(entry, self.attr_mail), :auth_source_id => self.id ] if onthefly_register? - end + } return nil if dn.empty? logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug? # authenticate user - ldap_con = initialize_ldap_con(dn, password) - return nil unless ldap_con.bind + begin + logger.debug "Trying to login as #{dn}" if logger && logger.debug? + initialize_ldap_con(dn, password) + rescue LDAP::Error => bindError + logger.debug "Login failed: #{bindError}" if logger && logger.debug? + return nil + end # return user's attributes logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? - attrs - rescue Net::LDAP::LdapError => text - raise "LdapError: " + text + attrs + rescue LDAP::Error => text + raise "LDAP Auth Error: " + text end # test the connection to the LDAP def test_connection ldap_con = initialize_ldap_con(self.account, self.account_password) - ldap_con.open { } - rescue Net::LDAP::LdapError => text - raise "LdapError: " + text + rescue LDAP::Error => text + raise "LDAP Error: " + text end - + def auth_method_name "LDAP" end - + private - + def strip_ldap_attributes [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? end end - + def initialize_ldap_con(ldap_user, ldap_password) - options = { :host => self.host, - :port => self.port, - :encryption => (self.tls ? :simple_tls : nil) - } - options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? - Net::LDAP.new options + logger.debug "Connecting to #{self.host}:#{self.port}, tls=#{self.tls}" if logger && logger.debug? + if self.tls + conn = LDAP::SSLConn.new(self.host, self.port, self.starttls) + else + conn = LDAP::Conn.new(self.host, self.port) + end + logger.debug "Dereference set option" if logger && logger.debug? + conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, self.protocol_version) + conn.set_option(LDAP::LDAP_OPT_DEREF, self.dereference) + if self.tls && self.starttls + logger.debug "Certificate set option" if logger && logger.debug? + conn.set_option(LDAP::LDAP_OPT_X_TLS_REQUIRE_CERT, self.require_cert) + end + + logger.debug "Trying to bind" if logger && logger.debug? + if !ldap_user.blank? || !ldap_password.blank? then + logger.debug "Bind as user #{ldap_user}" if logger && logger.debug? + conn.bind(ldap_user, ldap_password) + else + logger.debug "Anonymous bind" if logger && logger.debug? + conn.bind + end + rescue LDAP::Error => text + logger.debug "LDAP Connect Error: #{$!}" if logger && logger.debug? + raise end - + def self.get_attr(entry, attr_name) if !attr_name.blank? entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] end end + + def encode(value) + value = value.gsub("\\", "\\\\5c") + value = value.gsub("*", "\\\\2a") + value = value.gsub("(", "\\\\28") + value = value.gsub(")", "\\\\29") + value = value.gsub("\000", "\\\\00") + end end Index: app/views/auth_sources/_form.rhtml =================================================================== --- app/views/auth_sources/_form.rhtml (revision 2700) +++ app/views/auth_sources/_form.rhtml (working copy) @@ -9,10 +9,15 @@ <%= text_field 'auth_source', 'host' %>

-<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

+<%= text_field 'auth_source', 'port', :size => 6 %> +<%= check_box 'auth_source', 'tls' %> LDAPS +<%= check_box 'auth_source', 'starttls' %> START_TLS

+

+<%= select 'auth_source', 'protocol_version', [2, 3] %>

+

-<%= text_field 'auth_source', 'account' %>

+<%= text_field 'auth_source', 'account' %>

<%= password_field 'auth_source', 'account_password', :name => 'ignore', @@ -20,9 +25,17 @@ :onfocus => "this.value=''; this.name='auth_source[account_password]';", :onchange => "this.name='auth_source[account_password]';" %>

+

+<%= select 'auth_source', 'require_cert', [ ["Never", 0], ["Hard", 1], ["Demand", 2], ["Allow", 3], ["Try", 4] ] %>

+

<%= text_field 'auth_source', 'base_dn', :size => 60 %>

+

<%= text_field 'auth_source', 'filter' %>

+ +

+<%= select 'auth_source', 'dereference', [ ["Never", 0], ["Searching", 1], ["Finding", 2], ["Always", 3] ] %>

+

<%= check_box 'auth_source', 'onthefly_register' %>

Index: lang/en.yml =================================================================== --- lang/en.yml (revision 2700) +++ lang/en.yml (working copy) @@ -157,6 +157,10 @@ field_port: Port field_account: Account field_base_dn: Base DN +field_protocol_version: Protocol version +field_filter: Filter +field_dereference: Dereference +field_require_cert: Require certificate field_attr_login: Login attribute field_attr_firstname: Firstname attribute field_attr_lastname: Lastname attribute Index: db/migrate/102_add_auth_sources_filter_deref_advtls.rb =================================================================== --- db/migrate/102_add_auth_sources_filter_deref_advtls.rb (revision 0) +++ db/migrate/102_add_auth_sources_filter_deref_advtls.rb (revision 0) @@ -0,0 +1,17 @@ +class AddAuthSourcesFilterDerefAdvtls < ActiveRecord::Migration + def self.up + add_column :auth_sources, :starttls, :boolean, :default => false, :null => false + add_column :auth_sources, :filter, :string + add_column :auth_sources, :dereference, :integer + add_column :auth_sources, :require_cert, :integer + add_column :auth_sources, :protocol_version, :integer, :default => 3, :null => false + end + + def self.down + remove_column :auth_sources, :starttls + remove_column :auth_sources, :filter + remove_column :auth_sources, :dereference + remove_column :auth_sources, :require_cert + remove_column :auth_sources, :protocol_version + end +end