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.
- 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
|