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