activeldap 5.2.3 → 6.0.3

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.
@@ -1,4 +1,3 @@
1
- require "rubygems"
2
1
  require "active_model"
3
2
  require "active_support/core_ext"
4
3
 
@@ -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 = limit != 1 && 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
@@ -662,8 +682,7 @@ module ActiveLdap
662
682
  :scope => :base,
663
683
  :attributes => attrs,
664
684
  :limit => 1,
665
- :try_reconnect => try_reconnect,
666
- :use_paged_results => false) do |dn, attributes|
685
+ :try_reconnect => try_reconnect) do |dn, attributes|
667
686
  found_attributes = attributes
668
687
  end
669
688
  found_attributes
@@ -22,9 +22,23 @@ module ActiveLdap
22
22
  super do |host, port, method|
23
23
  uri = construct_uri(host, port, method == :ssl)
24
24
  with_start_tls = method == :start_tls
25
- info = {:uri => uri, :with_start_tls => with_start_tls}
26
- [log("connect", info) {JndiConnection.new(host, port, method, @timeout)},
27
- uri, with_start_tls]
25
+ follow_referrals = follow_referrals?(options)
26
+ info = {
27
+ :uri => uri,
28
+ :with_start_tls => with_start_tls,
29
+ :follow_referrals => follow_referrals,
30
+ }
31
+ [
32
+ log("connect", info) {
33
+ JndiConnection.new(host,
34
+ port,
35
+ method,
36
+ @timeout,
37
+ follow_referrals)
38
+ },
39
+ uri,
40
+ with_start_tls,
41
+ ]
28
42
  end
29
43
  end
30
44
 
@@ -46,12 +60,10 @@ module ActiveLdap
46
60
  end
47
61
 
48
62
  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)
63
+ super(options) do |search_options|
64
+ scope = search_options[:scope]
65
+ info = search_options.merge(scope: scope_name(scope))
66
+ execute(:search, info, search_options, &block)
55
67
  end
56
68
  end
57
69
 
@@ -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
@@ -73,13 +75,14 @@ module ActiveLdap
73
75
  end
74
76
  end
75
77
 
76
- def initialize(host, port, method, timeout)
78
+ def initialize(host, port, method, timeout, follow_referrals)
77
79
  @host = host
78
80
  @port = port
79
81
  @method = method
80
82
  @timeout = timeout
81
83
  @context = nil
82
84
  @tls = nil
85
+ @follow_referrals = follow_referrals
83
86
  end
84
87
 
85
88
  def unbind
@@ -108,23 +111,55 @@ module ActiveLdap
108
111
  bound?
109
112
  end
110
113
 
111
- def search(base, scope, filter, attrs, limit)
114
+ def search(options)
115
+ base = options[:base]
116
+ scope = options[:scope]
117
+ filter = options[:filter]
118
+ attributes = options[:attributes]
119
+ limit = options[:limit]
120
+ use_paged_results = options[:use_paged_results]
121
+ page_size = options[:page_size]
122
+
112
123
  controls = SearchControls.new
113
124
  controls.search_scope = scope
114
125
 
115
126
  controls.count_limit = limit if limit
116
- unless attrs.blank?
117
- controls.returning_attributes = attrs.to_java(:string)
127
+ unless attributes.blank?
128
+ controls.returning_attributes = attributes.to_java(:string)
129
+ end
130
+
131
+ page_cookie = nil
132
+ if use_paged_results
133
+ # https://devdocs.io/openjdk~8/javax/naming/ldap/pagedresultscontrol
134
+ @context.set_request_controls([build_paged_results_control(page_size)])
135
+ else
136
+ @context.set_request_controls([])
118
137
  end
119
138
 
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
139
+ escaped_base = escape_dn(base)
140
+
141
+ loop do
142
+ @context.search(escaped_base, filter, controls).each do |search_result|
143
+ yield(build_raw_search_result(search_result))
126
144
  end
127
- yield([result.name_in_namespace, attributes])
145
+
146
+ break unless use_paged_results
147
+
148
+ # Find the paged search cookie
149
+ response_controls = @context.get_response_controls
150
+ break unless response_controls
151
+ response_controls.each do |response_control|
152
+ next unless response_control.is_a?(PagedResultsResponseControl)
153
+ page_cookie = response_control.get_cookie
154
+ break
155
+ end
156
+
157
+ break unless page_cookie
158
+
159
+ # Set paged results control so we can keep getting results.
160
+ paged_results_control =
161
+ build_paged_results_control(page_size, page_cookie)
162
+ @context.set_request_controls([paged_results_control])
128
163
  end
129
164
  end
130
165
 
@@ -133,25 +168,32 @@ module ActiveLdap
133
168
  records.each do |record|
134
169
  attributes.put(record.to_java_attribute)
135
170
  end
136
- @context.create_subcontext(dn, attributes)
171
+ @context.set_request_controls([])
172
+ @context.create_subcontext(escape_dn(dn), attributes)
137
173
  end
138
174
 
139
175
  def modify(dn, records)
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(escape_dn(dn), items.to_java(ModificationItem))
142
179
  end
143
180
 
144
181
  def modify_rdn(dn, new_rdn, delete_old_rdn)
145
182
  # should use mutex
146
183
  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)
184
+ @context.set_request_controls([])
185
+ begin
186
+ @context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
187
+ @context.rename(escape_dn(dn), escape_dn(new_rdn))
188
+ ensure
189
+ @context.remove_from_environment(delete_rdn_key)
190
+ end
151
191
  end
152
192
 
153
193
  def delete(dn)
154
- @context.destroy_subcontext(dn)
194
+ escaped_dn = escape_dn(dn)
195
+ @context.set_request_controls([])
196
+ @context.destroy_subcontext(escaped_dn)
155
197
  end
156
198
 
157
199
  private
@@ -162,9 +204,10 @@ module ActiveLdap
162
204
  Context::PROVIDER_URL => ldap_uri,
163
205
  'com.sun.jndi.ldap.connect.timeout' => (@timeout * 1000).to_i.to_s,
164
206
  'com.sun.jndi.ldap.read.timeout' => (@timeout * 1000).to_i.to_s,
207
+ 'java.naming.ldap.derefAliases' => 'never',
208
+ 'java.naming.referral' => @follow_referrals ? 'follow' : 'ignore',
165
209
  }
166
- environment = HashTable.new(environment)
167
- context = InitialLdapContext.new(environment, nil)
210
+ context = InitialLdapContext.new(HashTable.new(environment), nil)
168
211
  if @method == :start_tls
169
212
  @tls = context.extended_operation(StartTlsRequest.new)
170
213
  @tls.negotiate
@@ -185,6 +228,26 @@ module ActiveLdap
185
228
  protocol = @method == :ssl ? "ldaps" : "ldap"
186
229
  "#{protocol}://#{@host}:#{@port}/"
187
230
  end
231
+
232
+ def escape_dn(dn)
233
+ javax.naming.ldap.LdapName.new(dn)
234
+ rescue Java::JavaLang::IllegalArgumentException, Java::JavaxNaming::InvalidNameException
235
+ dn
236
+ end
237
+
238
+ def build_paged_results_control(page_size, page_cookie=nil)
239
+ PagedResultsControl.new(page_size, page_cookie, Control::CRITICAL)
240
+ end
241
+
242
+ def build_raw_search_result(search_result)
243
+ attributes = {}
244
+ search_result.attributes.get_all.each do |attribute|
245
+ attributes[attribute.get_id] = attribute.get_all.collect do |value|
246
+ value.is_a?(String) ? value : String.from_java_bytes(value)
247
+ end
248
+ end
249
+ [search_result.name_in_namespace, attributes]
250
+ end
188
251
  end
189
252
  end
190
253
  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
@@ -220,15 +209,17 @@ module ActiveLdap
220
209
  def prepare_connection(options={})
221
210
  operation(options) do
222
211
  @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
223
- unless follow_referrals?(options)
224
- @connection.set_option(LDAP::LDAP_OPT_REFERRALS, 0)
225
- end
212
+ @ldap_follow_referrals = follow_referrals?(options) ? 1 : 0
213
+ @connection.set_option(LDAP::LDAP_OPT_REFERRALS,
214
+ @ldap_follow_referrals)
226
215
  end
227
216
  end
228
217
 
229
218
  def execute(method, info=nil, *args, &block)
230
219
  begin
231
220
  name = (info || {}).delete(:name) || method
221
+ @connection.set_option(LDAP::LDAP_OPT_REFERRALS,
222
+ @ldap_follow_referrals)
232
223
  log(name, info) {@connection.send(method, *args, &block)}
233
224
  rescue LDAP::ResultError
234
225
  @connection.assert_error_code
@@ -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 = {}