Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] add URL support to constructor, some smarter TLS defaults #280

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ Metrics/BlockNesting:
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 431
Exclude:
- 'lib/net/ldap.rb'

# Offense count: 22
Metrics/CyclomaticComplexity:
Expand Down
82 changes: 59 additions & 23 deletions lib/net/ldap.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- ruby encoding: utf-8 -*-
require 'ostruct'
require 'uri'

module Net # :nodoc:
class LDAP
Expand Down Expand Up @@ -335,6 +336,7 @@ class Net::LDAP

DefaultHost = "127.0.0.1"
DefaultPort = 389
DefaultTlsPort = 636
DefaultAuth = { :method => :anonymous }
DefaultTreebase = "dc=com"
DefaultForceNoPage = false
Expand Down Expand Up @@ -506,13 +508,13 @@ def self.result2string(code) #:nodoc:
# talking over the public internet), you need to set :tls_options
# something like this...
#
# Net::LDAP.new(
# # ... set host, bind dn, etc ...
# encryption: {
# method: :simple_tls,
# tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
# }
# )
# Net::LDAP.new(
# # ... set host, bind dn, etc ...
# encryption: {
# method: :simple_tls,
# tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
# }
# )
#
# The above will use the operating system-provided store of CA
# certificates to validate your LDAP server's cert.
Expand All @@ -527,29 +529,40 @@ def self.result2string(code) #:nodoc:
# `update-ca-certificates`), then the cert should pass validation.
# To ignore the OS's CA store, put your CA in a PEM-encoded file and...
#
# encryption: {
# method: :simple_tls,
# tls_options: { ca_file: '/path/to/my-little-ca.pem',
# ssl_version: 'TLSv1_1' },
# }
# encryption: {
# method: :simple_tls,
# tls_options: { ca_file: '/path/to/my-little-ca.pem',
# ssl_version: 'TLSv1_1' },
# }
#
# As you might guess, the above example also fails the connection
# if the client can't negotiate TLS v1.1.
# tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params
# For more details, see
# http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
# tls_options is ultimately passed to
# +OpenSSL::SSL::SSLContext#set_params+, For more details, see http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
#
# Instantiating a Net::LDAP object does <i>not</i> result in network
# traffic to the LDAP server. It simply stores the connection and binding
# parameters in the object. That's why Net::LDAP.new doesn't throw
# cert validation errors itself; #bind does instead.
def initialize(args = {})
@host = args[:host] || DefaultHost
@port = args[:port] || DefaultPort
# URI.parse('') returns a valid URI object, but with all its
# attributes set to nil. This is convenient for chained '||'
@url = URI.parse(args[:uri] || '')

unless [nil, 'ldaps', 'ldap'].include? @url.scheme
raise ProtocolNotSupported,
"scheme '#{@url.scheme}' unsupported, use 'ldap' or 'ldaps'"
end
@host = args[:host] || @url.host || DefaultHost
@port = args[:port] || @url.port || DefaultPort
@hosts = args[:hosts]
@verbose = false # Make this configurable with a switch on the class.
@auth = args[:auth] || DefaultAuth
@base = args[:base] || DefaultTreebase
@base = args[:base] || if @url.path && @url.path.length > 1
URI.decode(@url.path[1..-1])
else
DefaultTreebase
end
@force_no_page = args[:force_no_page] || DefaultForceNoPage
@encryption = normalize_encryption(args[:encryption]) # may be nil
@connect_timeout = args[:connect_timeout]
Expand Down Expand Up @@ -1331,12 +1344,35 @@ def new_connection
# Normalize encryption parameter the constructor accepts, expands a few
# convenience symbols into recognizable hashes
def normalize_encryption(args)
return if args.nil?
return args if args.is_a? Hash
valid_args =
"encryption may be hash, nil, or one of [:simple_tls, :start_tls, true]"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh someday, it'd be nice to slim down this list of acceptable arg types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Down the road, I'm thinking of splitting tls_options out as a top-level argument passed into Net::LDAP.new. @encryption should then be able to be just a symbol. Having it be a hash with two entries makes it more complicated to invoke correctly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having it be a hash with two entries makes it more complicated to invoke correctly

👍


case method = args.to_sym
when :simple_tls, :start_tls
{ :method => method, :tls_options => {} }
if args.nil?
return nil unless @url.scheme == 'ldaps'
{ method: :simple_tls,
tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS }
elsif args.is_a? Hash
if !args[:tls_options].nil? && args[:tls_options].to_s.to_sym == :default
args.merge(tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS)
else
args
end
else
case method = args.to_s.to_sym
when :simple_tls, :start_tls
{ method: method,
tls_options: {} }
when :true
scheme = if @url.scheme == 'ldaps' || @port == DefaultTlsPort
:simple_tls
else
:start_tls
end
{ method: scheme,
tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS }
else
raise ArgumentError, valid_args
end
end
end

Expand Down
1 change: 1 addition & 0 deletions lib/net/ldap/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ class BadAttributeError < Error; end
class FilterTypeUnknownError < Error; end
class FilterSyntaxInvalidError < Error; end
class EntryOverflowError < Error; end
class ProtocolNotSupported < Error; end
end