simple_ldap_authenticator 1.3.0 → 2.0.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README +36 -0
  3. data/lib/simple_ldap_authenticator.rb +96 -23
  4. metadata +1 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dbf224e93ab7c30ed11038747ecaec5dc263715cb9ab03172f6a1d605bac947
4
- data.tar.gz: bb5b1c047030368ea994ea55b35aee888596d5976323780109437805776a4b34
3
+ metadata.gz: 6077b51596ff8f01277e06222f7373de619040403ef347415de226c8802a9fa7
4
+ data.tar.gz: f6b9491e5055a8da2a57dbdd192a9547acc194edebe95c9d0f33158e945d5133
5
5
  SHA512:
6
- metadata.gz: 177e31bda9a64aa956f2f67bd484674d238fcf16c0721892dbfd7ceaf7ae0a78902accf2378879c1a8568ea29404aa2593f64d8f43187549395e1d4a165bbc9a
7
- data.tar.gz: 6d627bd78c11141794af994ba7ad9876e25e5666fbda3d3fe467bb15e722351a3b9af5b2d6b023d8ee02db59a58426ceed016a55d1607ec1dd39d0a9859db586
6
+ metadata.gz: f426214b597d888072cd0f0cb7ec66089ae55c7330b7fafbafb098d5314760fd32854f01dd285b25f546b88f750ef86c858fdac71ecd3d644a698637651703b4
7
+ data.tar.gz: fb996d2dbacf455d99b8f7aee099de3e2eaccd01a77fb29f6840ffe8721c0d39fb8c71c8d268d87ac63167062fdd2e1bf9b2958999a018402c6e7a263f4f4366
data/README CHANGED
@@ -17,4 +17,40 @@ Example Usage:
17
17
  SimpleLdapAuthenticator.valid?(username, password)
18
18
  # => true or false (or raise if there is an issue connecting to the server)
19
19
 
20
+ By default, a new connection object is created for each call to valid?,
21
+ so valid? is safe to call concurrently from multiple threads. If you only
22
+ use SimpleLdapAuthenticator from a single thread, you can reuse a single
23
+ connection object for all calls to valid?:
24
+
25
+ SimpleLdapAuthenticator.single_threaded = true
26
+
27
+ Do not enable single threaded mode if valid? may be called concurrently,
28
+ as that can result in invalid passwords being considered valid.
29
+
30
+ When use_ssl is true, the server's certificate and hostname are verified
31
+ using the default trusted certificates. You can set use_ssl to a hash of
32
+ TLS options to customize this, such as specifying the CA certificate(s) to
33
+ trust (useful for internal CAs and self-signed certificates, such as those
34
+ commonly used by Active Directory):
35
+
36
+ SimpleLdapAuthenticator.use_ssl = {ca_file: '/path/to/ca.pem'}
37
+
38
+ The :ca_file, :ca_path, and :verify_mode options are supported for both
39
+ ldap and net/ldap backends. For net/ldap, any option supported by
40
+ OpenSSL::SSL::SSLContext#set_params can be used. For ldap, only
41
+ those three options are supported. :verify_mode on ldap will default to
42
+ the LDAP behavior (which verifies by default unless the TLS_REQCERT
43
+ setting is set in the system's ldap.conf).
44
+
45
+ Before version 2, use_ssl = true did not verify the server's
46
+ certificate when using net/ldap. To get the previous behavior of not
47
+ verifying the server's certificate (not recommended, as it allows
48
+ man-in-the-middle attacks to capture passwords):
49
+
50
+ SimpleLdapAuthenticator.use_ssl = {verify_mode: OpenSSL::SSL::VERIFY_NONE}
51
+
52
+ Note that due to a bug in net/ldap, you should use domain names and
53
+ not IP addresses in the servers array, at least if you are using
54
+ LibreSSL (which follows the SSL spec more closely).
55
+
20
56
  github: http://github.com/jeremyevans/simple_ldap_authenticator
@@ -12,9 +12,43 @@
12
12
  # * login_format = '%s@domain.com' # Active Directory, OR
13
13
  # * login_format = 'cn=%s,cn=users,o=organization,c=us' # Other LDAP servers
14
14
  # * servers = ['dc1.domain.com', 'dc2.domain.com'] # names/addresses of LDAP servers to use
15
- # * use_ssl = true # for logging in via LDAPS
15
+ # * use_ssl = true # for logging in via LDAPS, verifying the server certificate
16
+ # * use_ssl = {ca_file: '/path/to/ca.pem'} # for logging in via LDAPS, with TLS options
16
17
  # * port = 3289 # instead of 389 for LDAP or 636 for LDAPS
17
18
  # * logger = Logger.new($stdout) # for logging authentication successes/failures
19
+ # * single_threaded = true # reuse a single connection, only safe if not using threads
20
+ #
21
+ # By default, a new connection object is created for each call to valid?,
22
+ # so that valid? is safe to call concurrently from multiple threads. If
23
+ # you are only using SimpleLdapAuthenticator in a single thread, you can set
24
+ # single_threaded = true, which reuses a single connection object for all
25
+ # calls to valid?. Do not set single_threaded = true if valid? may be
26
+ # called concurrently, as that can result in invalid passwords being
27
+ # considered valid.
28
+ #
29
+ # When use_ssl is true, the server's certificate and hostname are verified
30
+ # using the default trusted certificates. use_ssl can also be set to a hash
31
+ # of TLS options, which are merged into the default TLS options. The
32
+ # following TLS options are supported for both libraries:
33
+ #
34
+ # :ca_file :: File containing trusted CA certificates.
35
+ # :ca_path :: Directory containing trusted CA certificates.
36
+ # :verify_mode :: Whether to verify the server certificate, should be
37
+ # OpenSSL::SSL::VERIFY_PEER or OpenSSL::SSL::VERIFY_NONE
38
+ # if given.
39
+ #
40
+ # For net/ldap, any option supported by OpenSSL::SSL::SSLContext#set_params
41
+ # can be used. For ldap, only the three keys given above work.
42
+ #
43
+ # Before version 2, use_ssl = true did not verify the server certificate
44
+ # when using net/ldap. To restore that behavior (not recommended, as it allows
45
+ # man-in-the-middle attacks), you can disable verification:
46
+ #
47
+ # SimpleLdapAuthenticator.use_ssl = {verify_mode: OpenSSL::SSL::VERIFY_NONE}
48
+ #
49
+ # With ldap, whether to verify the certificate by default depends on the
50
+ # system's libldap configuration (TLS_REQCERT in ldap.conf). The above setting
51
+ # also disables verification when using ldap.
18
52
  #
19
53
  # The class is used as a singleton, you are not supposed to create an
20
54
  # instance of it. For example:
@@ -31,9 +65,11 @@ class SimpleLdapAuthenticator
31
65
  @servers = ['127.0.0.1']
32
66
  @use_ssl = false
33
67
  @login_format = '%s'
68
+ @single_threaded = false
69
+ @switch_server_mutex = Mutex.new
34
70
 
35
71
  class << self
36
- attr_accessor :servers, :use_ssl, :login_format, :logger, :ldap_library
72
+ attr_accessor :servers, :use_ssl, :login_format, :logger, :ldap_library, :single_threaded
37
73
  attr_writer :port, :connection
38
74
 
39
75
  # Load the required LDAP library, either 'ldap' or 'net/ldap'
@@ -64,16 +100,46 @@ class SimpleLdapAuthenticator
64
100
  servers[0]
65
101
  end
66
102
 
67
- # The connection to the LDAP server. A single connection is made and the
68
- # connection is only changed if a server returns an error other than
69
- # invalid password.
103
+ # The shared connection to the LDAP server, only used in single threaded
104
+ # mode. A single connection is made and the connection is only changed if
105
+ # a server returns an error other than invalid password.
70
106
  def connection
71
- return @connection if @connection
107
+ @connection ||= new_connection(server)
108
+ end
109
+
110
+ # Create a new connection object for the given LDAP server.
111
+ def new_connection(server)
72
112
  load_ldap_library
73
- @connection = if ldap_library == 'net/ldap'
74
- Net::LDAP.new(:host=>server, :port=>(port), :encryption=>(:simple_tls if use_ssl))
113
+ if ldap_library == 'net/ldap'
114
+ if use_ssl
115
+ tls_opts = use_ssl.is_a?(Hash) ? use_ssl.dup : {}
116
+ unless tls_opts[:verify_mode]
117
+ require "openssl"
118
+ tls_opts[:verify_mode] = OpenSSL::SSL::VERIFY_PEER
119
+ end
120
+ encryption_opts = {:method=>:simple_tls, :tls_options=>tls_opts}
121
+ end
122
+ Net::LDAP.new(:host=>server, :port=>port, :encryption=>encryption_opts)
123
+ elsif use_ssl
124
+ conn = LDAP::SSLConn.new(server, port)
125
+ if use_ssl.is_a?(Hash) && !use_ssl.empty?
126
+ use_ssl.each do |k, v|
127
+ case k
128
+ when :verify_mode
129
+ conn.set_option(LDAP::LDAP_OPT_X_TLS_REQUIRE_CERT, v)
130
+ when :ca_file
131
+ conn.set_option(LDAP::LDAP_OPT_X_TLS_CACERTFILE, v)
132
+ when :ca_path
133
+ conn.set_option(LDAP::LDAP_OPT_X_TLS_CACERTDIR, v)
134
+ else
135
+ raise ArgumentError, "unsupported TLS option for ldap library: #{k.inspect}"
136
+ end
137
+ end
138
+ conn.set_option(LDAP::LDAP_OPT_X_TLS_NEWCTX, 0)
139
+ end
140
+ conn
75
141
  else
76
- (use_ssl ? LDAP::SSLConn : LDAP::Conn).new(server, port)
142
+ LDAP::Conn.new(server, port)
77
143
  end
78
144
  end
79
145
 
@@ -83,23 +149,30 @@ class SimpleLdapAuthenticator
83
149
  end
84
150
 
85
151
  # Disconnect from current LDAP server and use a different LDAP server on the
86
- # next authentication attempt
87
- def switch_server
88
- self.connection = nil
89
- servers << servers.shift
152
+ # next authentication attempt. If failed_server is given, only switch
153
+ # servers if failed_server is still the current server, so that multiple
154
+ # threads failing on the same server only switch servers once.
155
+ def switch_server(failed_server = nil)
156
+ @switch_server_mutex.synchronize do
157
+ if failed_server.nil? || server.equal?(failed_server)
158
+ self.connection = nil
159
+ servers.rotate!
160
+ end
161
+ end
90
162
  end
91
163
 
92
164
  # Check the validity of a login/password combination
93
165
  def valid?(login, password)
94
166
  login = login.to_s
95
167
  password = password.to_s
96
- connection = self.connection
97
- if password == '' || password.include?("\0") || login.include?("\0")
98
- false
99
- elsif ldap_library == 'net/ldap'
100
- connection.authenticate(login_format % login, password)
168
+ return false if password == '' || password.include?("\0") || login.include?("\0")
169
+
170
+ server = self.server
171
+ if ldap_library == 'net/ldap'
172
+ connection = single_threaded ? self.connection : new_connection(server)
173
+ auth = {:method=>:simple, :username=>login_format % login, :password=>password}
101
174
  begin
102
- if connection.bind
175
+ if connection.bind(auth)
103
176
  logger.info("Authenticated #{login} by #{server}") if logger
104
177
  true
105
178
  else
@@ -107,15 +180,16 @@ class SimpleLdapAuthenticator
107
180
  if logger
108
181
  logger.info("Error attempting to authenticate #{login} by #{server}: #{result.code} #{result.message} #{result.error_message.to_s.strip}")
109
182
  end
110
- switch_server unless result.code == 49
183
+ switch_server(server) unless result.code == 49
111
184
  false
112
185
  end
113
186
  rescue Net::LDAP::Error, SocketError, SystemCallError => error
114
187
  logger.info("Error attempting to authenticate #{login} by #{server}: #{error.message}") if logger
115
- switch_server
188
+ switch_server(server)
116
189
  false
117
190
  end
118
191
  else
192
+ connection = (single_threaded && !(use_ssl.is_a?(Hash) && !use_ssl.empty?)) ? self.connection : new_connection(server)
119
193
  connection.unbind if connection.bound?
120
194
  begin
121
195
  connection.bind(login_format % login, password)
@@ -123,9 +197,8 @@ class SimpleLdapAuthenticator
123
197
  logger.info("Authenticated #{login} by #{server}") if logger
124
198
  true
125
199
  rescue LDAP::ResultError => error
126
- connection.unbind if connection.bound?
127
200
  logger.info("Error attempting to authenticate #{login} by #{server}: #{error.message}") if logger
128
- switch_server unless error.message == 'Invalid credentials'
201
+ switch_server(server) unless error.message == 'Invalid credentials'
129
202
  false
130
203
  end
131
204
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_ldap_authenticator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: eventmachine
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: net-ldap
42
28
  requirement: !ruby/object:Gem::Requirement