treequel 1.0.1 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/ChangeLog +176 -14
  2. data/LICENSE +1 -1
  3. data/Rakefile +61 -45
  4. data/Rakefile.local +20 -0
  5. data/bin/treequel +502 -269
  6. data/examples/ldap-rack-auth.rb +2 -0
  7. data/lib/treequel.rb +221 -18
  8. data/lib/treequel/branch.rb +410 -201
  9. data/lib/treequel/branchcollection.rb +25 -13
  10. data/lib/treequel/branchset.rb +42 -40
  11. data/lib/treequel/constants.rb +233 -3
  12. data/lib/treequel/control.rb +95 -0
  13. data/lib/treequel/controls/contentsync.rb +138 -0
  14. data/lib/treequel/controls/pagedresults.rb +162 -0
  15. data/lib/treequel/controls/sortedresults.rb +216 -0
  16. data/lib/treequel/directory.rb +212 -65
  17. data/lib/treequel/exceptions.rb +11 -12
  18. data/lib/treequel/filter.rb +1 -12
  19. data/lib/treequel/mixins.rb +83 -47
  20. data/lib/treequel/monkeypatches.rb +29 -0
  21. data/lib/treequel/schema.rb +23 -19
  22. data/lib/treequel/schema/attributetype.rb +33 -3
  23. data/lib/treequel/schema/ldapsyntax.rb +0 -11
  24. data/lib/treequel/schema/matchingrule.rb +0 -11
  25. data/lib/treequel/schema/matchingruleuse.rb +0 -11
  26. data/lib/treequel/schema/objectclass.rb +36 -10
  27. data/lib/treequel/schema/table.rb +159 -0
  28. data/lib/treequel/sequel_integration.rb +7 -7
  29. data/lib/treequel/utils.rb +4 -66
  30. data/rake/documentation.rb +89 -0
  31. data/rake/helpers.rb +375 -307
  32. data/rake/hg.rb +16 -2
  33. data/rake/manual.rb +11 -6
  34. data/rake/packaging.rb +20 -35
  35. data/rake/publishing.rb +22 -62
  36. data/spec/lib/constants.rb +20 -0
  37. data/spec/lib/control_behavior.rb +44 -0
  38. data/spec/lib/matchers.rb +51 -0
  39. data/spec/treequel/branch_spec.rb +88 -29
  40. data/spec/treequel/branchcollection_spec.rb +24 -1
  41. data/spec/treequel/branchset_spec.rb +123 -51
  42. data/spec/treequel/control_spec.rb +48 -0
  43. data/spec/treequel/controls/contentsync_spec.rb +38 -0
  44. data/spec/treequel/controls/pagedresults_spec.rb +138 -0
  45. data/spec/treequel/controls/sortedresults_spec.rb +171 -0
  46. data/spec/treequel/directory_spec.rb +186 -16
  47. data/spec/treequel/mixins_spec.rb +42 -3
  48. data/spec/treequel/schema/attributetype_spec.rb +22 -20
  49. data/spec/treequel/schema/objectclass_spec.rb +67 -46
  50. data/spec/treequel/schema/table_spec.rb +134 -0
  51. data/spec/treequel_spec.rb +277 -15
  52. metadata +89 -108
  53. data/bin/treequel.orig +0 -963
  54. data/examples/ldap-monitor.rb +0 -143
  55. data/examples/ldap-monitor/public/css/master.css +0 -328
  56. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  57. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  58. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  59. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  60. data/examples/ldap-monitor/public/images/plug.png +0 -0
  61. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  62. data/examples/ldap-monitor/public/images/tick.png +0 -0
  63. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  64. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  65. data/examples/ldap-monitor/views/backends.erb +0 -41
  66. data/examples/ldap-monitor/views/connections.erb +0 -74
  67. data/examples/ldap-monitor/views/databases.erb +0 -39
  68. data/examples/ldap-monitor/views/dump_subsystem.erb +0 -14
  69. data/examples/ldap-monitor/views/index.erb +0 -14
  70. data/examples/ldap-monitor/views/layout.erb +0 -35
  71. data/examples/ldap-monitor/views/listeners.erb +0 -30
  72. data/rake/rdoc.rb +0 -30
  73. data/rake/win32.rb +0 -190
@@ -13,18 +13,6 @@ require 'treequel/constants'
13
13
 
14
14
  # The object in Treequel that represents a connection to a directory, the
15
15
  # binding to that directory, and the base from which all DNs start.
16
- #
17
- # == Authors
18
- #
19
- # * Michael Granger <ged@FaerieMUD.org>
20
- # * Mahlon E. Smith <mahlon@martini.nu>
21
- #
22
- # :include: LICENSE
23
- #
24
- #--
25
- #
26
- # Please see the file LICENSE in the base directory for licensing details.
27
- #
28
16
  class Treequel::Directory
29
17
  include Treequel::Loggable,
30
18
  Treequel::Constants,
@@ -99,33 +87,37 @@ class Treequel::Directory
99
87
 
100
88
  ### Create a new Treequel::Directory with the given +options+. Options is a hash with one
101
89
  ### or more of the following key-value pairs:
102
- ###
103
- ### [:host]
104
- ### The LDAP host to connect to.
105
- ### [:port]
106
- ### The port to connect to.
107
- ### [:connect_type]
108
- ### The type of connection to establish. Must be one of +:plain+, +:tls+, or +:ssl+.
109
- ### [:base_dn]
110
- ### The base DN of the directory.
111
- ### [:bind_dn]
112
- ### The DN of the user to bind as.
113
- ### [:pass]
114
- ### The password to use when binding.
90
+ ###
91
+ ### @param [Hash] options the connection options
92
+ ### @option options [String] :host ('localhost')
93
+ ### The LDAP host to connect to
94
+ ### @option options [Fixnum] :port (LDAP::LDAP_PORT)
95
+ ### The port number to connect to
96
+ ### @option options [Symbol] :connect_type (:tls)
97
+ ### The type of connection to establish; :tls, :ssl, or :plain.
98
+ ### @option options [String] :base_dn (nil)
99
+ ### The base DN of the directory; defaults to the first naming context of
100
+ ### the directory's root DSE.
101
+ ### @option options [String] :bind_dn (nil)
102
+ ### The DN of the user to bind as; if unset, binds anonymously.
103
+ ### @option options [String] :pass (nil)
104
+ ### The password to use when binding.
115
105
  def initialize( options={} )
116
- options = DEFAULT_OPTIONS.merge( options )
106
+ options = DEFAULT_OPTIONS.merge( options )
117
107
 
118
- @host = options[:host]
119
- @port = options[:port]
120
- @connect_type = options[:connect_type]
108
+ @host = options[:host]
109
+ @port = options[:port]
110
+ @connect_type = options[:connect_type]
121
111
 
122
- @conn = nil
123
- @bound_as = nil
112
+ @conn = nil
113
+ @bound_user = nil
124
114
 
125
- @base_dn = options[:base_dn] || self.get_default_base_dn
126
- @syntax_mapping = DEFAULT_SYNTAX_MAPPING.dup
115
+ @base_dn = options[:base_dn] || self.get_default_base_dn
127
116
 
128
- @base = nil
117
+ @base = nil
118
+
119
+ @syntax_mapping = DEFAULT_SYNTAX_MAPPING.dup
120
+ @registered_controls = []
129
121
 
130
122
  # Immediately bind if credentials are passed to the initializer.
131
123
  if ( options[:bind_dn] && options[:pass] )
@@ -141,38 +133,57 @@ class Treequel::Directory
141
133
  # Delegate some methods to the #base Branch.
142
134
  def_method_delegators :base, *DELEGATED_BRANCH_METHODS
143
135
 
136
+ # Delegate some methods to the connection via the #conn method
137
+ def_method_delegators :conn, :controls, :referrals
138
+
139
+
144
140
  # The host to connect to.
141
+ # @return [String]
145
142
  attr_accessor :host
146
143
 
147
144
  # The port to connect to.
145
+ # @return [Fixnum]
148
146
  attr_accessor :port
149
147
 
150
148
  # The type of connection to establish
149
+ # @return [Symbol]
151
150
  attr_accessor :connect_type
152
151
 
153
152
  # The base DN of the directory
153
+ # @return [String]
154
154
  attr_accessor :base_dn
155
155
 
156
+ # The control modules that are registered with the directory
157
+ # @return [Array<Module>]
158
+ attr_reader :registered_controls
159
+
160
+ # The DN of the user the directory is bound as
161
+ # @return [String]
162
+ attr_reader :bound_user
163
+
156
164
 
157
165
  ### Fetch the Branch for the base node of the directory.
166
+ ### @return [Treequel::Branch]
158
167
  def base
159
168
  return @base ||= Treequel::Branch.new( self, self.base_dn )
160
169
  end
161
170
 
162
171
 
163
172
  ### Returns a string that describes the directory
173
+ ### @return [String]
164
174
  def to_s
165
175
  return "%s:%d (%s, %s, %s)" % [
166
176
  self.host,
167
177
  self.port,
168
178
  self.base_dn,
169
179
  self.connect_type,
170
- self.bound? ? @bound_as : 'anonymous'
180
+ self.bound? ? @bound_user : 'anonymous'
171
181
  ]
172
182
  end
173
183
 
174
184
 
175
185
  ### Return a human-readable representation of the object suitable for debugging
186
+ ### @return [String]
176
187
  def inspect
177
188
  return %{#<%s:0x%0x %s:%d (%s) base_dn=%p, bound as=%s, schema=%s>} % [
178
189
  self.class.name,
@@ -181,7 +192,7 @@ class Treequel::Directory
181
192
  self.port,
182
193
  @conn ? "connected" : "not connected",
183
194
  self.base_dn,
184
- @bound_as ? @bound_as.dump : "anonymous",
195
+ @bound_user ? @bound_user.dump : "anonymous",
185
196
  @schema ? @schema.inspect : "(schema not loaded)",
186
197
  ]
187
198
  end
@@ -189,12 +200,14 @@ class Treequel::Directory
189
200
 
190
201
  ### Return the LDAP::Conn object associated with this directory, creating it with the
191
202
  ### current options if necessary.
203
+ ### @return [LDAP::Conn, LDAP::SSLConn]
192
204
  def conn
193
205
  return @conn ||= self.connect
194
206
  end
195
207
 
196
208
 
197
209
  ### Return the URI object that corresponds to the directory.
210
+ ### @return [URI::LDAP]
198
211
  def uri
199
212
  uri_parts = {
200
213
  :scheme => self.connect_type == :ssl ? 'ldaps' : 'ldap',
@@ -208,30 +221,41 @@ class Treequel::Directory
208
221
 
209
222
 
210
223
  ### Bind as the specified +user_dn+ and +password+.
224
+ ###
225
+ ### @param [String, #dn] user_dn the DN of the user to bind as
226
+ ### @param [String] password the password to bind with
227
+ ###
228
+ ### @return [void]
211
229
  def bind( user_dn, password )
212
230
  user_dn = user_dn.dn if user_dn.respond_to?( :dn )
213
231
 
214
232
  self.log.info "Binding with connection %p as: %s" % [ self.conn, user_dn ]
215
233
  self.conn.bind( user_dn.to_s, password )
216
- @bound_as = user_dn.to_s
234
+ @bound_user = user_dn.to_s
217
235
  end
236
+ alias_method :bind_as, :bind
218
237
 
219
238
 
220
239
  ### Execute the provided +block+ after binding as +user_dn+ with the given +password+. After
221
240
  ### the block returns, the original binding (if any) will be restored.
241
+ ###
242
+ ### @param (see #bind)
243
+ ###
244
+ ### @return [void]
222
245
  def bound_as( user_dn, password )
223
246
  raise LocalJumpError, "no block given" unless block_given?
224
- previous_bind_dn = @bound_as
247
+ previous_bind_dn = @bound_user
225
248
  self.with_duplicate_conn do
226
249
  self.bind( user_dn, password )
227
250
  yield
228
251
  end
229
252
  ensure
230
- @bound_as = previous_bind_dn
253
+ @bound_user = previous_bind_dn
231
254
  end
232
255
 
233
256
 
234
- ### Returns +true+ if the directory's connection has already established a binding.
257
+ ### Returns +true+ if the directory's connection is already bound to the directory.
258
+ ### @return [Boolean]
235
259
  def bound?
236
260
  return self.conn.bound?
237
261
  end
@@ -239,6 +263,7 @@ class Treequel::Directory
239
263
 
240
264
 
241
265
  ### Ensure that the the receiver's connection is unbound.
266
+ ### @return [void]
242
267
  def unbind
243
268
  if @conn.bound?
244
269
  old_conn = @conn
@@ -249,6 +274,7 @@ class Treequel::Directory
249
274
 
250
275
 
251
276
  ### Return the RDN string to the given +dn+ from the base of the directory.
277
+ ### @param [#to_s] dn the DN of the entry
252
278
  def rdn_to( dn )
253
279
  base_re = Regexp.new( ',' + Regexp.quote(self.base_dn) + '$' )
254
280
  return dn.to_s.sub( base_re, '' )
@@ -257,6 +283,8 @@ class Treequel::Directory
257
283
 
258
284
  ### Given a Treequel::Branch object, find its corresponding LDAP::Entry and return
259
285
  ### it.
286
+ ###
287
+ ### @param [Treequel::Branch] branch the branch to look up
260
288
  def get_entry( branch )
261
289
  self.log.debug "Looking up entry for %p" % [ branch.dn ]
262
290
  return self.conn.search_ext2( branch.dn, SCOPE[:base], '(objectClass=*)' ).first
@@ -269,6 +297,8 @@ class Treequel::Directory
269
297
  ### Given a Treequel::Branch object, find its corresponding LDAP::Entry and return
270
298
  ### it with its operational attributes (http://tools.ietf.org/html/rfc4512#section-3.4)
271
299
  ### included.
300
+ ###
301
+ ### @param [Treequel::Branch] branch the branch to look up
272
302
  def get_extended_entry( branch )
273
303
  self.log.debug "Looking up entry (with operational attributes) for %p" % [ branch.dn ]
274
304
  return self.conn.search_ext2( branch.dn, SCOPE[:base], '(objectClass=*)', %w[* +] ).first
@@ -289,47 +319,96 @@ class Treequel::Directory
289
319
  end
290
320
 
291
321
 
292
- ### Perform a +scope+ search at +base+ using the specified +filter+. The +scope+ argument
293
- ### can be one of +:onelevel+, +:base+, or +:subtree+. Results will be returned as instances
294
- ### of the given +collectclass+.
295
- def search( base, scope=:subtree, filter='(objectClass=*)', parameters={} )
322
+ ### Perform a +scope+ search at +base+ using the specified +filter+.
323
+ ###
324
+ ### @param [String, #dn] base The base DN of the search.
325
+ ### @param [Symbol] scope The scope to use in the search; can be one of
326
+ ### +:onelevel+, +:base+, or +:subtree+.
327
+ ### @param [#to_s] filter The search filter (RFC4515), either as a String
328
+ ### or something that stringifies to an filter string.
329
+ ### @param [Hash] options Search options.
330
+ ###
331
+ ### @option options [Class] :results_class (Treequel::Branch)
332
+ ### The Class to use when wrapping results; if not specified, defaults to the class
333
+ ### of +base+ if it responds to #new_from_entry, or Treequel::Branch
334
+ ### if it does not.
335
+ ### @option options [Array<String, Symbol>] :selectattrs (['*'])
336
+ ### The attributes to return from the search; defaults to '*', which means to
337
+ ### return all non-operational attributes. Specifying '+' will cause the search
338
+ ### to include operational parameters as well.
339
+ ### @option options [Boolean] :attrsonly (false)
340
+ ### If +true, the LDAP::Entry objects returned from the search won't have attribute values.
341
+ ### This has no real effect on Treequel::Branches, but is provided in case other
342
+ ### +results_class+ classes need it.
343
+ ### @option options [Array<LDAP::Control>] :server_controls (nil)
344
+ ### Any server controls that should be sent with the search.
345
+ ### @option options [Array<LDAP::Control>] :client_controls (nil)
346
+ ### Any client controls that should be applied to the search.
347
+ ### @option options [Fixnum] :timeout_s (0)
348
+ ### The number of seconds (in addition to :timeout_us) after which the search request should
349
+ ### be aborted.
350
+ ### @option options [Fixnum] :timeout_us (0)
351
+ ### The number of microseconds (in addition to :timeout_s) after which the search request
352
+ ### should be aborted.
353
+ ### @option options [Fixnum] :limit
354
+ ### The maximum number of results to return from the server.
355
+ ### @option options [Array<String>] :sort_attribute
356
+ ### An Array of String attribute names to sort by.
357
+ ### @option options [Proc] :sort_func
358
+ ### A function that will provide sorting.
359
+ ###
360
+ ### @return [Array] the array of results, each of which is wrapped in the
361
+ ### options[:results_class]. If a block is given, it acts like a filter:
362
+ ### the return vaule from the block is returned instead.
363
+ ###
364
+ ### @yield [branch] an optional block, which will receive the results one at a time
365
+ ### @yieldparam [Treequel::Branch] branch the resulting entry, wrapped in
366
+ ### the options[:results_class].
367
+ def search( base, scope=:subtree, filter='(objectClass=*)', options={} )
296
368
  collectclass = nil
297
369
 
298
370
  # If the base argument is an object whose class knows how to create instances of itself
299
371
  # from an LDAP::Entry, use it instead of Treequel::Branch to wrap results
300
- if parameters.key?( :results_class )
301
- collectclass = parameters.delete( :results_class )
372
+ if options.key?( :results_class )
373
+ collectclass = options.delete( :results_class )
302
374
  else
303
375
  collectclass = base.class.respond_to?( :new_from_entry ) ? base.class : Treequel::Branch
304
376
  end
305
377
 
306
378
  # Format the arguments in the way #search_ext2 expects them
307
- base, scope, filter, searchparams =
308
- self.normalize_search_parameters( base, scope, filter, parameters )
379
+ base_dn, scope, filter, searchopts =
380
+ self.normalize_search_parameters( base, scope, filter, options )
309
381
 
310
- # Unwrap the search parameters from the hash in the correct order
382
+ # Unwrap the search options from the hash in the correct order
311
383
  self.log.debug {
312
384
  attrlist = SEARCH_PARAMETER_ORDER.inject([]) do |list, param|
313
- list << "%s: %p" % [ param, searchparams[param] ]
385
+ list << "%s: %p" % [ param, searchopts[param] ]
314
386
  end
315
387
  "searching with base: %p, scope: %p, filter: %p, %s" %
316
- [ base, scope, filter, attrlist.join(', ') ]
388
+ [ base_dn, scope, filter, attrlist.join(', ') ]
317
389
  }
318
- parameters = searchparams.values_at( *SEARCH_PARAMETER_ORDER )
390
+ parameters = searchopts.values_at( *SEARCH_PARAMETER_ORDER )
319
391
 
320
392
  # Wrap each result in the class derived from the 'base' argument
321
393
  self.log.debug "Searching via search_ext2 with arguments: %p" % [[
322
- base, scope, filter, *parameters
394
+ base_dn, scope, filter, *parameters
323
395
  ]]
324
- if block_given?
325
- self.conn.search_ext2( base, scope, filter, *parameters ).each do |entry|
326
- yield collectclass.new_from_entry( entry, self )
396
+
397
+ results = []
398
+
399
+ self.conn.search_ext2( base_dn, scope, filter, *parameters ).each do |entry|
400
+ branch = collectclass.new_from_entry( entry, self )
401
+ branch.include_operational_attrs = base.include_operational_attrs? if
402
+ base.respond_to?( :include_operational_attrs? )
403
+
404
+ if block_given?
405
+ results << yield( branch )
406
+ else
407
+ results << branch
327
408
  end
328
- else
329
- return self.conn.search_ext2( base, scope, filter, *parameters ).
330
- collect {|entry| collectclass.new_from_entry(entry, self) }
331
409
  end
332
410
 
411
+ return results
333
412
  rescue RuntimeError => err
334
413
  conn = self.conn
335
414
 
@@ -347,9 +426,14 @@ class Treequel::Directory
347
426
  ### Modify the entry specified by the given +dn+ with the specified +mods+, which can be
348
427
  ### either an Array of LDAP::Mod objects or a Hash of attribute/value pairs.
349
428
  def modify( branch, mods )
350
- normattrs = self.normalize_attributes( mods )
351
- self.log.debug "Modifying %s with attributes: %p" % [ branch.dn, normattrs ]
352
- self.conn.modify( branch.dn, normattrs )
429
+ if mods.first.respond_to?( :mod_op )
430
+ self.log.debug "Modifying %s with LDAP mod objects: %p" % [ branch.dn, mods ]
431
+ self.conn.modify( branch.dn, mods )
432
+ else
433
+ normattrs = self.normalize_attributes( mods )
434
+ self.log.debug "Modifying %s with: %p" % [ branch.dn, normattrs ]
435
+ self.conn.modify( branch.dn, normattrs )
436
+ end
353
437
  end
354
438
 
355
439
 
@@ -368,7 +452,7 @@ class Treequel::Directory
368
452
  # Merge RDN attributes with existing ones, combining any that exist in both
369
453
  self.log.debug "Smushing rdn attributes %p into %p" % [ branch.rdn_attributes, newdn ]
370
454
  newattrs.merge!( branch.rdn_attributes ) do |key, *values|
371
- values.flatten
455
+ values.flatten.uniq
372
456
  end
373
457
 
374
458
  normattrs = self.normalize_attributes( newattrs )
@@ -421,6 +505,25 @@ class Treequel::Directory
421
505
  end
422
506
 
423
507
 
508
+ ### Register the specified +modules+
509
+ def register_controls( *modules )
510
+ dse = self.conn.root_dse.first
511
+ supported_controls = dse['supportedControl']
512
+
513
+ modules.each do |mod|
514
+ oid = mod.const_get( :OID ) if mod.const_defined?( :OID )
515
+ raise NotImplementedError, "%s doesn't define an OID" % [ mod.name ] if oid.nil?
516
+
517
+ if supported_controls.include?( oid )
518
+ @registered_controls << mod
519
+ else
520
+ raise Treequel::UnsupportedControl,
521
+ "%s is not supported by %s" % [ mod.name, self.uri ]
522
+ end
523
+ end
524
+ end
525
+
526
+
424
527
  ### Map the specified +value+ to its Ruby datatype if one is registered for the given
425
528
  ### syntax +oid+. If there is no conversion registered, just return the +value+ as-is.
426
529
  def convert_syntax_value( oid, value )
@@ -435,6 +538,50 @@ class Treequel::Directory
435
538
  end
436
539
 
437
540
 
541
+ ### Return an Array of Symbols for the controls supported by the Directory, as listed
542
+ ### in the directory's root DSE. Any controls which aren't known (i.e., don't have an
543
+ ### entry in Treequel::Constants::CONTROL_NAMES), the numeric OID will be returned as-is.
544
+ def supported_controls
545
+ return self.supported_control_oids.collect {|oid| CONTROL_NAMES[oid] || oid }
546
+ end
547
+
548
+
549
+ ### Return an Array of OID strings representing the controls supported by the Directory,
550
+ ### as listed in the directory's root DSE.
551
+ def supported_control_oids
552
+ return self.conn.root_dse.first['supportedControl']
553
+ end
554
+
555
+
556
+ ### Return an Array of Symbols for the extensions supported by the Directory, as listed
557
+ ### in the directory's root DSE. Any extensions which aren't known (i.e., don't have an
558
+ ### entry in Treequel::Constants::EXTENSION_NAMES), the numeric OID will be returned as-is.
559
+ def supported_extensions
560
+ return self.supported_extension_oids.collect {|oid| EXTENSION_NAMES[oid] || oid }
561
+ end
562
+
563
+
564
+ ### Return an Array of OID strings representing the extensions supported by the Directory,
565
+ ### as listed in the directory's root DSE.
566
+ def supported_extension_oids
567
+ return self.conn.root_dse.first['supportedExtension']
568
+ end
569
+
570
+
571
+ ### Return an Array of Symbols for the features supported by the Directory, as listed
572
+ ### in the directory's root DSE. Any features which aren't known (i.e., don't have an
573
+ ### entry in Treequel::Constants::FEATURE_NAMES), the numeric OID will be returned as-is.
574
+ def supported_features
575
+ return self.supported_feature_oids.collect {|oid| FEATURE_NAMES[oid] || oid }
576
+ end
577
+
578
+
579
+ ### Return an Array of OID strings representing the features supported by the Directory,
580
+ ### as listed in the directory's root DSE.
581
+ def supported_feature_oids
582
+ return self.conn.root_dse.first['supportedFeatures']
583
+ end
584
+
438
585
 
439
586
  #########
440
587
  protected
@@ -457,10 +604,10 @@ class Treequel::Directory
457
604
  conn = LDAP::SSLConn.new( @host, @port, true )
458
605
  when :ssl
459
606
  self.log.debug "Connecting using SSL to %s:%d" % [ @host, @port ]
460
- conn = LDAP::SSLConn.new( host, port )
607
+ conn = LDAP::SSLConn.new( @host, @port )
461
608
  else
462
609
  self.log.debug "Connecting using an unencrypted connection to %s:%d" % [ @host, @port ]
463
- conn = LDAP::Conn.new( host, port )
610
+ conn = LDAP::Conn.new( @host, @port )
464
611
  end
465
612
 
466
613
  conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )