ruby-net-ldap 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|