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.
- data/ChangeLog +176 -14
- data/LICENSE +1 -1
- data/Rakefile +61 -45
- data/Rakefile.local +20 -0
- data/bin/treequel +502 -269
- data/examples/ldap-rack-auth.rb +2 -0
- data/lib/treequel.rb +221 -18
- data/lib/treequel/branch.rb +410 -201
- data/lib/treequel/branchcollection.rb +25 -13
- data/lib/treequel/branchset.rb +42 -40
- data/lib/treequel/constants.rb +233 -3
- data/lib/treequel/control.rb +95 -0
- data/lib/treequel/controls/contentsync.rb +138 -0
- data/lib/treequel/controls/pagedresults.rb +162 -0
- data/lib/treequel/controls/sortedresults.rb +216 -0
- data/lib/treequel/directory.rb +212 -65
- data/lib/treequel/exceptions.rb +11 -12
- data/lib/treequel/filter.rb +1 -12
- data/lib/treequel/mixins.rb +83 -47
- data/lib/treequel/monkeypatches.rb +29 -0
- data/lib/treequel/schema.rb +23 -19
- data/lib/treequel/schema/attributetype.rb +33 -3
- data/lib/treequel/schema/ldapsyntax.rb +0 -11
- data/lib/treequel/schema/matchingrule.rb +0 -11
- data/lib/treequel/schema/matchingruleuse.rb +0 -11
- data/lib/treequel/schema/objectclass.rb +36 -10
- data/lib/treequel/schema/table.rb +159 -0
- data/lib/treequel/sequel_integration.rb +7 -7
- data/lib/treequel/utils.rb +4 -66
- data/rake/documentation.rb +89 -0
- data/rake/helpers.rb +375 -307
- data/rake/hg.rb +16 -2
- data/rake/manual.rb +11 -6
- data/rake/packaging.rb +20 -35
- data/rake/publishing.rb +22 -62
- data/spec/lib/constants.rb +20 -0
- data/spec/lib/control_behavior.rb +44 -0
- data/spec/lib/matchers.rb +51 -0
- data/spec/treequel/branch_spec.rb +88 -29
- data/spec/treequel/branchcollection_spec.rb +24 -1
- data/spec/treequel/branchset_spec.rb +123 -51
- data/spec/treequel/control_spec.rb +48 -0
- data/spec/treequel/controls/contentsync_spec.rb +38 -0
- data/spec/treequel/controls/pagedresults_spec.rb +138 -0
- data/spec/treequel/controls/sortedresults_spec.rb +171 -0
- data/spec/treequel/directory_spec.rb +186 -16
- data/spec/treequel/mixins_spec.rb +42 -3
- data/spec/treequel/schema/attributetype_spec.rb +22 -20
- data/spec/treequel/schema/objectclass_spec.rb +67 -46
- data/spec/treequel/schema/table_spec.rb +134 -0
- data/spec/treequel_spec.rb +277 -15
- metadata +89 -108
- data/bin/treequel.orig +0 -963
- data/examples/ldap-monitor.rb +0 -143
- data/examples/ldap-monitor/public/css/master.css +0 -328
- data/examples/ldap-monitor/public/images/card_small.png +0 -0
- data/examples/ldap-monitor/public/images/chain_small.png +0 -0
- data/examples/ldap-monitor/public/images/globe_small.png +0 -0
- data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
- data/examples/ldap-monitor/public/images/plug.png +0 -0
- data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
- data/examples/ldap-monitor/public/images/tick.png +0 -0
- data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
- data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
- data/examples/ldap-monitor/views/backends.erb +0 -41
- data/examples/ldap-monitor/views/connections.erb +0 -74
- data/examples/ldap-monitor/views/databases.erb +0 -39
- data/examples/ldap-monitor/views/dump_subsystem.erb +0 -14
- data/examples/ldap-monitor/views/index.erb +0 -14
- data/examples/ldap-monitor/views/layout.erb +0 -35
- data/examples/ldap-monitor/views/listeners.erb +0 -30
- data/rake/rdoc.rb +0 -30
- 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
|