activeldap 5.2.4 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,8 @@ module ActiveLdap
30
30
  :scope,
31
31
  :sasl_options,
32
32
  :follow_referrals,
33
+ :use_paged_results,
34
+ :page_size,
33
35
  ]
34
36
 
35
37
  @@row_even = true
@@ -41,6 +43,7 @@ module ActiveLdap
41
43
  @bind_tried = false
42
44
  @entry_attributes = {}
43
45
  @follow_referrals = nil
46
+ @page_size = nil
44
47
  @configuration = configuration.dup
45
48
  @logger = @configuration.delete(:logger)
46
49
  @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
@@ -48,6 +51,7 @@ module ActiveLdap
48
51
  instance_variable_set("@#{name}", configuration[name])
49
52
  end
50
53
  @follow_referrals = true if @follow_referrals.nil?
54
+ @page_size ||= Configuration::DEFAULT_CONFIG[:page_size]
51
55
  @instrumenter = ActiveSupport::Notifications.instrumenter
52
56
  end
53
57
 
@@ -169,23 +173,39 @@ module ActiveLdap
169
173
  end
170
174
 
171
175
  def search(options={})
172
- filter = parse_filter(options[:filter]) || 'objectClass=*'
173
- attrs = options[:attributes] || []
174
- scope = ensure_scope(options[:scope] || @scope)
175
176
  base = options[:base]
177
+ base = ensure_dn_string(base)
178
+ attributes = options[:attributes] || []
179
+ attributes = attributes.to_a # just in case
176
180
  limit = options[:limit] || 0
177
181
  limit = nil if limit <= 0
182
+ use_paged_results = options[:use_paged_results]
183
+ if use_paged_results or use_paged_results.nil?
184
+ use_paged_results = supported_control.paged_results?
185
+ end
186
+ search_options = {
187
+ base: base,
188
+ scope: ensure_scope(options[:scope] || @scope),
189
+ filter: parse_filter(options[:filter]) || 'objectClass=*',
190
+ attributes: attributes,
191
+ limit: limit,
192
+ use_paged_results: use_paged_results,
193
+ page_size: options[:page_size] || @page_size,
194
+ }
178
195
 
179
- attrs = attrs.to_a # just in case
180
- base = ensure_dn_string(base)
181
196
  begin
182
197
  operation(options) do
183
- yield(base, scope, filter, attrs, limit)
198
+ yield(search_options)
184
199
  end
185
- rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax
200
+ rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax => error
186
201
  # Do nothing on failure
187
202
  @logger.info do
188
- args = [$!.class, $!.message, filter, attrs.inspect]
203
+ args = [
204
+ error.class.class,
205
+ error.message,
206
+ search_options[:filter],
207
+ search_options[:attributes].inspect,
208
+ ]
189
209
  _("Ignore error %s(%s): filter %s: attributes: %s") % args
190
210
  end
191
211
  end
@@ -46,12 +46,10 @@ module ActiveLdap
46
46
  end
47
47
 
48
48
  def search(options={}, &block)
49
- super(options) do |base, scope, filter, attrs, limit|
50
- info = {
51
- :base => base, :scope => scope_name(scope), :filter => filter,
52
- :attributes => attrs, :limit => limit,
53
- }
54
- execute(:search, info, base, scope, filter, attrs, limit, &block)
49
+ super(options) do |search_options|
50
+ scope = search_options[:scope]
51
+ info = search_options.merge(scope: scope_name(scope))
52
+ execute(:search, info, search_options, &block)
55
53
  end
56
54
  end
57
55
 
@@ -25,6 +25,8 @@ module ActiveLdap
25
25
  Context = naming.Context
26
26
  StartTlsRequest = ldap.StartTlsRequest
27
27
  Control = ldap.Control
28
+ PagedResultsControl = ldap.PagedResultsControl
29
+ PagedResultsResponseControl = ldap.PagedResultsResponseControl
28
30
 
29
31
  CommunicationException = naming.CommunicationException
30
32
  ServiceUnavailableException = naming.ServiceUnavailableException
@@ -108,23 +110,54 @@ module ActiveLdap
108
110
  bound?
109
111
  end
110
112
 
111
- def search(base, scope, filter, attrs, limit)
113
+ def search(options)
114
+ base = options[:base]
115
+ scope = options[:scope]
116
+ filter = options[:filter]
117
+ attributes = options[:attributes]
118
+ limit = options[:limit]
119
+ use_paged_results = options[:use_paged_results]
120
+ page_size = options[:page_size]
121
+
112
122
  controls = SearchControls.new
113
123
  controls.search_scope = scope
114
124
 
115
125
  controls.count_limit = limit if limit
116
- unless attrs.blank?
117
- controls.returning_attributes = attrs.to_java(:string)
126
+ unless attributes.blank?
127
+ controls.returning_attributes = attributes.to_java(:string)
118
128
  end
119
129
 
120
- @context.search(base, filter, controls).each do |result|
121
- attributes = {}
122
- result.attributes.get_all.each do |attribute|
123
- attributes[attribute.get_id] = attribute.get_all.collect do |value|
124
- value.is_a?(String) ? value : String.from_java_bytes(value)
125
- end
130
+ page_cookie = nil
131
+ if use_paged_results
132
+ # https://devdocs.io/openjdk~8/javax/naming/ldap/pagedresultscontrol
133
+ @context.set_request_controls([build_paged_results_control(page_size)])
134
+ else
135
+ @context.set_request_controls([])
136
+ end
137
+
138
+ escaped_base = escape_dn(base)
139
+ loop do
140
+ @context.search(escaped_base, filter, controls).each do |search_result|
141
+ yield(build_raw_search_result(search_result))
142
+ end
143
+
144
+ break unless use_paged_results
145
+
146
+ # Find the paged search cookie
147
+ response_controls = @context.get_response_controls
148
+ break unless response_controls
149
+ response_controls.each do |response_control|
150
+ next unless response_control.is_a?(PagedResultsResponseControl)
151
+ page_cookie = response_control.get_cookie
152
+ break
126
153
  end
127
- yield([result.name_in_namespace, attributes])
154
+
155
+ break unless page_cookie
156
+
157
+ # Set paged results control so we can keep getting results.
158
+ paged_results_control =
159
+ build_paged_results_control(page_size, page_cookie)
160
+ @context.set_request_controls([paged_results_control])
128
161
  end
129
162
  end
130
163
 
@@ -133,25 +166,35 @@ module ActiveLdap
133
166
  records.each do |record|
134
167
  attributes.put(record.to_java_attribute)
135
168
  end
136
- @context.create_subcontext(dn, attributes)
169
+ escaped_dn = escape_dn(dn)
170
+ @context.set_request_controls([])
171
+ @context.create_subcontext(escaped_dn, attributes)
137
172
  end
138
173
 
139
174
  def modify(dn, records)
175
+ escaped_dn = escape_dn(dn)
140
176
  items = records.collect(&:to_java_modification_item)
141
- @context.modify_attributes(dn, items.to_java(ModificationItem))
177
+ @context.set_request_controls([])
178
+ @context.modify_attributes(escaped_dn, items.to_java(ModificationItem))
142
179
  end
143
180
 
144
181
  def modify_rdn(dn, new_rdn, delete_old_rdn)
182
+ escaped_dn = escape_dn(dn)
145
183
  # should use mutex
146
184
  delete_rdn_key = "java.naming.ldap.deleteRDN"
147
- @context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
148
- @context.rename(dn, new_rdn)
149
- ensure
150
- @context.remove_from_environment(delete_rdn_key)
185
+ @context.set_request_controls([])
186
+ begin
187
+ @context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
188
+ @context.rename(escaped_dn, new_rdn)
189
+ ensure
190
+ @context.remove_from_environment(delete_rdn_key)
191
+ end
151
192
  end
152
193
 
153
194
  def delete(dn)
154
- @context.destroy_subcontext(dn)
195
+ escaped_dn = escape_dn(dn)
196
+ @context.set_request_controls([])
197
+ @context.destroy_subcontext(escaped_dn)
155
198
  end
156
199
 
157
200
  private
@@ -185,6 +228,51 @@ module ActiveLdap
185
228
  protocol = @method == :ssl ? "ldaps" : "ldap"
186
229
  "#{protocol}://#{@host}:#{@port}/"
187
230
  end
231
+
232
+ def escape_dn(dn)
233
+ parsed_dn = nil
234
+ begin
235
+ parsed_dn = DN.parse(dn)
236
+ rescue DistinguishedNameInvalid
237
+ return dn
238
+ end
239
+
240
+ escaped_rdns = parsed_dn.rdns.collect do |rdn|
241
+ escaped_rdn_strings = rdn.collect do |key, value|
242
+ escaped_value = DN.escape_value(value)
243
+ # We may need to escape the followings too:
244
+ # * ,
245
+ # * =
246
+ # * +
247
+ # * <
248
+ # * >
249
+ # * #
250
+ # * ;
251
+ #
252
+ # See javax.naming.ldap.Rdn.unescapeValue()
253
+ escaped_value = escaped_value.gsub(/\\\\/) do
254
+ "\\5C"
255
+ end
256
+ "#{key}=#{escaped_value}"
257
+ end
258
+ escaped_rdn_strings.join("+")
259
+ end
260
+ escaped_rdns.join(",")
261
+ end
262
+
263
+ def build_paged_results_control(page_size, page_cookie=nil)
264
+ PagedResultsControl.new(page_size, page_cookie, Control::CRITICAL)
265
+ end
266
+
267
+ def build_raw_search_result(search_result)
268
+ attributes = {}
269
+ search_result.attributes.get_all.each do |attribute|
270
+ attributes[attribute.get_id] = attribute.get_all.collect do |value|
271
+ value.is_a?(String) ? value : String.from_java_bytes(value)
272
+ end
273
+ end
274
+ [search_result.name_in_namespace, attributes]
275
+ end
188
276
  end
189
277
  end
190
278
  end
@@ -113,25 +113,11 @@ module ActiveLdap
113
113
  end
114
114
 
115
115
  def search(options={})
116
- super(options) do |base, scope, filter, attrs, limit|
116
+ super(options) do |search_options|
117
117
  begin
118
- use_paged_results = options[:use_paged_results]
119
- if use_paged_results or use_paged_results.nil?
120
- use_paged_results = supported_control.paged_results?
121
- end
122
- info = {
123
- :base => base, :scope => scope_name(scope),
124
- :filter => filter, :attributes => attrs, :limit => limit,
125
- }
126
- options = {
127
- :base => base,
128
- :scope => scope,
129
- :filter => filter,
130
- :attributes => attrs,
131
- :limit => limit,
132
- :use_paged_results => use_paged_results
133
- }
134
- execute(:search_full, info, options) do |entry|
118
+ scope = search_options[:scope]
119
+ info = search_options.merge(scope: scope_name(scope))
120
+ execute(:search_full, info, search_options) do |entry|
135
121
  attributes = {}
136
122
  entry.attrs.each do |attr|
137
123
  value = entry.vals(attr)
@@ -142,7 +128,10 @@ module ActiveLdap
142
128
  rescue RuntimeError
143
129
  if $!.message == "no result returned by search"
144
130
  @logger.debug do
145
- args = [filter, attrs.inspect]
131
+ args = [
132
+ search_options[:filter],
133
+ search_options[:attributes].inspect,
134
+ ]
146
135
  _("No matches: filter: %s: attributes: %s") % args
147
136
  end
148
137
  else
@@ -65,12 +65,28 @@ module LDAP
65
65
  attributes = options[:attributes]
66
66
  limit = options[:limit] || 0
67
67
  use_paged_results = options[:use_paged_results]
68
+ page_size = options[:page_size]
68
69
  if @@have_search_ext
69
70
  if use_paged_results
70
- paged_search(base, scope, filter, attributes, limit, &block)
71
+ paged_search(base,
72
+ scope,
73
+ filter,
74
+ attributes,
75
+ limit,
76
+ page_size,
77
+ &block)
71
78
  else
72
- search_ext(base, scope, filter, attributes,
73
- false, nil, nil, 0, 0, limit, &block)
79
+ search_ext(base,
80
+ scope,
81
+ filter,
82
+ attributes,
83
+ false,
84
+ nil,
85
+ nil,
86
+ 0,
87
+ 0,
88
+ limit,
89
+ &block)
74
90
  end
75
91
  else
76
92
  i = 0
@@ -120,27 +136,30 @@ module LDAP
120
136
  end
121
137
  end
122
138
 
123
- def paged_search(base, scope, filter, attributes, limit, &block)
124
- # work around a bug with openldap
125
- page_size = 126
139
+ def paged_search(base, scope, filter, attributes, limit, page_size, &block)
126
140
  cookie = ""
127
141
  critical = true
128
-
129
142
  loop do
130
143
  ber_string = LDAP::Control.encode(page_size, cookie)
131
144
  control = LDAP::Control.new(LDAP::LDAP_CONTROL_PAGEDRESULTS,
132
145
  ber_string,
133
146
  critical)
134
-
135
- search_ext(base, scope, filter, attributes,
136
- false, [control], nil, 0, 0, limit, &block)
147
+ search_ext(base,
148
+ scope,
149
+ filter,
150
+ attributes,
151
+ false,
152
+ [control],
153
+ nil,
154
+ 0,
155
+ 0,
156
+ limit,
157
+ &block)
137
158
 
138
159
  control = find_paged_results_control(@controls)
139
160
  break if control.nil?
140
- returned_size, cookie = control.decode
141
- returned_size = returned_size.to_i
142
- page_size = returned_size if returned_size > 0
143
161
 
162
+ _estimated_result_set_size, cookie = control.decode
144
163
  break if cookie.empty?
145
164
  end
146
165
  end
@@ -69,25 +69,16 @@ module ActiveLdap
69
69
  end
70
70
 
71
71
  def search(options={})
72
- use_paged_results = options[:use_paged_results]
73
- if use_paged_results or use_paged_results.nil?
74
- paged_results_supported = supported_control.paged_results?
75
- else
76
- paged_results_supported = false
77
- end
78
- super(options) do |base, scope, filter, attrs, limit|
72
+ super(options) do |search_options|
73
+ scope = search_options[:scope]
74
+ info = search_options.merge(scope: scope_name(scope))
79
75
  args = {
80
- :base => base,
81
- :scope => scope,
82
- :filter => filter,
83
- :attributes => attrs,
84
- :size => limit,
85
- :paged_searches_supported => paged_results_supported,
86
- }
87
- info = {
88
- :base => base, :scope => scope_name(scope),
89
- :filter => filter, :attributes => attrs, :limit => limit,
90
- :paged_results_supported => paged_results_supported,
76
+ base: search_options[:base],
77
+ scope: scope,
78
+ filter: search_options[:filter],
79
+ attributes: search_options[:attributes],
80
+ size: search_options[:limit],
81
+ paged_searcheds_supported: search_options[:paged_results_supported],
91
82
  }
92
83
  execute(:search, info, args) do |entry|
93
84
  attributes = {}
@@ -48,6 +48,15 @@ module ActiveLdap
48
48
  DEFAULT_CONFIG[:retry_on_timeout] = true
49
49
  DEFAULT_CONFIG[:follow_referrals] = true
50
50
 
51
+ # 500 is the default size limit value of OpenLDAP 2.4:
52
+ # https://openldap.org/doc/admin24/limits.html#Global%20Limits
53
+ #
54
+ # We may change this when we find LDAP server that its the default
55
+ # size limit is smaller than 500.
56
+ DEFAULT_CONFIG[:page_size] = 500
57
+ # Whether using paged results if available.
58
+ DEFAULT_CONFIG[:use_paged_results] = true
59
+
51
60
  DEFAULT_CONFIG[:logger] = nil
52
61
 
53
62
  module ClassMethods
@@ -103,8 +112,22 @@ module ActiveLdap
103
112
  end
104
113
  end
105
114
 
115
+ def parent_configuration(target)
116
+ if target.is_a?(Base)
117
+ target = target.class
118
+ else
119
+ target = target.superclass
120
+ end
121
+ while target <= Base
122
+ config = defined_configurations[target.active_connection_key]
123
+ return config if config
124
+ target = target.superclass
125
+ end
126
+ default_configuration
127
+ end
128
+
106
129
  def merge_configuration(user_configuration, target=self)
107
- configuration = default_configuration
130
+ configuration = parent_configuration(target).dup
108
131
  prepare_configuration(user_configuration).each do |key, value|
109
132
  case key
110
133
  when :base
@@ -165,11 +165,11 @@ module ActiveLdap
165
165
  connection.schema
166
166
  end
167
167
 
168
- private
169
168
  def active_connection_key(k=self)
170
169
  k.name.blank? ? k.object_id : k.name
171
170
  end
172
171
 
172
+ private
173
173
  def determine_active_connection_name
174
174
  key = active_connection_key
175
175
  if active_connections[key] or configuration(key)