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
@@ -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