net-ldap 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of net-ldap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +7 -0
- data/.travis.yml +19 -1
- data/CONTRIBUTING.md +54 -0
- data/Hacking.rdoc +2 -4
- data/History.rdoc +37 -0
- data/Manifest.txt +0 -4
- data/README.rdoc +8 -0
- data/Rakefile +1 -3
- data/lib/net/ber/core_ext.rb +5 -5
- data/lib/net/ber/core_ext/string.rb +7 -7
- data/lib/net/ber/core_ext/true_class.rb +2 -3
- data/lib/net/ldap.rb +134 -620
- data/lib/net/ldap/connection.rb +692 -0
- data/lib/net/ldap/dataset.rb +18 -4
- data/lib/net/ldap/entry.rb +1 -1
- data/lib/net/ldap/filter.rb +7 -7
- data/lib/net/ldap/password.rb +11 -11
- data/lib/net/ldap/pdu.rb +28 -4
- data/lib/net/ldap/version.rb +1 -1
- data/lib/net/snmp.rb +235 -241
- data/net-ldap.gemspec +7 -33
- data/script/install-openldap +47 -0
- data/script/package +7 -0
- data/script/release +16 -0
- data/test/ber/core_ext/test_array.rb +22 -0
- data/test/ber/core_ext/test_string.rb +25 -0
- data/test/ber/test_ber.rb +126 -0
- data/test/fixtures/openldap/memberof.ldif +33 -0
- data/test/fixtures/openldap/retcode.ldif +76 -0
- data/test/fixtures/openldap/slapd.conf.ldif +67 -0
- data/test/fixtures/seed.ldif +374 -0
- data/test/integration/test_add.rb +28 -0
- data/test/integration/test_ber.rb +30 -0
- data/test/integration/test_bind.rb +22 -0
- data/test/integration/test_delete.rb +31 -0
- data/test/integration/test_open.rb +88 -0
- data/test/integration/test_return_codes.rb +38 -0
- data/test/integration/test_search.rb +77 -0
- data/test/support/vm/openldap/.gitignore +1 -0
- data/test/support/vm/openldap/README.md +32 -0
- data/test/support/vm/openldap/Vagrantfile +33 -0
- data/test/test_dn.rb +44 -0
- data/test/test_entry.rb +62 -56
- data/test/test_filter.rb +98 -2
- data/test/test_filter_parser.rb +16 -0
- data/test/test_helper.rb +54 -0
- data/test/test_ldap.rb +60 -0
- data/test/test_ldap_connection.rb +382 -2
- data/test/test_ldif.rb +26 -1
- data/test/test_password.rb +3 -10
- data/test/test_rename.rb +2 -2
- data/test/test_search.rb +39 -0
- data/test/test_snmp.rb +1 -1
- data/test/test_ssl_ber.rb +40 -0
- metadata +70 -75
- data/.autotest +0 -11
- data/.gemtest +0 -0
- data/.rspec +0 -2
- data/autotest/discover.rb +0 -1
- data/spec/integration/ssl_ber_spec.rb +0 -39
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -28
- data/spec/unit/ber/ber_spec.rb +0 -141
- data/spec/unit/ber/core_ext/array_spec.rb +0 -24
- data/spec/unit/ber/core_ext/string_spec.rb +0 -51
- data/spec/unit/ldap/dn_spec.rb +0 -80
- data/spec/unit/ldap/entry_spec.rb +0 -51
- data/spec/unit/ldap/filter_parser_spec.rb +0 -26
- data/spec/unit/ldap/filter_spec.rb +0 -115
- data/spec/unit/ldap/search_spec.rb +0 -49
- data/spec/unit/ldap_spec.rb +0 -223
- data/test/common.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9fc3d7a463a0f673de926b440e144eae66937a4
|
4
|
+
data.tar.gz: 2d921f1be37ff33c20100650642f1123fcd2c161
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 803d644ca0fe9b7314df587e68a0ecc7f01f34316d30fd4a926a09dfc4e3ab7e73b388854f9f4eecc78c9b07817d21a0b342e893422d21b8a682c35dcf4a8fb1
|
7
|
+
data.tar.gz: e5143361c56f7c60cfe7272c054860403ec53812660730e141688be9fb56adf32b0121ae1d5e27c4a076a963b07b509db96e7a8cc43fa0eec7d5cf0b1e92dcdd
|
data/.gitignore
ADDED
data/.travis.yml
CHANGED
@@ -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
|
-
|
23
|
+
- rvm: rbx-19mode
|
24
|
+
- rvm: rbx-2
|
25
|
+
fast_finish: true
|
26
|
+
|
27
|
+
notifications:
|
28
|
+
email: false
|
data/CONTRIBUTING.md
ADDED
@@ -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
|
data/Hacking.rdoc
CHANGED
@@ -40,8 +40,8 @@ modification to +Contributors.rdoc+ to add yourself.
|
|
40
40
|
|
41
41
|
== Tests
|
42
42
|
|
43
|
-
The Net::LDAP team uses
|
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
|
data/History.rdoc
CHANGED
@@ -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
|
data/Manifest.txt
CHANGED
@@ -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
|
data/README.rdoc
CHANGED
@@ -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
|
data/lib/net/ber/core_ext.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
#
|
9
|
-
|
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
|
data/lib/net/ldap.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
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
|
-
|
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
|
-
#
|
489
|
-
#
|
490
|
-
#
|
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 =
|
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 ==
|
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
|
-
|
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 ==
|
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:
|
898
|
-
#
|
899
|
-
#
|
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,
|
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
|
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 ==
|
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 ==
|
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 ==
|
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 => [
|
1117
|
-
:altServer,
|
1118
|
-
:
|
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
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
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
|