ruby-net-ldap 0.0.1 → 0.0.2
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 +14 -0
- data/README +6 -3
- data/lib/net/ldap.rb +242 -125
- data/lib/net/ldap/entry.rb +89 -6
- data/lib/net/ldap/filter.rb +115 -11
- data/lib/net/ldap/pdu.rb +56 -6
- data/tests/testem.rb +2 -1
- data/tests/testfilter.rb +37 -0
- metadata +3 -2
data/ChangeLog
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
= Net::LDAP Changelog
|
2
2
|
|
3
|
+
== Net::LDAP 0.0.2: July 12, 2006
|
4
|
+
* Fixed malformation in distro tarball and gem.
|
5
|
+
* Improved documentation.
|
6
|
+
* Supported "paged search control."
|
7
|
+
* Added a range of API improvements.
|
8
|
+
* Thanks to Andre Nathan, andre@digirati.com.br, for valuable
|
9
|
+
suggestions.
|
10
|
+
* Added support for LE and GE search filters.
|
11
|
+
* Added support for Search referrals.
|
12
|
+
* Fixed a regression with openldap 2.2.x and higher caused
|
13
|
+
by the introduction of RFC-2696 controls. Thanks to Andre
|
14
|
+
Nathan for reporting the problem.
|
15
|
+
* Added support for RFC-2254 filter syntax.
|
16
|
+
|
3
17
|
== Net::LDAP 0.0.1: May 1, 2006
|
4
18
|
* Initial release.
|
5
19
|
* Client functionality is near-complete, although the APIs
|
data/README
CHANGED
@@ -3,19 +3,22 @@ Net::LDAP is an LDAP support library written in pure Ruby. It supports all
|
|
3
3
|
LDAP client features, and a subset of server features as well.
|
4
4
|
|
5
5
|
Homepage:: http://rubyforge.org/projects/net-ldap/
|
6
|
-
Copyright:: 2006 by Francis Cianfrocca
|
6
|
+
Copyright:: (C) 2006 by Francis Cianfrocca
|
7
7
|
|
8
8
|
Original developer: Francis Cianfrocca
|
9
9
|
Contributions by Austin Ziegler gratefully acknowledged.
|
10
10
|
|
11
11
|
== LICENCE NOTES
|
12
12
|
Please read the file LICENCE for licensing restrictions on this library. In
|
13
|
-
|
13
|
+
the simplest terms, this library is available under the same terms as Ruby
|
14
14
|
itself.
|
15
15
|
|
16
16
|
== Requirements
|
17
17
|
Net::LDAP requires Ruby 1.8.2 or better.
|
18
18
|
|
19
|
+
== Documentation
|
20
|
+
See Net::LDAP for documentation and usage samples.
|
21
|
+
|
19
22
|
#--
|
20
23
|
# Net::LDAP for Ruby.
|
21
24
|
# http://rubyforge.org/projects/net-ldap/
|
@@ -24,6 +27,6 @@ Net::LDAP requires Ruby 1.8.2 or better.
|
|
24
27
|
# Available under the same terms as Ruby. See LICENCE in the main
|
25
28
|
# distribution for full licensing information.
|
26
29
|
#
|
27
|
-
# $Id: README
|
30
|
+
# $Id: README 141 2006-07-12 10:37:37Z blackhedd $
|
28
31
|
#++
|
29
32
|
# vim: sts=2 sw=2 ts=4 et ai tw=77
|
data/lib/net/ldap.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: ldap.rb
|
1
|
+
# $Id: ldap.rb 141 2006-07-12 10:37:37Z blackhedd $
|
2
2
|
#
|
3
3
|
# Net::LDAP for Ruby
|
4
4
|
#
|
@@ -32,7 +32,7 @@ module Net
|
|
32
32
|
# == Net::LDAP
|
33
33
|
#
|
34
34
|
# This library provides a pure-Ruby implementation of the
|
35
|
-
# LDAP client protocol, per RFC-
|
35
|
+
# LDAP client protocol, per RFC-2251.
|
36
36
|
# It can be used to access any server which implements the
|
37
37
|
# LDAP protocol.
|
38
38
|
#
|
@@ -41,7 +41,25 @@ module Net
|
|
41
41
|
# the LDAP protocol itself, and thus presenting as Ruby-like
|
42
42
|
# a programming interface as possible.
|
43
43
|
#
|
44
|
-
#
|
44
|
+
# == Quick-start for the Impatient
|
45
|
+
# === Quick Example of a user-authentication against an LDAP directory:
|
46
|
+
#
|
47
|
+
# require 'rubygems'
|
48
|
+
# require 'net/ldap'
|
49
|
+
#
|
50
|
+
# ldap = Net::LDAP.new
|
51
|
+
# ldap.host = your_server_ip_address
|
52
|
+
# ldap.port = 389
|
53
|
+
# ldap.auth "joe_user", "opensesame"
|
54
|
+
# if ldap.bind
|
55
|
+
# # authentication succeeded
|
56
|
+
# else
|
57
|
+
# # authentication failed
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# === Quick Example of a search against an LDAP directory:
|
62
|
+
#
|
45
63
|
# require 'rubygems'
|
46
64
|
# require 'net/ldap'
|
47
65
|
#
|
@@ -69,14 +87,14 @@ module Net
|
|
69
87
|
# p ldap.get_operation_result
|
70
88
|
#
|
71
89
|
#
|
72
|
-
# ==
|
90
|
+
# == A Brief Introduction to LDAP
|
73
91
|
#
|
74
|
-
# We're going to provide a quick
|
92
|
+
# We're going to provide a quick, informal introduction to LDAP
|
75
93
|
# terminology and
|
76
94
|
# typical operations. If you're comfortable with this material, skip
|
77
95
|
# ahead to "How to use Net::LDAP." If you want a more rigorous treatment
|
78
96
|
# of this material, we recommend you start with the various IETF and ITU
|
79
|
-
# standards that
|
97
|
+
# standards that relate to LDAP.
|
80
98
|
#
|
81
99
|
# === Entities
|
82
100
|
# LDAP is an Internet-standard protocol used to access directory servers.
|
@@ -116,17 +134,17 @@ module Net
|
|
116
134
|
# range of attributes, and constrain their values according to standard
|
117
135
|
# rules.
|
118
136
|
#
|
119
|
-
# A good example of an attribute is <tt>
|
120
|
-
#
|
121
|
-
#
|
122
|
-
# an entity's <tt>
|
123
|
-
# jargon, that means that <tt>
|
137
|
+
# A good example of an attribute is <tt>sn,</tt> which stands for "Surname."
|
138
|
+
# This attribute is generally used to store a person's surname, or last name.
|
139
|
+
# Most directories enforce the standard convention that
|
140
|
+
# an entity's <tt>sn</tt> attribute have <i>exactly one</i> value. In LDAP
|
141
|
+
# jargon, that means that <tt>sn</tt> must be <i>present</i> and
|
124
142
|
# <i>single-valued.</i>
|
125
143
|
#
|
126
144
|
# Another attribute is <tt>mail,</tt> which is used to store email addresses.
|
127
145
|
# (No, there is no attribute called "email," perhaps because X.400 terminology
|
128
146
|
# predates the invention of the term <i>email.</i>) <tt>mail</tt> differs
|
129
|
-
# from <tt>
|
147
|
+
# from <tt>sn</tt> in that most directories permit any number of values for the
|
130
148
|
# <tt>mail</tt> attribute, including zero.
|
131
149
|
#
|
132
150
|
#
|
@@ -173,7 +191,7 @@ module Net
|
|
173
191
|
# set of attribute values for each entity, depending on what attributes the search requested.
|
174
192
|
#
|
175
193
|
# ==== Add
|
176
|
-
# #add
|
194
|
+
# #add specifies a new DN and an initial set of attribute values. If the operation
|
177
195
|
# succeeds, a new entity with the corresponding DN and attributes is added to the directory.
|
178
196
|
#
|
179
197
|
# ==== Modify
|
@@ -181,11 +199,11 @@ module Net
|
|
181
199
|
# the attribute values stored in the directory for a particular entity.
|
182
200
|
# #modify may add or delete attributes (which are lists of values) or it change attributes by
|
183
201
|
# adding to or deleting from their values.
|
184
|
-
#
|
202
|
+
# Net::LDAP provides three easier methods to modify an entry's attribute values:
|
185
203
|
# #add_attribute, #replace_attribute, and #delete_attribute.
|
186
204
|
#
|
187
205
|
# ==== Delete
|
188
|
-
# #delete
|
206
|
+
# #delete specifies an entity DN. If it succeeds, the entity and all its attributes
|
189
207
|
# is removed from the directory.
|
190
208
|
#
|
191
209
|
# ==== Rename (or Modify RDN)
|
@@ -238,7 +256,7 @@ module Net
|
|
238
256
|
|
239
257
|
class LdapError < Exception; end
|
240
258
|
|
241
|
-
VERSION = "0.0.
|
259
|
+
VERSION = "0.0.2"
|
242
260
|
|
243
261
|
|
244
262
|
SearchScope_BaseObject = 0
|
@@ -266,6 +284,7 @@ module Net
|
|
266
284
|
14 => :array, # CompareRequest
|
267
285
|
15 => :array, # CompareResponse
|
268
286
|
16 => :array, # AbandonRequest
|
287
|
+
19 => :array, # SearchResultReferral
|
269
288
|
24 => :array, # Unsolicited Notification
|
270
289
|
}
|
271
290
|
},
|
@@ -274,6 +293,10 @@ module Net
|
|
274
293
|
0 => :string, # password
|
275
294
|
1 => :string, # Kerberos v4
|
276
295
|
2 => :string, # Kerberos v5
|
296
|
+
},
|
297
|
+
:constructed => {
|
298
|
+
0 => :array, # RFC-2251 Control
|
299
|
+
3 => :array, # Seach referral
|
277
300
|
}
|
278
301
|
}
|
279
302
|
}
|
@@ -281,12 +304,16 @@ module Net
|
|
281
304
|
DefaultHost = "127.0.0.1"
|
282
305
|
DefaultPort = 389
|
283
306
|
DefaultAuth = {:method => :anonymous}
|
307
|
+
DefaultTreebase = "dc=com"
|
284
308
|
|
285
309
|
|
286
310
|
ResultStrings = {
|
287
311
|
0 => "Success",
|
288
312
|
1 => "Operations Error",
|
289
313
|
2 => "Protocol Error",
|
314
|
+
3 => "Time Limit Exceeded",
|
315
|
+
4 => "Size Limit Exceeded",
|
316
|
+
12 => "Unavailable crtical extension",
|
290
317
|
16 => "No Such Attribute",
|
291
318
|
17 => "Undefined Attribute Type",
|
292
319
|
20 => "Attribute or Value Exists",
|
@@ -303,13 +330,23 @@ module Net
|
|
303
330
|
68 => "Entry Already Exists"
|
304
331
|
}
|
305
332
|
|
333
|
+
|
334
|
+
module LdapControls
|
335
|
+
PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
|
336
|
+
end
|
337
|
+
|
338
|
+
|
306
339
|
#
|
307
340
|
# LDAP::result2string
|
308
341
|
#
|
309
|
-
def LDAP::result2string code
|
342
|
+
def LDAP::result2string code # :nodoc:
|
310
343
|
ResultStrings[code] || "unknown result (#{code})"
|
311
344
|
end
|
312
345
|
|
346
|
+
|
347
|
+
attr_accessor :host, :port, :base
|
348
|
+
|
349
|
+
|
313
350
|
# Instantiate an object of type Net::LDAP to perform directory operations.
|
314
351
|
# This constructor takes a Hash containing arguments. The following arguments
|
315
352
|
# are supported:
|
@@ -318,6 +355,7 @@ module Net
|
|
318
355
|
# * :auth => a Hash containing authorization parameters. Currently supported values include:
|
319
356
|
# {:method => :anonymous} and
|
320
357
|
# {:method => :simple, :username => your_user_name, :password => your_password }
|
358
|
+
# The password parameter may be a Proc that returns a String.
|
321
359
|
#
|
322
360
|
# Instantiating a Net::LDAP object does <i>not</i> result in network traffic to
|
323
361
|
# the LDAP server. It simply stores the connection and binding parameters in the
|
@@ -328,6 +366,11 @@ module Net
|
|
328
366
|
@port = args[:port] || DefaultPort
|
329
367
|
@verbose = false # Make this configurable with a switch on the class.
|
330
368
|
@auth = args[:auth] || DefaultAuth
|
369
|
+
@base = args[:base] || DefaultTreebase
|
370
|
+
|
371
|
+
if pr = @auth[:password] and pr.respond_to?(:call)
|
372
|
+
@auth[:password] = pr.call
|
373
|
+
end
|
331
374
|
|
332
375
|
# This variable is only set when we are created with LDAP::open.
|
333
376
|
# All of our internal methods will connect using it, or else
|
@@ -335,6 +378,45 @@ module Net
|
|
335
378
|
@open_connection = nil
|
336
379
|
end
|
337
380
|
|
381
|
+
# Convenience method to specify authentication credentials to the LDAP
|
382
|
+
# server. Currently supports simple authentication requiring
|
383
|
+
# a username and password.
|
384
|
+
#
|
385
|
+
# Observe that on most LDAP servers,
|
386
|
+
# the username is a complete DN. However, with A/D, it's often possible
|
387
|
+
# to give only a user-name rather than a complete DN. In the latter
|
388
|
+
# case, beware that many A/D servers are configured to permit anonymous
|
389
|
+
# (uncredentialled) binding, and will silently accept your binding
|
390
|
+
# as anonymous if you give an unrecognized username. This is not usually
|
391
|
+
# what you want. (See #get_operation_result.)
|
392
|
+
#
|
393
|
+
# <b>Important:</b> The password argument may be a Proc that returns a string.
|
394
|
+
# This makes it possible for you to write client programs that solicit
|
395
|
+
# passwords from users or from other data sources without showing them
|
396
|
+
# in your code or on command lines.
|
397
|
+
#
|
398
|
+
# require 'net/ldap'
|
399
|
+
#
|
400
|
+
# ldap = Net::LDAP.new
|
401
|
+
# ldap.host = server_ip_address
|
402
|
+
# ldap.authenticate "cn=Your Username,cn=Users,dc=example,dc=com", "your_psw"
|
403
|
+
#
|
404
|
+
# Alternatively (with a password block):
|
405
|
+
#
|
406
|
+
# require 'net/ldap'
|
407
|
+
#
|
408
|
+
# ldap = Net::LDAP.new
|
409
|
+
# ldap.host = server_ip_address
|
410
|
+
# psw = proc { your_psw_function }
|
411
|
+
# ldap.authenticate "cn=Your Username,cn=Users,dc=example,dc=com", psw
|
412
|
+
#
|
413
|
+
def authenticate username, password
|
414
|
+
password = password.call if password.respond_to?(:call)
|
415
|
+
@auth = {:method => :simple, :username => username, :password => password}
|
416
|
+
end
|
417
|
+
|
418
|
+
alias_method :auth, :authenticate
|
419
|
+
|
338
420
|
# #open takes the same parameters as #new. #open makes a network connection to the
|
339
421
|
# LDAP server and then passes a newly-created Net::LDAP object to the caller-supplied block.
|
340
422
|
# Within the block, you can call any of the instance methods of Net::LDAP to
|
@@ -409,38 +491,6 @@ module Net
|
|
409
491
|
end
|
410
492
|
|
411
493
|
|
412
|
-
# <i>DEPRECATED.</i> Performs an LDAP search, waits for the operation to complete, and
|
413
|
-
# passes a result set to the caller-supplied block.
|
414
|
-
#--
|
415
|
-
# If an open call is in progress (@open_connection will be non-nil),
|
416
|
-
# then ASSUME a bind has been performed and accepted, and just
|
417
|
-
# execute the search.
|
418
|
-
# If @open_connection is nil, then we have to connect, bind,
|
419
|
-
# search, and then disconnect. (The disconnect is not strictly
|
420
|
-
# necessary but it's friendlier to the network to do it here
|
421
|
-
# rather than waiting for Ruby's GC.)
|
422
|
-
# Note that in the standalone case, we're permitting the caller
|
423
|
-
# to modify the auth parms.
|
424
|
-
#
|
425
|
-
def searchx args
|
426
|
-
if @open_connection
|
427
|
-
@result = @open_connection.searchx( args ) {|values|
|
428
|
-
yield( values ) if block_given?
|
429
|
-
}
|
430
|
-
else
|
431
|
-
@result = 0
|
432
|
-
conn = Connection.new( :host => @host, :port => @port )
|
433
|
-
if (@result = conn.bind( args[:auth] || @auth )) == 0
|
434
|
-
@result = conn.searchx( args ) {|values|
|
435
|
-
yield( values ) if block_given?
|
436
|
-
}
|
437
|
-
end
|
438
|
-
conn.close
|
439
|
-
end
|
440
|
-
|
441
|
-
@result == 0
|
442
|
-
end
|
443
|
-
|
444
494
|
# Searches the LDAP directory for directory entries.
|
445
495
|
# Takes a hash argument with parameters. Supported parameters include:
|
446
496
|
# * :base (a string specifying the tree-base for the search);
|
@@ -456,6 +506,8 @@ module Net
|
|
456
506
|
# be called 1000 times. If the search returns no entries, the block will
|
457
507
|
# not be called.
|
458
508
|
#
|
509
|
+
#--
|
510
|
+
# ORIGINAL TEXT, replaced 04May06.
|
459
511
|
# #search returns either a result-set or a boolean, depending on the
|
460
512
|
# value of the <tt>:return_result</tt> argument. The default behavior is to return
|
461
513
|
# a result set, which is a hash. Each key in the hash is a string specifying
|
@@ -463,6 +515,13 @@ module Net
|
|
463
515
|
# If you request a result set and #search fails with an error, it will return nil.
|
464
516
|
# Call #get_operation_result to get the error information returned by
|
465
517
|
# the LDAP server.
|
518
|
+
#++
|
519
|
+
# #search returns either a result-set or a boolean, depending on the
|
520
|
+
# value of the <tt>:return_result</tt> argument. The default behavior is to return
|
521
|
+
# a result set, which is an Array of objects of class Net::LDAP::Entry.
|
522
|
+
# If you request a result set and #search fails with an error, it will return nil.
|
523
|
+
# Call #get_operation_result to get the error information returned by
|
524
|
+
# the LDAP server.
|
466
525
|
#
|
467
526
|
# When <tt>:return_result => false,</tt> #search will
|
468
527
|
# return only a Boolean, to indicate whether the operation succeeded. This can improve performance
|
@@ -503,12 +562,19 @@ module Net
|
|
503
562
|
# that the caller can set to suppress the return of a result set,
|
504
563
|
# if he's planning to process every entry as it comes from the server.
|
505
564
|
#
|
506
|
-
|
507
|
-
|
565
|
+
# REINTERPRETED the result set, 04May06. Originally this was a hash
|
566
|
+
# of entries keyed by DNs. But let's get away from making users
|
567
|
+
# handle DNs. Change it to a plain array. Eventually we may
|
568
|
+
# want to return a Dataset object that delegates to an internal
|
569
|
+
# array, so we can provide sort methods and what-not.
|
570
|
+
#
|
571
|
+
def search args = {}
|
572
|
+
args[:base] ||= @base
|
573
|
+
result_set = (args and args[:return_result] == false) ? nil : []
|
508
574
|
|
509
575
|
if @open_connection
|
510
576
|
@result = @open_connection.search( args ) {|entry|
|
511
|
-
result_set
|
577
|
+
result_set << entry if result_set
|
512
578
|
yield( entry ) if block_given?
|
513
579
|
}
|
514
580
|
else
|
@@ -516,7 +582,7 @@ module Net
|
|
516
582
|
conn = Connection.new( :host => @host, :port => @port )
|
517
583
|
if (@result = conn.bind( args[:auth] || @auth )) == 0
|
518
584
|
@result = conn.search( args ) {|entry|
|
519
|
-
|
585
|
+
result_set << entry if result_set
|
520
586
|
yield( entry ) if block_given?
|
521
587
|
}
|
522
588
|
end
|
@@ -526,12 +592,46 @@ module Net
|
|
526
592
|
@result == 0 and result_set
|
527
593
|
end
|
528
594
|
|
529
|
-
# #bind connects to
|
595
|
+
# #bind connects to an LDAP server and requests authentication
|
530
596
|
# based on the <tt>:auth</tt> parameter passed to #open or #new.
|
531
597
|
# It takes no parameters.
|
532
|
-
#
|
533
|
-
#
|
534
|
-
#
|
598
|
+
#
|
599
|
+
# User code does not need to call #bind directly. It will be called
|
600
|
+
# implicitly by the library whenever you invoke an LDAP operation,
|
601
|
+
# such as #search or #add.
|
602
|
+
#
|
603
|
+
# It is useful, however, to call #bind in your own code when the
|
604
|
+
# only operation you intend to perform against the directory is
|
605
|
+
# to validate a login credential. #bind returns true or false
|
606
|
+
# to indicate whether the binding was successful. Reasons for
|
607
|
+
# failure include malformed or unrecognized usernames and
|
608
|
+
# incorrect passwords. Use #get_operation_result to find out
|
609
|
+
# what happened in case of failure.
|
610
|
+
#
|
611
|
+
# Here's a typical example using #bind to authenticate a
|
612
|
+
# credential which was (perhaps) solicited from the user of a
|
613
|
+
# web site:
|
614
|
+
#
|
615
|
+
# require 'net/ldap'
|
616
|
+
# ldap = Net::LDAP.new
|
617
|
+
# ldap.host = your_server_ip_address
|
618
|
+
# ldap.port = 389
|
619
|
+
# ldap.auth your_user_name, your_user_password
|
620
|
+
# if ldap.bind
|
621
|
+
# # authentication succeeded
|
622
|
+
# else
|
623
|
+
# # authentication failed
|
624
|
+
# p ldap.get_operation_result
|
625
|
+
# end
|
626
|
+
#
|
627
|
+
# You don't have to create a new instance of Net::LDAP every time
|
628
|
+
# you perform a binding in this way. If you prefer, you can cache the Net::LDAP object
|
629
|
+
# and re-use it to perform subsequent bindings, <i>provided</i> you call
|
630
|
+
# #auth to specify a new credential before calling #bind. Otherwise, you'll
|
631
|
+
# just re-authenticate the previous user! (You don't need to re-set
|
632
|
+
# the values of #host and #port.) As noted in the documentation for #auth,
|
633
|
+
# the password parameter can be a Ruby Proc instead of a String.
|
634
|
+
#
|
535
635
|
#--
|
536
636
|
# If there is an @open_connection, then perform the bind
|
537
637
|
# on it. Otherwise, connect, bind, and disconnect.
|
@@ -864,92 +964,109 @@ module Net
|
|
864
964
|
#--
|
865
965
|
# WARNING: this code substantially recapitulates the searchx method.
|
866
966
|
#
|
967
|
+
# 02May06: Well, I added support for RFC-2696-style paged searches.
|
968
|
+
# This is used on all queries because the extension is marked non-critical.
|
969
|
+
# As far as I know, only A/D uses this, but it's required for A/D. Otherwise
|
970
|
+
# you won't get more than 1000 results back from a query.
|
971
|
+
# This implementation is kindof clunky and should probably be refactored.
|
972
|
+
# Also, is it my imagination, or are A/Ds the slowest directory servers ever???
|
973
|
+
#
|
867
974
|
def search args = {}
|
868
975
|
search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
|
869
976
|
search_base = (args && args[:base]) || "dc=example,dc=com"
|
870
977
|
search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
|
978
|
+
return_referrals = args && args[:return_referrals] == true
|
871
979
|
|
872
980
|
attributes_only = (args and args[:attributes_only] == true)
|
873
981
|
scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
|
874
982
|
raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope)
|
875
983
|
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
0.to_ber,
|
882
|
-
attributes_only.to_ber,
|
883
|
-
search_filter.to_ber,
|
884
|
-
search_attributes.to_ber_sequence
|
885
|
-
].to_ber_appsequence(3)
|
886
|
-
pkt = [next_msgid.to_ber, request].to_ber_sequence
|
887
|
-
@conn.write pkt
|
888
|
-
|
984
|
+
# An interesting value for the size limit would be close to A/D's built-in
|
985
|
+
# page limit of 1000 records, but openLDAP newer than version 2.2.0 chokes
|
986
|
+
# on anything bigger than 126. You get a silent error that is easily visible
|
987
|
+
# by running slapd in debug mode. Go figure.
|
988
|
+
rfc2696_cookie = [126, ""]
|
889
989
|
result_code = 0
|
890
990
|
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
991
|
+
loop {
|
992
|
+
# should collect this into a private helper to clarify the structure
|
993
|
+
|
994
|
+
request = [
|
995
|
+
search_base.to_ber,
|
996
|
+
scope.to_ber_enumerated,
|
997
|
+
0.to_ber_enumerated,
|
998
|
+
0.to_ber,
|
999
|
+
0.to_ber,
|
1000
|
+
attributes_only.to_ber,
|
1001
|
+
search_filter.to_ber,
|
1002
|
+
search_attributes.to_ber_sequence
|
1003
|
+
].to_ber_appsequence(3)
|
1004
|
+
|
1005
|
+
controls = [
|
1006
|
+
[
|
1007
|
+
LdapControls::PagedResults.to_ber,
|
1008
|
+
false.to_ber, # criticality MUST be false to interoperate with normal LDAPs.
|
1009
|
+
rfc2696_cookie.map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber
|
1010
|
+
].to_ber_sequence
|
1011
|
+
].to_ber_contextspecific(0)
|
1012
|
+
|
1013
|
+
pkt = [next_msgid.to_ber, request, controls].to_ber_sequence
|
1014
|
+
@conn.write pkt
|
1015
|
+
|
1016
|
+
result_code = 0
|
1017
|
+
controls = []
|
1018
|
+
|
1019
|
+
while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
|
1020
|
+
case pdu.app_tag
|
1021
|
+
when 4 # search-data
|
1022
|
+
yield( pdu.search_entry ) if block_given?
|
1023
|
+
when 19 # search-referral
|
1024
|
+
if return_referrals
|
1025
|
+
if block_given?
|
1026
|
+
se = Net::LDAP::Entry.new
|
1027
|
+
se[:search_referrals] = (pdu.search_referrals || [])
|
1028
|
+
yield se
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
#p pdu.referrals
|
1032
|
+
when 5 # search-result
|
1033
|
+
result_code = pdu.result_code
|
1034
|
+
controls = pdu.result_controls
|
1035
|
+
break
|
1036
|
+
else
|
1037
|
+
raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
|
1038
|
+
end
|
900
1039
|
end
|
901
|
-
end
|
902
1040
|
|
903
|
-
|
904
|
-
|
1041
|
+
# When we get here, we have seen a type-5 response.
|
1042
|
+
# If there is no error AND there is an RFC-2696 cookie,
|
1043
|
+
# then query again for the next page of results.
|
1044
|
+
# If not, we're done.
|
1045
|
+
# Don't screw this up or we'll break every search we do.
|
1046
|
+
more_pages = false
|
1047
|
+
if result_code == 0 and controls
|
1048
|
+
controls.each do |c|
|
1049
|
+
if c.oid == LdapControls::PagedResults
|
1050
|
+
more_pages = false # just in case some bogus server sends us >1 of these.
|
1051
|
+
if c.value and c.value.length > 0
|
1052
|
+
cookie = c.value.read_ber[1]
|
1053
|
+
if cookie and cookie.length > 0
|
1054
|
+
rfc2696_cookie[1] = cookie
|
1055
|
+
more_pages = true
|
1056
|
+
end
|
1057
|
+
end
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
end
|
905
1061
|
|
1062
|
+
break unless more_pages
|
1063
|
+
} # loop
|
906
1064
|
|
907
|
-
|
908
|
-
|
909
|
-
# Original implementation, this doesn't return until all data have been
|
910
|
-
# received from the server.
|
911
|
-
# TODO, certain search parameters are hardcoded.
|
912
|
-
# TODO, if we mis-parse the server results or the results are wrong, we can block
|
913
|
-
# forever. That's because we keep reading results until we get a type-5 packet,
|
914
|
-
# which might never come. We need to support the time-limit in the protocol.
|
915
|
-
#--
|
916
|
-
# WARNING: this code substantially recapitulates the search method.
|
917
|
-
#
|
918
|
-
def searchx args
|
919
|
-
search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
|
920
|
-
search_base = (args && args[:base]) || "dc=example,dc=com"
|
921
|
-
search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
|
922
|
-
request = [
|
923
|
-
search_base.to_ber,
|
924
|
-
2.to_ber_enumerated,
|
925
|
-
0.to_ber_enumerated,
|
926
|
-
0.to_ber,
|
927
|
-
0.to_ber,
|
928
|
-
false.to_ber,
|
929
|
-
search_filter.to_ber,
|
930
|
-
search_attributes.to_ber_sequence
|
931
|
-
].to_ber_appsequence(3)
|
932
|
-
pkt = [next_msgid.to_ber, request].to_ber_sequence
|
933
|
-
@conn.write pkt
|
1065
|
+
result_code
|
1066
|
+
end
|
934
1067
|
|
935
|
-
search_results = {}
|
936
|
-
result_code = 0
|
937
1068
|
|
938
|
-
while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
|
939
|
-
case pdu.app_tag
|
940
|
-
when 4 # search-data
|
941
|
-
search_results [pdu.search_dn] = pdu.search_attributes
|
942
|
-
when 5 # search-result
|
943
|
-
result_code = pdu.result_code
|
944
|
-
block_given? and yield( search_results )
|
945
|
-
break
|
946
|
-
else
|
947
|
-
raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
|
948
|
-
end
|
949
|
-
end
|
950
1069
|
|
951
|
-
result_code
|
952
|
-
end
|
953
1070
|
|
954
1071
|
#--
|
955
1072
|
# modify
|
data/lib/net/ldap/entry.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: entry.rb
|
1
|
+
# $Id: entry.rb 123 2006-05-18 03:52:38Z blackhedd $
|
2
2
|
#
|
3
3
|
# LDAP Entry (search-result) support classes
|
4
4
|
#
|
@@ -33,46 +33,129 @@ module Net
|
|
33
33
|
class LDAP
|
34
34
|
|
35
35
|
|
36
|
+
# Objects of this class represent individual entries in an LDAP
|
37
|
+
# directory. User code generally does not instantiate this class.
|
38
|
+
# Net::LDAP#search provides objects of this class to user code,
|
39
|
+
# either as block parameters or as return values.
|
40
|
+
#
|
41
|
+
# In LDAP-land, an "entry" is a collection of attributes that are
|
42
|
+
# uniquely and globally identified by a DN ("Distinguished Name").
|
43
|
+
# Attributes are identified by short, descriptive words or phrases.
|
44
|
+
# Although a directory is
|
45
|
+
# free to implement any attribute name, most of them follow rigorous
|
46
|
+
# standards so that the range of commonly-encountered attribute
|
47
|
+
# names is not large.
|
48
|
+
#
|
49
|
+
# An attribute name is case-insensitive. Most directories also
|
50
|
+
# restrict the range of characters allowed in attribute names.
|
51
|
+
# To simplify handling attribute names, Net::LDAP::Entry
|
52
|
+
# internally converts them to a standard format. Therefore, the
|
53
|
+
# methods which take attribute names can take Strings or Symbols,
|
54
|
+
# and work correctly regardless of case or capitalization.
|
55
|
+
#
|
56
|
+
# An attribute consists of zero or more data items called
|
57
|
+
# <i>values.</i> An entry is the combination of a unique DN, a set of attribute
|
58
|
+
# names, and a (possibly-empty) array of values for each attribute.
|
59
|
+
#
|
60
|
+
# Class Net::LDAP::Entry provides convenience methods for dealing
|
61
|
+
# with LDAP entries.
|
62
|
+
# In addition to the methods documented below, you may access individual
|
63
|
+
# attributes of an entry simply by giving the attribute name as
|
64
|
+
# the name of a method call. For example:
|
65
|
+
# ldap.search( ... ) do |entry|
|
66
|
+
# puts "Common name: #{entry.cn}"
|
67
|
+
# puts "Email addresses:"
|
68
|
+
# entry.mail.each {|ma| puts ma}
|
69
|
+
# end
|
70
|
+
# If you use this technique to access an attribute that is not present
|
71
|
+
# in a particular Entry object, a NoMethodError exception will be raised.
|
72
|
+
#
|
73
|
+
#--
|
74
|
+
# Ugly problem to fix someday: We key off the internal hash with
|
75
|
+
# a canonical form of the attribute name: convert to a string,
|
76
|
+
# downcase, then take the symbol. Unfortunately we do this in
|
77
|
+
# at least three places. Should do it in ONE place.
|
36
78
|
class Entry
|
37
79
|
|
38
|
-
|
80
|
+
# This constructor is not generally called by user code.
|
81
|
+
def initialize dn = nil # :nodoc:
|
39
82
|
@myhash = Hash.new {|k,v| k[v] = [] }
|
40
83
|
@myhash[:dn] = [dn]
|
41
84
|
end
|
42
85
|
|
43
86
|
|
44
|
-
def []= name, value
|
87
|
+
def []= name, value # :nodoc:
|
45
88
|
sym = name.to_s.downcase.intern
|
46
89
|
@myhash[sym] = value
|
47
90
|
end
|
48
91
|
|
49
92
|
|
50
93
|
#--
|
51
|
-
# We have to deal with this one as we do []=
|
94
|
+
# We have to deal with this one as we do with []=
|
52
95
|
# because this one and not the other one gets called
|
53
96
|
# in formulations like entry["CN"] << cn.
|
54
97
|
#
|
55
|
-
def [] name
|
98
|
+
def [] name # :nodoc:
|
56
99
|
name = name.to_s.downcase.intern unless name.is_a?(Symbol)
|
57
100
|
@myhash[name]
|
58
101
|
end
|
59
102
|
|
103
|
+
# Returns the dn of the Entry as a String.
|
60
104
|
def dn
|
61
105
|
self[:dn][0]
|
62
106
|
end
|
63
107
|
|
108
|
+
# Returns an array of the attribute names present in the Entry.
|
64
109
|
def attribute_names
|
65
110
|
@myhash.keys
|
66
111
|
end
|
67
112
|
|
113
|
+
# Accesses each of the attributes present in the Entry.
|
114
|
+
# Calls a user-supplied block with each attribute in turn,
|
115
|
+
# passing two arguments to the block: a Symbol giving
|
116
|
+
# the name of the attribute, and a (possibly empty)
|
117
|
+
# Array of data values.
|
118
|
+
#
|
68
119
|
def each
|
69
120
|
if block_given?
|
70
|
-
attribute_names.each {|a|
|
121
|
+
attribute_names.each {|a|
|
122
|
+
attr_name,values = a,self[a]
|
123
|
+
yield attr_name, values
|
124
|
+
}
|
71
125
|
end
|
72
126
|
end
|
73
127
|
|
74
128
|
alias_method :each_attribute, :each
|
75
129
|
|
130
|
+
|
131
|
+
#--
|
132
|
+
# Convenience method to convert unknown method names
|
133
|
+
# to attribute references. Of course the method name
|
134
|
+
# comes to us as a symbol, so let's save a little time
|
135
|
+
# and not bother with the to_s.downcase two-step.
|
136
|
+
# Of course that means that a method name like mAIL
|
137
|
+
# won't work, but we shouldn't be encouraging that
|
138
|
+
# kind of bad behavior in the first place.
|
139
|
+
# Maybe we should thow something if the caller sends
|
140
|
+
# arguments or a block...
|
141
|
+
#
|
142
|
+
def method_missing *args, &block # :nodoc:
|
143
|
+
s = args[0].to_s.downcase.intern
|
144
|
+
if attribute_names.include?(s)
|
145
|
+
self[s]
|
146
|
+
elsif s.to_s[-1] == 61 and s.to_s.length > 1
|
147
|
+
value = args[1] or raise RuntimeError.new( "unable to set value" )
|
148
|
+
value = [value] unless value.is_a?(Array)
|
149
|
+
name = s.to_s[0..-2].intern
|
150
|
+
self[name] = value
|
151
|
+
else
|
152
|
+
raise NoMethodError.new( "undefined method '#{s}'" )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def write
|
157
|
+
end
|
158
|
+
|
76
159
|
end # class Entry
|
77
160
|
|
78
161
|
|
data/lib/net/ldap/filter.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: filter.rb
|
1
|
+
# $Id: filter.rb 132 2006-06-27 17:35:18Z blackhedd $
|
2
2
|
#
|
3
3
|
#
|
4
4
|
#----------------------------------------------------------------------------
|
@@ -75,11 +75,13 @@ class Filter
|
|
75
75
|
# This example selects any entry with a <tt>mail</tt> value containing
|
76
76
|
# the substring "anderson":
|
77
77
|
# f = Net::LDAP::Filter.eq( "mail", "*anderson*" )
|
78
|
+
#--
|
79
|
+
# Removed gt and lt. They ain't in the standard!
|
78
80
|
#
|
79
81
|
def Filter::eq attribute, value; Filter.new :eq, attribute, value; end
|
80
82
|
def Filter::ne attribute, value; Filter.new :ne, attribute, value; end
|
81
|
-
def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
|
82
|
-
def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
|
83
|
+
#def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
|
84
|
+
#def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
|
83
85
|
def Filter::ge attribute, value; Filter.new :ge, attribute, value; end
|
84
86
|
def Filter::le attribute, value; Filter.new :le, attribute, value; end
|
85
87
|
|
@@ -110,6 +112,7 @@ class Filter
|
|
110
112
|
#
|
111
113
|
#--
|
112
114
|
# This operator can't be !, evidently. Try it.
|
115
|
+
# Removed GT and LT. They're not in the RFC.
|
113
116
|
def ~@; Filter.new :not, self, nil; end
|
114
117
|
|
115
118
|
|
@@ -119,10 +122,10 @@ class Filter
|
|
119
122
|
"(!(#{@left}=#{@right}))"
|
120
123
|
when :eq
|
121
124
|
"(#{@left}=#{@right})"
|
122
|
-
when :gt
|
123
|
-
|
124
|
-
when :lt
|
125
|
-
|
125
|
+
#when :gt
|
126
|
+
# "#{@left}>#{@right}"
|
127
|
+
#when :lt
|
128
|
+
# "#{@left}<#{@right}"
|
126
129
|
when :ge
|
127
130
|
"#{@left}>=#{@right}"
|
128
131
|
when :le
|
@@ -180,7 +183,7 @@ class Filter
|
|
180
183
|
case @op
|
181
184
|
when :eq
|
182
185
|
if @right == "*" # present
|
183
|
-
@left.to_ber_contextspecific 7
|
186
|
+
@left.to_s.to_ber_contextspecific 7
|
184
187
|
elsif @right =~ /[\*]/ #substring
|
185
188
|
ary = @right.split( /[\*]+/ )
|
186
189
|
final_star = @right =~ /[\*]$/
|
@@ -191,17 +194,21 @@ class Filter
|
|
191
194
|
seq << ary.shift.to_ber_contextspecific(0)
|
192
195
|
end
|
193
196
|
n_any_strings = ary.length - (final_star ? 0 : 1)
|
194
|
-
p n_any_strings
|
197
|
+
#p n_any_strings
|
195
198
|
n_any_strings.times {
|
196
199
|
seq << ary.shift.to_ber_contextspecific(1)
|
197
200
|
}
|
198
201
|
unless final_star
|
199
202
|
seq << ary.shift.to_ber_contextspecific(2)
|
200
203
|
end
|
201
|
-
[@left.to_ber, seq.to_ber].to_ber_contextspecific 4
|
204
|
+
[@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific 4
|
202
205
|
else #equality
|
203
|
-
[@left.to_ber, @right.to_ber].to_ber_contextspecific 3
|
206
|
+
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 3
|
204
207
|
end
|
208
|
+
when :ge
|
209
|
+
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 5
|
210
|
+
when :le
|
211
|
+
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 6
|
205
212
|
when :and
|
206
213
|
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
|
207
214
|
ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 )
|
@@ -270,9 +277,106 @@ class Filter
|
|
270
277
|
end
|
271
278
|
end
|
272
279
|
|
280
|
+
# Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
|
281
|
+
# to a Net::LDAP::Filter.
|
282
|
+
def self.construct ldap_filter_string
|
283
|
+
FilterParser.new(ldap_filter_string).filter
|
284
|
+
end
|
285
|
+
|
286
|
+
# Synonym for #construct.
|
287
|
+
# to a Net::LDAP::Filter.
|
288
|
+
def self.from_rfc2254 ldap_filter_string
|
289
|
+
construct ldap_filter_string
|
290
|
+
end
|
273
291
|
|
274
292
|
end # class Net::LDAP::Filter
|
275
293
|
|
294
|
+
|
295
|
+
class FilterParser #:nodoc:
|
296
|
+
|
297
|
+
attr_reader :filter
|
298
|
+
|
299
|
+
def initialize str
|
300
|
+
require 'strscan'
|
301
|
+
@filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" )
|
302
|
+
end
|
303
|
+
|
304
|
+
def parse scanner
|
305
|
+
parse_filter_branch(scanner) or parse_paren_expression(scanner)
|
306
|
+
end
|
307
|
+
|
308
|
+
def parse_paren_expression scanner
|
309
|
+
if scanner.scan /\s*\(\s*/
|
310
|
+
b = if scanner.scan /\s*\&\s*/
|
311
|
+
a = nil
|
312
|
+
branches = []
|
313
|
+
while br = parse_paren_expression(scanner)
|
314
|
+
branches << br
|
315
|
+
end
|
316
|
+
if branches.length >= 2
|
317
|
+
a = branches.shift
|
318
|
+
while branches.length > 0
|
319
|
+
a = a & branches.shift
|
320
|
+
end
|
321
|
+
a
|
322
|
+
end
|
323
|
+
elsif scanner.scan /\s*\|\s*/
|
324
|
+
# TODO: DRY!
|
325
|
+
a = nil
|
326
|
+
branches = []
|
327
|
+
while br = parse_paren_expression(scanner)
|
328
|
+
branches << br
|
329
|
+
end
|
330
|
+
if branches.length >= 2
|
331
|
+
a = branches.shift
|
332
|
+
while branches.length > 0
|
333
|
+
a = a | branches.shift
|
334
|
+
end
|
335
|
+
a
|
336
|
+
end
|
337
|
+
elsif scanner.scan /\s*\!\s*/
|
338
|
+
br = parse_paren_expression(scanner)
|
339
|
+
if br
|
340
|
+
~ br
|
341
|
+
end
|
342
|
+
else
|
343
|
+
parse_filter_branch( scanner )
|
344
|
+
end
|
345
|
+
|
346
|
+
if b and scanner.scan( /\s*\)\s*/ )
|
347
|
+
b
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def parse_filter_branch scanner
|
353
|
+
scanner.scan /\s*/
|
354
|
+
if token = scanner.scan( /[\w\-_]+/ )
|
355
|
+
scanner.scan /\s*/
|
356
|
+
if op = scanner.scan( /\=|\<\=|\<|\>\=|\>|\!\=/ )
|
357
|
+
scanner.scan /\s*/
|
358
|
+
if value = scanner.scan( /[\w\*\.]+/ )
|
359
|
+
case op
|
360
|
+
when "="
|
361
|
+
Filter.eq( token, value )
|
362
|
+
when "!="
|
363
|
+
Filter.ne( token, value )
|
364
|
+
when "<"
|
365
|
+
Filter.lt( token, value )
|
366
|
+
when "<="
|
367
|
+
Filter.le( token, value )
|
368
|
+
when ">"
|
369
|
+
Filter.gt( token, value )
|
370
|
+
when ">="
|
371
|
+
Filter.ge( token, value )
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
end # class Net::LDAP::FilterParser
|
379
|
+
|
276
380
|
end # class Net::LDAP
|
277
381
|
end # module Net
|
278
382
|
|
data/lib/net/ldap/pdu.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: pdu.rb
|
1
|
+
# $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $
|
2
2
|
#
|
3
3
|
# LDAP PDU support classes
|
4
4
|
#
|
@@ -43,15 +43,20 @@ class LdapPdu
|
|
43
43
|
AddResponse = 9
|
44
44
|
DeleteResponse = 11
|
45
45
|
ModifyRDNResponse = 13
|
46
|
+
SearchResultReferral = 19
|
46
47
|
|
47
48
|
attr_reader :msg_id, :app_tag
|
48
49
|
attr_reader :search_dn, :search_attributes, :search_entry
|
50
|
+
attr_reader :search_referrals
|
49
51
|
|
50
52
|
#
|
51
53
|
# initialize
|
52
54
|
# An LDAP PDU always looks like a BerSequence with
|
53
|
-
# two elements: an integer (message-id number), and
|
55
|
+
# at least two elements: an integer (message-id number), and
|
54
56
|
# an application-specific sequence.
|
57
|
+
# Some LDAPv3 packets also include an optional
|
58
|
+
# third element, which is a sequence of "controls"
|
59
|
+
# (See RFC 2251, section 4.1.12).
|
55
60
|
# The application-specific tag in the sequence tells
|
56
61
|
# us what kind of packet it is, and each kind has its
|
57
62
|
# own format, defined in RFC-1777.
|
@@ -62,6 +67,10 @@ class LdapPdu
|
|
62
67
|
# it remains to be seen whether there are servers out
|
63
68
|
# there that will not work well with our approach.
|
64
69
|
#
|
70
|
+
# Added a controls-processor to SearchResult.
|
71
|
+
# Didn't add it everywhere because it just _feels_
|
72
|
+
# like it will need to be refactored.
|
73
|
+
#
|
65
74
|
def initialize ber_object
|
66
75
|
begin
|
67
76
|
@msg_id = ber_object[0].to_i
|
@@ -76,8 +85,11 @@ class LdapPdu
|
|
76
85
|
parse_ldap_result ber_object[1]
|
77
86
|
when SearchReturnedData
|
78
87
|
parse_search_return ber_object[1]
|
88
|
+
when SearchResultReferral
|
89
|
+
parse_search_referral ber_object[1]
|
79
90
|
when SearchResult
|
80
91
|
parse_ldap_result ber_object[1]
|
92
|
+
parse_controls(ber_object[2]) if ber_object[2]
|
81
93
|
when ModifyResponse
|
82
94
|
parse_ldap_result ber_object[1]
|
83
95
|
when AddResponse
|
@@ -101,8 +113,12 @@ class LdapPdu
|
|
101
113
|
@ldap_result and @ldap_result[code]
|
102
114
|
end
|
103
115
|
|
116
|
+
# Return RFC-2251 Controls if any.
|
117
|
+
# Messy. Does this functionality belong somewhere else?
|
118
|
+
def result_controls
|
119
|
+
@ldap_controls || []
|
120
|
+
end
|
104
121
|
|
105
|
-
private
|
106
122
|
|
107
123
|
#
|
108
124
|
# parse_ldap_result
|
@@ -111,6 +127,7 @@ class LdapPdu
|
|
111
127
|
sequence.length >= 3 or raise LdapPduError
|
112
128
|
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
|
113
129
|
end
|
130
|
+
private :parse_ldap_result
|
114
131
|
|
115
132
|
#
|
116
133
|
# parse_search_return
|
@@ -136,17 +153,50 @@ class LdapPdu
|
|
136
153
|
# we also return @search_entry, which is an LDAP::Entry object.
|
137
154
|
# If that works out well, then we'll remove the first two.
|
138
155
|
#
|
156
|
+
# Provisionally removed obsolete search_attributes and search_dn, 04May06.
|
157
|
+
#
|
139
158
|
def parse_search_return sequence
|
140
159
|
sequence.length >= 2 or raise LdapPduError
|
141
160
|
@search_entry = LDAP::Entry.new( sequence[0] )
|
142
|
-
|
143
|
-
|
161
|
+
#@search_dn = sequence[0]
|
162
|
+
#@search_attributes = {}
|
144
163
|
sequence[1].each {|seq|
|
145
164
|
@search_entry[seq[0]] = seq[1]
|
146
|
-
|
165
|
+
#@search_attributes[seq[0].downcase.intern] = seq[1]
|
147
166
|
}
|
148
167
|
end
|
149
168
|
|
169
|
+
#
|
170
|
+
# A search referral is a sequence of one or more LDAP URIs.
|
171
|
+
# Any number of search-referral replies can be returned by the server, interspersed
|
172
|
+
# with normal replies in any order.
|
173
|
+
# Until I can think of a better way to do this, we'll return the referrals as an array.
|
174
|
+
# It'll be up to higher-level handlers to expose something reasonable to the client.
|
175
|
+
def parse_search_referral uris
|
176
|
+
@search_referrals = uris
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
|
181
|
+
# of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
|
182
|
+
# Octet String. If only two fields are given, the second one may be
|
183
|
+
# either criticality or data, since criticality has a default value.
|
184
|
+
# Someday we may want to come back here and add support for some of
|
185
|
+
# more-widely used controls. RFC-2696 is a good example.
|
186
|
+
#
|
187
|
+
def parse_controls sequence
|
188
|
+
@ldap_controls = sequence.map do |control|
|
189
|
+
o = OpenStruct.new
|
190
|
+
o.oid,o.criticality,o.value = control[0],control[1],control[2]
|
191
|
+
if o.criticality and o.criticality.is_a?(String)
|
192
|
+
o.value = o.criticality
|
193
|
+
o.criticality = false
|
194
|
+
end
|
195
|
+
o
|
196
|
+
end
|
197
|
+
end
|
198
|
+
private :parse_controls
|
199
|
+
|
150
200
|
|
151
201
|
end
|
152
202
|
|
data/tests/testem.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: testem.rb
|
1
|
+
# $Id: testem.rb 121 2006-05-15 18:36:24Z blackhedd $
|
2
2
|
#
|
3
3
|
#
|
4
4
|
|
@@ -7,5 +7,6 @@ require 'tests/testber'
|
|
7
7
|
require 'tests/testldif'
|
8
8
|
require 'tests/testldap'
|
9
9
|
require 'tests/testpsw'
|
10
|
+
require 'tests/testfilter'
|
10
11
|
|
11
12
|
|
data/tests/testfilter.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# $Id: testfilter.rb 122 2006-05-15 20:03:56Z blackhedd $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
$:.unshift "lib"
|
8
|
+
|
9
|
+
require 'net/ldap'
|
10
|
+
|
11
|
+
|
12
|
+
class TestFilter < Test::Unit::TestCase
|
13
|
+
|
14
|
+
def setup
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_rfc_2254
|
22
|
+
p Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " )
|
23
|
+
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
|
24
|
+
p Net::LDAP::Filter.from_rfc2254( "uid<george*" )
|
25
|
+
p Net::LDAP::Filter.from_rfc2254( "uid <= george*" )
|
26
|
+
p Net::LDAP::Filter.from_rfc2254( "uid>george*" )
|
27
|
+
p Net::LDAP::Filter.from_rfc2254( "uid>=george*" )
|
28
|
+
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
|
29
|
+
|
30
|
+
p Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" )
|
31
|
+
p Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" )
|
32
|
+
p Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" )
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ruby-net-ldap
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.0.2
|
7
|
+
date: 2006-07-12 00:00:00 -04:00
|
8
8
|
summary: A pure Ruby LDAP client library.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -32,6 +32,7 @@ files:
|
|
32
32
|
- LICENCE
|
33
33
|
- ChangeLog
|
34
34
|
- COPYING
|
35
|
+
- tests/testfilter.rb
|
35
36
|
- tests/testpsw.rb
|
36
37
|
- tests/testem.rb
|
37
38
|
- tests/testdata.ldif
|