powerhome-activeldap 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +6 -0
- data/COPYING +340 -0
- data/Gemfile +12 -0
- data/LICENSE +59 -0
- data/README.textile +140 -0
- data/TODO +32 -0
- data/benchmark/README.md +64 -0
- data/benchmark/bench-backend.rb +247 -0
- data/benchmark/bench-instantiate.rb +98 -0
- data/benchmark/config.yaml.sample +5 -0
- data/doc/text/development.textile +54 -0
- data/doc/text/news.textile +811 -0
- data/doc/text/rails.textile +144 -0
- data/doc/text/tutorial.textile +1010 -0
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +53 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +50 -0
- data/examples/usermod-binary-add-time +54 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +85 -0
- data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
- data/lib/active_ldap/acts/tree.rb +78 -0
- data/lib/active_ldap/adapter/base.rb +707 -0
- data/lib/active_ldap/adapter/jndi.rb +184 -0
- data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
- data/lib/active_ldap/adapter/ldap.rb +290 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
- data/lib/active_ldap/adapter/net_ldap.rb +309 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
- data/lib/active_ldap/association/belongs_to.rb +47 -0
- data/lib/active_ldap/association/belongs_to_many.rb +58 -0
- data/lib/active_ldap/association/children.rb +21 -0
- data/lib/active_ldap/association/collection.rb +105 -0
- data/lib/active_ldap/association/has_many.rb +31 -0
- data/lib/active_ldap/association/has_many_utils.rb +44 -0
- data/lib/active_ldap/association/has_many_wrap.rb +75 -0
- data/lib/active_ldap/association/proxy.rb +107 -0
- data/lib/active_ldap/associations.rb +205 -0
- data/lib/active_ldap/attribute_methods.rb +23 -0
- data/lib/active_ldap/attribute_methods/before_type_cast.rb +24 -0
- data/lib/active_ldap/attribute_methods/dirty.rb +43 -0
- data/lib/active_ldap/attribute_methods/query.rb +31 -0
- data/lib/active_ldap/attribute_methods/read.rb +44 -0
- data/lib/active_ldap/attribute_methods/write.rb +38 -0
- data/lib/active_ldap/attributes.rb +176 -0
- data/lib/active_ldap/base.rb +1410 -0
- data/lib/active_ldap/callbacks.rb +71 -0
- data/lib/active_ldap/command.rb +49 -0
- data/lib/active_ldap/compatible.rb +44 -0
- data/lib/active_ldap/configuration.rb +147 -0
- data/lib/active_ldap/connection.rb +299 -0
- data/lib/active_ldap/distinguished_name.rb +291 -0
- data/lib/active_ldap/entry_attribute.rb +78 -0
- data/lib/active_ldap/escape.rb +12 -0
- data/lib/active_ldap/get_text.rb +20 -0
- data/lib/active_ldap/get_text/parser.rb +161 -0
- data/lib/active_ldap/helper.rb +92 -0
- data/lib/active_ldap/human_readable.rb +133 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/ldif.rb +930 -0
- data/lib/active_ldap/log_subscriber.rb +50 -0
- data/lib/active_ldap/object_class.rb +95 -0
- data/lib/active_ldap/operations.rb +624 -0
- data/lib/active_ldap/persistence.rb +100 -0
- data/lib/active_ldap/populate.rb +53 -0
- data/lib/active_ldap/railtie.rb +43 -0
- data/lib/active_ldap/railties/controller_runtime.rb +48 -0
- data/lib/active_ldap/schema.rb +701 -0
- data/lib/active_ldap/schema/syntaxes.rb +422 -0
- data/lib/active_ldap/timeout.rb +75 -0
- data/lib/active_ldap/timeout_stub.rb +17 -0
- data/lib/active_ldap/user_password.rb +99 -0
- data/lib/active_ldap/validations.rb +200 -0
- data/lib/active_ldap/version.rb +3 -0
- data/lib/active_ldap/xml.rb +139 -0
- data/lib/rails/generators/active_ldap/model/USAGE +18 -0
- data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
- data/lib/rails/generators/active_ldap/model/templates/model_active_ldap.rb +3 -0
- data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
- data/lib/rails/generators/active_ldap/scaffold/templates/ldap.yml +19 -0
- data/po/en/active-ldap.po +4029 -0
- data/po/ja/active-ldap.po +4060 -0
- data/test/add-phonetic-attribute-options-to-slapd.ldif +10 -0
- data/test/al-test-utils.rb +428 -0
- data/test/command.rb +111 -0
- data/test/config.yaml.sample +6 -0
- data/test/fixtures/lower_case_object_class_schema.rb +802 -0
- data/test/run-test.rb +34 -0
- data/test/test_acts_as_tree.rb +60 -0
- data/test/test_adapter.rb +121 -0
- data/test/test_associations.rb +701 -0
- data/test/test_attributes.rb +117 -0
- data/test/test_base.rb +1214 -0
- data/test/test_base_per_instance.rb +61 -0
- data/test/test_bind.rb +62 -0
- data/test/test_callback.rb +31 -0
- data/test/test_configuration.rb +40 -0
- data/test/test_connection.rb +82 -0
- data/test/test_connection_per_class.rb +112 -0
- data/test/test_connection_per_dn.rb +112 -0
- data/test/test_dirty.rb +98 -0
- data/test/test_dn.rb +172 -0
- data/test/test_find.rb +176 -0
- data/test/test_groupadd.rb +50 -0
- data/test/test_groupdel.rb +46 -0
- data/test/test_groupls.rb +107 -0
- data/test/test_groupmod.rb +51 -0
- data/test/test_ldif.rb +1890 -0
- data/test/test_load.rb +133 -0
- data/test/test_lpasswd.rb +75 -0
- data/test/test_object_class.rb +74 -0
- data/test/test_persistence.rb +131 -0
- data/test/test_reflection.rb +175 -0
- data/test/test_schema.rb +559 -0
- data/test/test_syntax.rb +444 -0
- data/test/test_user.rb +217 -0
- data/test/test_user_password.rb +108 -0
- data/test/test_useradd-binary.rb +62 -0
- data/test/test_useradd.rb +57 -0
- data/test/test_userdel.rb +48 -0
- data/test/test_userls.rb +91 -0
- data/test/test_usermod-binary-add-time.rb +65 -0
- data/test/test_usermod-binary-add.rb +64 -0
- data/test/test_usermod-binary-del.rb +66 -0
- data/test/test_usermod-lang-add.rb +59 -0
- data/test/test_usermod.rb +58 -0
- data/test/test_validation.rb +274 -0
- metadata +379 -0
@@ -0,0 +1,707 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
require 'active_ldap/schema'
|
4
|
+
require 'active_ldap/entry_attribute'
|
5
|
+
require 'active_ldap/ldap_error'
|
6
|
+
|
7
|
+
module ActiveLdap
|
8
|
+
module Adapter
|
9
|
+
class Base
|
10
|
+
include GetTextSupport
|
11
|
+
|
12
|
+
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :timeout,
|
13
|
+
:retry_on_timeout, :retry_limit,
|
14
|
+
:retry_wait, :bind_dn, :password,
|
15
|
+
:password_block, :try_sasl,
|
16
|
+
:sasl_mechanisms, :sasl_quiet,
|
17
|
+
:allow_anonymous, :store_password,
|
18
|
+
:scope, :sasl_options]
|
19
|
+
|
20
|
+
@@row_even = true
|
21
|
+
|
22
|
+
attr_reader :runtime
|
23
|
+
def initialize(configuration={})
|
24
|
+
@runtime = 0
|
25
|
+
@connection = nil
|
26
|
+
@disconnected = false
|
27
|
+
@bound = false
|
28
|
+
@bind_tried = false
|
29
|
+
@entry_attributes = {}
|
30
|
+
@configuration = configuration.dup
|
31
|
+
@logger = @configuration.delete(:logger)
|
32
|
+
@configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
|
33
|
+
VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
|
34
|
+
instance_variable_set("@#{name}", configuration[name])
|
35
|
+
end
|
36
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_runtime
|
40
|
+
runtime, @runtime = @runtime, 0
|
41
|
+
runtime
|
42
|
+
end
|
43
|
+
|
44
|
+
def connect(options={})
|
45
|
+
host = options[:host] || @host
|
46
|
+
method = options[:method] || @method || :plain
|
47
|
+
port = options[:port] || @port || ensure_port(method)
|
48
|
+
method = ensure_method(method)
|
49
|
+
@disconnected = false
|
50
|
+
@bound = false
|
51
|
+
@bind_tried = false
|
52
|
+
@connection, @uri, @with_start_tls = yield(host, port, method)
|
53
|
+
prepare_connection(options)
|
54
|
+
bind(options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def disconnect!(options={})
|
58
|
+
unbind(options)
|
59
|
+
@connection = @uri = @with_start_tls = nil
|
60
|
+
@disconnected = true
|
61
|
+
end
|
62
|
+
|
63
|
+
def rebind(options={})
|
64
|
+
unbind(options) if bound?
|
65
|
+
connect(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def bind(options={})
|
69
|
+
@bind_tried = true
|
70
|
+
|
71
|
+
bind_dn = ensure_dn_string(options[:bind_dn] || @bind_dn)
|
72
|
+
try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
|
73
|
+
if options.has_key?(:allow_anonymous)
|
74
|
+
allow_anonymous = options[:allow_anonymous]
|
75
|
+
else
|
76
|
+
allow_anonymous = @allow_anonymous
|
77
|
+
end
|
78
|
+
options = options.merge(:allow_anonymous => allow_anonymous)
|
79
|
+
|
80
|
+
# Rough bind loop:
|
81
|
+
# Attempt 1: SASL if available
|
82
|
+
# Attempt 2: SIMPLE with credentials if password block
|
83
|
+
# Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
|
84
|
+
if try_sasl and sasl_bind(bind_dn, options)
|
85
|
+
@logger.info {_('Bound to %s by SASL as %s') % [target, bind_dn]}
|
86
|
+
elsif simple_bind(bind_dn, options)
|
87
|
+
@logger.info {_('Bound to %s by simple as %s') % [target, bind_dn]}
|
88
|
+
elsif allow_anonymous and bind_as_anonymous(options)
|
89
|
+
@logger.info {_('Bound to %s as anonymous') % target}
|
90
|
+
else
|
91
|
+
message = yield if block_given?
|
92
|
+
message ||= _('All authentication methods for %s exhausted.') % target
|
93
|
+
raise AuthenticationError, message
|
94
|
+
end
|
95
|
+
|
96
|
+
@bound = true
|
97
|
+
@bound
|
98
|
+
end
|
99
|
+
|
100
|
+
def unbind(options={})
|
101
|
+
yield if @connection and (@bind_tried or bound?)
|
102
|
+
@bind_tried = @bound = false
|
103
|
+
end
|
104
|
+
|
105
|
+
def bind_as_anonymous(options={})
|
106
|
+
yield
|
107
|
+
end
|
108
|
+
|
109
|
+
def connecting?
|
110
|
+
!@connection.nil? and !@disconnected
|
111
|
+
end
|
112
|
+
|
113
|
+
def bound?
|
114
|
+
connecting? and @bound
|
115
|
+
end
|
116
|
+
|
117
|
+
def schema(options={})
|
118
|
+
@schema ||= operation(options) do
|
119
|
+
base = options[:base]
|
120
|
+
attrs = options[:attributes]
|
121
|
+
|
122
|
+
attrs ||= [
|
123
|
+
'objectClasses',
|
124
|
+
'attributeTypes',
|
125
|
+
'matchingRules',
|
126
|
+
'matchingRuleUse',
|
127
|
+
'dITStructureRules',
|
128
|
+
'dITContentRules',
|
129
|
+
'nameForms',
|
130
|
+
'ldapSyntaxes',
|
131
|
+
#'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER.
|
132
|
+
]
|
133
|
+
base ||= root_dse_values('subschemaSubentry', options)[0]
|
134
|
+
base ||= 'cn=schema'
|
135
|
+
schema = nil
|
136
|
+
search(:base => base,
|
137
|
+
:scope => :base,
|
138
|
+
:filter => '(objectClass=subschema)',
|
139
|
+
:attributes => attrs,
|
140
|
+
:limit => 1) do |dn, attributes|
|
141
|
+
schema = Schema.new(attributes)
|
142
|
+
end
|
143
|
+
schema || Schema.new([])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def naming_contexts
|
148
|
+
root_dse_values('namingContexts')
|
149
|
+
end
|
150
|
+
|
151
|
+
def entry_attribute(object_classes)
|
152
|
+
@entry_attributes[object_classes.uniq.sort] ||=
|
153
|
+
EntryAttribute.new(schema, object_classes)
|
154
|
+
end
|
155
|
+
|
156
|
+
def search(options={})
|
157
|
+
filter = parse_filter(options[:filter]) || 'objectClass=*'
|
158
|
+
attrs = options[:attributes] || []
|
159
|
+
scope = ensure_scope(options[:scope] || @scope)
|
160
|
+
base = options[:base]
|
161
|
+
limit = options[:limit] || 0
|
162
|
+
limit = nil if limit <= 0
|
163
|
+
|
164
|
+
attrs = attrs.to_a # just in case
|
165
|
+
base = ensure_dn_string(base)
|
166
|
+
begin
|
167
|
+
operation(options) do
|
168
|
+
yield(base, scope, filter, attrs, limit)
|
169
|
+
end
|
170
|
+
rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax
|
171
|
+
# Do nothing on failure
|
172
|
+
@logger.info do
|
173
|
+
args = [$!.class, $!.message, filter, attrs.inspect]
|
174
|
+
_("Ignore error %s(%s): filter %s: attributes: %s") % args
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def delete(targets, options={})
|
180
|
+
targets = [targets] unless targets.is_a?(Array)
|
181
|
+
return if targets.empty?
|
182
|
+
begin
|
183
|
+
operation(options) do
|
184
|
+
targets.each do |target|
|
185
|
+
target = ensure_dn_string(target)
|
186
|
+
begin
|
187
|
+
yield(target)
|
188
|
+
rescue LdapError::UnwillingToPerform, LdapError::InsufficientAccess
|
189
|
+
raise OperationNotPermitted, _("%s: %s") % [$!.message, target]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
rescue LdapError::NoSuchObject
|
194
|
+
raise EntryNotFound, _("No such entry: %s") % target
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def add(dn, entries, options={})
|
199
|
+
dn = ensure_dn_string(dn)
|
200
|
+
begin
|
201
|
+
operation(options) do
|
202
|
+
yield(dn, entries)
|
203
|
+
end
|
204
|
+
rescue LdapError::NoSuchObject
|
205
|
+
raise EntryNotFound, _("No such entry: %s") % dn
|
206
|
+
rescue LdapError::InvalidDnSyntax
|
207
|
+
raise DistinguishedNameInvalid.new(dn)
|
208
|
+
rescue LdapError::AlreadyExists
|
209
|
+
raise EntryAlreadyExist, _("%s: %s") % [$!.message, dn]
|
210
|
+
rescue LdapError::StrongAuthRequired
|
211
|
+
raise StrongAuthenticationRequired, _("%s: %s") % [$!.message, dn]
|
212
|
+
rescue LdapError::ObjectClassViolation
|
213
|
+
raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
|
214
|
+
rescue LdapError::UnwillingToPerform
|
215
|
+
raise OperationNotPermitted, _("%s: %s") % [$!.message, dn]
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def modify(dn, entries, options={})
|
220
|
+
dn = ensure_dn_string(dn)
|
221
|
+
begin
|
222
|
+
operation(options) do
|
223
|
+
begin
|
224
|
+
yield(dn, entries)
|
225
|
+
rescue LdapError::UnwillingToPerform, LdapError::InsufficientAccess
|
226
|
+
raise OperationNotPermitted, _("%s: %s") % [$!.message, target]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
rescue LdapError::UndefinedType
|
230
|
+
raise
|
231
|
+
rescue LdapError::ObjectClassViolation
|
232
|
+
raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
|
237
|
+
dn = ensure_dn_string(dn)
|
238
|
+
operation(options) do
|
239
|
+
yield(dn, new_rdn, delete_old_rdn, new_superior)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def log_info(name, runtime_in_seconds, info=nil)
|
244
|
+
return unless @logger
|
245
|
+
return unless @logger.debug?
|
246
|
+
message = "LDAP: #{name} (#{'%.1f' % (runtime_in_seconds * 1000)}ms)"
|
247
|
+
@logger.debug(format_log_entry(message, info))
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
def ensure_port(method)
|
252
|
+
if method == :ssl
|
253
|
+
URI::LDAPS::DEFAULT_PORT
|
254
|
+
else
|
255
|
+
URI::LDAP::DEFAULT_PORT
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def prepare_connection(options)
|
260
|
+
end
|
261
|
+
|
262
|
+
def operation(options)
|
263
|
+
retried = false
|
264
|
+
options = options.dup
|
265
|
+
options[:try_reconnect] = true unless options.has_key?(:try_reconnect)
|
266
|
+
try_reconnect = false
|
267
|
+
begin
|
268
|
+
reconnect_if_need(options)
|
269
|
+
try_reconnect = options[:try_reconnect]
|
270
|
+
with_timeout(try_reconnect, options) do
|
271
|
+
yield
|
272
|
+
end
|
273
|
+
rescue ConnectionError
|
274
|
+
if try_reconnect and !retried
|
275
|
+
retried = true
|
276
|
+
@disconnected = true
|
277
|
+
retry
|
278
|
+
else
|
279
|
+
raise
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def need_credential_sasl_mechanism?(mechanism)
|
285
|
+
not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism)
|
286
|
+
end
|
287
|
+
|
288
|
+
def password(bind_dn, options={})
|
289
|
+
passwd = options[:password] || @password
|
290
|
+
return passwd if passwd
|
291
|
+
|
292
|
+
password_block = options[:password_block] || @password_block
|
293
|
+
# TODO: Give a warning to reconnect users with password clearing
|
294
|
+
# Get the passphrase for the first time, or anew if we aren't storing
|
295
|
+
if password_block.respond_to?(:call)
|
296
|
+
passwd = password_block.call(bind_dn)
|
297
|
+
else
|
298
|
+
@logger.error {_('password_block not nil or Proc object. Ignoring.')}
|
299
|
+
return nil
|
300
|
+
end
|
301
|
+
|
302
|
+
# Store the password for quick reference later
|
303
|
+
if options.has_key?(:store_password)
|
304
|
+
store_password = options[:store_password]
|
305
|
+
else
|
306
|
+
store_password = @store_password
|
307
|
+
end
|
308
|
+
@password = store_password ? passwd : nil
|
309
|
+
|
310
|
+
passwd
|
311
|
+
end
|
312
|
+
|
313
|
+
def with_timeout(try_reconnect=true, options={}, &block)
|
314
|
+
n_retries = 0
|
315
|
+
retry_limit = options[:retry_limit] || @retry_limit
|
316
|
+
begin
|
317
|
+
do_in_timeout(@timeout, &block)
|
318
|
+
rescue Timeout::Error => e
|
319
|
+
@logger.error {_('Requested action timed out.')}
|
320
|
+
if @retry_on_timeout and retry_limit < 0 and n_retries <= retry_limit
|
321
|
+
if connecting?
|
322
|
+
retry
|
323
|
+
elsif try_reconnect
|
324
|
+
retry if with_timeout(false, options) {reconnect(options)}
|
325
|
+
end
|
326
|
+
end
|
327
|
+
@logger.error {e.message}
|
328
|
+
raise TimeoutError, e.message
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def do_in_timeout(timeout, &block)
|
333
|
+
Timeout.alarm(timeout, &block)
|
334
|
+
end
|
335
|
+
|
336
|
+
def sasl_bind(bind_dn, options={})
|
337
|
+
# Get all SASL mechanisms
|
338
|
+
mechanisms = operation(options) do
|
339
|
+
root_dse_values("supportedSASLMechanisms")
|
340
|
+
end
|
341
|
+
|
342
|
+
if options.has_key?(:sasl_quiet)
|
343
|
+
sasl_quiet = options[:sasl_quiet]
|
344
|
+
else
|
345
|
+
sasl_quiet = @sasl_quiet
|
346
|
+
end
|
347
|
+
|
348
|
+
sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
|
349
|
+
sasl_mechanisms.each do |mechanism|
|
350
|
+
next unless mechanisms.include?(mechanism)
|
351
|
+
return true if yield(bind_dn, mechanism, sasl_quiet)
|
352
|
+
end
|
353
|
+
false
|
354
|
+
end
|
355
|
+
|
356
|
+
def simple_bind(bind_dn, options={})
|
357
|
+
return false unless bind_dn
|
358
|
+
|
359
|
+
passwd = password(bind_dn, options)
|
360
|
+
return false unless passwd
|
361
|
+
|
362
|
+
if passwd.empty?
|
363
|
+
if options[:allow_anonymous]
|
364
|
+
@logger.info {_("Skip simple bind with empty password.")}
|
365
|
+
return false
|
366
|
+
else
|
367
|
+
raise AuthenticationError,
|
368
|
+
_("Can't use empty password for simple bind.")
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
begin
|
373
|
+
yield(bind_dn, passwd)
|
374
|
+
rescue LdapError::InvalidDnSyntax
|
375
|
+
raise DistinguishedNameInvalid.new(bind_dn)
|
376
|
+
rescue LdapError::InvalidCredentials
|
377
|
+
false
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def parse_filter(filter, operator=nil)
|
382
|
+
return nil if filter.nil?
|
383
|
+
if !filter.is_a?(String) and !filter.respond_to?(:collect)
|
384
|
+
filter = filter.to_s
|
385
|
+
end
|
386
|
+
|
387
|
+
case filter
|
388
|
+
when String
|
389
|
+
parse_filter_string(filter)
|
390
|
+
when Hash
|
391
|
+
components = filter.sort_by {|k, v| k.to_s}.collect do |key, value|
|
392
|
+
construct_component(key, value, operator)
|
393
|
+
end
|
394
|
+
construct_filter(components, operator)
|
395
|
+
else
|
396
|
+
operator, components = normalize_array_filter(filter, operator)
|
397
|
+
components = construct_components(components, operator)
|
398
|
+
construct_filter(components, operator)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def parse_filter_string(filter)
|
403
|
+
if /\A\s*\z/.match(filter)
|
404
|
+
nil
|
405
|
+
else
|
406
|
+
if filter[0, 1] == "("
|
407
|
+
filter
|
408
|
+
else
|
409
|
+
"(#{filter})"
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def normalize_array_filter(filter, operator=nil)
|
415
|
+
filter_operator, *components = filter
|
416
|
+
if filter_logical_operator?(filter_operator)
|
417
|
+
operator = filter_operator
|
418
|
+
else
|
419
|
+
components.unshift(filter_operator)
|
420
|
+
components = [components] unless filter_operator.is_a?(Array)
|
421
|
+
end
|
422
|
+
[operator, components]
|
423
|
+
end
|
424
|
+
|
425
|
+
def extract_filter_value_options(value)
|
426
|
+
options = {}
|
427
|
+
if value.is_a?(Array)
|
428
|
+
case value[0]
|
429
|
+
when Hash
|
430
|
+
options = value[0]
|
431
|
+
value = value[1]
|
432
|
+
when "=", "~=", "<=", ">="
|
433
|
+
options[:operator] = value[0]
|
434
|
+
if value.size > 2
|
435
|
+
value = value[1..-1]
|
436
|
+
else
|
437
|
+
value = value[1]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
[value, options]
|
442
|
+
end
|
443
|
+
|
444
|
+
def construct_components(components, operator)
|
445
|
+
components.collect do |component|
|
446
|
+
if component.is_a?(Array)
|
447
|
+
if filter_logical_operator?(component[0])
|
448
|
+
parse_filter(component)
|
449
|
+
elsif component.size == 2
|
450
|
+
key, value = component
|
451
|
+
if value.is_a?(Hash)
|
452
|
+
parse_filter(value, key)
|
453
|
+
else
|
454
|
+
construct_component(key, value, operator)
|
455
|
+
end
|
456
|
+
else
|
457
|
+
construct_component(component[0], component[1..-1], operator)
|
458
|
+
end
|
459
|
+
elsif component.is_a?(Symbol)
|
460
|
+
assert_filter_logical_operator(component)
|
461
|
+
nil
|
462
|
+
else
|
463
|
+
parse_filter(component, operator)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def construct_component(key, value, operator=nil)
|
469
|
+
value, options = extract_filter_value_options(value)
|
470
|
+
comparison_operator = options[:operator] || "="
|
471
|
+
if collection?(value)
|
472
|
+
return nil if value.empty?
|
473
|
+
operator, value = normalize_array_filter(value, operator)
|
474
|
+
values = []
|
475
|
+
value.each do |val|
|
476
|
+
if collection?(val)
|
477
|
+
values.concat(val.collect {|v| [key, comparison_operator, v]})
|
478
|
+
else
|
479
|
+
values << [key, comparison_operator, val]
|
480
|
+
end
|
481
|
+
end
|
482
|
+
values[0] = values[0][1] if filter_logical_operator?(values[0][1])
|
483
|
+
parse_filter(values, operator)
|
484
|
+
else
|
485
|
+
[
|
486
|
+
"(",
|
487
|
+
escape_filter_key(key),
|
488
|
+
comparison_operator,
|
489
|
+
escape_filter_value(value, options),
|
490
|
+
")"
|
491
|
+
].join
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def escape_filter_key(key)
|
496
|
+
escape_filter_value(key.to_s)
|
497
|
+
end
|
498
|
+
|
499
|
+
def escape_filter_value(value, options={})
|
500
|
+
case value
|
501
|
+
when Numeric, DN
|
502
|
+
value = value.to_s
|
503
|
+
when Time
|
504
|
+
value = Schema::GeneralizedTime.new.normalize_value(value)
|
505
|
+
end
|
506
|
+
value.gsub(/(?:[:()\\\0]|\*\*?)/) do |s|
|
507
|
+
if s == "*"
|
508
|
+
s
|
509
|
+
else
|
510
|
+
s = "*" if s == "**"
|
511
|
+
if s.respond_to?(:getbyte)
|
512
|
+
"\\%02X" % s.getbyte(0)
|
513
|
+
else
|
514
|
+
"\\%02X" % s[0]
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def construct_filter(components, operator=nil)
|
521
|
+
operator = normalize_filter_logical_operator(operator)
|
522
|
+
components = components.compact
|
523
|
+
case components.size
|
524
|
+
when 0
|
525
|
+
nil
|
526
|
+
when 1
|
527
|
+
filter = components[0]
|
528
|
+
filter = "(!#{filter})" if operator == :not
|
529
|
+
filter
|
530
|
+
else
|
531
|
+
"(#{operator == :and ? '&' : '|'}#{components.join})"
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
def collection?(object)
|
536
|
+
!object.is_a?(String) and object.respond_to?(:each)
|
537
|
+
end
|
538
|
+
|
539
|
+
LOGICAL_OPERATORS = [:and, :or, :not, :&, :|]
|
540
|
+
def filter_logical_operator?(operator)
|
541
|
+
LOGICAL_OPERATORS.include?(operator)
|
542
|
+
end
|
543
|
+
|
544
|
+
def normalize_filter_logical_operator(operator)
|
545
|
+
assert_filter_logical_operator(operator)
|
546
|
+
case (operator || :and)
|
547
|
+
when :and, :&
|
548
|
+
:and
|
549
|
+
when :or, :|
|
550
|
+
:or
|
551
|
+
else
|
552
|
+
:not
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def assert_filter_logical_operator(operator)
|
557
|
+
return if operator.nil?
|
558
|
+
unless filter_logical_operator?(operator)
|
559
|
+
raise ArgumentError,
|
560
|
+
_("invalid logical operator: %s: available operators: %s") %
|
561
|
+
[operator.inspect, LOGICAL_OPERATORS.inspect]
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# Attempts to reconnect up to the number of times allowed
|
566
|
+
# If forced, try once then fail with ConnectionError if not connected.
|
567
|
+
def reconnect(options={})
|
568
|
+
options = options.dup
|
569
|
+
force = options[:force]
|
570
|
+
retry_limit = options[:retry_limit] || @retry_limit
|
571
|
+
retry_wait = options[:retry_wait] || @retry_wait
|
572
|
+
options[:reconnect_attempts] ||= 0
|
573
|
+
|
574
|
+
loop do
|
575
|
+
@logger.debug {_('Attempting to reconnect')}
|
576
|
+
disconnect!
|
577
|
+
|
578
|
+
# Reset the attempts if this was forced.
|
579
|
+
options[:reconnect_attempts] = 0 if force
|
580
|
+
options[:reconnect_attempts] += 1 if retry_limit >= 0
|
581
|
+
begin
|
582
|
+
connect(options)
|
583
|
+
break
|
584
|
+
rescue AuthenticationError
|
585
|
+
raise
|
586
|
+
rescue => detail
|
587
|
+
@logger.error do
|
588
|
+
_("Reconnect to server failed: %s\n" \
|
589
|
+
"Reconnect to server failed backtrace:\n" \
|
590
|
+
"%s") % [detail.exception, detail.backtrace.join("\n")]
|
591
|
+
end
|
592
|
+
# Do not loop if forced
|
593
|
+
raise ConnectionError, detail.message if force
|
594
|
+
end
|
595
|
+
|
596
|
+
unless can_reconnect?(options)
|
597
|
+
raise ConnectionError,
|
598
|
+
_('Giving up trying to reconnect to LDAP server.')
|
599
|
+
end
|
600
|
+
|
601
|
+
# Sleep before looping
|
602
|
+
sleep retry_wait
|
603
|
+
end
|
604
|
+
|
605
|
+
true
|
606
|
+
end
|
607
|
+
|
608
|
+
def reconnect_if_need(options={})
|
609
|
+
return if connecting?
|
610
|
+
with_timeout(false, options) do
|
611
|
+
reconnect(options)
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
# Determine if we have exceed the retry limit or not.
|
616
|
+
# True is reconnecting is allowed - False if not.
|
617
|
+
def can_reconnect?(options={})
|
618
|
+
retry_limit = options[:retry_limit] || @retry_limit
|
619
|
+
reconnect_attempts = options[:reconnect_attempts] || 0
|
620
|
+
|
621
|
+
retry_limit < 0 or reconnect_attempts <= retry_limit
|
622
|
+
end
|
623
|
+
|
624
|
+
def root_dse_values(key, options={})
|
625
|
+
dse = root_dse([key], options)
|
626
|
+
return [] if dse.nil?
|
627
|
+
normalized_key = key.downcase
|
628
|
+
dse.each do |_key, _value|
|
629
|
+
return _value if _key.downcase == normalized_key
|
630
|
+
end
|
631
|
+
[]
|
632
|
+
end
|
633
|
+
|
634
|
+
def root_dse(attrs, options={})
|
635
|
+
found_attributes = nil
|
636
|
+
search(:base => "",
|
637
|
+
:scope => :base,
|
638
|
+
:attributes => attrs,
|
639
|
+
:limit => 1) do |dn, attributes|
|
640
|
+
found_attributes = attributes
|
641
|
+
end
|
642
|
+
found_attributes
|
643
|
+
end
|
644
|
+
|
645
|
+
def construct_uri(host, port, ssl)
|
646
|
+
protocol = ssl ? "ldaps" : "ldap"
|
647
|
+
URI.parse("#{protocol}://#{host}:#{port}").to_s
|
648
|
+
end
|
649
|
+
|
650
|
+
def target
|
651
|
+
return nil if @uri.nil?
|
652
|
+
if @with_start_tls
|
653
|
+
"#{@uri}(StartTLS)"
|
654
|
+
else
|
655
|
+
@uri
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
def log(name, info=nil)
|
660
|
+
if block_given?
|
661
|
+
result = nil
|
662
|
+
@instrumenter.instrument(
|
663
|
+
"log_info.active_ldap",
|
664
|
+
:info => info,
|
665
|
+
:name => name) { result = yield }
|
666
|
+
result
|
667
|
+
else
|
668
|
+
log_info(name, 0, info)
|
669
|
+
nil
|
670
|
+
end
|
671
|
+
rescue Exception
|
672
|
+
log_info("#{name}: FAILED", 0,
|
673
|
+
(info || {}).merge(:error => $!.class.name,
|
674
|
+
:error_message => $!.message))
|
675
|
+
raise
|
676
|
+
end
|
677
|
+
|
678
|
+
def format_log_entry(message, info=nil)
|
679
|
+
if ActiveLdap::Base.colorize_logging
|
680
|
+
if @@row_even
|
681
|
+
message_color, dump_color = "4;36;1", "0;1"
|
682
|
+
else
|
683
|
+
@@row_even = true
|
684
|
+
message_color, dump_color = "4;35;1", "0"
|
685
|
+
end
|
686
|
+
@@row_even = !@@row_even
|
687
|
+
|
688
|
+
log_entry = " \e[#{message_color}m#{message}\e[0m"
|
689
|
+
log_entry << ": \e[#{dump_color}m#{info.inspect}\e[0m" if info
|
690
|
+
log_entry
|
691
|
+
else
|
692
|
+
log_entry = message
|
693
|
+
log_entry += ": #{info.inspect}" if info
|
694
|
+
log_entry
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
def ensure_dn_string(dn)
|
699
|
+
if dn.is_a?(DN)
|
700
|
+
dn.to_s
|
701
|
+
else
|
702
|
+
dn
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|