activeldap 5.2.4 → 6.0.0
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.
- checksums.yaml +4 -4
- data/.yardopts +3 -1
- data/doc/text/development.md +26 -0
- data/doc/text/{news.textile → news.md} +299 -271
- data/doc/text/{rails.textile → rails.md} +35 -33
- data/doc/text/{tutorial.textile → tutorial.md} +177 -182
- data/lib/active_ldap/adapter/base.rb +28 -8
- data/lib/active_ldap/adapter/jndi.rb +4 -6
- data/lib/active_ldap/adapter/jndi_connection.rb +105 -17
- data/lib/active_ldap/adapter/ldap.rb +8 -19
- data/lib/active_ldap/adapter/ldap_ext.rb +32 -13
- data/lib/active_ldap/adapter/net_ldap.rb +9 -18
- data/lib/active_ldap/configuration.rb +24 -1
- data/lib/active_ldap/connection.rb +1 -1
- data/lib/active_ldap/human_readable.rb +5 -4
- data/lib/active_ldap/operations.rb +21 -4
- data/lib/active_ldap/version.rb +1 -1
- data/test/al-test-utils.rb +84 -37
- data/test/test_base.rb +14 -14
- data/test/test_connection.rb +4 -0
- data/test/test_find.rb +12 -2
- data/test/test_supported_control.rb +1 -1
- data/test/test_validation.rb +19 -15
- metadata +13 -15
- data/README.textile +0 -141
- data/doc/text/development.textile +0 -54
@@ -30,6 +30,8 @@ module ActiveLdap
|
|
30
30
|
:scope,
|
31
31
|
:sasl_options,
|
32
32
|
:follow_referrals,
|
33
|
+
:use_paged_results,
|
34
|
+
:page_size,
|
33
35
|
]
|
34
36
|
|
35
37
|
@@row_even = true
|
@@ -41,6 +43,7 @@ module ActiveLdap
|
|
41
43
|
@bind_tried = false
|
42
44
|
@entry_attributes = {}
|
43
45
|
@follow_referrals = nil
|
46
|
+
@page_size = nil
|
44
47
|
@configuration = configuration.dup
|
45
48
|
@logger = @configuration.delete(:logger)
|
46
49
|
@configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
|
@@ -48,6 +51,7 @@ module ActiveLdap
|
|
48
51
|
instance_variable_set("@#{name}", configuration[name])
|
49
52
|
end
|
50
53
|
@follow_referrals = true if @follow_referrals.nil?
|
54
|
+
@page_size ||= Configuration::DEFAULT_CONFIG[:page_size]
|
51
55
|
@instrumenter = ActiveSupport::Notifications.instrumenter
|
52
56
|
end
|
53
57
|
|
@@ -169,23 +173,39 @@ module ActiveLdap
|
|
169
173
|
end
|
170
174
|
|
171
175
|
def search(options={})
|
172
|
-
filter = parse_filter(options[:filter]) || 'objectClass=*'
|
173
|
-
attrs = options[:attributes] || []
|
174
|
-
scope = ensure_scope(options[:scope] || @scope)
|
175
176
|
base = options[:base]
|
177
|
+
base = ensure_dn_string(base)
|
178
|
+
attributes = options[:attributes] || []
|
179
|
+
attributes = attributes.to_a # just in case
|
176
180
|
limit = options[:limit] || 0
|
177
181
|
limit = nil if limit <= 0
|
182
|
+
use_paged_results = options[:use_paged_results]
|
183
|
+
if use_paged_results or use_paged_results.nil?
|
184
|
+
use_paged_results = supported_control.paged_results?
|
185
|
+
end
|
186
|
+
search_options = {
|
187
|
+
base: base,
|
188
|
+
scope: ensure_scope(options[:scope] || @scope),
|
189
|
+
filter: parse_filter(options[:filter]) || 'objectClass=*',
|
190
|
+
attributes: attributes,
|
191
|
+
limit: limit,
|
192
|
+
use_paged_results: use_paged_results,
|
193
|
+
page_size: options[:page_size] || @page_size,
|
194
|
+
}
|
178
195
|
|
179
|
-
attrs = attrs.to_a # just in case
|
180
|
-
base = ensure_dn_string(base)
|
181
196
|
begin
|
182
197
|
operation(options) do
|
183
|
-
yield(
|
198
|
+
yield(search_options)
|
184
199
|
end
|
185
|
-
rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax
|
200
|
+
rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax => error
|
186
201
|
# Do nothing on failure
|
187
202
|
@logger.info do
|
188
|
-
args = [
|
203
|
+
args = [
|
204
|
+
error.class.class,
|
205
|
+
error.message,
|
206
|
+
search_options[:filter],
|
207
|
+
search_options[:attributes].inspect,
|
208
|
+
]
|
189
209
|
_("Ignore error %s(%s): filter %s: attributes: %s") % args
|
190
210
|
end
|
191
211
|
end
|
@@ -46,12 +46,10 @@ module ActiveLdap
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def search(options={}, &block)
|
49
|
-
super(options) do |
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}
|
54
|
-
execute(:search, info, base, scope, filter, attrs, limit, &block)
|
49
|
+
super(options) do |search_options|
|
50
|
+
scope = search_options[:scope]
|
51
|
+
info = search_options.merge(scope: scope_name(scope))
|
52
|
+
execute(:search, info, search_options, &block)
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
@@ -25,6 +25,8 @@ module ActiveLdap
|
|
25
25
|
Context = naming.Context
|
26
26
|
StartTlsRequest = ldap.StartTlsRequest
|
27
27
|
Control = ldap.Control
|
28
|
+
PagedResultsControl = ldap.PagedResultsControl
|
29
|
+
PagedResultsResponseControl = ldap.PagedResultsResponseControl
|
28
30
|
|
29
31
|
CommunicationException = naming.CommunicationException
|
30
32
|
ServiceUnavailableException = naming.ServiceUnavailableException
|
@@ -108,23 +110,54 @@ module ActiveLdap
|
|
108
110
|
bound?
|
109
111
|
end
|
110
112
|
|
111
|
-
def search(
|
113
|
+
def search(options)
|
114
|
+
base = options[:base]
|
115
|
+
scope = options[:scope]
|
116
|
+
filter = options[:filter]
|
117
|
+
attributes = options[:attributes]
|
118
|
+
limit = options[:limit]
|
119
|
+
use_paged_results = options[:use_paged_results]
|
120
|
+
page_size = options[:page_size]
|
121
|
+
|
112
122
|
controls = SearchControls.new
|
113
123
|
controls.search_scope = scope
|
114
124
|
|
115
125
|
controls.count_limit = limit if limit
|
116
|
-
unless
|
117
|
-
controls.returning_attributes =
|
126
|
+
unless attributes.blank?
|
127
|
+
controls.returning_attributes = attributes.to_java(:string)
|
118
128
|
end
|
119
129
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
130
|
+
page_cookie = nil
|
131
|
+
if use_paged_results
|
132
|
+
# https://devdocs.io/openjdk~8/javax/naming/ldap/pagedresultscontrol
|
133
|
+
@context.set_request_controls([build_paged_results_control(page_size)])
|
134
|
+
else
|
135
|
+
@context.set_request_controls([])
|
136
|
+
end
|
137
|
+
|
138
|
+
escaped_base = escape_dn(base)
|
139
|
+
loop do
|
140
|
+
@context.search(escaped_base, filter, controls).each do |search_result|
|
141
|
+
yield(build_raw_search_result(search_result))
|
142
|
+
end
|
143
|
+
|
144
|
+
break unless use_paged_results
|
145
|
+
|
146
|
+
# Find the paged search cookie
|
147
|
+
response_controls = @context.get_response_controls
|
148
|
+
break unless response_controls
|
149
|
+
response_controls.each do |response_control|
|
150
|
+
next unless response_control.is_a?(PagedResultsResponseControl)
|
151
|
+
page_cookie = response_control.get_cookie
|
152
|
+
break
|
126
153
|
end
|
127
|
-
|
154
|
+
|
155
|
+
break unless page_cookie
|
156
|
+
|
157
|
+
# Set paged results control so we can keep getting results.
|
158
|
+
paged_results_control =
|
159
|
+
build_paged_results_control(page_size, page_cookie)
|
160
|
+
@context.set_request_controls([paged_results_control])
|
128
161
|
end
|
129
162
|
end
|
130
163
|
|
@@ -133,25 +166,35 @@ module ActiveLdap
|
|
133
166
|
records.each do |record|
|
134
167
|
attributes.put(record.to_java_attribute)
|
135
168
|
end
|
136
|
-
|
169
|
+
escaped_dn = escape_dn(dn)
|
170
|
+
@context.set_request_controls([])
|
171
|
+
@context.create_subcontext(escaped_dn, attributes)
|
137
172
|
end
|
138
173
|
|
139
174
|
def modify(dn, records)
|
175
|
+
escaped_dn = escape_dn(dn)
|
140
176
|
items = records.collect(&:to_java_modification_item)
|
141
|
-
@context.
|
177
|
+
@context.set_request_controls([])
|
178
|
+
@context.modify_attributes(escaped_dn, items.to_java(ModificationItem))
|
142
179
|
end
|
143
180
|
|
144
181
|
def modify_rdn(dn, new_rdn, delete_old_rdn)
|
182
|
+
escaped_dn = escape_dn(dn)
|
145
183
|
# should use mutex
|
146
184
|
delete_rdn_key = "java.naming.ldap.deleteRDN"
|
147
|
-
@context.
|
148
|
-
|
149
|
-
|
150
|
-
|
185
|
+
@context.set_request_controls([])
|
186
|
+
begin
|
187
|
+
@context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
|
188
|
+
@context.rename(escaped_dn, new_rdn)
|
189
|
+
ensure
|
190
|
+
@context.remove_from_environment(delete_rdn_key)
|
191
|
+
end
|
151
192
|
end
|
152
193
|
|
153
194
|
def delete(dn)
|
154
|
-
|
195
|
+
escaped_dn = escape_dn(dn)
|
196
|
+
@context.set_request_controls([])
|
197
|
+
@context.destroy_subcontext(escaped_dn)
|
155
198
|
end
|
156
199
|
|
157
200
|
private
|
@@ -185,6 +228,51 @@ module ActiveLdap
|
|
185
228
|
protocol = @method == :ssl ? "ldaps" : "ldap"
|
186
229
|
"#{protocol}://#{@host}:#{@port}/"
|
187
230
|
end
|
231
|
+
|
232
|
+
def escape_dn(dn)
|
233
|
+
parsed_dn = nil
|
234
|
+
begin
|
235
|
+
parsed_dn = DN.parse(dn)
|
236
|
+
rescue DistinguishedNameInvalid
|
237
|
+
return dn
|
238
|
+
end
|
239
|
+
|
240
|
+
escaped_rdns = parsed_dn.rdns.collect do |rdn|
|
241
|
+
escaped_rdn_strings = rdn.collect do |key, value|
|
242
|
+
escaped_value = DN.escape_value(value)
|
243
|
+
# We may need to escape the followings too:
|
244
|
+
# * ,
|
245
|
+
# * =
|
246
|
+
# * +
|
247
|
+
# * <
|
248
|
+
# * >
|
249
|
+
# * #
|
250
|
+
# * ;
|
251
|
+
#
|
252
|
+
# See javax.naming.ldap.Rdn.unescapeValue()
|
253
|
+
escaped_value = escaped_value.gsub(/\\\\/) do
|
254
|
+
"\\5C"
|
255
|
+
end
|
256
|
+
"#{key}=#{escaped_value}"
|
257
|
+
end
|
258
|
+
escaped_rdn_strings.join("+")
|
259
|
+
end
|
260
|
+
escaped_rdns.join(",")
|
261
|
+
end
|
262
|
+
|
263
|
+
def build_paged_results_control(page_size, page_cookie=nil)
|
264
|
+
PagedResultsControl.new(page_size, page_cookie, Control::CRITICAL)
|
265
|
+
end
|
266
|
+
|
267
|
+
def build_raw_search_result(search_result)
|
268
|
+
attributes = {}
|
269
|
+
search_result.attributes.get_all.each do |attribute|
|
270
|
+
attributes[attribute.get_id] = attribute.get_all.collect do |value|
|
271
|
+
value.is_a?(String) ? value : String.from_java_bytes(value)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
[search_result.name_in_namespace, attributes]
|
275
|
+
end
|
188
276
|
end
|
189
277
|
end
|
190
278
|
end
|
@@ -113,25 +113,11 @@ module ActiveLdap
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def search(options={})
|
116
|
-
super(options) do |
|
116
|
+
super(options) do |search_options|
|
117
117
|
begin
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
122
|
-
info = {
|
123
|
-
:base => base, :scope => scope_name(scope),
|
124
|
-
:filter => filter, :attributes => attrs, :limit => limit,
|
125
|
-
}
|
126
|
-
options = {
|
127
|
-
:base => base,
|
128
|
-
:scope => scope,
|
129
|
-
:filter => filter,
|
130
|
-
:attributes => attrs,
|
131
|
-
:limit => limit,
|
132
|
-
:use_paged_results => use_paged_results
|
133
|
-
}
|
134
|
-
execute(:search_full, info, options) do |entry|
|
118
|
+
scope = search_options[:scope]
|
119
|
+
info = search_options.merge(scope: scope_name(scope))
|
120
|
+
execute(:search_full, info, search_options) do |entry|
|
135
121
|
attributes = {}
|
136
122
|
entry.attrs.each do |attr|
|
137
123
|
value = entry.vals(attr)
|
@@ -142,7 +128,10 @@ module ActiveLdap
|
|
142
128
|
rescue RuntimeError
|
143
129
|
if $!.message == "no result returned by search"
|
144
130
|
@logger.debug do
|
145
|
-
args = [
|
131
|
+
args = [
|
132
|
+
search_options[:filter],
|
133
|
+
search_options[:attributes].inspect,
|
134
|
+
]
|
146
135
|
_("No matches: filter: %s: attributes: %s") % args
|
147
136
|
end
|
148
137
|
else
|
@@ -65,12 +65,28 @@ module LDAP
|
|
65
65
|
attributes = options[:attributes]
|
66
66
|
limit = options[:limit] || 0
|
67
67
|
use_paged_results = options[:use_paged_results]
|
68
|
+
page_size = options[:page_size]
|
68
69
|
if @@have_search_ext
|
69
70
|
if use_paged_results
|
70
|
-
paged_search(base,
|
71
|
+
paged_search(base,
|
72
|
+
scope,
|
73
|
+
filter,
|
74
|
+
attributes,
|
75
|
+
limit,
|
76
|
+
page_size,
|
77
|
+
&block)
|
71
78
|
else
|
72
|
-
search_ext(base,
|
73
|
-
|
79
|
+
search_ext(base,
|
80
|
+
scope,
|
81
|
+
filter,
|
82
|
+
attributes,
|
83
|
+
false,
|
84
|
+
nil,
|
85
|
+
nil,
|
86
|
+
0,
|
87
|
+
0,
|
88
|
+
limit,
|
89
|
+
&block)
|
74
90
|
end
|
75
91
|
else
|
76
92
|
i = 0
|
@@ -120,27 +136,30 @@ module LDAP
|
|
120
136
|
end
|
121
137
|
end
|
122
138
|
|
123
|
-
def paged_search(base, scope, filter, attributes, limit, &block)
|
124
|
-
# work around a bug with openldap
|
125
|
-
page_size = 126
|
139
|
+
def paged_search(base, scope, filter, attributes, limit, page_size, &block)
|
126
140
|
cookie = ""
|
127
141
|
critical = true
|
128
|
-
|
129
142
|
loop do
|
130
143
|
ber_string = LDAP::Control.encode(page_size, cookie)
|
131
144
|
control = LDAP::Control.new(LDAP::LDAP_CONTROL_PAGEDRESULTS,
|
132
145
|
ber_string,
|
133
146
|
critical)
|
134
|
-
|
135
|
-
|
136
|
-
|
147
|
+
search_ext(base,
|
148
|
+
scope,
|
149
|
+
filter,
|
150
|
+
attributes,
|
151
|
+
false,
|
152
|
+
[control],
|
153
|
+
nil,
|
154
|
+
0,
|
155
|
+
0,
|
156
|
+
limit,
|
157
|
+
&block)
|
137
158
|
|
138
159
|
control = find_paged_results_control(@controls)
|
139
160
|
break if control.nil?
|
140
|
-
returned_size, cookie = control.decode
|
141
|
-
returned_size = returned_size.to_i
|
142
|
-
page_size = returned_size if returned_size > 0
|
143
161
|
|
162
|
+
_estimated_result_set_size, cookie = control.decode
|
144
163
|
break if cookie.empty?
|
145
164
|
end
|
146
165
|
end
|
@@ -69,25 +69,16 @@ module ActiveLdap
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def search(options={})
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
else
|
76
|
-
paged_results_supported = false
|
77
|
-
end
|
78
|
-
super(options) do |base, scope, filter, attrs, limit|
|
72
|
+
super(options) do |search_options|
|
73
|
+
scope = search_options[:scope]
|
74
|
+
info = search_options.merge(scope: scope_name(scope))
|
79
75
|
args = {
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
83
|
-
:attributes
|
84
|
-
:
|
85
|
-
:
|
86
|
-
}
|
87
|
-
info = {
|
88
|
-
:base => base, :scope => scope_name(scope),
|
89
|
-
:filter => filter, :attributes => attrs, :limit => limit,
|
90
|
-
:paged_results_supported => paged_results_supported,
|
76
|
+
base: search_options[:base],
|
77
|
+
scope: scope,
|
78
|
+
filter: search_options[:filter],
|
79
|
+
attributes: search_options[:attributes],
|
80
|
+
size: search_options[:limit],
|
81
|
+
paged_searcheds_supported: search_options[:paged_results_supported],
|
91
82
|
}
|
92
83
|
execute(:search, info, args) do |entry|
|
93
84
|
attributes = {}
|
@@ -48,6 +48,15 @@ module ActiveLdap
|
|
48
48
|
DEFAULT_CONFIG[:retry_on_timeout] = true
|
49
49
|
DEFAULT_CONFIG[:follow_referrals] = true
|
50
50
|
|
51
|
+
# 500 is the default size limit value of OpenLDAP 2.4:
|
52
|
+
# https://openldap.org/doc/admin24/limits.html#Global%20Limits
|
53
|
+
#
|
54
|
+
# We may change this when we find LDAP server that its the default
|
55
|
+
# size limit is smaller than 500.
|
56
|
+
DEFAULT_CONFIG[:page_size] = 500
|
57
|
+
# Whether using paged results if available.
|
58
|
+
DEFAULT_CONFIG[:use_paged_results] = true
|
59
|
+
|
51
60
|
DEFAULT_CONFIG[:logger] = nil
|
52
61
|
|
53
62
|
module ClassMethods
|
@@ -103,8 +112,22 @@ module ActiveLdap
|
|
103
112
|
end
|
104
113
|
end
|
105
114
|
|
115
|
+
def parent_configuration(target)
|
116
|
+
if target.is_a?(Base)
|
117
|
+
target = target.class
|
118
|
+
else
|
119
|
+
target = target.superclass
|
120
|
+
end
|
121
|
+
while target <= Base
|
122
|
+
config = defined_configurations[target.active_connection_key]
|
123
|
+
return config if config
|
124
|
+
target = target.superclass
|
125
|
+
end
|
126
|
+
default_configuration
|
127
|
+
end
|
128
|
+
|
106
129
|
def merge_configuration(user_configuration, target=self)
|
107
|
-
configuration =
|
130
|
+
configuration = parent_configuration(target).dup
|
108
131
|
prepare_configuration(user_configuration).each do |key, value|
|
109
132
|
case key
|
110
133
|
when :base
|
@@ -165,11 +165,11 @@ module ActiveLdap
|
|
165
165
|
connection.schema
|
166
166
|
end
|
167
167
|
|
168
|
-
private
|
169
168
|
def active_connection_key(k=self)
|
170
169
|
k.name.blank? ? k.object_id : k.name
|
171
170
|
end
|
172
171
|
|
172
|
+
private
|
173
173
|
def determine_active_connection_name
|
174
174
|
key = active_connection_key
|
175
175
|
if active_connections[key] or configuration(key)
|