net-ldap 0.11 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 093221df6c10572671e2d05af258d473d1c17e78
4
- data.tar.gz: e27ca24638d8d46f2a8a39900fb90d8ca6a7f1fe
3
+ metadata.gz: 8c96669822fdcf032465b410615500b91a2f31ec
4
+ data.tar.gz: 643738d3f05ae5469ef1eb773326d5943eacf923
5
5
  SHA512:
6
- metadata.gz: 7f1bdba62c083cfc5b128226107f94815fb6d8b2d4d04fb47e30271289d6ffa193dc5e9363a2f44a04b7d5a30af08a97bba3d5afeda4ce035d7581f533db639a
7
- data.tar.gz: 9ff4126e94e791948777a1ed4396c21cdf62fb9a446a4eb240364302367c08a27566243af69bdf907fd2f8bfbe92117513eccc033db97f3c47acefab348971a1
6
+ metadata.gz: 6a7a455c387b73745d6da20d890181c20776562751f0c4bd43b9eb9a7ad9d2637771e1dfdaecbeb7e951ba25bca9859219e32e1154a88eed12e162152f47df0e
7
+ data.tar.gz: e92116ed732575ba7f0f05fb52f1148bd9c432de28cd3d0391b72835a091294dd7fe260c3b5aa1c96266b31edc9dd861cedd5d6066f57520dfb5c19e05a7b727
@@ -2,25 +2,28 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - 2.1.2
5
+ - 2.1
6
+ - 2.2
6
7
  # optional
8
+ - ruby-head
7
9
  - jruby-19mode
8
- - rbx-19mode
10
+ - jruby-head
9
11
  - rbx-2
10
12
 
11
13
  env:
12
14
  - INTEGRATION=openldap
13
15
 
14
16
  install:
15
- - if [ "$INTEGRATION" = "openldap" ]; then ./script/install-openldap; fi
17
+ - if [ "$INTEGRATION" = "openldap" ]; then sudo script/install-openldap; fi
16
18
  - bundle install
17
19
 
18
20
  script: bundle exec rake ci
19
21
 
20
22
  matrix:
21
23
  allow_failures:
24
+ - rvm: ruby-head
22
25
  - rvm: jruby-19mode
23
- - rvm: rbx-19mode
26
+ - rvm: jruby-head
24
27
  - rvm: rbx-2
25
28
  fast_finish: true
26
29
 
@@ -1,3 +1,21 @@
1
+ === Net::LDAP 0.12.0
2
+
3
+ * DRY up connection handling logic {#224}[https://github.com/ruby-ldap/ruby-net-ldap/pull/224]
4
+ * Define auth adapters {#226}[https://github.com/ruby-ldap/ruby-net-ldap/pull/226]
5
+ * add slash to attribute value filter {#225}[https://github.com/ruby-ldap/ruby-net-ldap/pull/225]
6
+ * Add the ability to provide a list of hosts for a connection {#223}[https://github.com/ruby-ldap/ruby-net-ldap/pull/223]
7
+ * Specify the port of LDAP server by giving INTEGRATION_PORT {#221}[https://github.com/ruby-ldap/ruby-net-ldap/pull/221]
8
+ * Correctly set BerIdentifiedString values to UTF-8 {#212}[https://github.com/ruby-ldap/ruby-net-ldap/pull/212]
9
+ * Raise Net::LDAP::ConnectionRefusedError when new connection is refused. {#213}[https://github.com/ruby-ldap/ruby-net-ldap/pull/213]
10
+ * obscure auth password upon #inspect, added test, closes #216 {#217}[https://github.com/ruby-ldap/ruby-net-ldap/pull/217]
11
+ * Fixing incorrect error class name {#207}[https://github.com/ruby-ldap/ruby-net-ldap/pull/207]
12
+ * Travis update {#205}[https://github.com/ruby-ldap/ruby-net-ldap/pull/205]
13
+ * Remove obsolete rbx-19mode from Travis {#204}[https://github.com/ruby-ldap/ruby-net-ldap/pull/204]
14
+ * mv "sudo" from script/install-openldap to .travis.yml {#199}[https://github.com/ruby-ldap/ruby-net-ldap/pull/199]
15
+ * Remove meaningless shebang {#200}[https://github.com/ruby-ldap/ruby-net-ldap/pull/200]
16
+ * Fix Travis CI build {#202}[https://github.com/ruby-ldap/ruby-net-ldap/pull/202]
17
+ * README.rdoc: fix travis link {#195}[https://github.com/ruby-ldap/ruby-net-ldap/pull/195]
18
+
1
19
  === Net::LDAP 0.11
2
20
  * Major enhancements:
3
21
  * #183 Specific errors subclassing Net::LDAP::Error
@@ -1,4 +1,4 @@
1
- = Net::LDAP for Ruby {<img src="https://travis-ci.org/ruby-ldap/ruby-net-ldap.png" />}[https://travis-ci.org/github/ruby-net-ldap]
1
+ = Net::LDAP for Ruby {<img src="https://travis-ci.org/ruby-ldap/ruby-net-ldap.png" />}[https://travis-ci.org/ruby-ldap/ruby-net-ldap]
2
2
 
3
3
  == Description
4
4
 
@@ -37,6 +37,14 @@ sources.
37
37
 
38
38
  Simply require either 'net-ldap' or 'net/ldap'.
39
39
 
40
+ == Extensions
41
+
42
+ This library focuses on the core LDAP RFCs referenced in the description.
43
+ However, we recognize there are commonly used extensions to the spec that are
44
+ useful. If there is another library which handles it, we list it here.
45
+
46
+ * {resolv-srv}[https://rubygems.org/gems/resolv-srv]: Support RFC2782 SRV record lookup and failover
47
+
40
48
  == Develop
41
49
 
42
50
  This task will run the test suite and the
@@ -55,10 +63,11 @@ To run the integration tests against an LDAP server:
55
63
 
56
64
  This section is for gem maintainers to cut a new version of the gem.
57
65
 
66
+ * Check out a new branch `release-VERSION`
58
67
  * Update lib/net/ldap/version.rb to next version number X.X.X following {semver}(http://semver.org/).
59
- * Update `History.rdoc`. Get latest changes with `git log --oneline vLAST_RELEASE..HEAD | grep Merge`
60
-
61
- * On the master branch, run `script/release`
68
+ * Update `History.rdoc`. Get latest changes with `script/changelog`
69
+ * Open a pull request with these changes for review
70
+ * After merging, on the master branch, run `script/release`
62
71
 
63
72
  :include: Contributors.rdoc
64
73
 
data/Rakefile CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env rake
2
1
  # -*- ruby encoding: utf-8 -*-
3
2
  # vim: syntax=ruby
4
3
 
@@ -296,9 +296,11 @@ end
296
296
  class Net::BER::BerIdentifiedString < String
297
297
  attr_accessor :ber_identifier
298
298
  def initialize args
299
- super args
300
- # LDAP uses UTF-8 encoded strings
301
- self.encode('UTF-8') if self.respond_to?(:encoding) rescue self
299
+ super begin
300
+ args.respond_to?(:encode) ? args.encode('UTF-8') : args
301
+ rescue
302
+ args
303
+ end
302
304
  end
303
305
  end
304
306
 
@@ -27,6 +27,12 @@ require 'net/ldap/instrumentation'
27
27
  require 'net/ldap/connection'
28
28
  require 'net/ldap/version'
29
29
  require 'net/ldap/error'
30
+ require 'net/ldap/auth_adapter'
31
+ require 'net/ldap/auth_adapter/simple'
32
+ require 'net/ldap/auth_adapter/sasl'
33
+
34
+ Net::LDAP::AuthAdapter.register([:simple, :anon, :anonymous], Net::LDAP::AuthAdapter::Simple)
35
+ Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapter::Sasl)
30
36
 
31
37
  # == Quick-start for the Impatient
32
38
  # === Quick Example of a user-authentication against an LDAP directory:
@@ -432,6 +438,7 @@ class Net::LDAP
432
438
 
433
439
  attr_accessor :host
434
440
  attr_accessor :port
441
+ attr_accessor :hosts
435
442
  attr_accessor :base
436
443
 
437
444
  # Instantiate an object of type Net::LDAP to perform directory operations.
@@ -440,6 +447,8 @@ class Net::LDAP
440
447
  # described below. The following arguments are supported:
441
448
  # * :host => the LDAP server's IP-address (default 127.0.0.1)
442
449
  # * :port => the LDAP server's TCP port (default 389)
450
+ # * :hosts => an enumerable of pairs of hosts and corresponding ports with
451
+ # which to attempt opening connections (default [[host, port]])
443
452
  # * :auth => a Hash containing authorization parameters. Currently
444
453
  # supported values include: {:method => :anonymous} and {:method =>
445
454
  # :simple, :username => your_user_name, :password => your_password }
@@ -468,6 +477,7 @@ class Net::LDAP
468
477
  def initialize(args = {})
469
478
  @host = args[:host] || DefaultHost
470
479
  @port = args[:port] || DefaultPort
480
+ @hosts = args[:hosts]
471
481
  @verbose = false # Make this configurable with a switch on the class.
472
482
  @auth = args[:auth] || DefaultAuth
473
483
  @base = args[:base] || DefaultTreebase
@@ -1195,6 +1205,13 @@ class Net::LDAP
1195
1205
  @server_caps[:supportedcontrol].include?(Net::LDAP::LDAPControls::PAGED_RESULTS)
1196
1206
  end
1197
1207
 
1208
+ # Mask auth password
1209
+ def inspect
1210
+ inspected = super
1211
+ inspected.gsub! @auth[:password], "*******" if @auth[:password]
1212
+ inspected
1213
+ end
1214
+
1198
1215
  private
1199
1216
 
1200
1217
  # Yields an open connection if there is one, otherwise establishes a new
@@ -1223,6 +1240,7 @@ class Net::LDAP
1223
1240
  Net::LDAP::Connection.new \
1224
1241
  :host => @host,
1225
1242
  :port => @port,
1243
+ :hosts => @hosts,
1226
1244
  :encryption => @encryption,
1227
1245
  :instrumentation_service => @instrumentation_service
1228
1246
  end
@@ -0,0 +1,29 @@
1
+ module Net
2
+ class LDAP
3
+ class AuthAdapter
4
+ def self.register(names, adapter)
5
+ names = Array(names)
6
+ @adapters ||= {}
7
+ names.each do |name|
8
+ @adapters[name] = adapter
9
+ end
10
+ end
11
+
12
+ def self.[](name)
13
+ a = @adapters[name]
14
+ if a.nil?
15
+ raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{name})"
16
+ end
17
+ return a
18
+ end
19
+
20
+ def initialize(conn)
21
+ @connection = conn
22
+ end
23
+
24
+ def bind
25
+ raise "bind method must be overwritten"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ require 'net/ldap/auth_adapter'
2
+ require 'net/ldap/auth_adapter/sasl'
3
+
4
+ module Net
5
+ class LDAP
6
+ module AuthAdapers
7
+ #--
8
+ # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
9
+ # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
10
+ # integrate it without introducing an external dependency.
11
+ #
12
+ # This authentication method is accessed by calling #bind with a :method
13
+ # parameter of :gss_spnego. It requires :username and :password
14
+ # attributes, just like the :simple authentication method. It performs a
15
+ # GSS-SPNEGO authentication with the server, which is presumed to be a
16
+ # Microsoft Active Directory.
17
+ #++
18
+ class GSS_SPNEGO < Net::LDAP::AuthAdapter
19
+ def bind(auth)
20
+ require 'ntlm'
21
+
22
+ user, psw = [auth[:username] || auth[:dn], auth[:password]]
23
+ raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
24
+
25
+ nego = proc { |challenge|
26
+ t2_msg = NTLM::Message.parse(challenge)
27
+ t3_msg = t2_msg.response({ :user => user, :password => psw },
28
+ { :ntlmv2 => true })
29
+ t3_msg.serialize
30
+ }
31
+
32
+ Net::LDAP::AuthAdapter::Sasl.new(@connection).
33
+ bind(:method => :sasl, :mechanism => "GSS-SPNEGO",
34
+ :initial_credential => NTLM::Message::Type1.new.serialize,
35
+ :challenge_response => nego)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ require 'net/ldap/auth_adapter'
2
+
3
+ module Net
4
+ class LDAP
5
+ class AuthAdapter
6
+ class Sasl < Net::LDAP::AuthAdapter
7
+ #--
8
+ # Required parameters: :mechanism, :initial_credential and
9
+ # :challenge_response
10
+ #
11
+ # Mechanism is a string value that will be passed in the SASL-packet's
12
+ # "mechanism" field.
13
+ #
14
+ # Initial credential is most likely a string. It's passed in the initial
15
+ # BindRequest that goes to the server. In some protocols, it may be empty.
16
+ #
17
+ # Challenge-response is a Ruby proc that takes a single parameter and
18
+ # returns an object that will typically be a string. The
19
+ # challenge-response block is called when the server returns a
20
+ # BindResponse with a result code of 14 (saslBindInProgress). The
21
+ # challenge-response block receives a parameter containing the data
22
+ # returned by the server in the saslServerCreds field of the LDAP
23
+ # BindResponse packet. The challenge-response block may be called multiple
24
+ # times during the course of a SASL authentication, and each time it must
25
+ # return a value that will be passed back to the server as the credential
26
+ # data in the next BindRequest packet.
27
+ #++
28
+ def bind(auth)
29
+ mech, cred, chall = auth[:mechanism], auth[:initial_credential],
30
+ auth[:challenge_response]
31
+ raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall)
32
+
33
+ message_id = @connection.next_msgid
34
+
35
+ n = 0
36
+ loop {
37
+ sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
38
+ request = [
39
+ Net::LDAP::Connection::LdapVersion.to_ber, "".to_ber, sasl
40
+ ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
41
+
42
+ @connection.send(:write, request, nil, message_id)
43
+ pdu = @connection.queued_read(message_id)
44
+
45
+ if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
46
+ raise Net::LDAP::NoBindResultError, "no bind result"
47
+ end
48
+
49
+ return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
50
+ raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
51
+
52
+ cred = chall.call(pdu.result_server_sasl_creds)
53
+ }
54
+
55
+ raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ require 'net/ldap/auth_adapter'
2
+
3
+ module Net
4
+ class LDAP
5
+ class AuthAdapter
6
+ class Simple < AuthAdapter
7
+ def bind(auth)
8
+ user, psw = if auth[:method] == :simple
9
+ [auth[:username] || auth[:dn], auth[:password]]
10
+ else
11
+ ["", ""]
12
+ end
13
+
14
+ raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
15
+
16
+ message_id = @connection.next_msgid
17
+ request = [
18
+ Net::LDAP::Connection::LdapVersion.to_ber, user.to_ber,
19
+ psw.to_ber_contextspecific(0)
20
+ ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
21
+
22
+ @connection.send(:write, request, nil, message_id)
23
+ pdu = @connection.queued_read(message_id)
24
+
25
+ if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
26
+ raise Net::LDAP::NoBindResultError, "no bind result"
27
+ end
28
+
29
+ pdu
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -9,23 +9,42 @@ class Net::LDAP::Connection #:nodoc:
9
9
  def initialize(server)
10
10
  @instrumentation_service = server[:instrumentation_service]
11
11
 
12
- begin
13
- @conn = server[:socket] || TCPSocket.new(server[:host], server[:port])
14
- rescue SocketError
15
- raise Net::LDAP::Error, "No such address or other socket error."
16
- rescue Errno::ECONNREFUSED
17
- raise Net::LDAP::Error, "Server #{server[:host]} refused connection on port #{server[:port]}."
18
- rescue Errno::EHOSTUNREACH => error
19
- raise Net::LDAP::Error, "Host #{server[:host]} was unreachable (#{error.message})"
20
- rescue Errno::ETIMEDOUT
21
- raise Net::LDAP::Error, "Connection to #{server[:host]} timed out."
12
+ if server[:socket]
13
+ prepare_socket(server)
14
+ else
15
+ server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?
16
+ open_connection(server)
22
17
  end
23
18
 
24
- if server[:encryption]
25
- setup_encryption server[:encryption]
19
+ yield self if block_given?
20
+ end
21
+
22
+ def prepare_socket(server)
23
+ socket = server[:socket]
24
+ encryption = server[:encryption]
25
+
26
+ @conn = socket
27
+ setup_encryption encryption if encryption
28
+ end
29
+
30
+ def open_connection(server)
31
+ hosts = server[:hosts]
32
+ encryption = server[:encryption]
33
+
34
+ errors = []
35
+ hosts.each do |host, port|
36
+ begin
37
+ prepare_socket(server.merge(socket: TCPSocket.new(host, port)))
38
+ return
39
+ rescue Net::LDAP::Error, SocketError, SystemCallError,
40
+ OpenSSL::SSL::SSLError => e
41
+ # Ensure the connection is closed in the event a setup failure.
42
+ close
43
+ errors << [e, host, port]
44
+ end
26
45
  end
27
46
 
28
- yield self if block_given?
47
+ raise Net::LDAP::ConnectionError.new(errors)
29
48
  end
30
49
 
31
50
  module GetbyteForSSLSocket
@@ -63,18 +82,18 @@ class Net::LDAP::Connection #:nodoc:
63
82
  end
64
83
 
65
84
  #--
66
- # Helper method called only from new, and only after we have a
67
- # successfully-opened @conn instance variable, which is a TCP connection.
68
- # Depending on the received arguments, we establish SSL, potentially
69
- # replacing the value of @conn accordingly. Don't generate any errors here
70
- # if no encryption is requested. DO raise Net::LDAP::Error objects if encryption
71
- # is requested and we have trouble setting it up. That includes if OpenSSL
72
- # is not set up on the machine. (Question: how does the Ruby OpenSSL
73
- # wrapper react in that case?) DO NOT filter exceptions raised by the
74
- # OpenSSL library. Let them pass back to the user. That should make it
75
- # easier for us to debug the problem reports. Presumably (hopefully?) that
76
- # will also produce recognizable errors if someone tries to use this on a
77
- # machine without OpenSSL.
85
+ # Helper method called only from prepare_socket or open_connection, and only
86
+ # after we have a successfully-opened @conn instance variable, which is a TCP
87
+ # connection. Depending on the received arguments, we establish SSL,
88
+ # potentially replacing the value of @conn accordingly. Don't generate any
89
+ # errors here if no encryption is requested. DO raise Net::LDAP::Error objects
90
+ # if encryption is requested and we have trouble setting it up. That includes
91
+ # if OpenSSL is not set up on the machine. (Question: how does the Ruby
92
+ # OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the
93
+ # OpenSSL library. Let them pass back to the user. That should make it easier
94
+ # for us to debug the problem reports. Presumably (hopefully?) that will also
95
+ # produce recognizable errors if someone tries to use this on a machine
96
+ # without OpenSSL.
78
97
  #
79
98
  # The simple_tls method is intended as the simplest, stupidest, easiest
80
99
  # solution for people who want nothing more than encrypted comms with the
@@ -124,6 +143,7 @@ class Net::LDAP::Connection #:nodoc:
124
143
  # have to call it, but perhaps it will come in handy someday.
125
144
  #++
126
145
  def close
146
+ return if @conn.nil?
127
147
  @conn.close
128
148
  @conn = nil
129
149
  end
@@ -218,130 +238,11 @@ class Net::LDAP::Connection #:nodoc:
218
238
  def bind(auth)
219
239
  instrument "bind.net_ldap_connection" do |payload|
220
240
  payload[:method] = meth = auth[:method]
221
- if [:simple, :anonymous, :anon].include?(meth)
222
- bind_simple auth
223
- elsif meth == :sasl
224
- bind_sasl(auth)
225
- elsif meth == :gss_spnego
226
- bind_gss_spnego(auth)
227
- else
228
- raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{meth})"
229
- end
241
+ adapter = Net::LDAP::AuthAdapter[meth]
242
+ adapter.new(self).bind(auth)
230
243
  end
231
244
  end
232
245
 
233
- #--
234
- # Implements a simple user/psw authentication. Accessed by calling #bind
235
- # with a method of :simple or :anonymous.
236
- #++
237
- def bind_simple(auth)
238
- user, psw = if auth[:method] == :simple
239
- [auth[:username] || auth[:dn], auth[:password]]
240
- else
241
- ["", ""]
242
- end
243
-
244
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
245
-
246
- message_id = next_msgid
247
- request = [
248
- LdapVersion.to_ber, user.to_ber,
249
- psw.to_ber_contextspecific(0)
250
- ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
251
-
252
- write(request, nil, message_id)
253
- pdu = queued_read(message_id)
254
-
255
- if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
256
- raise Net::LDAP::NoBindResultError, "no bind result"
257
- end
258
-
259
- pdu
260
- end
261
-
262
- #--
263
- # Required parameters: :mechanism, :initial_credential and
264
- # :challenge_response
265
- #
266
- # Mechanism is a string value that will be passed in the SASL-packet's
267
- # "mechanism" field.
268
- #
269
- # Initial credential is most likely a string. It's passed in the initial
270
- # BindRequest that goes to the server. In some protocols, it may be empty.
271
- #
272
- # Challenge-response is a Ruby proc that takes a single parameter and
273
- # returns an object that will typically be a string. The
274
- # challenge-response block is called when the server returns a
275
- # BindResponse with a result code of 14 (saslBindInProgress). The
276
- # challenge-response block receives a parameter containing the data
277
- # returned by the server in the saslServerCreds field of the LDAP
278
- # BindResponse packet. The challenge-response block may be called multiple
279
- # times during the course of a SASL authentication, and each time it must
280
- # return a value that will be passed back to the server as the credential
281
- # data in the next BindRequest packet.
282
- #++
283
- def bind_sasl(auth)
284
- mech, cred, chall = auth[:mechanism], auth[:initial_credential],
285
- auth[:challenge_response]
286
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall)
287
-
288
- message_id = next_msgid
289
-
290
- n = 0
291
- loop {
292
- sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
293
- request = [
294
- LdapVersion.to_ber, "".to_ber, sasl
295
- ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
296
-
297
- write(request, nil, message_id)
298
- pdu = queued_read(message_id)
299
-
300
- if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
301
- raise Net::LDAP::NoBindResultError, "no bind result"
302
- end
303
-
304
- return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
305
- raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
306
-
307
- cred = chall.call(pdu.result_server_sasl_creds)
308
- }
309
-
310
- raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
311
- end
312
- private :bind_sasl
313
-
314
- #--
315
- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
316
- # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
317
- # integrate it without introducing an external dependency.
318
- #
319
- # This authentication method is accessed by calling #bind with a :method
320
- # parameter of :gss_spnego. It requires :username and :password
321
- # attributes, just like the :simple authentication method. It performs a
322
- # GSS-SPNEGO authentication with the server, which is presumed to be a
323
- # Microsoft Active Directory.
324
- #++
325
- def bind_gss_spnego(auth)
326
- require 'ntlm'
327
-
328
- user, psw = [auth[:username] || auth[:dn], auth[:password]]
329
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
330
-
331
- nego = proc { |challenge|
332
- t2_msg = NTLM::Message.parse(challenge)
333
- t3_msg = t2_msg.response({ :user => user, :password => psw },
334
- { :ntlmv2 => true })
335
- t3_msg.serialize
336
- }
337
-
338
- bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO",
339
- :initial_credential => NTLM::Message::Type1.new.serialize,
340
- :challenge_response => nego)
341
- end
342
- private :bind_gss_spnego
343
-
344
-
345
246
  #--
346
247
  # Allow the caller to specify a sort control
347
248
  #
@@ -652,7 +553,7 @@ class Net::LDAP::Connection #:nodoc:
652
553
  pdu = queued_read(message_id)
653
554
 
654
555
  if !pdu || pdu.app_tag != Net::LDAP::PDU::AddResponse
655
- raise Net::LDAP::ResponseMissingError, "response missing or invalid"
556
+ raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
656
557
  end
657
558
 
658
559
  pdu
@@ -9,7 +9,41 @@ class Net::LDAP
9
9
 
10
10
  class AlreadyOpenedError < Error; end
11
11
  class SocketError < Error; end
12
- class ConnectionRefusedError < Error; end
12
+ class ConnectionRefusedError < Error;
13
+ def initialize(*args)
14
+ warn_deprecation_message
15
+ super
16
+ end
17
+
18
+ def message
19
+ warn_deprecation_message
20
+ super
21
+ end
22
+
23
+ private
24
+ def warn_deprecation_message
25
+ warn "Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead."
26
+ end
27
+ end
28
+ class ConnectionError < Error
29
+ def self.new(errors)
30
+ error = errors.first.first
31
+ if errors.size == 1
32
+ if error.kind_of? Errno::ECONNREFUSED
33
+ return Net::LDAP::ConnectionRefusedError.new(error.message)
34
+ end
35
+
36
+ return Net::LDAP::Error.new(error.message)
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ def initialize(errors)
43
+ message = "Unable to connect to any given server: \n #{errors.map { |e, h, p| "#{e.class}: #{e.message} (#{h}:#{p})" }.join("\n ")}"
44
+ super(message)
45
+ end
46
+ end
13
47
  class NoOpenSSLError < Error; end
14
48
  class NoStartTLSResultError < Error; end
15
49
  class NoSearchBaseError < Error; end
@@ -752,7 +752,7 @@ class Net::LDAP::Filter
752
752
  scanner.scan(/\s*/)
753
753
  if op = scanner.scan(/<=|>=|!=|:=|=/)
754
754
  scanner.scan(/\s*/)
755
- if value = scanner.scan(/(?:[-\[\]{}\w*.+:@=,#\$%&!'^~\s\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u)
755
+ if value = scanner.scan(/(?:[-\[\]{}\w*.+\/:@=,#\$%&!'^~\s\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u)
756
756
  # 20100313 AZ: Assumes that "(uid=george*)" is the same as
757
757
  # "(uid=george* )". The standard doesn't specify, but I can find
758
758
  # no examples that suggest otherwise.
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  class LDAP
3
- VERSION = "0.11"
3
+ VERSION = "0.12.0"
4
4
  end
5
5
  end
@@ -32,4 +32,5 @@ the most recent LDAP RFCs (4510-4519, plutions of 4520-4532).}
32
32
  s.add_development_dependency("flexmock", "~> 1.3")
33
33
  s.add_development_dependency("rake", "~> 10.0")
34
34
  s.add_development_dependency("rubocop", "~> 0.28.0")
35
+ s.add_development_dependency("test-unit")
35
36
  end
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # Usage: script/changelog [-r <repo>] [-b <base>] [-h <head>]
3
+ #
4
+ # repo: BASE string of GitHub REPOsitory url. e.g. "user_or_org/REPOsitory". Defaults to git remote url.
5
+ # base: git ref to compare from. e.g. "v1.3.1". Defaults to latest git tag.
6
+ # head: git ref to compare to. Defaults to "HEAD".
7
+ #
8
+ # Generate a changelog preview from pull requests merged between `base` and
9
+ # `head`.
10
+ #
11
+ # https://github.com/jch/release-scripts/blob/master/changelog
12
+ set -e
13
+
14
+ [ $# -eq 0 ] && set -- --help
15
+ while [[ $# > 1 ]]
16
+ do
17
+ key="$1"
18
+ case $key in
19
+ -r|--repo)
20
+ repo="$2"
21
+ shift
22
+ ;;
23
+ -b|--base)
24
+ base="$2"
25
+ shift
26
+ ;;
27
+ -h|--head)
28
+ head="$2"
29
+ shift
30
+ ;;
31
+ *)
32
+ ;;
33
+ esac
34
+ shift
35
+ done
36
+
37
+ repo="${repo:-$(git remote -v | grep push | awk '{print $2}' | cut -d'/' -f4- | sed 's/\.git//')}"
38
+ base="${base:-$(git tag -l | sort -t. -k 1,1n -k 2,2n -k 3,3n | tail -n 1)}"
39
+ head="${head:-HEAD}"
40
+ api_url="https://api.github.com"
41
+
42
+ # get merged PR's. Better way is to query the API for these, but this is easier
43
+ for pr in $(git log --oneline $base..$head | grep "Merge pull request" | awk '{gsub("#",""); print $5}')
44
+ do
45
+ # frustrated with trying to pull out the right values, fell back to ruby
46
+ curl -s "$api_url/repos/$repo/pulls/$pr" | ruby -rjson -e 'pr=JSON.parse(STDIN.read); puts "* #{pr[%q(title)]} {##{pr[%q(number)]}}[#{pr[%q(html_url)]}]"'
47
+ done
@@ -6,68 +6,69 @@ BASE_PATH="$( cd `dirname $0`/../test/fixtures/openldap && pwd )"
6
6
  SEED_PATH="$( cd `dirname $0`/../test/fixtures && pwd )"
7
7
 
8
8
  dpkg -s slapd time ldap-utils gnutls-bin ssl-cert > /dev/null ||\
9
- DEBIAN_FRONTEND=noninteractive sudo -E apt-get install -y --force-yes slapd time ldap-utils gnutls-bin ssl-cert
9
+ DEBIAN_FRONTEND=noninteractive apt-get update -y --force-yes && \
10
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes slapd time ldap-utils gnutls-bin ssl-cert
10
11
 
11
- sudo /etc/init.d/slapd stop
12
+ /etc/init.d/slapd stop
12
13
 
13
14
  TMPDIR=$(mktemp -d)
14
15
  cd $TMPDIR
15
16
 
16
17
  # Delete data and reconfigure.
17
- sudo cp -v /var/lib/ldap/DB_CONFIG ./DB_CONFIG
18
- sudo rm -rf /etc/ldap/slapd.d/*
19
- sudo rm -rf /var/lib/ldap/*
20
- sudo cp -v ./DB_CONFIG /var/lib/ldap/DB_CONFIG
21
- sudo slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/slapd.conf.ldif
18
+ cp -v /var/lib/ldap/DB_CONFIG ./DB_CONFIG
19
+ rm -rf /etc/ldap/slapd.d/*
20
+ rm -rf /var/lib/ldap/*
21
+ cp -v ./DB_CONFIG /var/lib/ldap/DB_CONFIG
22
+ slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/slapd.conf.ldif
22
23
  # Load memberof and ref-int overlays and configure them.
23
- sudo slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/memberof.ldif
24
+ slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/memberof.ldif
24
25
  # Load retcode overlay and configure
25
- sudo slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/retcode.ldif
26
+ slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/retcode.ldif
26
27
 
27
28
  # Add base domain.
28
- sudo slapadd -F /etc/ldap/slapd.d <<EOM
29
+ slapadd -F /etc/ldap/slapd.d <<EOM
29
30
  dn: dc=rubyldap,dc=com
30
31
  objectClass: top
31
32
  objectClass: domain
32
33
  dc: rubyldap
33
34
  EOM
34
35
 
35
- sudo chown -R openldap.openldap /etc/ldap/slapd.d
36
- sudo chown -R openldap.openldap /var/lib/ldap
36
+ chown -R openldap.openldap /etc/ldap/slapd.d
37
+ chown -R openldap.openldap /var/lib/ldap
37
38
 
38
- sudo /etc/init.d/slapd start
39
+ /etc/init.d/slapd start
39
40
 
40
41
  # Import seed data.
41
42
  # NOTE: use ldapadd in order for memberOf and refint to apply, instead of:
42
- # cat $SEED_PATH/seed.ldif | sudo slapadd -F /etc/ldap/slapd.d
43
- /usr/bin/time sudo ldapadd -x -D "cn=admin,dc=rubyldap,dc=com" -w passworD1 \
43
+ # cat $SEED_PATH/seed.ldif | slapadd -F /etc/ldap/slapd.d
44
+ /usr/bin/time ldapadd -x -D "cn=admin,dc=rubyldap,dc=com" -w passworD1 \
44
45
  -h localhost -p 389 \
45
46
  -f $SEED_PATH/seed.ldif
46
47
 
47
- sudo rm -rf $TMPDIR
48
+ rm -rf $TMPDIR
48
49
 
49
50
  # SSL
50
51
 
51
- sudo sh -c "certtool --generate-privkey > /etc/ssl/private/cakey.pem"
52
+ sh -c "certtool --generate-privkey > /etc/ssl/private/cakey.pem"
52
53
 
53
- sudo sh -c "cat > /etc/ssl/ca.info <<EOF
54
+ sh -c "cat > /etc/ssl/ca.info <<EOF
54
55
  cn = rubyldap
55
56
  ca
56
57
  cert_signing_key
57
58
  EOF"
58
59
 
59
60
  # Create the self-signed CA certificate:
60
- sudo certtool --generate-self-signed \
61
+ certtool --generate-self-signed \
61
62
  --load-privkey /etc/ssl/private/cakey.pem \
62
63
  --template /etc/ssl/ca.info \
63
64
  --outfile /etc/ssl/certs/cacert.pem
64
65
 
65
66
  # Make a private key for the server:
66
- sudo certtool --generate-privkey \
67
+ certtool --generate-privkey \
67
68
  --bits 1024 \
68
69
  --outfile /etc/ssl/private/ldap01_slapd_key.pem
69
70
 
70
- sudo sh -c "cat > /etc/ssl/ldap01.info <<EOF
71
+ sh -c "cat > /etc/ssl/ldap01.info <<EOF
71
72
  organization = Example Company
72
73
  cn = ldap01.example.com
73
74
  tls_www_server
@@ -77,14 +78,14 @@ expiration_days = 3650
77
78
  EOF"
78
79
 
79
80
  # Create the server certificate
80
- sudo certtool --generate-certificate \
81
+ certtool --generate-certificate \
81
82
  --load-privkey /etc/ssl/private/ldap01_slapd_key.pem \
82
83
  --load-ca-certificate /etc/ssl/certs/cacert.pem \
83
84
  --load-ca-privkey /etc/ssl/private/cakey.pem \
84
85
  --template /etc/ssl/ldap01.info \
85
86
  --outfile /etc/ssl/certs/ldap01_slapd_cert.pem
86
87
 
87
- sudo ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF | true
88
+ ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF | true
88
89
  dn: cn=config
89
90
  add: olcTLSCACertificateFile
90
91
  olcTLSCACertificateFile: /etc/ssl/certs/cacert.pem
@@ -101,11 +102,11 @@ EOF
101
102
  # protected by TLS/SSL whereas LDAPS, like HTTPS, is a distinct
102
103
  # encrypted-from-the-start protocol that operates over TCP port 636. But we
103
104
  # enable it for testing here.
104
- sudo sed -i -e 's|^SLAPD_SERVICES="\(.*\)"|SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"|' /etc/default/slapd
105
+ sed -i -e 's|^SLAPD_SERVICES="\(.*\)"|SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"|' /etc/default/slapd
105
106
 
106
- sudo adduser openldap ssl-cert
107
- sudo chgrp ssl-cert /etc/ssl/private/ldap01_slapd_key.pem
108
- sudo chmod g+r /etc/ssl/private/ldap01_slapd_key.pem
109
- sudo chmod o-r /etc/ssl/private/ldap01_slapd_key.pem
107
+ adduser openldap ssl-cert
108
+ chgrp ssl-cert /etc/ssl/private/ldap01_slapd_key.pem
109
+ chmod g+r /etc/ssl/private/ldap01_slapd_key.pem
110
+ chmod o-r /etc/ssl/private/ldap01_slapd_key.pem
110
111
 
111
- sudo service slapd restart
112
+ service slapd restart
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class TestAuthAdapter < Test::Unit::TestCase
4
+ def test_undefined_auth_adapter
5
+ flexmock(TCPSocket).should_receive(:new).ordered.with('ldap.example.com', 379).once.and_return(nil)
6
+ conn = Net::LDAP::Connection.new(host: 'ldap.example.com', port: 379)
7
+ assert_raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (foo)" do
8
+ conn.bind(method: :foo)
9
+ end
10
+ end
11
+ end
@@ -14,6 +14,10 @@ class TestFilterParser < Test::Unit::TestCase
14
14
  assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(cn=[{something}])")
15
15
  end
16
16
 
17
+ def test_slash
18
+ assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(departmentNumber=FOO//BAR/FOO)")
19
+ end
20
+
17
21
  def test_colons
18
22
  assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(ismemberof=cn=edu:berkeley:app:calmessages:deans,ou=campus groups,dc=berkeley,dc=edu)")
19
23
  end
@@ -56,7 +56,7 @@ class LDAPIntegrationTestCase < Test::Unit::TestCase
56
56
  @service = MockInstrumentationService.new
57
57
  @ldap = Net::LDAP.new \
58
58
  host: ENV.fetch('INTEGRATION_HOST', 'localhost'),
59
- port: 389,
59
+ port: ENV.fetch('INTEGRATION_PORT', 389),
60
60
  admin_user: 'uid=admin,dc=rubyldap,dc=com',
61
61
  admin_password: 'passworD1',
62
62
  search_domains: %w(dc=rubyldap,dc=com),
@@ -57,4 +57,11 @@ class TestLDAPInstrumentation < Test::Unit::TestCase
57
57
  assert_equal "(uid=user1)", payload[:filter]
58
58
  assert_equal result.size, payload[:size]
59
59
  end
60
+
61
+ def test_obscure_auth
62
+ password = "opensesame"
63
+ assert_include(@subject.inspect, "anonymous")
64
+ @subject.auth "joe_user", password
65
+ assert_not_include(@subject.inspect, password)
66
+ end
60
67
  end
@@ -1,6 +1,52 @@
1
1
  require_relative 'test_helper'
2
2
 
3
3
  class TestLDAPConnection < Test::Unit::TestCase
4
+ def capture_stderr
5
+ stderr, $stderr = $stderr, StringIO.new
6
+ yield
7
+ $stderr.string
8
+ ensure
9
+ $stderr = stderr
10
+ end
11
+
12
+ def test_list_of_hosts_with_first_host_successful
13
+ hosts = [
14
+ ['test.mocked.com', 636],
15
+ ['test2.mocked.com', 636],
16
+ ['test3.mocked.com', 636],
17
+ ]
18
+ flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_return(nil)
19
+ flexmock(TCPSocket).should_receive(:new).ordered.never
20
+ Net::LDAP::Connection.new(:hosts => hosts)
21
+ end
22
+
23
+ def test_list_of_hosts_with_first_host_failure
24
+ hosts = [
25
+ ['test.mocked.com', 636],
26
+ ['test2.mocked.com', 636],
27
+ ['test3.mocked.com', 636],
28
+ ]
29
+ flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
30
+ flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_return(nil)
31
+ flexmock(TCPSocket).should_receive(:new).ordered.never
32
+ Net::LDAP::Connection.new(:hosts => hosts)
33
+ end
34
+
35
+ def test_list_of_hosts_with_all_hosts_failure
36
+ hosts = [
37
+ ['test.mocked.com', 636],
38
+ ['test2.mocked.com', 636],
39
+ ['test3.mocked.com', 636],
40
+ ]
41
+ flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
42
+ flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_raise(SocketError)
43
+ flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[2]).once.and_raise(SocketError)
44
+ flexmock(TCPSocket).should_receive(:new).ordered.never
45
+ assert_raise Net::LDAP::ConnectionError do
46
+ Net::LDAP::Connection.new(:hosts => hosts)
47
+ end
48
+ end
49
+
4
50
  def test_unresponsive_host
5
51
  assert_raise Net::LDAP::Error do
6
52
  Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
@@ -14,6 +60,16 @@ class TestLDAPConnection < Test::Unit::TestCase
14
60
  end
15
61
  end
16
62
 
63
+ def test_connection_refused
64
+ flexmock(TCPSocket).should_receive(:new).and_raise(Errno::ECONNREFUSED)
65
+ stderr = capture_stderr do
66
+ assert_raise Net::LDAP::ConnectionRefusedError do
67
+ Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
68
+ end
69
+ end
70
+ assert_equal("Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead.\n", stderr)
71
+ end
72
+
17
73
  def test_raises_unknown_exceptions
18
74
  error = Class.new(StandardError)
19
75
  flexmock(TCPSocket).should_receive(:new).and_raise(error)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-ldap
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.11'
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francis Cianfrocca
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2015-01-21 00:00:00.000000000 Z
16
+ date: 2015-10-27 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: flexmock
@@ -57,6 +57,20 @@ dependencies:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
59
  version: 0.28.0
60
+ - !ruby/object:Gem::Dependency
61
+ name: test-unit
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
60
74
  description: |-
61
75
  Net::LDAP for Ruby (also called net-ldap) implements client access for the
62
76
  Lightweight Directory Access Protocol (LDAP), an IETF standard protocol for
@@ -106,6 +120,10 @@ files:
106
120
  - lib/net/ber/core_ext/string.rb
107
121
  - lib/net/ber/core_ext/true_class.rb
108
122
  - lib/net/ldap.rb
123
+ - lib/net/ldap/auth_adapter.rb
124
+ - lib/net/ldap/auth_adapter/gss_spnego.rb
125
+ - lib/net/ldap/auth_adapter/sasl.rb
126
+ - lib/net/ldap/auth_adapter/simple.rb
109
127
  - lib/net/ldap/connection.rb
110
128
  - lib/net/ldap/dataset.rb
111
129
  - lib/net/ldap/dn.rb
@@ -118,6 +136,7 @@ files:
118
136
  - lib/net/ldap/version.rb
119
137
  - lib/net/snmp.rb
120
138
  - net-ldap.gemspec
139
+ - script/changelog
121
140
  - script/install-openldap
122
141
  - script/package
123
142
  - script/release
@@ -139,6 +158,7 @@ files:
139
158
  - test/support/vm/openldap/.gitignore
140
159
  - test/support/vm/openldap/README.md
141
160
  - test/support/vm/openldap/Vagrantfile
161
+ - test/test_auth_adapter.rb
142
162
  - test/test_dn.rb
143
163
  - test/test_entry.rb
144
164
  - test/test_filter.rb
@@ -177,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
197
  version: '0'
178
198
  requirements: []
179
199
  rubyforge_project:
180
- rubygems_version: 2.2.2
200
+ rubygems_version: 2.2.3
181
201
  signing_key:
182
202
  specification_version: 4
183
203
  summary: Net::LDAP for Ruby (also called net-ldap) implements client access for the
@@ -202,6 +222,7 @@ test_files:
202
222
  - test/support/vm/openldap/.gitignore
203
223
  - test/support/vm/openldap/README.md
204
224
  - test/support/vm/openldap/Vagrantfile
225
+ - test/test_auth_adapter.rb
205
226
  - test/test_dn.rb
206
227
  - test/test_entry.rb
207
228
  - test/test_filter.rb