@@ -20,19 +20,39 @@ class ConfigurationError < StandardError; end
2020 class AuthenticationError < StandardError ; end
2121 class ConnectionError < StandardError ; end
2222
23- VALID_ADAPTER_CONFIGURATION_KEYS = [ :host , :port , :method , :bind_dn , :password , :try_sasl , :sasl_mechanisms , :uid , :base , :allow_anonymous , :filter ]
23+ VALID_ADAPTER_CONFIGURATION_KEYS = [
24+ :hosts , :host , :port , :encryption , :disable_verify_certificates , :bind_dn , :password , :try_sasl ,
25+ :sasl_mechanisms , :uid , :base , :allow_anonymous , :filter , :tls_options ,
26+
27+ # Deprecated
28+ :method ,
29+ :ca_file ,
30+ :ssl_version ,
31+ ]
2432
2533 # A list of needed keys. Possible alternatives are specified using sub-lists.
26- MUST_HAVE_KEYS = [ :host , :port , :method , [ :uid , :filter ] , :base ]
34+ MUST_HAVE_KEYS = [
35+ :base ,
36+ [ :encryption , :method ] , # :method is deprecated
37+ [ :hosts , :host ] ,
38+ [ :hosts , :port ] ,
39+ [ :uid , :filter ] ,
40+ ]
41+
42+ ENCRYPTION_METHOD = {
43+ simple_tls : :simple_tls ,
44+ start_tls : :start_tls ,
45+ plain : nil ,
2746
28- METHOD = {
47+ # Deprecated. This mapping aimed to be user-friendly, but only caused
48+ # confusion. Better to pass through the actual `Net::LDAP` encryption type.
2949 ssl : :simple_tls ,
3050 tls : :start_tls ,
31- plain : nil ,
3251 }
3352
3453 attr_accessor :bind_dn , :password
3554 attr_reader :connection , :uid , :base , :auth , :filter
55+
3656 def self . validate ( configuration = { } )
3757 message = [ ]
3858 MUST_HAVE_KEYS . each do |names |
@@ -53,17 +73,14 @@ def initialize(configuration = {})
5373 VALID_ADAPTER_CONFIGURATION_KEYS . each do |name |
5474 instance_variable_set ( "@#{ name } " , @configuration [ name ] )
5575 end
56- method = ensure_method ( @method )
5776 config = {
77+ base : @base ,
78+ hosts : @hosts ,
5879 host : @host ,
5980 port : @port ,
60- base : @base ,
81+ encryption : encryption_options
6182 }
62- @bind_method = if @try_sasl
63- :sasl
64- else
65- ( ( @allow_anonymous || !@bind_dn || !@password ) ? :anonymous : :simple )
66- end
83+ @bind_method = @try_sasl ? :sasl : ( @allow_anonymous ||!@bind_dn ||!@password ? :anonymous : :simple )
6784
6885 @auth = sasl_auths ( { username : @bind_dn , password : @password } ) . first if @bind_method == :sasl
6986 @auth ||= {
@@ -72,7 +89,6 @@ def initialize(configuration = {})
7289 password : @password ,
7390 }
7491 config [ :auth ] = @auth
75- config [ :encryption ] = method
7692 @connection = Net ::LDAP . new ( config )
7793 end
7894
@@ -103,14 +119,49 @@ def bind_as(args = {})
103119
104120 private
105121
106- def ensure_method ( method )
122+ def encryption_options
123+ translated_method = translate_method
124+ return nil unless translated_method
125+
126+ {
127+ method : translated_method ,
128+ tls_options : tls_options ( translated_method )
129+ }
130+ end
131+
132+ def translate_method
133+ method = @encryption || @method
107134 method ||= "plain"
108135 normalized_method = method . to_s . downcase . to_sym
109- return METHOD [ normalized_method ] if METHOD . has_key? ( normalized_method )
110136
111- available_methods = METHOD . keys . collect { |m | m . inspect } . join ( ", " )
112- format = "%s is not one of the available connect methods: %s"
113- raise ConfigurationError , format % [ method . inspect , available_methods ]
137+ unless ENCRYPTION_METHOD . has_key? ( normalized_method )
138+ available_methods = ENCRYPTION_METHOD . keys . collect { |m | m . inspect } . join ( ", " )
139+ format = "%s is not one of the available connect methods: %s"
140+ raise ConfigurationError , format % [ method . inspect , available_methods ]
141+ end
142+
143+ ENCRYPTION_METHOD [ normalized_method ]
144+ end
145+
146+
147+ def tls_options ( translated_method )
148+ return { } if translated_method == nil # (plain)
149+
150+ options = default_options
151+
152+ if @tls_options
153+ # Prevent blank config values from overwriting SSL defaults
154+ configured_options = sanitize_hash_values ( @tls_options )
155+ configured_options = symbolize_hash_keys ( configured_options )
156+
157+ options . merge! ( configured_options )
158+ end
159+
160+ # Retain backward compatibility until deprecated configs are removed.
161+ options [ :ca_file ] = @ca_file if @ca_file
162+ options [ :ssl_version ] = @ssl_version if @ssl_version
163+
164+ options
114165 end
115166
116167 def sasl_auths ( options = { } )
@@ -157,6 +208,39 @@ def sasl_bind_setup_gss_spnego(options)
157208 }
158209 [ Net ::NTLM ::Message ::Type1 . new . serialize , nego ]
159210 end
211+
212+ private
213+
214+ def default_options
215+ if @disable_verify_certificates
216+ # It is important to explicitly set verify_mode for two reasons:
217+ # 1. The behavior of OpenSSL is undefined when verify_mode is not set.
218+ # 2. The net-ldap gem implementation verifies the certificate hostname
219+ # unless verify_mode is set to VERIFY_NONE.
220+ { verify_mode : OpenSSL ::SSL ::VERIFY_NONE }
221+ else
222+ OpenSSL ::SSL ::SSLContext ::DEFAULT_PARAMS . dup
223+ end
224+ end
225+
226+ # Removes keys that have blank values
227+ #
228+ # This gem may not always be in the context of Rails so we
229+ # do this rather than `.blank?`.
230+ def sanitize_hash_values ( hash )
231+ hash . delete_if do |_ , value |
232+ value . nil? ||
233+ ( value . is_a? ( String ) && value !~ /\S / )
234+ end
235+ end
236+
237+ def symbolize_hash_keys ( hash )
238+ hash . keys . each do |key |
239+ hash [ key . to_sym ] = hash [ key ]
240+ end
241+
242+ hash
243+ end
160244 end
161245 end
162246end
0 commit comments