Skip to content

Commit c722c80

Browse files
committed
🔀 Merge branch 'gl-master' into sync-gl
# Conflicts: # .gitignore # .gitlab-ci.yml # Gemfile # README.md # gitlab_omniauth-ldap.gemspec # lib/omniauth-ldap/adaptor.rb # lib/omniauth-ldap/version.rb # lib/omniauth/strategies/ldap.rb # spec/omniauth-ldap/adaptor_spec.rb # spec/omniauth/strategies/ldap_spec.rb # spec/spec_helper.rb
2 parents 7dddeaa + 2d7627d commit c722c80

File tree

13 files changed

+403
-60
lines changed

13 files changed

+403
-60
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.project
2+
.tags
3+
14
# Build Artifacts
25
/pkg/
36
/tmp/

.gitlab-ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# - template: Security/SAST.gitlab-ci.yml
1414

1515
default:
16-
image: ruby
16+
image: "ruby:${RUBY_VERSION}"
1717

1818
variables:
1919
BUNDLE_INSTALL_FLAGS: "--quiet --jobs=$(nproc) --retry=3"
@@ -39,7 +39,6 @@ workflow:
3939
- if: '$CI_COMMIT_TAG'
4040

4141
.test_template-current: &test_definition-current
42-
image: ruby:${RUBY_VERSION}
4342
stage: test
4443
script:
4544
# || true so we don't fail here, because it'll probably work even if the gem update fails
@@ -67,7 +66,6 @@ workflow:
6766
- vendor/ruby
6867

6968
.test_template-legacy: &test_definition-legacy
70-
image: ruby:${RUBY_VERSION}
7169
stage: test
7270
script:
7371
# RUBYGEMS_VERSION because we support EOL Ruby still...

CHANGELOG

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## 2.1.1
2+
- Add a String check to `tls_options` sanitization to allow other objects
3+
4+
## 2.1.0
5+
- Expose `:tls_options` SSL configuration option. Deprecate :ca_file, :ssl_version
6+
7+
## 2.0.4
8+
- Improve log message when invalid credentials are used
9+
10+
## 2.0.3
11+
- Protects against wrong request method call to callback

Gemfile.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ GIT
1313
PATH
1414
remote: .
1515
specs:
16-
omniauth-ldap (2.0.0)
17-
net-ldap (~> 0.16)
18-
omniauth (>= 1)
19-
pyu-ruby-sasl (~> 0.0.3.3)
20-
rack (>= 1)
21-
rubyntlm (~> 0.6.2)
16+
omniauth-ldap (2.3.0)
17+
net-ldap (~> 0.16, < 1)
18+
omniauth (>= 1, < 3)
19+
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
20+
rack (>= 1, < 4)
21+
rubyntlm (~> 0.6.2, < 1)
2222
version_gem (~> 1.1, >= 1.1.9)
2323

2424
GEM

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ use OmniAuth::Strategies::LDAP,
5757
title: "My LDAP",
5858
host: "10.101.10.1",
5959
port: 389,
60-
method: :plain,
60+
encryption: :plain,
6161
base: "dc=intridea,dc=com",
6262
uid: "sAMAccountName",
6363
name_proc: proc { |name| name.gsub(/@.*$/, "") },
6464
bind_dn: "default_bind_dn",
65-
password: "password"
65+
password: "password",
66+
tls_options: {
67+
ssl_version: 'TLSv1_2',
68+
ciphers: ["AES-128-CBC", "AES-128-CBC-HMAC-SHA1", "AES-128-CBC-HMAC-SHA256"]
69+
}
6670
# Or, alternatively:
6771
# use OmniAuth::Strategies::LDAP, filter: '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))'
6872
```
@@ -642,8 +646,8 @@ Thanks for RTFM. ☺️
642646
[📌changelog]: CHANGELOG.md
643647
[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
644648
[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
645-
[📌gitmoji]:https://gitmoji.dev
646-
[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
649+
[📌gitmoji]: https://gitmoji.dev
650+
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
647651
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
648652
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.076-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
649653
[🔐security]: SECURITY.md

omniauth-ldap.gemspec renamed to gitlab_omniauth-ldap.gemspec

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,16 @@ Gem::Specification.new do |spec|
9797
# Listed files are the relative paths from bindir above.
9898
spec.executables = []
9999

100-
spec.add_dependency("net-ldap", "~> 0.16") # ruby >= 2.0
100+
spec.add_dependency("net-ldap", "~> 0.16", "< 1") # ruby >= 2.0
101101
# nkf/kconv has been part of Ruby since long ago.
102102
# Eventually it became a standard gem, but was changed to a bundled gem in Ruby 3.4.
103103
# In general, kconv and iconv have been superseded since Ruby 1.9 by the built-in
104104
# encoding support provided by String#encode, String#force_encoding, and similar methods.
105-
# spec.add_dependency("nkf") # ruby >= 2.3
106-
spec.add_dependency("omniauth", ">= 1") # ruby >= 0.0
107-
spec.add_dependency("pyu-ruby-sasl", "~> 0.0.3.3") # ruby >= 0.0
108-
spec.add_dependency("rack", ">= 1") # ruby >= 0.0
109-
spec.add_dependency("rubyntlm", "~> 0.6.2") # ruby >= 1.8.7
105+
# spec.add_dependency("nkf") # ruby >= 2.3
106+
spec.add_dependency("omniauth", ">= 1", "< 3") # ruby >= 0.0
107+
spec.add_dependency("pyu-ruby-sasl", ">= 0.0.3.3", "< 0.1") # ruby >= 0.0
108+
spec.add_dependency("rack", ">= 1", "< 4") # ruby >= 0.0
109+
spec.add_dependency("rubyntlm", "~> 0.6.2", "< 1") # ruby >= 1.8.7
110110

111111
# Utilities
112112
spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.9") # ruby >= 2.2.0

lib/omniauth-ldap/adaptor.rb

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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
162246
end

lib/omniauth-ldap/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module OmniAuth
22
module LDAP
33
module Version
4-
VERSION = "2.0.0"
4+
VERSION = "2.3.0"
55
end
66
VERSION = Version::VERSION # Make VERSION available in traditional way
77
end

lib/omniauth/strategies/ldap.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
require "omniauth"
2+
require "omniauth/version"
23

34
module OmniAuth
45
module Strategies
56
class LDAP
67
OMNIAUTH_GTE_V2 = Gem::Version.new(OmniAuth::VERSION) >= Gem::Version.new("2.0.0")
78
include OmniAuth::Strategy
89

10+
InvalidCredentialsError = Class.new(StandardError)
11+
912
CONFIG = {
1013
"name" => "cn",
1114
"first_name" => "givenName",
@@ -31,6 +34,9 @@ class LDAP
3134
end
3235
option :port, 389
3336
option :method, :plain
37+
option :disable_verify_certificates, false
38+
option :ca_file, nil
39+
option :ssl_version, nil # use OpenSSL default if nil
3440
option :uid, "sAMAccountName"
3541
option :name_proc, lambda { |n| n }
3642

@@ -59,10 +65,14 @@ def request_phase
5965
def callback_phase
6066
@adaptor = OmniAuth::LDAP::Adaptor.new(@options)
6167

68+
return fail!(:invalid_request_method) unless valid_request_method?
6269
return fail!(:missing_credentials) if missing_credentials?
6370
begin
6471
@ldap_user_info = @adaptor.bind_as(filter: filter(@adaptor), size: 1, password: request.params["password"])
65-
return fail!(:invalid_credentials) unless @ldap_user_info
72+
73+
unless @ldap_user_info
74+
return fail!(:invalid_credentials, InvalidCredentialsError.new("Invalid credentials for #{request.params['username']}"))
75+
end
6676

6777
@user_info = self.class.map_user(CONFIG, @ldap_user_info)
6878
super
@@ -73,9 +83,10 @@ def callback_phase
7383

7484
def filter(adaptor)
7585
if adaptor.filter && !adaptor.filter.empty?
76-
Net::LDAP::Filter.construct(adaptor.filter % {username: @options[:name_proc].call(request.params["username"])})
86+
username = Net::LDAP::Filter.escape(@options[:name_proc].call(request.params['username']))
87+
Net::LDAP::Filter.construct(adaptor.filter % { username: username })
7788
else
78-
Net::LDAP::Filter.eq(adaptor.uid, @options[:name_proc].call(request.params["username"]))
89+
Net::LDAP::Filter.equals(adaptor.uid, @options[:name_proc].call(request.params["username"]))
7990
end
8091
end
8192

@@ -128,6 +139,10 @@ def map_user(mapper, object)
128139

129140
protected
130141

142+
def valid_request_method?
143+
request.env['REQUEST_METHOD'] == 'POST'
144+
end
145+
131146
def missing_credentials?
132147
request.params["username"].nil? || request.params["username"].empty? || request.params["password"].nil? || request.params["password"].empty?
133148
end # missing_credentials?

0 commit comments

Comments
 (0)