net-ldap 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of net-ldap might be problematic. Click here for more details.

Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/.travis.yml +19 -1
  4. data/CONTRIBUTING.md +54 -0
  5. data/Hacking.rdoc +2 -4
  6. data/History.rdoc +37 -0
  7. data/Manifest.txt +0 -4
  8. data/README.rdoc +8 -0
  9. data/Rakefile +1 -3
  10. data/lib/net/ber/core_ext.rb +5 -5
  11. data/lib/net/ber/core_ext/string.rb +7 -7
  12. data/lib/net/ber/core_ext/true_class.rb +2 -3
  13. data/lib/net/ldap.rb +134 -620
  14. data/lib/net/ldap/connection.rb +692 -0
  15. data/lib/net/ldap/dataset.rb +18 -4
  16. data/lib/net/ldap/entry.rb +1 -1
  17. data/lib/net/ldap/filter.rb +7 -7
  18. data/lib/net/ldap/password.rb +11 -11
  19. data/lib/net/ldap/pdu.rb +28 -4
  20. data/lib/net/ldap/version.rb +1 -1
  21. data/lib/net/snmp.rb +235 -241
  22. data/net-ldap.gemspec +7 -33
  23. data/script/install-openldap +47 -0
  24. data/script/package +7 -0
  25. data/script/release +16 -0
  26. data/test/ber/core_ext/test_array.rb +22 -0
  27. data/test/ber/core_ext/test_string.rb +25 -0
  28. data/test/ber/test_ber.rb +126 -0
  29. data/test/fixtures/openldap/memberof.ldif +33 -0
  30. data/test/fixtures/openldap/retcode.ldif +76 -0
  31. data/test/fixtures/openldap/slapd.conf.ldif +67 -0
  32. data/test/fixtures/seed.ldif +374 -0
  33. data/test/integration/test_add.rb +28 -0
  34. data/test/integration/test_ber.rb +30 -0
  35. data/test/integration/test_bind.rb +22 -0
  36. data/test/integration/test_delete.rb +31 -0
  37. data/test/integration/test_open.rb +88 -0
  38. data/test/integration/test_return_codes.rb +38 -0
  39. data/test/integration/test_search.rb +77 -0
  40. data/test/support/vm/openldap/.gitignore +1 -0
  41. data/test/support/vm/openldap/README.md +32 -0
  42. data/test/support/vm/openldap/Vagrantfile +33 -0
  43. data/test/test_dn.rb +44 -0
  44. data/test/test_entry.rb +62 -56
  45. data/test/test_filter.rb +98 -2
  46. data/test/test_filter_parser.rb +16 -0
  47. data/test/test_helper.rb +54 -0
  48. data/test/test_ldap.rb +60 -0
  49. data/test/test_ldap_connection.rb +382 -2
  50. data/test/test_ldif.rb +26 -1
  51. data/test/test_password.rb +3 -10
  52. data/test/test_rename.rb +2 -2
  53. data/test/test_search.rb +39 -0
  54. data/test/test_snmp.rb +1 -1
  55. data/test/test_ssl_ber.rb +40 -0
  56. metadata +70 -75
  57. data/.autotest +0 -11
  58. data/.gemtest +0 -0
  59. data/.rspec +0 -2
  60. data/autotest/discover.rb +0 -1
  61. data/spec/integration/ssl_ber_spec.rb +0 -39
  62. data/spec/spec.opts +0 -2
  63. data/spec/spec_helper.rb +0 -28
  64. data/spec/unit/ber/ber_spec.rb +0 -141
  65. data/spec/unit/ber/core_ext/array_spec.rb +0 -24
  66. data/spec/unit/ber/core_ext/string_spec.rb +0 -51
  67. data/spec/unit/ldap/dn_spec.rb +0 -80
  68. data/spec/unit/ldap/entry_spec.rb +0 -51
  69. data/spec/unit/ldap/filter_parser_spec.rb +0 -26
  70. data/spec/unit/ldap/filter_spec.rb +0 -115
  71. data/spec/unit/ldap/search_spec.rb +0 -49
  72. data/spec/unit/ldap_spec.rb +0 -223
  73. data/test/common.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 487807bc08da7baeb4941090020468a8e60fe14a
4
- data.tar.gz: 047ccb6638a81c96eb1bfca1a684726a71bfc1a8
3
+ metadata.gz: f9fc3d7a463a0f673de926b440e144eae66937a4
4
+ data.tar.gz: 2d921f1be37ff33c20100650642f1123fcd2c161
5
5
  SHA512:
6
- metadata.gz: 6d02d5a7af488a5f7ee8b8050c5d281d6e76b7c40baeb1a1275a18b103d2f76cf0fea8dfa865efe2ce243d92b87c3d6abdfa1b47615cf95fb5cd5e9b9806e70a
7
- data.tar.gz: 380cc5961a9a85a1d196203b6712061b5c8b8a4c1008dcfcee67556029269e264d2d7463e2e98572c06020dbae44dbb8ace34f06d584d33a9f1b5f6169f90d9d
6
+ metadata.gz: 803d644ca0fe9b7314df587e68a0ecc7f01f34316d30fd4a926a09dfc4e3ab7e73b388854f9f4eecc78c9b07817d21a0b342e893422d21b8a682c35dcf4a8fb1
7
+ data.tar.gz: e5143361c56f7c60cfe7272c054860403ec53812660730e141688be9fb56adf32b0121ae1d5e27c4a076a963b07b509db96e7a8cc43fa0eec7d5cf0b1e92dcdd
@@ -0,0 +1,7 @@
1
+ *~
2
+ *.swp
3
+ .rvmrc
4
+ pkg/
5
+ doc/
6
+ publish/
7
+ Gemfile.lock
@@ -2,9 +2,27 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+ - 2.1.2
6
+ # optional
5
7
  - jruby-19mode
6
8
  - rbx-19mode
9
+ - rbx-2
10
+
11
+ env:
12
+ - INTEGRATION=openldap
13
+
14
+ install:
15
+ - if [ "$INTEGRATION" = "openldap" ]; then ./script/install-openldap; fi
16
+ - bundle install
17
+
18
+ script: bundle exec rake
19
+
7
20
  matrix:
8
21
  allow_failures:
9
22
  - rvm: jruby-19mode
10
- script: bundle exec rake spec
23
+ - rvm: rbx-19mode
24
+ - rvm: rbx-2
25
+ fast_finish: true
26
+
27
+ notifications:
28
+ email: false
@@ -0,0 +1,54 @@
1
+ # Contribution guide
2
+
3
+ Thank you for using net-ldap. If you'd like to help, keep these guidelines in
4
+ mind.
5
+
6
+ ## Submitting a New Issue
7
+
8
+ If you find a bug, or would like to propose an idea, file a [new issue][issues].
9
+ Include as many details as possible:
10
+
11
+ - Version of net-ldap gem
12
+ - LDAP server version
13
+ - Queries, connection information, any other input
14
+ - output or error messages
15
+
16
+ ## Sending a Pull Request
17
+
18
+ [Pull requests][pr] are always welcome!
19
+
20
+ Check out [the project's issues list][issues] for ideas on what could be improved.
21
+
22
+ Before sending, please add tests and ensure the test suite passes.
23
+
24
+ To run the full suite:
25
+
26
+ `bundle exec rake`
27
+
28
+ To run a specific test file:
29
+
30
+ `bundle exec ruby test/test_ldap.rb`
31
+
32
+ To run a specific test:
33
+
34
+ `bundle exec ruby test/test_ldap.rb -n test_instrument_bind`
35
+
36
+ Pull requests will trigger automatic continuous integration builds on
37
+ [TravisCI][travis]. To run integration tests locally, see the `test/support`
38
+ folder.
39
+
40
+ ## Styleguide
41
+
42
+ ```ruby
43
+ # 1.9+ style hashes
44
+ {key: "value"}
45
+
46
+ # Multi-line arguments with `\`
47
+ MyClass.new \
48
+ foo: 'bar',
49
+ baz: 'garply'
50
+ ```
51
+
52
+ [issues]: https://github.com/ruby-net-ldap/ruby-net-ldap/issues
53
+ [pr]: https://help.github.com/articles/using-pull-requests
54
+ [travis]: https://travis-ci.org/ruby-ldap/ruby-net-ldap
@@ -40,8 +40,8 @@ modification to +Contributors.rdoc+ to add yourself.
40
40
 
41
41
  == Tests
42
42
 
43
- The Net::LDAP team uses RSpec for unit testing; all changes must have rspec
44
- tests for any new or changed features.
43
+ The Net::LDAP team uses [Minitest](http://docs.seattlerb.org/minitest/) for unit
44
+ testing; all changes must have tests for any new or changed features.
45
45
 
46
46
  Your changes should have been tested against at least one real LDAP server; the
47
47
  current tests are not sufficient to find all possible bugs. It's unlikely that
@@ -57,8 +57,6 @@ installed using RubyGems.
57
57
 
58
58
  * *hoe*
59
59
  * *hoe-git*
60
- * *metaid*
61
- * *rspec*
62
60
  * *flexmock*
63
61
 
64
62
  == Participation
@@ -1,3 +1,40 @@
1
+ === Net::LDAP 0.9.0
2
+ * Major changes:
3
+ * Dropped support for ruby 1.8.7, ruby >= 1.9.3 now required
4
+ * Major enhancements:
5
+ * Add support for search time limit parameter
6
+ * Instrument received messages, PDU parsing
7
+ * Minor enhancments:
8
+ * Add support for querying ActiveDirectory capabilities from root dse
9
+ * Bug fixes:
10
+ * Fix reads for multiple concurrent requests with shared, open connections mixing up the results
11
+ * Fix search size option
12
+ * Fix BER encoding bug
13
+ * Code clean-up:
14
+ * Added integration test suite
15
+ * Switch to minitest
16
+
17
+ * Details
18
+ * #150 Support querying ActiveDirectory capabilities when searching root dse
19
+ * #142 Encode true as xFF
20
+ * #124, #145, #146, #152 Cleanup gemspec
21
+ * #138, #144 Track response messages by message id
22
+ * #141 Magic number/constant cleanup
23
+ * #119, #129, #130, #132, #133, #137 Integration tests
24
+ * #115 Search timeout support
25
+ * #140 Fix search size option
26
+ * #139 Cleanup and inline documentation for Net::LDAP::Connection#search
27
+ * #131 Instrumentation
28
+ * #116 Refactor Connection#write
29
+ * #126 Update gitignore
30
+ * #128 Fix whitespace
31
+ * #113, #121 Switch to minitest
32
+ * #123 Base64 encoded dn
33
+ * #114 Separate file for Net::LDAP::Connection
34
+ * #104 Parse version spec in LDIF datasets
35
+ * #106 ldap.modify doc fixes
36
+ * #111 Fix test deprecations
37
+
1
38
  === Net::LDAP 0.5.0 / 2013-07-22
2
39
  * Major changes:
3
40
  * Required Ruby version is >=1.9.3
@@ -1,5 +1,3 @@
1
- .autotest
2
- .rspec
3
1
  .travis.yml
4
2
  Contributors.rdoc
5
3
  Gemfile
@@ -9,7 +7,6 @@ License.rdoc
9
7
  Manifest.txt
10
8
  README.rdoc
11
9
  Rakefile
12
- autotest/discover.rb
13
10
  lib/net-ldap.rb
14
11
  lib/net/ber.rb
15
12
  lib/net/ber/ber_parser.rb
@@ -32,7 +29,6 @@ lib/net/ldap/version.rb
32
29
  lib/net/snmp.rb
33
30
  net-ldap.gemspec
34
31
  spec/integration/ssl_ber_spec.rb
35
- spec/spec.opts
36
32
  spec/spec_helper.rb
37
33
  spec/unit/ber/ber_spec.rb
38
34
  spec/unit/ber/core_ext/array_spec.rb
@@ -37,6 +37,14 @@ sources.
37
37
 
38
38
  Simply require either 'net-ldap' or 'net/ldap'.
39
39
 
40
+ == Release
41
+
42
+ This section is for gem maintainers to cut a new version of the gem.
43
+
44
+ * Update lib/html/pipeline/version.rb to next version number X.X.X following {semver}(http://semver.org/).
45
+ * Update CHANGELOG.md. Get latest changes with `git log --oneline vLAST_RELEASE..HEAD | grep Merge`
46
+ * On the master branch, run `script/release`
47
+
40
48
  :include: Contributors.rdoc
41
49
 
42
50
  :include: License.rdoc
data/Rakefile CHANGED
@@ -30,9 +30,7 @@ Hoe.spec 'net-ldap' do |spec|
30
30
 
31
31
  spec.extra_dev_deps << [ "hoe-git", "~> 1" ]
32
32
  spec.extra_dev_deps << [ "hoe-gemspec", "~> 1" ]
33
- spec.extra_dev_deps << [ "metaid", "~> 1" ]
34
33
  spec.extra_dev_deps << [ "flexmock", ">= 1.3.0" ]
35
- spec.extra_dev_deps << [ "rspec", "~> 2.0" ]
36
34
 
37
35
  spec.clean_globs << "coverage"
38
36
 
@@ -70,7 +68,7 @@ namespace :old do
70
68
  end
71
69
  end
72
70
 
73
- desc "Run a full set of integration and unit tests"
71
+ desc "Run a full set of integration and unit tests"
74
72
  task :cruise => [:test, :spec]
75
73
 
76
74
  # vim: syntax=ruby
@@ -28,35 +28,35 @@ end
28
28
 
29
29
  require 'net/ber/core_ext/array'
30
30
  # :stopdoc:
31
- class Array
31
+ class Array
32
32
  include Net::BER::Extensions::Array
33
33
  end
34
34
  # :startdoc:
35
35
 
36
36
  require 'net/ber/core_ext/bignum'
37
37
  # :stopdoc:
38
- class Bignum
38
+ class Bignum
39
39
  include Net::BER::Extensions::Bignum
40
40
  end
41
41
  # :startdoc:
42
42
 
43
43
  require 'net/ber/core_ext/fixnum'
44
44
  # :stopdoc:
45
- class Fixnum
45
+ class Fixnum
46
46
  include Net::BER::Extensions::Fixnum
47
47
  end
48
48
  # :startdoc:
49
49
 
50
50
  require 'net/ber/core_ext/true_class'
51
51
  # :stopdoc:
52
- class TrueClass
52
+ class TrueClass
53
53
  include Net::BER::Extensions::TrueClass
54
54
  end
55
55
  # :startdoc:
56
56
 
57
57
  require 'net/ber/core_ext/false_class'
58
58
  # :stopdoc:
59
- class FalseClass
59
+ class FalseClass
60
60
  include Net::BER::Extensions::FalseClass
61
61
  end
62
62
  # :startdoc:
@@ -16,13 +16,13 @@ module Net::BER::Extensions::String
16
16
  [code].pack('C') + raw_string.length.to_ber_length_encoding + raw_string
17
17
  end
18
18
 
19
- ##
20
- # Converts a string to a BER string but does *not* encode to UTF-8 first.
21
- # This is required for proper representation of binary data for Microsoft
22
- # Active Directory
23
- def to_ber_bin(code = 0x04)
24
- [code].pack('C') + length.to_ber_length_encoding + self
25
- end
19
+ ##
20
+ # Converts a string to a BER string but does *not* encode to UTF-8 first.
21
+ # This is required for proper representation of binary data for Microsoft
22
+ # Active Directory
23
+ def to_ber_bin(code = 0x04)
24
+ [code].pack('C') + length.to_ber_length_encoding + self
25
+ end
26
26
 
27
27
  def raw_utf8_encoded
28
28
  if self.respond_to?(:encode)
@@ -5,8 +5,7 @@ module Net::BER::Extensions::TrueClass
5
5
  ##
6
6
  # Converts +true+ to the BER wireline representation of +true+.
7
7
  def to_ber
8
- # 20100319 AZ: Note that this may not be the completely correct value,
9
- # per some test documentation. We need to determine the truth of this.
10
- "\001\001\001"
8
+ # http://tools.ietf.org/html/rfc4511#section-5.1
9
+ "\001\001\xFF".force_encoding("ASCII-8BIT")
11
10
  end
12
11
  end
@@ -24,6 +24,7 @@ require 'net/ldap/dataset'
24
24
  require 'net/ldap/password'
25
25
  require 'net/ldap/entry'
26
26
  require 'net/ldap/instrumentation'
27
+ require 'net/ldap/connection'
27
28
  require 'net/ldap/version'
28
29
 
29
30
  # == Quick-start for the Impatient
@@ -316,33 +317,107 @@ class Net::LDAP
316
317
  DefaultPort = 389
317
318
  DefaultAuth = { :method => :anonymous }
318
319
  DefaultTreebase = "dc=com"
319
- DefaultForceNoPage = false
320
+ DefaultForceNoPage = false
320
321
 
321
322
  StartTlsOid = "1.3.6.1.4.1.1466.20037"
322
323
 
324
+ # https://tools.ietf.org/html/rfc4511#section-4.1.9
325
+ # https://tools.ietf.org/html/rfc4511#appendix-A
326
+ ResultCodeSuccess = 0
327
+ ResultCodeOperationsError = 1
328
+ ResultCodeProtocolError = 2
329
+ ResultCodeTimeLimitExceeded = 3
330
+ ResultCodeSizeLimitExceeded = 4
331
+ ResultCodeCompareFalse = 5
332
+ ResultCodeCompareTrue = 6
333
+ ResultCodeAuthMethodNotSupported = 7
334
+ ResultCodeStrongerAuthRequired = 8
335
+ ResultCodeReferral = 10
336
+ ResultCodeAdminLimitExceeded = 11
337
+ ResultCodeUnavailableCriticalExtension = 12
338
+ ResultCodeConfidentialityRequired = 13
339
+ ResultCodeSaslBindInProgress = 14
340
+ ResultCodeNoSuchAttribute = 16
341
+ ResultCodeUndefinedAttributeType = 17
342
+ ResultCodeInappropriateMatching = 18
343
+ ResultCodeConstraintViolation = 19
344
+ ResultCodeAttributeOrValueExists = 20
345
+ ResultCodeInvalidAttributeSyntax = 21
346
+ ResultCodeNoSuchObject = 32
347
+ ResultCodeAliasProblem = 33
348
+ ResultCodeInvalidDNSyntax = 34
349
+ ResultCodeAliasDereferencingProblem = 36
350
+ ResultCodeInappropriateAuthentication = 48
351
+ ResultCodeInvalidCredentials = 49
352
+ ResultCodeInsufficientAccessRights = 50
353
+ ResultCodeBusy = 51
354
+ ResultCodeUnavailable = 52
355
+ ResultCodeUnwillingToPerform = 53
356
+ ResultCodeNamingViolation = 64
357
+ ResultCodeObjectClassViolation = 65
358
+ ResultCodeNotAllowedOnNonLeaf = 66
359
+ ResultCodeNotAllowedOnRDN = 67
360
+ ResultCodeEntryAlreadyExists = 68
361
+ ResultCodeObjectClassModsProhibited = 69
362
+ ResultCodeAffectsMultipleDSAs = 71
363
+ ResultCodeOther = 80
364
+
365
+ # https://tools.ietf.org/html/rfc4511#appendix-A.1
366
+ ResultCodesNonError = [
367
+ ResultCodeSuccess,
368
+ ResultCodeCompareFalse,
369
+ ResultCodeCompareTrue,
370
+ ResultCodeReferral,
371
+ ResultCodeSaslBindInProgress
372
+ ]
373
+
374
+ # nonstandard list of "successful" result codes for searches
375
+ ResultCodesSearchSuccess = [
376
+ ResultCodeSuccess,
377
+ ResultCodeTimeLimitExceeded,
378
+ ResultCodeSizeLimitExceeded
379
+ ]
380
+
381
+ # map of result code to human message
323
382
  ResultStrings = {
324
- 0 => "Success",
325
- 1 => "Operations Error",
326
- 2 => "Protocol Error",
327
- 3 => "Time Limit Exceeded",
328
- 4 => "Size Limit Exceeded",
329
- 10 => "Referral",
330
- 12 => "Unavailable crtical extension",
331
- 14 => "saslBindInProgress",
332
- 16 => "No Such Attribute",
333
- 17 => "Undefined Attribute Type",
334
- 19 => "Constraint Violation",
335
- 20 => "Attribute or Value Exists",
336
- 32 => "No Such Object",
337
- 34 => "Invalid DN Syntax",
338
- 48 => "Inappropriate Authentication",
339
- 49 => "Invalid Credentials",
340
- 50 => "Insufficient Access Rights",
341
- 51 => "Busy",
342
- 52 => "Unavailable",
343
- 53 => "Unwilling to perform",
344
- 65 => "Object Class Violation",
345
- 68 => "Entry Already Exists"
383
+ ResultCodeSuccess => "Success",
384
+ ResultCodeOperationsError => "Operations Error",
385
+ ResultCodeProtocolError => "Protocol Error",
386
+ ResultCodeTimeLimitExceeded => "Time Limit Exceeded",
387
+ ResultCodeSizeLimitExceeded => "Size Limit Exceeded",
388
+ ResultCodeCompareFalse => "False Comparison",
389
+ ResultCodeCompareTrue => "True Comparison",
390
+ ResultCodeAuthMethodNotSupported => "Auth Method Not Supported",
391
+ ResultCodeStrongerAuthRequired => "Stronger Auth Needed",
392
+ ResultCodeReferral => "Referral",
393
+ ResultCodeAdminLimitExceeded => "Admin Limit Exceeded",
394
+ ResultCodeUnavailableCriticalExtension => "Unavailable crtical extension",
395
+ ResultCodeConfidentialityRequired => "Confidentiality Required",
396
+ ResultCodeSaslBindInProgress => "saslBindInProgress",
397
+ ResultCodeNoSuchAttribute => "No Such Attribute",
398
+ ResultCodeUndefinedAttributeType => "Undefined Attribute Type",
399
+ ResultCodeInappropriateMatching => "Inappropriate Matching",
400
+ ResultCodeConstraintViolation => "Constraint Violation",
401
+ ResultCodeAttributeOrValueExists => "Attribute or Value Exists",
402
+ ResultCodeInvalidAttributeSyntax => "Invalide Attribute Syntax",
403
+ ResultCodeNoSuchObject => "No Such Object",
404
+ ResultCodeAliasProblem => "Alias Problem",
405
+ ResultCodeInvalidDNSyntax => "Invalid DN Syntax",
406
+ ResultCodeAliasDereferencingProblem => "Alias Dereferencing Problem",
407
+ ResultCodeInappropriateAuthentication => "Inappropriate Authentication",
408
+ ResultCodeInvalidCredentials => "Invalid Credentials",
409
+ ResultCodeInsufficientAccessRights => "Insufficient Access Rights",
410
+ ResultCodeBusy => "Busy",
411
+ ResultCodeUnavailable => "Unavailable",
412
+ ResultCodeUnwillingToPerform => "Unwilling to perform",
413
+ ResultCodeNamingViolation => "Naming Violation",
414
+ ResultCodeObjectClassViolation => "Object Class Violation",
415
+ ResultCodeNotAllowedOnNonLeaf => "Not Allowed On Non-Leaf",
416
+ ResultCodeNotAllowedOnRDN => "Not Allowed On RDN",
417
+ ResultCodeEntryAlreadyExists => "Entry Already Exists",
418
+ ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited",
419
+ ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs",
420
+ ResultCodeOther => "Other"
346
421
  }
347
422
 
348
423
  module LDAPControls
@@ -397,7 +472,7 @@ class Net::LDAP
397
472
  @verbose = false # Make this configurable with a switch on the class.
398
473
  @auth = args[:auth] || DefaultAuth
399
474
  @base = args[:base] || DefaultTreebase
400
- @force_no_page = args[:force_no_page] || DefaultForceNoPage
475
+ @force_no_page = args[:force_no_page] || DefaultForceNoPage
401
476
  encryption args[:encryption] # may be nil
402
477
 
403
478
  if pr = @auth[:password] and pr.respond_to?(:call)
@@ -485,9 +560,9 @@ class Net::LDAP
485
560
  # standard port for simple-TLS encrypted connections is 636. Be sure you
486
561
  # are using the correct port.
487
562
  #
488
- # <i>[Note: a future version of Net::LDAP will support the STARTTLS LDAP
489
- # control, which will enable encrypted communications on the same TCP port
490
- # used for unencrypted connections.]</i>
563
+ # The :start_tls like the :simple_tls encryption method also encrypts all
564
+ # communcations with the LDAP server. With the exception that it operates
565
+ # over the standard TCP port.
491
566
  def encryption(args)
492
567
  case args
493
568
  when :simple_tls, :start_tls
@@ -548,7 +623,7 @@ class Net::LDAP
548
623
  elsif result
549
624
  os.code = result
550
625
  else
551
- os.code = 0
626
+ os.code = Net::LDAP::ResultCodeSuccess
552
627
  end
553
628
  os.message = Net::LDAP.result2string(os.code)
554
629
  os
@@ -609,6 +684,7 @@ class Net::LDAP
609
684
  # Net::LDAP::SearchScope_WholeSubtree. Default is WholeSubtree.)
610
685
  # * :size (an integer indicating the maximum number of search entries to
611
686
  # return. Default is zero, which signifies no limit.)
687
+ # * :time (an integer restricting the maximum time in seconds allowed for a search. Default is zero, no time limit RFC 4511 4.5.1.5)
612
688
  # * :deref (one of: Net::LDAP::DerefAliases_Never, Net::LDAP::DerefAliases_Search,
613
689
  # Net::LDAP::DerefAliases_Find, Net::LDAP::DerefAliases_Always. Default is Never.)
614
690
  #
@@ -665,7 +741,7 @@ class Net::LDAP
665
741
  :port => @port,
666
742
  :encryption => @encryption,
667
743
  :instrumentation_service => @instrumentation_service
668
- if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
744
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
669
745
  @result = conn.search(args) { |entry|
670
746
  result_set << entry if result_set
671
747
  yield entry if block_given?
@@ -677,7 +753,11 @@ class Net::LDAP
677
753
  end
678
754
 
679
755
  if return_result_set
680
- (!@result.nil? && @result.result_code == 0) ? result_set : nil
756
+ unless @result.nil?
757
+ if ResultCodesSearchSuccess.include?(@result.result_code)
758
+ result_set
759
+ end
760
+ end
681
761
  else
682
762
  @result.success?
683
763
  end
@@ -860,7 +940,7 @@ class Net::LDAP
860
940
  :port => @port,
861
941
  :encryption => @encryption,
862
942
  :instrumentation_service => @instrumentation_service
863
- if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
943
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
864
944
  @result = conn.add(args)
865
945
  end
866
946
  ensure
@@ -894,9 +974,10 @@ class Net::LDAP
894
974
  # operations in order.
895
975
  #
896
976
  # Each of the operations appearing in the Array must itself be an Array
897
- # with exactly three elements: an operator:: must be :add, :replace, or
898
- # :delete an attribute name:: the attribute name (string or symbol) to
899
- # modify a value:: either a string or an array of strings.
977
+ # with exactly three elements:
978
+ # an operator :: must be :add, :replace, or :delete
979
+ # an attribute name :: the attribute name (string or symbol) to modify
980
+ # a value :: either a string or an array of strings.
900
981
  #
901
982
  # The :add operator will, unsurprisingly, add the specified values to the
902
983
  # specified attribute. If the attribute does not already exist, :add will
@@ -939,13 +1020,13 @@ class Net::LDAP
939
1020
  # may not get extended information that will tell you which one failed.
940
1021
  # #modify has no notion of an atomic transaction. If you specify a chain
941
1022
  # of modifications in one call to #modify, and one of them fails, the
942
- # preceding ones will usually not be "rolled back, " resulting in a
1023
+ # preceding ones will usually not be "rolled back", resulting in a
943
1024
  # partial update. This is a limitation of the LDAP protocol, not of
944
1025
  # Net::LDAP.
945
1026
  #
946
1027
  # The lack of transactional atomicity in LDAP means that you're usually
947
1028
  # better off using the convenience methods #add_attribute,
948
- # #replace_attribute, and #delete_attribute, which are are wrappers over
1029
+ # #replace_attribute, and #delete_attribute, which are wrappers over
949
1030
  # #modify. However, certain LDAP servers may provide concurrency
950
1031
  # semantics, in which the several operations contained in a single #modify
951
1032
  # call are not interleaved with other modification-requests received
@@ -963,7 +1044,7 @@ class Net::LDAP
963
1044
  :port => @port,
964
1045
  :encryption => @encryption,
965
1046
  :instrumentation_service => @instrumentation_service
966
- if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
1047
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
967
1048
  @result = conn.modify(args)
968
1049
  end
969
1050
  ensure
@@ -1040,7 +1121,7 @@ class Net::LDAP
1040
1121
  :port => @port,
1041
1122
  :encryption => @encryption,
1042
1123
  :instrumentation_service => @instrumentation_service
1043
- if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
1124
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
1044
1125
  @result = conn.rename(args)
1045
1126
  end
1046
1127
  ensure
@@ -1073,7 +1154,7 @@ class Net::LDAP
1073
1154
  :port => @port,
1074
1155
  :encryption => @encryption,
1075
1156
  :instrumentation_service => @instrumentation_service
1076
- if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
1157
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
1077
1158
  @result = conn.delete(args)
1078
1159
  end
1079
1160
  ensure
@@ -1113,9 +1194,16 @@ class Net::LDAP
1113
1194
  def search_root_dse
1114
1195
  rs = search(:ignore_server_caps => true, :base => "",
1115
1196
  :scope => SearchScope_BaseObject,
1116
- :attributes => [ :namingContexts, :supportedLdapVersion,
1117
- :altServer, :supportedControl, :supportedExtension,
1118
- :supportedFeatures, :supportedSASLMechanisms])
1197
+ :attributes => [
1198
+ :altServer,
1199
+ :namingContexts,
1200
+ :supportedCapabilities,
1201
+ :supportedControl,
1202
+ :supportedExtension,
1203
+ :supportedFeatures,
1204
+ :supportedLdapVersion,
1205
+ :supportedSASLMechanisms
1206
+ ])
1119
1207
  (rs and rs.first) or Net::LDAP::Entry.new
1120
1208
  end
1121
1209
 
@@ -1166,585 +1254,11 @@ class Net::LDAP
1166
1254
  # MUST refactor the root_dse call out.
1167
1255
  #++
1168
1256
  def paged_searches_supported?
1169
- # active directory returns that it supports paged results. However
1170
- # it returns binary data in the rfc2696_cookie which throws an
1171
- # encoding exception breaking searching.
1172
- return false if @force_no_page
1257
+ # active directory returns that it supports paged results. However
1258
+ # it returns binary data in the rfc2696_cookie which throws an
1259
+ # encoding exception breaking searching.
1260
+ return false if @force_no_page
1173
1261
  @server_caps ||= search_root_dse
1174
1262
  @server_caps[:supportedcontrol].include?(Net::LDAP::LDAPControls::PAGED_RESULTS)
1175
1263
  end
1176
1264
  end # class LDAP
1177
-
1178
- # This is a private class used internally by the library. It should not
1179
- # be called by user code.
1180
- class Net::LDAP::Connection #:nodoc:
1181
- include Net::LDAP::Instrumentation
1182
-
1183
- LdapVersion = 3
1184
- MaxSaslChallenges = 10
1185
-
1186
- def initialize(server)
1187
- @instrumentation_service = server[:instrumentation_service]
1188
-
1189
- begin
1190
- @conn = TCPSocket.new(server[:host], server[:port])
1191
- rescue SocketError
1192
- raise Net::LDAP::LdapError, "No such address or other socket error."
1193
- rescue Errno::ECONNREFUSED
1194
- raise Net::LDAP::LdapError, "Server #{server[:host]} refused connection on port #{server[:port]}."
1195
- end
1196
-
1197
- if server[:encryption]
1198
- setup_encryption server[:encryption]
1199
- end
1200
-
1201
- yield self if block_given?
1202
- end
1203
-
1204
- module GetbyteForSSLSocket
1205
- def getbyte
1206
- getc.ord
1207
- end
1208
- end
1209
-
1210
- module FixSSLSocketSyncClose
1211
- def close
1212
- super
1213
- io.close
1214
- end
1215
- end
1216
-
1217
- def self.wrap_with_ssl(io)
1218
- raise Net::LDAP::LdapError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL
1219
- ctx = OpenSSL::SSL::SSLContext.new
1220
- conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
1221
- conn.connect
1222
-
1223
- # Doesn't work:
1224
- # conn.sync_close = true
1225
-
1226
- conn.extend(GetbyteForSSLSocket) unless conn.respond_to?(:getbyte)
1227
- conn.extend(FixSSLSocketSyncClose)
1228
-
1229
- conn
1230
- end
1231
-
1232
- #--
1233
- # Helper method called only from new, and only after we have a
1234
- # successfully-opened @conn instance variable, which is a TCP connection.
1235
- # Depending on the received arguments, we establish SSL, potentially
1236
- # replacing the value of @conn accordingly. Don't generate any errors here
1237
- # if no encryption is requested. DO raise Net::LDAP::LdapError objects if encryption
1238
- # is requested and we have trouble setting it up. That includes if OpenSSL
1239
- # is not set up on the machine. (Question: how does the Ruby OpenSSL
1240
- # wrapper react in that case?) DO NOT filter exceptions raised by the
1241
- # OpenSSL library. Let them pass back to the user. That should make it
1242
- # easier for us to debug the problem reports. Presumably (hopefully?) that
1243
- # will also produce recognizable errors if someone tries to use this on a
1244
- # machine without OpenSSL.
1245
- #
1246
- # The simple_tls method is intended as the simplest, stupidest, easiest
1247
- # solution for people who want nothing more than encrypted comms with the
1248
- # LDAP server. It doesn't do any server-cert validation and requires
1249
- # nothing in the way of key files and root-cert files, etc etc. OBSERVE:
1250
- # WE REPLACE the value of @conn, which is presumed to be a connected
1251
- # TCPSocket object.
1252
- #
1253
- # The start_tls method is supported by many servers over the standard LDAP
1254
- # port. It does not require an alternative port for encrypted
1255
- # communications, as with simple_tls. Thanks for Kouhei Sutou for
1256
- # generously contributing the :start_tls path.
1257
- #++
1258
- def setup_encryption(args)
1259
- case args[:method]
1260
- when :simple_tls
1261
- @conn = self.class.wrap_with_ssl(@conn)
1262
- # additional branches requiring server validation and peer certs, etc.
1263
- # go here.
1264
- when :start_tls
1265
- msgid = next_msgid.to_ber
1266
- request = [Net::LDAP::StartTlsOid.to_ber_contextspecific(0)].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
1267
- request_pkt = [msgid, request].to_ber_sequence
1268
- write request_pkt
1269
- be = read
1270
- raise Net::LDAP::LdapError, "no start_tls result" if be.nil?
1271
- pdu = Net::LDAP::PDU.new(be)
1272
- raise Net::LDAP::LdapError, "no start_tls result" if pdu.nil?
1273
- if pdu.result_code.zero?
1274
- @conn = self.class.wrap_with_ssl(@conn)
1275
- else
1276
- raise Net::LDAP::LdapError, "start_tls failed: #{pdu.result_code}"
1277
- end
1278
- else
1279
- raise Net::LDAP::LdapError, "unsupported encryption method #{args[:method]}"
1280
- end
1281
- end
1282
-
1283
- #--
1284
- # This is provided as a convenience method to make sure a connection
1285
- # object gets closed without waiting for a GC to happen. Clients shouldn't
1286
- # have to call it, but perhaps it will come in handy someday.
1287
- #++
1288
- def close
1289
- @conn.close
1290
- @conn = nil
1291
- end
1292
-
1293
- # Internal: Reads and parses data from the configured connection.
1294
- #
1295
- # - syntax: the BER syntax to use to parse the read data with
1296
- #
1297
- # Returns basic BER objects.
1298
- def read(syntax = Net::LDAP::AsnSyntax)
1299
- instrument "read.net_ldap_connection", :syntax => syntax do |payload|
1300
- @conn.read_ber(syntax) do |id, content_length|
1301
- payload[:object_type_id] = id
1302
- payload[:content_length] = content_length
1303
- end
1304
- end
1305
- end
1306
- private :read
1307
-
1308
- # Internal: Writes the given packet to the configured connection.
1309
- #
1310
- # - packet: the BER data packet to write on the socket.
1311
- #
1312
- # Returns the return value from writing to the connection, which in some
1313
- # cases is the Integer number of bytes written to the socket.
1314
- def write(packet)
1315
- instrument "write.net_ldap_connection" do |payload|
1316
- payload[:content_length] = @conn.write(packet)
1317
- end
1318
- end
1319
- private :write
1320
-
1321
- def next_msgid
1322
- @msgid ||= 0
1323
- @msgid += 1
1324
- end
1325
-
1326
- def bind(auth)
1327
- instrument "bind.net_ldap_connection" do |payload|
1328
- payload[:method] = meth = auth[:method]
1329
- if [:simple, :anonymous, :anon].include?(meth)
1330
- bind_simple auth
1331
- elsif meth == :sasl
1332
- bind_sasl(auth)
1333
- elsif meth == :gss_spnego
1334
- bind_gss_spnego(auth)
1335
- else
1336
- raise Net::LDAP::LdapError, "Unsupported auth method (#{meth})"
1337
- end
1338
- end
1339
- end
1340
-
1341
- #--
1342
- # Implements a simple user/psw authentication. Accessed by calling #bind
1343
- # with a method of :simple or :anonymous.
1344
- #++
1345
- def bind_simple(auth)
1346
- user, psw = if auth[:method] == :simple
1347
- [auth[:username] || auth[:dn], auth[:password]]
1348
- else
1349
- ["", ""]
1350
- end
1351
-
1352
- raise Net::LDAP::LdapError, "Invalid binding information" unless (user && psw)
1353
-
1354
- msgid = next_msgid.to_ber
1355
- request = [LdapVersion.to_ber, user.to_ber,
1356
- psw.to_ber_contextspecific(0)].to_ber_appsequence(0)
1357
- request_pkt = [msgid, request].to_ber_sequence
1358
- write request_pkt
1359
-
1360
- (be = read and pdu = Net::LDAP::PDU.new(be)) or raise Net::LDAP::LdapError, "no bind result"
1361
-
1362
- pdu
1363
- end
1364
-
1365
- #--
1366
- # Required parameters: :mechanism, :initial_credential and
1367
- # :challenge_response
1368
- #
1369
- # Mechanism is a string value that will be passed in the SASL-packet's
1370
- # "mechanism" field.
1371
- #
1372
- # Initial credential is most likely a string. It's passed in the initial
1373
- # BindRequest that goes to the server. In some protocols, it may be empty.
1374
- #
1375
- # Challenge-response is a Ruby proc that takes a single parameter and
1376
- # returns an object that will typically be a string. The
1377
- # challenge-response block is called when the server returns a
1378
- # BindResponse with a result code of 14 (saslBindInProgress). The
1379
- # challenge-response block receives a parameter containing the data
1380
- # returned by the server in the saslServerCreds field of the LDAP
1381
- # BindResponse packet. The challenge-response block may be called multiple
1382
- # times during the course of a SASL authentication, and each time it must
1383
- # return a value that will be passed back to the server as the credential
1384
- # data in the next BindRequest packet.
1385
- #++
1386
- def bind_sasl(auth)
1387
- mech, cred, chall = auth[:mechanism], auth[:initial_credential],
1388
- auth[:challenge_response]
1389
- raise Net::LDAP::LdapError, "Invalid binding information" unless (mech && cred && chall)
1390
-
1391
- n = 0
1392
- loop {
1393
- msgid = next_msgid.to_ber
1394
- sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
1395
- request = [LdapVersion.to_ber, "".to_ber, sasl].to_ber_appsequence(0)
1396
- request_pkt = [msgid, request].to_ber_sequence
1397
- write request_pkt
1398
-
1399
- (be = read and pdu = Net::LDAP::PDU.new(be)) or raise Net::LDAP::LdapError, "no bind result"
1400
- return pdu unless pdu.result_code == 14 # saslBindInProgress
1401
- raise Net::LDAP::LdapError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
1402
-
1403
- cred = chall.call(pdu.result_server_sasl_creds)
1404
- }
1405
-
1406
- raise Net::LDAP::LdapError, "why are we here?"
1407
- end
1408
- private :bind_sasl
1409
-
1410
- #--
1411
- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
1412
- # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
1413
- # integrate it without introducing an external dependency.
1414
- #
1415
- # This authentication method is accessed by calling #bind with a :method
1416
- # parameter of :gss_spnego. It requires :username and :password
1417
- # attributes, just like the :simple authentication method. It performs a
1418
- # GSS-SPNEGO authentication with the server, which is presumed to be a
1419
- # Microsoft Active Directory.
1420
- #++
1421
- def bind_gss_spnego(auth)
1422
- require 'ntlm'
1423
-
1424
- user, psw = [auth[:username] || auth[:dn], auth[:password]]
1425
- raise Net::LDAP::LdapError, "Invalid binding information" unless (user && psw)
1426
-
1427
- nego = proc { |challenge|
1428
- t2_msg = NTLM::Message.parse(challenge)
1429
- t3_msg = t2_msg.response({ :user => user, :password => psw },
1430
- { :ntlmv2 => true })
1431
- t3_msg.serialize
1432
- }
1433
-
1434
- bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO",
1435
- :initial_credential => NTLM::Message::Type1.new.serialize,
1436
- :challenge_response => nego)
1437
- end
1438
- private :bind_gss_spnego
1439
-
1440
-
1441
- #--
1442
- # Allow the caller to specify a sort control
1443
- #
1444
- # The format of the sort control needs to be:
1445
- #
1446
- # :sort_control => ["cn"] # just a string
1447
- # or
1448
- # :sort_control => [["cn", "matchingRule", true]] #attribute, matchingRule, direction (true / false)
1449
- # or
1450
- # :sort_control => ["givenname","sn"] #multiple strings or arrays
1451
- #
1452
- def encode_sort_controls(sort_definitions)
1453
- return sort_definitions unless sort_definitions
1454
-
1455
- sort_control_values = sort_definitions.map do |control|
1456
- control = Array(control) # if there is only an attribute name as a string then infer the orderinrule and reverseorder
1457
- control[0] = String(control[0]).to_ber,
1458
- control[1] = String(control[1]).to_ber,
1459
- control[2] = (control[2] == true).to_ber
1460
- control.to_ber_sequence
1461
- end
1462
- sort_control = [
1463
- Net::LDAP::LDAPControls::SORT_REQUEST.to_ber,
1464
- false.to_ber,
1465
- sort_control_values.to_ber_sequence.to_s.to_ber
1466
- ].to_ber_sequence
1467
- end
1468
-
1469
- #--
1470
- # Alternate implementation, this yields each search entry to the caller as
1471
- # it are received.
1472
- #
1473
- # TODO: certain search parameters are hardcoded.
1474
- # TODO: if we mis-parse the server results or the results are wrong, we
1475
- # can block forever. That's because we keep reading results until we get a
1476
- # type-5 packet, which might never come. We need to support the time-limit
1477
- # in the protocol.
1478
- #++
1479
- def search(args = {})
1480
- search_filter = (args && args[:filter]) ||
1481
- Net::LDAP::Filter.eq("objectclass", "*")
1482
- search_filter = Net::LDAP::Filter.construct(search_filter) if search_filter.is_a?(String)
1483
- search_base = (args && args[:base]) || "dc=example, dc=com"
1484
- search_attributes = ((args && args[:attributes]) || []).map { |attr| attr.to_s.to_ber}
1485
- return_referrals = args && args[:return_referrals] == true
1486
- sizelimit = (args && args[:size].to_i) || 0
1487
- raise Net::LDAP::LdapError, "invalid search-size" unless sizelimit >= 0
1488
- paged_searches_supported = (args && args[:paged_searches_supported])
1489
-
1490
- attributes_only = (args and args[:attributes_only] == true)
1491
- scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
1492
- raise Net::LDAP::LdapError, "invalid search scope" unless Net::LDAP::SearchScopes.include?(scope)
1493
-
1494
- sort_control = encode_sort_controls(args.fetch(:sort_controls){ false })
1495
-
1496
- deref = args[:deref] || Net::LDAP::DerefAliases_Never
1497
- raise Net::LDAP::LdapError.new( "invalid alias dereferencing value" ) unless Net::LDAP::DerefAliasesArray.include?(deref)
1498
-
1499
-
1500
- # An interesting value for the size limit would be close to A/D's
1501
- # built-in page limit of 1000 records, but openLDAP newer than version
1502
- # 2.2.0 chokes on anything bigger than 126. You get a silent error that
1503
- # is easily visible by running slapd in debug mode. Go figure.
1504
- #
1505
- # Changed this around 06Sep06 to support a caller-specified search-size
1506
- # limit. Because we ALWAYS do paged searches, we have to work around the
1507
- # problem that it's not legal to specify a "normal" sizelimit (in the
1508
- # body of the search request) that is larger than the page size we're
1509
- # requesting. Unfortunately, I have the feeling that this will break
1510
- # with LDAP servers that don't support paged searches!!!
1511
- #
1512
- # (Because we pass zero as the sizelimit on search rounds when the
1513
- # remaining limit is larger than our max page size of 126. In these
1514
- # cases, I think the caller's search limit will be ignored!)
1515
- #
1516
- # CONFIRMED: This code doesn't work on LDAPs that don't support paged
1517
- # searches when the size limit is larger than 126. We're going to have
1518
- # to do a root-DSE record search and not do a paged search if the LDAP
1519
- # doesn't support it. Yuck.
1520
- rfc2696_cookie = [126, ""]
1521
- result_pdu = nil
1522
- n_results = 0
1523
-
1524
- instrument "search.net_ldap_connection",
1525
- :filter => search_filter,
1526
- :base => search_base,
1527
- :scope => scope,
1528
- :limit => sizelimit,
1529
- :sort => sort_control,
1530
- :referrals => return_referrals,
1531
- :deref => deref,
1532
- :attributes => search_attributes do |payload|
1533
- loop do
1534
- # should collect this into a private helper to clarify the structure
1535
- query_limit = 0
1536
- if sizelimit > 0
1537
- if paged_searches_supported
1538
- query_limit = (((sizelimit - n_results) < 126) ? (sizelimit -
1539
- n_results) : 0)
1540
- else
1541
- query_limit = sizelimit
1542
- end
1543
- end
1544
-
1545
- request = [
1546
- search_base.to_ber,
1547
- scope.to_ber_enumerated,
1548
- deref.to_ber_enumerated,
1549
- query_limit.to_ber, # size limit
1550
- 0.to_ber,
1551
- attributes_only.to_ber,
1552
- search_filter.to_ber,
1553
- search_attributes.to_ber_sequence
1554
- ].to_ber_appsequence(3)
1555
-
1556
- # rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
1557
- # this breaks when calling to_ber. (Can't force binary data to UTF-8)
1558
- # we have to disable paging (even though server supports it) to get around this...
1559
-
1560
- controls = []
1561
- controls <<
1562
- [
1563
- Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
1564
- # Criticality MUST be false to interoperate with normal LDAPs.
1565
- false.to_ber,
1566
- rfc2696_cookie.map{ |v| v.to_ber}.to_ber_sequence.to_s.to_ber
1567
- ].to_ber_sequence if paged_searches_supported
1568
- controls << sort_control if sort_control
1569
- controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
1570
-
1571
- pkt = [next_msgid.to_ber, request, controls].compact.to_ber_sequence
1572
- write pkt
1573
-
1574
- result_pdu = nil
1575
- controls = []
1576
-
1577
- while (be = read) && (pdu = Net::LDAP::PDU.new(be))
1578
- case pdu.app_tag
1579
- when Net::LDAP::PDU::SearchReturnedData
1580
- n_results += 1
1581
- yield pdu.search_entry if block_given?
1582
- when Net::LDAP::PDU::SearchResultReferral
1583
- if return_referrals
1584
- if block_given?
1585
- se = Net::LDAP::Entry.new
1586
- se[:search_referrals] = (pdu.search_referrals || [])
1587
- yield se
1588
- end
1589
- end
1590
- when Net::LDAP::PDU::SearchResult
1591
- result_pdu = pdu
1592
- controls = pdu.result_controls
1593
- if return_referrals && pdu.result_code == 10
1594
- if block_given?
1595
- se = Net::LDAP::Entry.new
1596
- se[:search_referrals] = (pdu.search_referrals || [])
1597
- yield se
1598
- end
1599
- end
1600
- break
1601
- else
1602
- raise Net::LDAP::LdapError, "invalid response-type in search: #{pdu.app_tag}"
1603
- end
1604
- end
1605
-
1606
- # count number of pages of results
1607
- payload[:page_count] ||= 0
1608
- payload[:page_count] += 1
1609
-
1610
- # When we get here, we have seen a type-5 response. If there is no
1611
- # error AND there is an RFC-2696 cookie, then query again for the next
1612
- # page of results. If not, we're done. Don't screw this up or we'll
1613
- # break every search we do.
1614
- #
1615
- # Noticed 02Sep06, look at the read_ber call in this loop, shouldn't
1616
- # that have a parameter of AsnSyntax? Does this just accidentally
1617
- # work? According to RFC-2696, the value expected in this position is
1618
- # of type OCTET STRING, covered in the default syntax supported by
1619
- # read_ber, so I guess we're ok.
1620
- more_pages = false
1621
- if result_pdu.result_code == 0 and controls
1622
- controls.each do |c|
1623
- if c.oid == Net::LDAP::LDAPControls::PAGED_RESULTS
1624
- # just in case some bogus server sends us more than 1 of these.
1625
- more_pages = false
1626
- if c.value and c.value.length > 0
1627
- cookie = c.value.read_ber[1]
1628
- if cookie and cookie.length > 0
1629
- rfc2696_cookie[1] = cookie
1630
- more_pages = true
1631
- end
1632
- end
1633
- end
1634
- end
1635
- end
1636
-
1637
- break unless more_pages
1638
- end # loop
1639
-
1640
- # track total result count
1641
- payload[:result_count] = n_results
1642
-
1643
- result_pdu || OpenStruct.new(:status => :failure, :result_code => 1, :message => "Invalid search")
1644
- end # instrument
1645
- end
1646
-
1647
- MODIFY_OPERATIONS = { #:nodoc:
1648
- :add => 0,
1649
- :delete => 1,
1650
- :replace => 2
1651
- }
1652
-
1653
- def self.modify_ops(operations)
1654
- ops = []
1655
- if operations
1656
- operations.each { |op, attrib, values|
1657
- # TODO, fix the following line, which gives a bogus error if the
1658
- # opcode is invalid.
1659
- op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
1660
- values = [ values ].flatten.map { |v| v.to_ber if v }.to_ber_set
1661
- values = [ attrib.to_s.to_ber, values ].to_ber_sequence
1662
- ops << [ op_ber, values ].to_ber
1663
- }
1664
- end
1665
- ops
1666
- end
1667
-
1668
- #--
1669
- # TODO: need to support a time limit, in case the server fails to respond.
1670
- # TODO: We're throwing an exception here on empty DN. Should return a
1671
- # proper error instead, probaby from farther up the chain.
1672
- # TODO: If the user specifies a bogus opcode, we'll throw a confusing
1673
- # error here ("to_ber_enumerated is not defined on nil").
1674
- #++
1675
- def modify(args)
1676
- modify_dn = args[:dn] or raise "Unable to modify empty DN"
1677
- ops = self.class.modify_ops args[:operations]
1678
- request = [ modify_dn.to_ber,
1679
- ops.to_ber_sequence ].to_ber_appsequence(6)
1680
- pkt = [ next_msgid.to_ber, request ].to_ber_sequence
1681
- write pkt
1682
-
1683
- (be = read) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == Net::LDAP::PDU::ModifyResponse) or raise Net::LDAP::LdapError, "response missing or invalid"
1684
-
1685
- pdu
1686
- end
1687
-
1688
- #--
1689
- # TODO: need to support a time limit, in case the server fails to respond.
1690
- # Unlike other operation-methods in this class, we return a result hash
1691
- # rather than a simple result number. This is experimental, and eventually
1692
- # we'll want to do this with all the others. The point is to have access
1693
- # to the error message and the matched-DN returned by the server.
1694
- #++
1695
- def add(args)
1696
- add_dn = args[:dn] or raise Net::LDAP::LdapError, "Unable to add empty DN"
1697
- add_attrs = []
1698
- a = args[:attributes] and a.each { |k, v|
1699
- add_attrs << [ k.to_s.to_ber, Array(v).map { |m| m.to_ber}.to_ber_set ].to_ber_sequence
1700
- }
1701
-
1702
- request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8)
1703
- pkt = [next_msgid.to_ber, request].to_ber_sequence
1704
- write pkt
1705
-
1706
- (be = read) &&
1707
- (pdu = Net::LDAP::PDU.new(be)) &&
1708
- (pdu.app_tag == Net::LDAP::PDU::AddResponse) or
1709
- raise Net::LDAP::LdapError, "response missing or invalid"
1710
-
1711
- pdu
1712
- end
1713
-
1714
- #--
1715
- # TODO: need to support a time limit, in case the server fails to respond.
1716
- #++
1717
- def rename(args)
1718
- old_dn = args[:olddn] or raise "Unable to rename empty DN"
1719
- new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN"
1720
- delete_attrs = args[:delete_attributes] ? true : false
1721
- new_superior = args[:new_superior]
1722
-
1723
- request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber]
1724
- request << new_superior.to_ber_contextspecific(0) unless new_superior == nil
1725
-
1726
- pkt = [next_msgid.to_ber, request.to_ber_appsequence(12)].to_ber_sequence
1727
- write pkt
1728
-
1729
- (be = read) &&
1730
- (pdu = Net::LDAP::PDU.new( be )) && (pdu.app_tag == Net::LDAP::PDU::ModifyRDNResponse) or
1731
- raise Net::LDAP::LdapError.new( "response missing or invalid" )
1732
-
1733
- pdu
1734
- end
1735
-
1736
- #--
1737
- # TODO, need to support a time limit, in case the server fails to respond.
1738
- #++
1739
- def delete(args)
1740
- dn = args[:dn] or raise "Unable to delete empty DN"
1741
- controls = args.include?(:control_codes) ? args[:control_codes].to_ber_control : nil #use nil so we can compact later
1742
- request = dn.to_s.to_ber_application_string(10)
1743
- pkt = [next_msgid.to_ber, request, controls].compact.to_ber_sequence
1744
- write pkt
1745
-
1746
- (be = read) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == Net::LDAP::PDU::DeleteResponse) or raise Net::LDAP::LdapError, "response missing or invalid"
1747
-
1748
- pdu
1749
- end
1750
- end # class Connection