activeldap 5.2.3 → 6.0.3
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} +345 -271
- data/doc/text/{rails.textile → rails.md} +35 -33
- data/doc/text/{tutorial.textile → tutorial.md} +177 -185
- data/lib/active_ldap.rb +0 -1
- data/lib/active_ldap/adapter/base.rb +29 -10
- data/lib/active_ldap/adapter/jndi.rb +21 -9
- data/lib/active_ldap/adapter/jndi_connection.rb +83 -20
- data/lib/active_ldap/adapter/ldap.rb +13 -22
- data/lib/active_ldap/adapter/ldap_ext.rb +32 -13
- data/lib/active_ldap/adapter/net_ldap.rb +9 -18
- data/lib/active_ldap/base.rb +11 -1
- 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/persistence.rb +3 -2
- data/lib/active_ldap/validations.rb +12 -4
- data/lib/active_ldap/version.rb +1 -1
- data/test/add-phonetic-attribute-options-to-slapd.ldif +1 -1
- data/test/al-test-utils.rb +125 -38
- data/test/enable-dynamic-groups.ldif +22 -0
- data/test/enable-start-tls.ldif +1 -1
- data/test/run-test.rb +0 -4
- data/test/test_base.rb +76 -18
- data/test/test_base_per_instance.rb +33 -1
- data/test/test_connection.rb +4 -0
- data/test/test_entry.rb +1 -0
- data/test/test_find.rb +12 -2
- data/test/test_supported_control.rb +1 -1
- data/test/test_validation.rb +28 -15
- metadata +18 -32
- data/README.textile +0 -141
- data/doc/text/development.textile +0 -54
data/lib/active_ldap.rb
CHANGED
|
@@ -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 = limit != 1 && 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
|
|
@@ -662,8 +682,7 @@ module ActiveLdap
|
|
|
662
682
|
:scope => :base,
|
|
663
683
|
:attributes => attrs,
|
|
664
684
|
:limit => 1,
|
|
665
|
-
:try_reconnect => try_reconnect,
|
|
666
|
-
:use_paged_results => false) do |dn, attributes|
|
|
685
|
+
:try_reconnect => try_reconnect) do |dn, attributes|
|
|
667
686
|
found_attributes = attributes
|
|
668
687
|
end
|
|
669
688
|
found_attributes
|
|
@@ -22,9 +22,23 @@ module ActiveLdap
|
|
|
22
22
|
super do |host, port, method|
|
|
23
23
|
uri = construct_uri(host, port, method == :ssl)
|
|
24
24
|
with_start_tls = method == :start_tls
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
follow_referrals = follow_referrals?(options)
|
|
26
|
+
info = {
|
|
27
|
+
:uri => uri,
|
|
28
|
+
:with_start_tls => with_start_tls,
|
|
29
|
+
:follow_referrals => follow_referrals,
|
|
30
|
+
}
|
|
31
|
+
[
|
|
32
|
+
log("connect", info) {
|
|
33
|
+
JndiConnection.new(host,
|
|
34
|
+
port,
|
|
35
|
+
method,
|
|
36
|
+
@timeout,
|
|
37
|
+
follow_referrals)
|
|
38
|
+
},
|
|
39
|
+
uri,
|
|
40
|
+
with_start_tls,
|
|
41
|
+
]
|
|
28
42
|
end
|
|
29
43
|
end
|
|
30
44
|
|
|
@@ -46,12 +60,10 @@ module ActiveLdap
|
|
|
46
60
|
end
|
|
47
61
|
|
|
48
62
|
def search(options={}, &block)
|
|
49
|
-
super(options) do |
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
execute(:search, info, base, scope, filter, attrs, limit, &block)
|
|
63
|
+
super(options) do |search_options|
|
|
64
|
+
scope = search_options[:scope]
|
|
65
|
+
info = search_options.merge(scope: scope_name(scope))
|
|
66
|
+
execute(:search, info, search_options, &block)
|
|
55
67
|
end
|
|
56
68
|
end
|
|
57
69
|
|
|
@@ -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
|
|
@@ -73,13 +75,14 @@ module ActiveLdap
|
|
|
73
75
|
end
|
|
74
76
|
end
|
|
75
77
|
|
|
76
|
-
def initialize(host, port, method, timeout)
|
|
78
|
+
def initialize(host, port, method, timeout, follow_referrals)
|
|
77
79
|
@host = host
|
|
78
80
|
@port = port
|
|
79
81
|
@method = method
|
|
80
82
|
@timeout = timeout
|
|
81
83
|
@context = nil
|
|
82
84
|
@tls = nil
|
|
85
|
+
@follow_referrals = follow_referrals
|
|
83
86
|
end
|
|
84
87
|
|
|
85
88
|
def unbind
|
|
@@ -108,23 +111,55 @@ module ActiveLdap
|
|
|
108
111
|
bound?
|
|
109
112
|
end
|
|
110
113
|
|
|
111
|
-
def search(
|
|
114
|
+
def search(options)
|
|
115
|
+
base = options[:base]
|
|
116
|
+
scope = options[:scope]
|
|
117
|
+
filter = options[:filter]
|
|
118
|
+
attributes = options[:attributes]
|
|
119
|
+
limit = options[:limit]
|
|
120
|
+
use_paged_results = options[:use_paged_results]
|
|
121
|
+
page_size = options[:page_size]
|
|
122
|
+
|
|
112
123
|
controls = SearchControls.new
|
|
113
124
|
controls.search_scope = scope
|
|
114
125
|
|
|
115
126
|
controls.count_limit = limit if limit
|
|
116
|
-
unless
|
|
117
|
-
controls.returning_attributes =
|
|
127
|
+
unless attributes.blank?
|
|
128
|
+
controls.returning_attributes = attributes.to_java(:string)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
page_cookie = nil
|
|
132
|
+
if use_paged_results
|
|
133
|
+
# https://devdocs.io/openjdk~8/javax/naming/ldap/pagedresultscontrol
|
|
134
|
+
@context.set_request_controls([build_paged_results_control(page_size)])
|
|
135
|
+
else
|
|
136
|
+
@context.set_request_controls([])
|
|
118
137
|
end
|
|
119
138
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
end
|
|
139
|
+
escaped_base = escape_dn(base)
|
|
140
|
+
|
|
141
|
+
loop do
|
|
142
|
+
@context.search(escaped_base, filter, controls).each do |search_result|
|
|
143
|
+
yield(build_raw_search_result(search_result))
|
|
126
144
|
end
|
|
127
|
-
|
|
145
|
+
|
|
146
|
+
break unless use_paged_results
|
|
147
|
+
|
|
148
|
+
# Find the paged search cookie
|
|
149
|
+
response_controls = @context.get_response_controls
|
|
150
|
+
break unless response_controls
|
|
151
|
+
response_controls.each do |response_control|
|
|
152
|
+
next unless response_control.is_a?(PagedResultsResponseControl)
|
|
153
|
+
page_cookie = response_control.get_cookie
|
|
154
|
+
break
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
break unless page_cookie
|
|
158
|
+
|
|
159
|
+
# Set paged results control so we can keep getting results.
|
|
160
|
+
paged_results_control =
|
|
161
|
+
build_paged_results_control(page_size, page_cookie)
|
|
162
|
+
@context.set_request_controls([paged_results_control])
|
|
128
163
|
end
|
|
129
164
|
end
|
|
130
165
|
|
|
@@ -133,25 +168,32 @@ module ActiveLdap
|
|
|
133
168
|
records.each do |record|
|
|
134
169
|
attributes.put(record.to_java_attribute)
|
|
135
170
|
end
|
|
136
|
-
@context.
|
|
171
|
+
@context.set_request_controls([])
|
|
172
|
+
@context.create_subcontext(escape_dn(dn), attributes)
|
|
137
173
|
end
|
|
138
174
|
|
|
139
175
|
def modify(dn, records)
|
|
140
176
|
items = records.collect(&:to_java_modification_item)
|
|
141
|
-
@context.
|
|
177
|
+
@context.set_request_controls([])
|
|
178
|
+
@context.modify_attributes(escape_dn(dn), items.to_java(ModificationItem))
|
|
142
179
|
end
|
|
143
180
|
|
|
144
181
|
def modify_rdn(dn, new_rdn, delete_old_rdn)
|
|
145
182
|
# should use mutex
|
|
146
183
|
delete_rdn_key = "java.naming.ldap.deleteRDN"
|
|
147
|
-
@context.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
@context.set_request_controls([])
|
|
185
|
+
begin
|
|
186
|
+
@context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
|
|
187
|
+
@context.rename(escape_dn(dn), escape_dn(new_rdn))
|
|
188
|
+
ensure
|
|
189
|
+
@context.remove_from_environment(delete_rdn_key)
|
|
190
|
+
end
|
|
151
191
|
end
|
|
152
192
|
|
|
153
193
|
def delete(dn)
|
|
154
|
-
|
|
194
|
+
escaped_dn = escape_dn(dn)
|
|
195
|
+
@context.set_request_controls([])
|
|
196
|
+
@context.destroy_subcontext(escaped_dn)
|
|
155
197
|
end
|
|
156
198
|
|
|
157
199
|
private
|
|
@@ -162,9 +204,10 @@ module ActiveLdap
|
|
|
162
204
|
Context::PROVIDER_URL => ldap_uri,
|
|
163
205
|
'com.sun.jndi.ldap.connect.timeout' => (@timeout * 1000).to_i.to_s,
|
|
164
206
|
'com.sun.jndi.ldap.read.timeout' => (@timeout * 1000).to_i.to_s,
|
|
207
|
+
'java.naming.ldap.derefAliases' => 'never',
|
|
208
|
+
'java.naming.referral' => @follow_referrals ? 'follow' : 'ignore',
|
|
165
209
|
}
|
|
166
|
-
|
|
167
|
-
context = InitialLdapContext.new(environment, nil)
|
|
210
|
+
context = InitialLdapContext.new(HashTable.new(environment), nil)
|
|
168
211
|
if @method == :start_tls
|
|
169
212
|
@tls = context.extended_operation(StartTlsRequest.new)
|
|
170
213
|
@tls.negotiate
|
|
@@ -185,6 +228,26 @@ module ActiveLdap
|
|
|
185
228
|
protocol = @method == :ssl ? "ldaps" : "ldap"
|
|
186
229
|
"#{protocol}://#{@host}:#{@port}/"
|
|
187
230
|
end
|
|
231
|
+
|
|
232
|
+
def escape_dn(dn)
|
|
233
|
+
javax.naming.ldap.LdapName.new(dn)
|
|
234
|
+
rescue Java::JavaLang::IllegalArgumentException, Java::JavaxNaming::InvalidNameException
|
|
235
|
+
dn
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def build_paged_results_control(page_size, page_cookie=nil)
|
|
239
|
+
PagedResultsControl.new(page_size, page_cookie, Control::CRITICAL)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def build_raw_search_result(search_result)
|
|
243
|
+
attributes = {}
|
|
244
|
+
search_result.attributes.get_all.each do |attribute|
|
|
245
|
+
attributes[attribute.get_id] = attribute.get_all.collect do |value|
|
|
246
|
+
value.is_a?(String) ? value : String.from_java_bytes(value)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
[search_result.name_in_namespace, attributes]
|
|
250
|
+
end
|
|
188
251
|
end
|
|
189
252
|
end
|
|
190
253
|
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
|
|
@@ -220,15 +209,17 @@ module ActiveLdap
|
|
|
220
209
|
def prepare_connection(options={})
|
|
221
210
|
operation(options) do
|
|
222
211
|
@connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
212
|
+
@ldap_follow_referrals = follow_referrals?(options) ? 1 : 0
|
|
213
|
+
@connection.set_option(LDAP::LDAP_OPT_REFERRALS,
|
|
214
|
+
@ldap_follow_referrals)
|
|
226
215
|
end
|
|
227
216
|
end
|
|
228
217
|
|
|
229
218
|
def execute(method, info=nil, *args, &block)
|
|
230
219
|
begin
|
|
231
220
|
name = (info || {}).delete(:name) || method
|
|
221
|
+
@connection.set_option(LDAP::LDAP_OPT_REFERRALS,
|
|
222
|
+
@ldap_follow_referrals)
|
|
232
223
|
log(name, info) {@connection.send(method, *args, &block)}
|
|
233
224
|
rescue LDAP::ResultError
|
|
234
225
|
@connection.assert_error_code
|
|
@@ -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 = {}
|