Index: app/models/auth_source_ldap.rb =================================================================== --- app/models/auth_source_ldap.rb (revision 2700) +++ app/models/auth_source_ldap.rb (working copy) @@ -15,7 +15,10 @@ # 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 @@ -24,75 +27,106 @@ validates_length_of :account, :base_dn, :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 - + before_validation :strip_ldap_attributes - + def after_initialize self.port = 389 if self.port == 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 + ldap_con.unbind + begin + result = ldap_con.bind(dn, password) + rescue LDAP::Error => bindError + return nil + end # return user's attributes logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? - attrs - rescue Net::LDAP::LdapError => text + attrs + rescue LDAP::Error => text raise "LdapError: " + 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 + rescue LDAP::Error => text raise "LdapError: " + 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 + if self.tls + conn = LDAP::SSLConn.new(self.host, self.port, self.starttls) + else + conn = LDAP::Conn.new(self.host, self.port) + end + conn.set_option(LDAP::LDAP_OPT_DEREF, self.dereference) + conn.set_option(LDAP::LDAP_OPT_X_TLS_REQUIRE_CERT, self.require_cert) + + 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 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,7 +9,9 @@ <%= 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

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

@@ -20,9 +22,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,9 @@ field_port: Port field_account: Account field_base_dn: Base DN +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,15 @@ +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 + 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 + end +end