diff --git a/app/models/auth_source_ldap.rb b/app/models/auth_source_ldap.rb index 5a32ffc..5e1c1dd 100644 --- a/app/models/auth_source_ldap.rb +++ b/app/models/auth_source_ldap.rb @@ -14,18 +14,19 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Modified by Daniel Marczisovszky (marczi@dev-labs.com) +# to allow dereferencing aliases, START_TLS require 'iconv' -require 'net/ldap' -require 'net/ldap/dn' +require 'ldap' require 'timeout' class AuthSourceLdap < AuthSource validates_presence_of :host, :port, :attr_login validates_length_of :name, :host, :maximum => 60, :allow_nil => true - validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true + validates_length_of :account, :base_dn, :filter, :maximum => 255, :allow_blank => 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 validates_numericality_of :timeout, :only_integer => true, :allow_blank => true validate :validate_filter @@ -34,6 +35,7 @@ class AuthSourceLdap < AuthSource def initialize(attributes=nil, *args) super self.port = 389 if self.port == 0 + self.protocol_version = 3 if self.protocol_version == 0 end def authenticate(login, password) @@ -46,18 +48,17 @@ class AuthSourceLdap < AuthSource return attrs.except(:dn) end end - rescue Net::LDAP::LdapError => e - raise AuthSourceException.new(e.message) + rescue LDAP::Error => text + raise AuthSourceException.new(text) end # test the connection to the LDAP def test_connection with_timeout do ldap_con = initialize_ldap_con(self.account, self.account_password) - ldap_con.open { } end - rescue Net::LDAP::LdapError => e - raise AuthSourceException.new(e.message) + rescue LDAP::Error => text + raise AuthSourceException.new(text) end def auth_method_name @@ -78,16 +79,16 @@ class AuthSourceLdap < AuthSource def ldap_filter if filter.present? - Net::LDAP::Filter.construct(filter) + LDAP::Filter.construct(filter) end - rescue Net::LDAP::LdapError + rescue LDAP::Error nil end def validate_filter - if filter.present? && ldap_filter.nil? - errors.add(:filter, :invalid) - end + #if filter.present? && ldap_filter.nil? + # errors.add(:filter, :invalid) + #end end def strip_ldap_attributes @@ -97,12 +98,31 @@ class AuthSourceLdap < AuthSource 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 get_user_attributes_from_ldap_entry(entry) @@ -128,30 +148,59 @@ class AuthSourceLdap < AuthSource # Check if a DN (user record) authenticates with the password def authenticate_dn(dn, password) if dn.present? && password.present? - initialize_ldap_con(dn, password).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 end end # Get the user's dn and any attributes for them, given their login def get_user_dn(login, password) - ldap_con = nil - if self.account && self.account.include?("$login") - ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) + #ldap_con = nil + #if self.account && self.account.include?("$login") + # ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) + #else + # ldap_con = initialize_ldap_con(self.account, self.account_password) + #end + #login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) + #object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) + attrs = {} + + #search_filter = object_filter & login_filter + #if f = ldap_filter + # search_filter = search_filter & f + #end + + #ldap_con.search( :base => self.base_dn, + # :filter => search_filter, + # :attributes=> search_attributes) do |entry| + + # 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 - login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) - object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) - attrs = {} - search_filter = object_filter & login_filter - if f = ldap_filter - search_filter = search_filter & f + if self.filter.empty? + filter = self.attr_login + "=" + encode(login) + else + filter = self.filter.gsub("$login", encode(login)) end - - ldap_con.search( :base => self.base_dn, - :filter => search_filter, - :attributes=> search_attributes) do |entry| + + +# ldap_con.search( :base => self.base_dn, +# :filter => object_filter & login_filter, +# :attributes=> search_attributes) 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| if onthefly_register? attrs = get_user_attributes_from_ldap_entry(entry) @@ -160,7 +209,7 @@ class AuthSourceLdap < AuthSource end logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug? - end + } attrs end @@ -170,4 +219,12 @@ class AuthSourceLdap < AuthSource 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 diff --git a/app/views/auth_sources/_form_auth_source_ldap.html.erb b/app/views/auth_sources/_form_auth_source_ldap.html.erb index 2f6d0b0..21e89a8 100644 --- a/app/views/auth_sources/_form_auth_source_ldap.html.erb +++ b/app/views/auth_sources/_form_auth_source_ldap.html.erb @@ -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,11 +25,23 @@ :onfocus => "this.value=''; this.name='auth_source[account_password]';", :onchange => "this.name='auth_source[account_password]';" %>

+

+<%= select 'auth_source', 'require_cert', [ [l(:field_require_cert_never), 0], + [l(:field_require_cert_hard), 1], + [l(:field_require_cert_demand), 2], + [l(:field_require_cert_allow), 3], + [l(:field_require_cert_try), 4] ] %>

+

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

-

-<%= text_field 'auth_source', 'filter', :size => 60 %>

+

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

+ +

+<%= select 'auth_source', 'dereference', [ [l(:field_dereference_never), 0], + [l(:field_dereference_searching), 1], + [l(:field_dereference_finding), 2], + [l(:field_dereference_always), 3] ] %>

<%= text_field 'auth_source', 'timeout', :size => 4 %>

diff --git a/config/locales/de.yml b/config/locales/de.yml index b39b2a9..904cc34 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -281,6 +281,19 @@ de: field_port: Port field_account: Konto field_base_dn: Base DN + field_protocol_version: Protokol Version + field_filter: Filter + field_dereference: LDAP Aliase dereferenzieren + field_dereference_never: Nie (standard) + field_dereference_searching: Beim suchen + field_dereference_finding: Beim finden + field_dereference_always: Immer + field_require_cert: Zertifikat verlangen + field_require_cert_never: Nie + field_require_cert_hard: Hart + field_require_cert_demand: Anfordern + field_require_cert_allow: Erlauben + field_require_cert_try: Versuchen field_attr_login: Mitgliedsname-Attribut field_attr_firstname: Vorname-Attribut field_attr_lastname: Name-Attribut diff --git a/config/locales/en.yml b/config/locales/en.yml index ecd15a3..a7caa25 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -275,6 +275,19 @@ en: field_port: Port field_account: Account field_base_dn: Base DN + field_protocol_version: Protocol version + field_filter: Filter + field_dereference: Dereference LDAP aliases + field_dereference_never: Never (default) + field_dereference_searching: Searching + field_dereference_finding: Finding + field_dereference_always: Always + field_require_cert: Require certificate + field_require_cert_never: Never + field_require_cert_hard: Hard + field_require_cert_demand: Demand + field_require_cert_allow: Allow + field_require_cert_try: Try field_attr_login: Login attribute field_attr_firstname: Firstname attribute field_attr_lastname: Lastname attribute diff --git a/db/migrate/20110812010830_add_auth_sources_filter_deref_advtls.rb b/db/migrate/20110812010830_add_auth_sources_filter_deref_advtls.rb new file mode 100644 index 0000000..bd4c687 --- /dev/null +++ b/db/migrate/20110812010830_add_auth_sources_filter_deref_advtls.rb @@ -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