treequel 1.0.1 → 1.0.4

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.
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 )