##// END OF EJS Templates
LDAP account creation fails when first name/last name contain non ASCII (#21453)....
Jean-Philippe Lang -
r14642:624cdea92a6d
parent child
Show More
@@ -1,203 +1,204
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'net/ldap'
18 require 'net/ldap'
19 require 'net/ldap/dn'
19 require 'net/ldap/dn'
20 require 'timeout'
20 require 'timeout'
21
21
22 class AuthSourceLdap < AuthSource
22 class AuthSourceLdap < AuthSource
23 NETWORK_EXCEPTIONS = [
23 NETWORK_EXCEPTIONS = [
24 Net::LDAP::LdapError,
24 Net::LDAP::LdapError,
25 Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::ECONNRESET,
25 Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::ECONNRESET,
26 Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
26 Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
27 SocketError
27 SocketError
28 ]
28 ]
29
29
30 validates_presence_of :host, :port, :attr_login
30 validates_presence_of :host, :port, :attr_login
31 validates_length_of :name, :host, :maximum => 60, :allow_nil => true
31 validates_length_of :name, :host, :maximum => 60, :allow_nil => true
32 validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_blank => true
32 validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_blank => true
33 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
33 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
34 validates_numericality_of :port, :only_integer => true
34 validates_numericality_of :port, :only_integer => true
35 validates_numericality_of :timeout, :only_integer => true, :allow_blank => true
35 validates_numericality_of :timeout, :only_integer => true, :allow_blank => true
36 validate :validate_filter
36 validate :validate_filter
37
37
38 before_validation :strip_ldap_attributes
38 before_validation :strip_ldap_attributes
39
39
40 def initialize(attributes=nil, *args)
40 def initialize(attributes=nil, *args)
41 super
41 super
42 self.port = 389 if self.port == 0
42 self.port = 389 if self.port == 0
43 end
43 end
44
44
45 def authenticate(login, password)
45 def authenticate(login, password)
46 return nil if login.blank? || password.blank?
46 return nil if login.blank? || password.blank?
47
47
48 with_timeout do
48 with_timeout do
49 attrs = get_user_dn(login, password)
49 attrs = get_user_dn(login, password)
50 if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
50 if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
51 logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
51 logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
52 return attrs.except(:dn)
52 return attrs.except(:dn)
53 end
53 end
54 end
54 end
55 rescue *NETWORK_EXCEPTIONS => e
55 rescue *NETWORK_EXCEPTIONS => e
56 raise AuthSourceException.new(e.message)
56 raise AuthSourceException.new(e.message)
57 end
57 end
58
58
59 # test the connection to the LDAP
59 # test the connection to the LDAP
60 def test_connection
60 def test_connection
61 with_timeout do
61 with_timeout do
62 ldap_con = initialize_ldap_con(self.account, self.account_password)
62 ldap_con = initialize_ldap_con(self.account, self.account_password)
63 ldap_con.open { }
63 ldap_con.open { }
64 end
64 end
65 rescue *NETWORK_EXCEPTIONS => e
65 rescue *NETWORK_EXCEPTIONS => e
66 raise AuthSourceException.new(e.message)
66 raise AuthSourceException.new(e.message)
67 end
67 end
68
68
69 def auth_method_name
69 def auth_method_name
70 "LDAP"
70 "LDAP"
71 end
71 end
72
72
73 # Returns true if this source can be searched for users
73 # Returns true if this source can be searched for users
74 def searchable?
74 def searchable?
75 !account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")}
75 !account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")}
76 end
76 end
77
77
78 # Searches the source for users and returns an array of results
78 # Searches the source for users and returns an array of results
79 def search(q)
79 def search(q)
80 q = q.to_s.strip
80 q = q.to_s.strip
81 return [] unless searchable? && q.present?
81 return [] unless searchable? && q.present?
82
82
83 results = []
83 results = []
84 search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q)
84 search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q)
85 ldap_con = initialize_ldap_con(self.account, self.account_password)
85 ldap_con = initialize_ldap_con(self.account, self.account_password)
86 ldap_con.search(:base => self.base_dn,
86 ldap_con.search(:base => self.base_dn,
87 :filter => search_filter,
87 :filter => search_filter,
88 :attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail],
88 :attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail],
89 :size => 10) do |entry|
89 :size => 10) do |entry|
90 attrs = get_user_attributes_from_ldap_entry(entry)
90 attrs = get_user_attributes_from_ldap_entry(entry)
91 attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login)
91 attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login)
92 results << attrs
92 results << attrs
93 end
93 end
94 results
94 results
95 rescue *NETWORK_EXCEPTIONS => e
95 rescue *NETWORK_EXCEPTIONS => e
96 raise AuthSourceException.new(e.message)
96 raise AuthSourceException.new(e.message)
97 end
97 end
98
98
99 private
99 private
100
100
101 def with_timeout(&block)
101 def with_timeout(&block)
102 timeout = self.timeout
102 timeout = self.timeout
103 timeout = 20 unless timeout && timeout > 0
103 timeout = 20 unless timeout && timeout > 0
104 Timeout.timeout(timeout) do
104 Timeout.timeout(timeout) do
105 return yield
105 return yield
106 end
106 end
107 rescue Timeout::Error => e
107 rescue Timeout::Error => e
108 raise AuthSourceTimeoutException.new(e.message)
108 raise AuthSourceTimeoutException.new(e.message)
109 end
109 end
110
110
111 def ldap_filter
111 def ldap_filter
112 if filter.present?
112 if filter.present?
113 Net::LDAP::Filter.construct(filter)
113 Net::LDAP::Filter.construct(filter)
114 end
114 end
115 rescue Net::LDAP::LdapError, Net::LDAP::FilterSyntaxInvalidError
115 rescue Net::LDAP::LdapError, Net::LDAP::FilterSyntaxInvalidError
116 nil
116 nil
117 end
117 end
118
118
119 def base_filter
119 def base_filter
120 filter = Net::LDAP::Filter.eq("objectClass", "*")
120 filter = Net::LDAP::Filter.eq("objectClass", "*")
121 if f = ldap_filter
121 if f = ldap_filter
122 filter = filter & f
122 filter = filter & f
123 end
123 end
124 filter
124 filter
125 end
125 end
126
126
127 def validate_filter
127 def validate_filter
128 if filter.present? && ldap_filter.nil?
128 if filter.present? && ldap_filter.nil?
129 errors.add(:filter, :invalid)
129 errors.add(:filter, :invalid)
130 end
130 end
131 end
131 end
132
132
133 def strip_ldap_attributes
133 def strip_ldap_attributes
134 [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
134 [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
135 write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
135 write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
136 end
136 end
137 end
137 end
138
138
139 def initialize_ldap_con(ldap_user, ldap_password)
139 def initialize_ldap_con(ldap_user, ldap_password)
140 options = { :host => self.host,
140 options = { :host => self.host,
141 :port => self.port,
141 :port => self.port,
142 :encryption => (self.tls ? :simple_tls : nil)
142 :encryption => (self.tls ? :simple_tls : nil)
143 }
143 }
144 options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank?
144 options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank?
145 Net::LDAP.new options
145 Net::LDAP.new options
146 end
146 end
147
147
148 def get_user_attributes_from_ldap_entry(entry)
148 def get_user_attributes_from_ldap_entry(entry)
149 {
149 {
150 :dn => entry.dn,
150 :dn => entry.dn,
151 :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
151 :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
152 :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
152 :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
153 :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
153 :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
154 :auth_source_id => self.id
154 :auth_source_id => self.id
155 }
155 }
156 end
156 end
157
157
158 # Return the attributes needed for the LDAP search. It will only
158 # Return the attributes needed for the LDAP search. It will only
159 # include the user attributes if on-the-fly registration is enabled
159 # include the user attributes if on-the-fly registration is enabled
160 def search_attributes
160 def search_attributes
161 if onthefly_register?
161 if onthefly_register?
162 ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]
162 ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]
163 else
163 else
164 ['dn']
164 ['dn']
165 end
165 end
166 end
166 end
167
167
168 # Check if a DN (user record) authenticates with the password
168 # Check if a DN (user record) authenticates with the password
169 def authenticate_dn(dn, password)
169 def authenticate_dn(dn, password)
170 if dn.present? && password.present?
170 if dn.present? && password.present?
171 initialize_ldap_con(dn, password).bind
171 initialize_ldap_con(dn, password).bind
172 end
172 end
173 end
173 end
174
174
175 # Get the user's dn and any attributes for them, given their login
175 # Get the user's dn and any attributes for them, given their login
176 def get_user_dn(login, password)
176 def get_user_dn(login, password)
177 ldap_con = nil
177 ldap_con = nil
178 if self.account && self.account.include?("$login")
178 if self.account && self.account.include?("$login")
179 ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password)
179 ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password)
180 else
180 else
181 ldap_con = initialize_ldap_con(self.account, self.account_password)
181 ldap_con = initialize_ldap_con(self.account, self.account_password)
182 end
182 end
183 attrs = {}
183 attrs = {}
184 search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login)
184 search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login)
185 ldap_con.search( :base => self.base_dn,
185 ldap_con.search( :base => self.base_dn,
186 :filter => search_filter,
186 :filter => search_filter,
187 :attributes=> search_attributes) do |entry|
187 :attributes=> search_attributes) do |entry|
188 if onthefly_register?
188 if onthefly_register?
189 attrs = get_user_attributes_from_ldap_entry(entry)
189 attrs = get_user_attributes_from_ldap_entry(entry)
190 else
190 else
191 attrs = {:dn => entry.dn}
191 attrs = {:dn => entry.dn}
192 end
192 end
193 logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug?
193 logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug?
194 end
194 end
195 attrs
195 attrs
196 end
196 end
197
197
198 def self.get_attr(entry, attr_name)
198 def self.get_attr(entry, attr_name)
199 if !attr_name.blank?
199 if !attr_name.blank?
200 entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
200 value = entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
201 value.force_encoding('UTF-8')
201 end
202 end
202 end
203 end
203 end
204 end
General Comments 0
You need to be logged in to leave comments. Login now