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
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'ldap'
4
+ require 'ldap/control'
5
+
6
+ require 'treequel'
7
+
8
+
9
+ # Virtual interface methods for Control modules.
10
+ #
11
+ # @abstract To make a concrete derivative, include this module in a module that
12
+ # implements either {#get_client_controls} or {#get_server_controls} and #each.
13
+ # Your implementation of #each should +super+ with a block that does the
14
+ # necessary extraction of the result controls and yields back to the original
15
+ # block.
16
+ #
17
+ # @example
18
+ # module Treequel::MyControl
19
+ # include Treequel::Control
20
+ #
21
+ # # The control's OID
22
+ # OID = '1.3.6.1.4.1.984454.16.1'
23
+ #
24
+ # # If your control has some value associated with it, you can provide
25
+ # # an initializer to set up an instance variable or two.
26
+ # def initialize
27
+ # @my_control_value = 18
28
+ # end
29
+ #
30
+ # attr_accessor :my_control_value
31
+ #
32
+ # # This is the interface users will use to set values used in the control,
33
+ # # like so:
34
+ # # branchset.controlled_somehow( value )
35
+ # def controlled_somehow( value )
36
+ # self.my_control_value = value
37
+ # end
38
+ #
39
+ # # This is overridden so you can fetch controls set by the server before
40
+ # # iterating. The #each in Treequel::Branchset will yield to this block
41
+ # # after performing a search.
42
+ # def each( &block )
43
+ # super do |branch|
44
+ # if my_control = branch.controls.find {|control| control.oid == OID }
45
+ # server_control_value = my_control.decode
46
+ # # ... do something with the returned server_control_value
47
+ # end
48
+ #
49
+ # block.call( branch )
50
+ # end
51
+ # end
52
+ #
53
+ # # This is how you inject your control into the search; Treequel::Branchset
54
+ # # will call this before running the search and add the results to its
55
+ # # server_controls. If you're implementing a client control, override
56
+ # # the #get_client_controls method instead. Be sure to super() so that any
57
+ # # controls registered before yours have a chance to add their objects too.
58
+ # def get_server_controls
59
+ # controls = super
60
+ # if self.my_control_value
61
+ # value = LDAP::Control.encode( self.my_control_value )
62
+ # controls << LDAP::Control.new( OID, value, true )
63
+ # end
64
+ #
65
+ # return controls
66
+ # end
67
+ #
68
+ # end
69
+ #
70
+ module Treequel::Control
71
+
72
+ ### Control API interface method.
73
+ ###
74
+ ### If your control is a client control, you should super() to this method
75
+ ### and add your control (in the form of an LDAP::Control object) to the
76
+ ### resulting Array before returning it.
77
+ def get_client_controls
78
+ return []
79
+ end
80
+
81
+
82
+ ### Control API interface method.
83
+ ###
84
+ ### If your control is a server control, you should super() to this method
85
+ ### and add your control (in the form of an LDAP::Control object) to the
86
+ ### resulting Array before returning it.
87
+ def get_server_controls
88
+ return []
89
+ end
90
+
91
+ end # module Treequel::Control
92
+
93
+ # vim: set nosta noet ts=4 sw=4:
94
+
95
+
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'ldap'
5
+ require 'openssl'
6
+
7
+ require 'treequel'
8
+ require 'treequel/control'
9
+ require 'treequel/constants'
10
+
11
+
12
+ # A Treequel::Control module that implements the "Content Sync"
13
+ # control (RFC 4533)
14
+ #
15
+ # *NOTICE:* This control currently doesn't do anything, as it depends on
16
+ # Intermediate Responses, which the underlying Ruby-LDAP library doesn't
17
+ # support (yet).
18
+ #
19
+ # == Usage
20
+ #
21
+ # As with all Controls, you must first register the control with the
22
+ # Treequel::Directory object you're intending to search:
23
+ #
24
+ # dir = Treequel.directory( 'ldap://ldap.acme.com/dc=acme,dc=com' )
25
+ # dir.register_controls( Treequel::ContentSyncControl )
26
+ #
27
+ # Once that's done, any Treequel::Branchset you create will have the #on_sync
28
+ # method:
29
+ #
30
+ # # Build DHCP records out of all the hosts in the directory, then rebuild
31
+ # # everything when a host record changes.
32
+ # hosts = dir.filter( :ou => Hosts ).collection
33
+ # hosts.filter( :objectClass => :ipHost ).on_sync do ||
34
+ # #
35
+ # end
36
+ #
37
+ # @see http://deveiate.org/projects/Treequel/ticket/6 Ticket: Add support for
38
+ # the RFC4533 Content Sync operation
39
+ #
40
+ module Treequel::ContentSyncControl
41
+ include Treequel::Control,
42
+ Treequel::Constants
43
+
44
+ # The control's OID
45
+ OID = CONTROL_OIDS[:sync]
46
+
47
+ # Sync mode constants (from RFC4533, section 2.2)
48
+ SYNC_MODE_REFRESH = 1
49
+ SYNC_MODE_REFRESH_AND_PERSIST = 3
50
+
51
+ ### Add the requisite instance variables to including Branchsets.
52
+ def initialize
53
+ self.log.notice "The ContentSync control doesn't work yet -- it requires support for " +
54
+ "IntermediateResponses, which Ruby-LDAP doesn't do yet. " +
55
+ "See http://deveiate.org/projects/Treequel/ticket/6 for updates on the " +
56
+ "status of this."
57
+ @content_sync_callback = nil
58
+ end
59
+
60
+
61
+ ######
62
+ public
63
+ ######
64
+
65
+ # The callback to call when results change
66
+ attr_accessor :content_sync_callback
67
+
68
+
69
+ ### Clone the Branchset with a persistent change callback.
70
+ def on_sync( &callback )
71
+ newset = self.clone
72
+ newset.content_sync_callback = callback
73
+
74
+ return newset
75
+ end
76
+
77
+
78
+ ### Override the Enumerable method to update the cookie value each time a page
79
+ ### is fetched.
80
+ def each( &block )
81
+ super do |branch|
82
+ self.log.debug "Looking for the sync control in controls: %p" % [ branch.controls ]
83
+ branch.controls.each do |control|
84
+ self.log.debug " got a %s control: %p" % [
85
+ CONTROL_NAMES[control.oid],
86
+ control.decode,
87
+ ]
88
+
89
+ case control.oid
90
+ when CONTROL_OIDS[:sync_state]
91
+ self.log.debug " got a 'state' control"
92
+ block.call( branch )
93
+ when CONTROL_OIDS[:sync_done]
94
+ self.log.debug " got a 'done' control"
95
+ break
96
+ else
97
+ self.log.info " got an unexpected control (%p)" % [ control ]
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+
104
+ #########
105
+ protected
106
+ #########
107
+
108
+ ### Make the ASN.1 string for the control value out of the given +mode+,
109
+ ### +cookie+, +reload_hint+.
110
+ def make_sync_control_value( mode, cookie, reload_hint )
111
+ # (http://tools.ietf.org/html/rfc4533#section-2.2):
112
+ # syncRequestValue ::= SEQUENCE {
113
+ # mode ENUMERATED {
114
+ # -- 0 unused
115
+ # refreshOnly (1),
116
+ # -- 2 reserved
117
+ # refreshAndPersist (3)
118
+ # },
119
+ # cookie syncCookie OPTIONAL,
120
+ # reloadHint BOOLEAN DEFAULT FALSE
121
+ # }
122
+ encoded_vals = [
123
+ OpenSSL::ASN1::Enumerated.new( SYNC_MODE_REFRESH_AND_PERSIST )
124
+ ]
125
+ return OpenSSL::ASN1::Sequence.new( encoded_vals ).to_der
126
+ end
127
+
128
+
129
+ ### Treequel::Control API -- Get a configured LDAP::Control object for this
130
+ ### Branchset.
131
+ def get_server_controls
132
+ controls = super
133
+ value = self.make_sync_control_value( 1, '', false )
134
+ return controls << LDAP::Control.new( OID, value, true )
135
+ end
136
+
137
+ end # module Treequel::ContentSyncControl
138
+
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'ldap'
5
+
6
+ require 'treequel'
7
+ require 'treequel/control'
8
+
9
+
10
+ # A Treequel::Control module that implements the "LDAP Control Extension
11
+ # for Simple Paged Results Manipulation" (RFC 2696).
12
+ #
13
+ # == Usage
14
+ #
15
+ # As with all Controls, you must first register the control with the
16
+ # Treequel::Directory object you're intending to search:
17
+ #
18
+ # dir = Treequel.directory( 'ldap://ldap.acme.com/dc=acme,dc=com' )
19
+ # dir.register_controls( Treequel::PagedResultsControl )
20
+ #
21
+ # Once that's done, any Treequel::Branchset you create will have the
22
+ # #with_paged_results method that will allow you to specify the number
23
+ # of results you wish to be returned per "page":
24
+ #
25
+ # # Fetch people in pages
26
+ # people = dir.ou( :People )
27
+ # paged_people = people.filter( :objectClass => :person ).with_paged_results( 25 )
28
+ #
29
+ # The Branchset will also respond to #has_more_results?, which will
30
+ # be true while there are additional pages to be fetched, or before
31
+ # the search has taken place:
32
+ #
33
+ # # Display each set of 25, waiting for keypress between each set
34
+ # while paged_people.has_more_results?
35
+ # # do something with this set of 25 people...
36
+ # end
37
+ #
38
+ module Treequel::PagedResultsControl
39
+ include Treequel::Control
40
+
41
+ # The control's OID
42
+ OID = '1.2.840.113556.1.4.319'
43
+
44
+ # The default number of results per page
45
+ DEFAULT_PAGE_SIZE = 100
46
+
47
+
48
+ ### Add the control's instance variables to including Branchsets.
49
+ def initialize
50
+ @paged_results_cookie = ''
51
+ @paged_results_setsize = nil
52
+ end
53
+
54
+
55
+ ######
56
+ public
57
+ ######
58
+
59
+ # The number of results per page
60
+ attr_accessor :paged_results_setsize
61
+
62
+ # The (opaque) cookie value that will be sent to the server on the next search.
63
+ attr_accessor :paged_results_cookie
64
+
65
+
66
+ ### Clone the Branchset with a paged results control.
67
+ ### @param [Fixnum] setsize The size of each result set. If this is nil or 0, removes
68
+ ### paging from the Branchset.
69
+ ### @return [Treequel::Branchset] a clone of the receiver with paging set to +setsize+
70
+ def with_paged_results( setsize=DEFAULT_PAGE_SIZE )
71
+ self.log.warn "This control will likely not work in ruby-ldap versions " +
72
+ " <= 0.9.9. See http://code.google.com/p/ruby-activeldap/issues/" +
73
+ "detail?id=38 for details." if LDAP::PATCH_VERSION < 10
74
+
75
+ newset = self.clone
76
+
77
+ if setsize.nil? || setsize.zero?
78
+ newset.paged_results_setsize = nil
79
+ else
80
+ newset.paged_results_setsize = setsize
81
+ end
82
+
83
+ return newset
84
+ end
85
+
86
+
87
+ ### Clone the Branchset without paging and return it.
88
+ ### @return [Treequel::Branchset] a clone of the receiver, stripped of its paging
89
+ def without_paging
90
+ copy = self.clone
91
+ copy.without_paging!
92
+ return copy
93
+ end
94
+
95
+
96
+ ### Remove any paging control associated with the receiving Branchset.
97
+ ### @return [void]
98
+ def without_paging!
99
+ self.paged_results_cookie = ''
100
+ self.paged_results_setsize = nil
101
+ end
102
+
103
+
104
+ ### Returns +true+ if the first page of results has been fetched and there are
105
+ ### more pages remaining.
106
+ ### @return [Boolean]
107
+ def has_more_results?
108
+ return true unless self.done_paging?
109
+ end
110
+
111
+
112
+ ### Returns +true+ if results have yet to be fetched, or if they have all been
113
+ ### fetched.
114
+ ### @return [Boolean]
115
+ def done_paging?
116
+ return self.paged_results_cookie == ''
117
+ end
118
+
119
+
120
+ ### Override the Enumerable method to update the cookie value each time a page
121
+ ### is fetched.
122
+ ### @yieldparam [Treequel::Branch] branch the branch that's a result of the search.
123
+ def each( &block )
124
+ super do |branch|
125
+ if paged_control = branch.controls.find {|control| control.oid == OID }
126
+ returned_size, cookie = paged_control.decode
127
+ self.log.debug "Paged control in result with size = %p, cookie = %p" %
128
+ [ returned_size, cookie ]
129
+ self.paged_results_cookie = cookie
130
+ else
131
+ self.log.debug "No paged control in results. Setting cookie to ''."
132
+ self.paged_results_cookie = ''
133
+ end
134
+
135
+ block.call( branch )
136
+ end
137
+ end
138
+
139
+
140
+ #########
141
+ protected
142
+ #########
143
+
144
+ ### Treequel::Control API -- Get the set of server controls currently configured for
145
+ ### the receiver.
146
+ ### @return [Array<LDAP::Control>] the configured controls
147
+ def get_server_controls
148
+ controls = super
149
+ if pagesize = self.paged_results_setsize && self.paged_results_setsize.nonzero?
150
+ self.log.debug "Setting up paging for sets of %d" % [ pagesize ]
151
+ value = LDAP::Control.encode( pagesize.to_i, self.paged_results_cookie.to_s )
152
+ controls << LDAP::Control.new( OID, value, true )
153
+ else
154
+ self.log.debug "No paging for this %p; not adding the PagedResults control" %
155
+ [ self.class ]
156
+ end
157
+
158
+ return controls
159
+ end
160
+
161
+ end # module Treequel::PagedResultsControl
162
+
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'ldap'
5
+
6
+ require 'treequel'
7
+ require 'treequel/control'
8
+ require 'treequel/sequel_integration'
9
+
10
+
11
+ # A Treequel::Control module that implements the "LDAP Control Extension for Server
12
+ # Side Sorting of Search Results" (RFC 2891).
13
+ #
14
+ # == Usage
15
+ #
16
+ # As with all Controls, you must first register the control with the
17
+ # Treequel::Directory object you're intending to search:
18
+ #
19
+ # dir = Treequel.directory( 'ldap://ldap.acme.com/dc=acme,dc=com' )
20
+ # dir.register_controls( Treequel::SortedResultsControl )
21
+ #
22
+ # Once that's done, any Treequel::Branchset you create will have the
23
+ # #order method that will allow you to specify one or more attributes by which the server
24
+ # should sort results before returning them:
25
+ #
26
+ # # Fetch people sorted by their last name, then first name, then
27
+ # # by employeeNumber
28
+ # people = dir.ou( :People )
29
+ # sorted_people = people.filter( :objectClass => :person ).
30
+ # order( :sn, :givenName, :employeeNumber )
31
+ module Treequel::SortedResultsControl
32
+ include Treequel::Control,
33
+ Treequel::Constants
34
+
35
+ # The control's request OID
36
+ OID = CONTROL_OIDS[:sortrequest]
37
+
38
+ # The control's response OID
39
+ RESPONSE_OID = CONTROL_OIDS[:sortresult]
40
+
41
+ # Descriptions of result codes
42
+ RESPONSE_RESULT_DESCRIPTIONS = {
43
+ # success (0), -- results are sorted
44
+ 0 => 'Success',
45
+
46
+ # operationsError (1), -- server internal failure
47
+ 1 => 'Operations error; server internal failure',
48
+
49
+ # timeLimitExceeded (3), -- timelimit reached before
50
+ # -- sorting was completed
51
+ 3 => 'Time limit reached before sorting was completed',
52
+
53
+ # strongAuthRequired (8), -- refused to return sorted
54
+ # -- results via insecure
55
+ # -- protocol
56
+ 8 => 'Stronger auth required: refusing to return sorted results via insecure protocol',
57
+
58
+ # adminLimitExceeded (11), -- too many matching entries
59
+ # -- for the server to sort
60
+ 11 => 'Admin limit exceeded: too many matching entries for the server to sort',
61
+
62
+ # noSuchAttribute (16), -- unrecognized attribute
63
+ # -- type in sort key
64
+ 16 => 'No such attribute',
65
+
66
+ # inappropriateMatching (18), -- unrecognized or
67
+ # -- inappropriate matching
68
+ # -- rule in sort key
69
+ 18 => 'Inappropriate matching: unrecognized or inappropriate matching rule in sort key',
70
+
71
+ # insufficientAccessRights (50), -- refused to return sorted
72
+ # -- results to this client
73
+ 50 => 'Insufficient access rights: refusing to return sorted results to this client',
74
+
75
+ # busy (51), -- too busy to process
76
+ 51 => 'Busy: too busy to process',
77
+
78
+ # unwillingToPerform (53), -- unable to sort
79
+ 52 => 'Unwilling to perform: unable to sort',
80
+
81
+ # other (80)
82
+ 80 => 'Other: non-specific result code (80)',
83
+ }
84
+
85
+
86
+ # A struct for tracking sorting criteria
87
+ Criterion = Struct.new( 'SortedResultsControlCriterion', :type, :ordering_rule, :reverse_order )
88
+
89
+
90
+ ### Add the requisite instance variables to including Branchsets.
91
+ def initialize
92
+ self.log.debug "initializing %p" % [ self ]
93
+ @sort_order_criteria = []
94
+ end
95
+
96
+
97
+ ######
98
+ public
99
+ ######
100
+
101
+ # The ordering criteria, if any
102
+ attr_accessor :sort_order_criteria
103
+
104
+
105
+ ### Clone the Branchset with a server-side sorted results control added and return it.
106
+ def order( *attributes )
107
+ self.log.warn "This control will likely not work in ruby-ldap versions " +
108
+ " <= 0.9.9. See http://code.google.com/p/ruby-activeldap/issues/" +
109
+ "detail?id=38 for details." if LDAP::PATCH_VERSION < 10
110
+
111
+ if attributes.flatten.empty?
112
+ self.log.debug "cloning %p with no order" % [ self ]
113
+ return self.unordered
114
+ else
115
+ criteria = attributes.collect do |attrspec|
116
+ case attrspec
117
+ when Symbol
118
+ Criterion.new( attrspec.to_s )
119
+
120
+ when Sequel::SQL::Expression
121
+ Criterion.new( attrspec.expression.to_s, nil, attrspec.descending )
122
+
123
+ else
124
+ raise ArgumentError,
125
+ "unsupported order specification type %s" % [ attrspec.class.name ]
126
+ end
127
+ end
128
+
129
+ self.log.debug "cloning %p with order criteria: %p" % [ self, criteria ]
130
+ copy = self.clone
131
+ copy.sort_order_criteria += criteria
132
+
133
+ return copy
134
+ end
135
+ end
136
+
137
+
138
+ ### Clone the Branchset without a server-side sorted results control and return it.
139
+ def unordered
140
+ copy = self.clone
141
+ copy.unordered!
142
+ return copy
143
+ end
144
+
145
+
146
+ ### Remove any server-side sorted results control associated with the receiving
147
+ ### Branchset, returning any removed criteria as an Array.
148
+ def unordered!
149
+ return self.sort_order_criteria.slice!( 0..-1 )
150
+ end
151
+
152
+
153
+ ### Override the Enumerable method to update the cookie value each time a page
154
+ ### is fetched.
155
+ def each( &block )
156
+ super do |branch|
157
+ if sorted_control = branch.controls.find {|control| control.oid == RESPONSE_OID }
158
+ sortResult, attributeType = sorted_control.decode
159
+ if sortResult.nonzero?
160
+ self.log.error "got non-zero response code for sort: %d (%s)" %
161
+ [ sortResult, RESPONSE_RESULT_DESCRIPTIONS[sortResult] ]
162
+ raise Treequel::ControlError, RESPONSE_RESULT_DESCRIPTIONS[sortResult]
163
+ else
164
+ self.log.debug "got 'success' sort response code."
165
+ end
166
+ end
167
+
168
+ block.call( branch )
169
+ end
170
+ end
171
+
172
+
173
+ #########
174
+ protected
175
+ #########
176
+
177
+ ### Make the ASN.1 string for the control value out of the given +mode+,
178
+ ### +cookie+, +reload_hint+.
179
+ def make_sorted_control_value( sort_criteria )
180
+
181
+ ### (http://tools.ietf.org/html/rfc2891#section-1.1):
182
+ # SortKeyList ::= SEQUENCE OF SEQUENCE {
183
+ # attributeType AttributeDescription,
184
+ # orderingRule [0] MatchingRuleId OPTIONAL,
185
+ # reverseOrder [1] BOOLEAN DEFAULT FALSE }
186
+ encoded_vals = sort_criteria.collect do |criterion|
187
+ seq = []
188
+ seq << OpenSSL::ASN1::OctetString.new( criterion.type )
189
+ seq << OpenSSL::ASN1::ObjectId.new( criterion.ordering_rule ) if criterion.ordering_rule
190
+ seq << OpenSSL::ASN1::Boolean.new( true ) if criterion.reverse_order
191
+ OpenSSL::ASN1::Sequence.new( seq )
192
+ end
193
+
194
+ return OpenSSL::ASN1::Sequence.new( encoded_vals ).to_der
195
+ end
196
+
197
+
198
+ ### Treequel::Control API -- Get a configured LDAP::Control object for this
199
+ ### Branchset.
200
+ def get_server_controls
201
+ controls = super
202
+ criteria = self.sort_order_criteria
203
+
204
+ if criteria.empty?
205
+ self.log.debug "No sort order criteria; skipping the server-side sort control"
206
+ else
207
+ self.log.debug "Found %d sort order criteria; generating a server-side sort control" %
208
+ [ criteria.length ]
209
+ asn1_string = self.make_sorted_control_value( criteria )
210
+ controls << LDAP::Control.new( OID, asn1_string, true )
211
+ end
212
+
213
+ return controls
214
+ end
215
+
216
+ end