simple_ldap_authenticator 1.2.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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README +36 -0
  4. data/lib/simple_ldap_authenticator.rb +100 -24
  5. metadata +7 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 370835649201c04c545cb53d363385e36b1d34ae4f3a1f08dd91b7f889725866
4
- data.tar.gz: 9d3118b82945916bd3043b22304777891943ad7ee450b4855b0bfe09e816af55
3
+ metadata.gz: 6077b51596ff8f01277e06222f7373de619040403ef347415de226c8802a9fa7
4
+ data.tar.gz: f6b9491e5055a8da2a57dbdd192a9547acc194edebe95c9d0f33158e945d5133
5
5
  SHA512:
6
- metadata.gz: 0fce93068f8fb5d2e38f304860c6a4e7334a7bb408d0d3a75e41f5ff218c72107062dec33de8fd3455317296b98f6580bac7cb8a1c72c9ada4e0b5cec4d96715
7
- data.tar.gz: c5b333d7c4b0359867a3176c0edb49fa1911cf329ebf41676049fa9999eb51bc57e931680a86c91e40cdee60112d3d57cbe9bdfe506e898b4f4ac32160429d98
6
+ metadata.gz: f426214b597d888072cd0f0cb7ec66089ae55c7330b7fafbafb098d5314760fd32854f01dd285b25f546b88f750ef86c858fdac71ecd3d644a698637651703b4
7
+ data.tar.gz: fb996d2dbacf455d99b8f7aee099de3e2eaccd01a77fb29f6840ffe8721c0d39fb8c71c8d268d87ac63167062fdd2e1bf9b2958999a018402c6e7a263f4f4366
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2022 Jeremy Evans
1
+ Copyright (c) 2006-2026 Jeremy Evans and contributors
2
2
 
3
3
  test/ldapserver.rb Copyright (c) 2006-2011 by Francis Cianfrocca and other contributors.
4
4
 
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,36 +149,47 @@ 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
106
- logger.info("Error attempting to authenticate #{login} by #{server}: #{connection.get_operation_result.code} #{connection.get_operation_result.message}") if logger
107
- switch_server unless connection.get_operation_result.code == 49
179
+ result = connection.get_operation_result
180
+ if logger
181
+ logger.info("Error attempting to authenticate #{login} by #{server}: #{result.code} #{result.message} #{result.error_message.to_s.strip}")
182
+ end
183
+ switch_server(server) unless result.code == 49
108
184
  false
109
185
  end
110
186
  rescue Net::LDAP::Error, SocketError, SystemCallError => error
111
187
  logger.info("Error attempting to authenticate #{login} by #{server}: #{error.message}") if logger
112
- switch_server
188
+ switch_server(server)
113
189
  false
114
190
  end
115
191
  else
192
+ connection = (single_threaded && !(use_ssl.is_a?(Hash) && !use_ssl.empty?)) ? self.connection : new_connection(server)
116
193
  connection.unbind if connection.bound?
117
194
  begin
118
195
  connection.bind(login_format % login, password)
@@ -120,9 +197,8 @@ class SimpleLdapAuthenticator
120
197
  logger.info("Authenticated #{login} by #{server}") if logger
121
198
  true
122
199
  rescue LDAP::ResultError => error
123
- connection.unbind if connection.bound?
124
200
  logger.info("Error attempting to authenticate #{login} by #{server}: #{error.message}") if logger
125
- switch_server unless error.message == 'Invalid credentials'
201
+ switch_server(server) unless error.message == 'Invalid credentials'
126
202
  false
127
203
  end
128
204
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_ldap_authenticator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-09-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: minitest-global_expectations
@@ -24,20 +23,6 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: eventmachine
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
26
  - !ruby/object:Gem::Dependency
42
27
  name: net-ldap
43
28
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +51,6 @@ dependencies:
66
51
  - - ">="
67
52
  - !ruby/object:Gem::Version
68
53
  version: '0'
69
- description:
70
54
  email: code@jeremyevans.net
71
55
  executables: []
72
56
  extensions: []
@@ -76,14 +60,14 @@ files:
76
60
  - LICENSE
77
61
  - README
78
62
  - lib/simple_ldap_authenticator.rb
79
- homepage:
80
- licenses: []
63
+ homepage: http://github.com/jeremyevans/simple_ldap_authenticator
64
+ licenses:
65
+ - MIT
81
66
  metadata:
82
67
  bug_tracker_uri: https://github.com/jeremyevans/simple_ldap_authenticator/issues
83
68
  changelog_uri: https://github.com/jeremyevans/simple_ldap_authenticator/blob/master/CHANGELOG
84
69
  mailing_list_uri: https://github.com/jeremyevans/simple_ldap_authenticator/discussions
85
70
  source_code_uri: https://github.com/jeremyevans/simple_ldap_authenticator
86
- post_install_message:
87
71
  rdoc_options:
88
72
  - "--inline-source"
89
73
  - "--line-numbers"
@@ -95,15 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
79
  requirements:
96
80
  - - ">="
97
81
  - !ruby/object:Gem::Version
98
- version: '0'
82
+ version: 1.9.2
99
83
  required_rubygems_version: !ruby/object:Gem::Requirement
100
84
  requirements:
101
85
  - - ">="
102
86
  - !ruby/object:Gem::Version
103
87
  version: '0'
104
88
  requirements: []
105
- rubygems_version: 3.5.16
106
- signing_key:
89
+ rubygems_version: 4.0.20
107
90
  specification_version: 4
108
91
  summary: Easy authentication to an LDAP server(s)
109
92
  test_files: []