Skip to content

Commit e728b00

Browse files
committed
Merge pull request #10 from jhollingsworth/sync-intridea-omniauth-ldap
Sync with intridea/omniauth-ldap
2 parents daa9b62 + 76d7754 commit e728b00

File tree

9 files changed

+220
-54
lines changed

9 files changed

+220
-54
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ branches:
22
only:
33
- 'master'
44
rvm:
5-
- 1.9.2
5+
- 2.0.0
66
script: "bundle exec rspec spec"

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
gitlab_omniauth-ldap (1.0.2)
4+
gitlab_omniauth-ldap (1.0.4)
55
net-ldap (~> 0.3.1)
66
omniauth (~> 1.0)
77
pyu-ruby-sasl (~> 0.0.3.1)

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Use the LDAP strategy as a middleware in your application:
1111
:method => :plain,
1212
:base => 'dc=intridea, dc=com',
1313
:uid => 'sAMAccountName',
14+
:name_proc => Proc.new {|name| name.gsub(/@.*$/,'')},
15+
:bind_dn => 'default_bind_dn',
16+
# Or, alternatively:
17+
#:filter => '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))'
1418
:name_proc => Proc.new {|name| name.gsub(/@.*$/,'')}
1519
:bind_dn => 'default_bind_dn'
1620
:password => 'password'
@@ -29,6 +33,9 @@ Allowed values of :method are: :plain, :ssl, :tls.
2933
:uid is the LDAP attribute name for the user name in the login form.
3034
typically AD would be 'sAMAccountName' or 'UserPrincipalName', while OpenLDAP is 'uid'.
3135

36+
:filter is the LDAP filter used to search the user entry. It can be used in place of :uid for more flexibility.
37+
`%{username}` will be replaced by the user name processed by :name_proc.
38+
3239
:name_proc allows you to match the user name entered with the format of the :uid attributes.
3340
For example, value of 'sAMAccountName' in AD contains only the windows user name. If your user prefers using
3441
email to login, a name_proc as above will trim the email string down to just the windows login name.

gitlab_omniauth-ldap.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Gem::Specification.new do |gem|
77
gem.description = %q{A LDAP strategy for OmniAuth.}
88
gem.summary = %q{A LDAP strategy for OmniAuth.}
99
gem.homepage = "https://github.com/gitlabhq/omniauth-ldap"
10+
gem.license = "MIT"
1011

1112
gem.add_runtime_dependency 'omniauth', '~> 1.0'
1213
gem.add_runtime_dependency 'net-ldap', '~> 0.3.1'

lib/omniauth-ldap/adaptor.rb

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
require 'rack'
44
require 'net/ldap'
55
require 'net/ntlm'
6-
require 'uri'
76
require 'sasl'
87
require 'kconv'
98
module OmniAuth
@@ -14,9 +13,10 @@ class ConfigurationError < StandardError; end
1413
class AuthenticationError < StandardError; end
1514
class ConnectionError < StandardError; end
1615

17-
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
16+
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous, :filter]
1817

19-
MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base]
18+
# A list of needed keys. Possible alternatives are specified using sub-lists.
19+
MUST_HAVE_KEYS = [:host, :port, :method, [:uid, :filter], :base]
2020

2121
METHOD = {
2222
:ssl => :simple_tls,
@@ -25,11 +25,15 @@ class ConnectionError < StandardError; end
2525
}
2626

2727
attr_accessor :bind_dn, :password
28-
attr_reader :connection, :uid, :base, :auth
28+
attr_reader :connection, :uid, :base, :auth, :filter
2929
def self.validate(configuration={})
3030
message = []
31-
MUST_HAVE_KEYS.each do |name|
32-
message << name if configuration[name].nil?
31+
MUST_HAVE_KEYS.each do |names|
32+
names = [names].flatten
33+
missing_keys = names.select{|name| configuration[name].nil?}
34+
if missing_keys == names
35+
message << names.join(' or ')
36+
end
3337
end
3438
raise ArgumentError.new(message.join(",") +" MUST be provided") unless message.empty?
3539
end
@@ -48,7 +52,6 @@ def initialize(configuration={})
4852
:encryption => method,
4953
:base => @base
5054
}
51-
@uri = construct_uri(@host, @port, @method != :plain)
5255

5356
@bind_method = @try_sasl ? :sasl : (@allow_anonymous||!@bind_dn||!@password ? :anonymous : :simple)
5457

@@ -140,10 +143,6 @@ def sasl_bind_setup_gss_spnego(options)
140143
[Net::NTLM::Message::Type1.new.serialize, nego]
141144
end
142145

143-
def construct_uri(host, port, ssl)
144-
protocol = ssl ? "ldaps" : "ldap"
145-
URI.parse("#{protocol}://#{host}:#{port}").to_s
146-
end
147146
end
148147
end
149148
end

lib/omniauth-ldap/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module OmniAuth
22
module LDAP
3-
VERSION = "1.0.3"
3+
VERSION = "1.0.4"
44
end
55
end

lib/omniauth/strategies/ldap.rb

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module OmniAuth
44
module Strategies
55
class LDAP
6-
class MissingCredentialsError < StandardError; end
76
include OmniAuth::Strategy
87
@@config = {
98
'name' => 'cn',
@@ -38,14 +37,10 @@ def request_phase
3837
def callback_phase
3938
@adaptor = OmniAuth::LDAP::Adaptor.new @options
4039

40+
return fail!(:missing_credentials) if missing_credentials?
4141
begin
42-
# GITLAB security patch
43-
# Dont allow blank password for ldap auth
44-
if request['username'].nil? || request['username'].empty? || request['password'].nil? || request['password'].empty?
45-
raise MissingCredentialsError.new("Missing login credentials")
46-
end
42+
@ldap_user_info = @adaptor.bind_as(:filter => filter(@adaptor), :size => 1, :password => request['password'])
4743

48-
@ldap_user_info = @adaptor.bind_as(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @options[:name_proc].call(request['username'])),:size => 1, :password => request['password'])
4944
return fail!(:invalid_credentials) if !@ldap_user_info
5045

5146
@user_info = self.class.map_user(@@config, @ldap_user_info)
@@ -55,6 +50,14 @@ def callback_phase
5550
end
5651
end
5752

53+
def filter adaptor
54+
if adaptor.filter and !adaptor.filter.empty?
55+
Net::LDAP::Filter.construct(adaptor.filter % {username: @options[:name_proc].call(request['username'])})
56+
else
57+
Net::LDAP::Filter.eq(adaptor.uid, @options[:name_proc].call(request['username']))
58+
end
59+
end
60+
5861
uid {
5962
@user_info["uid"]
6063
}
@@ -70,14 +73,14 @@ def self.map_user(mapper, object)
7073
mapper.each do |key, value|
7174
case value
7275
when String
73-
user[key] = object[value.downcase.to_sym].first if object[value.downcase.to_sym]
76+
user[key] = object[value.downcase.to_sym].first if object.respond_to? value.downcase.to_sym
7477
when Array
75-
value.each {|v| (user[key] = object[v.downcase.to_sym].first; break;) if object[v.downcase.to_sym]}
78+
value.each {|v| (user[key] = object[v.downcase.to_sym].first; break;) if object.respond_to? v.downcase.to_sym}
7679
when Hash
7780
value.map do |key1, value1|
7881
pattern = key1.dup
7982
value1.each_with_index do |v,i|
80-
part = ''; v.collect(&:downcase).collect(&:to_sym).each {|v1| (part = object[v1].first; break;) if object[v1]}
83+
part = ''; v.collect(&:downcase).collect(&:to_sym).each {|v1| (part = object[v1].first; break;) if object.respond_to? v1}
8184
pattern.gsub!("%#{i}",part||'')
8285
end
8386
user[key] = pattern
@@ -86,6 +89,12 @@ def self.map_user(mapper, object)
8689
end
8790
user
8891
end
92+
93+
protected
94+
95+
def missing_credentials?
96+
request['username'].nil? or request['username'].empty? or request['password'].nil? or request['password'].empty?
97+
end # missing_credentials?
8998
end
9099
end
91100
end

spec/omniauth-ldap/adaptor_spec.rb

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
describe "OmniAuth::LDAP::Adaptor" do
33

44
describe 'initialize' do
5-
65
it 'should throw exception when must have field is not set' do
76
#[:host, :port, :method, :bind_dn]
8-
lambda { OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain'})}.should raise_error(ArgumentError)
7+
lambda { OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain'})}.should raise_error(ArgumentError)
98
end
9+
1010
it 'should throw exception when method is not supported' do
11-
lambda { OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'myplain', uid: 'uid', port: 389, base: 'dc=com'})}.should raise_error(OmniAuth::LDAP::Adaptor::ConfigurationError)
11+
lambda { OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'myplain', uid: 'uid', port: 389, base: 'dc=com'})}.should raise_error(OmniAuth::LDAP::Adaptor::ConfigurationError)
1212
end
1313

1414
it 'should setup ldap connection with anonymous' do
@@ -17,54 +17,59 @@
1717
adaptor.connection.host.should == '192.168.1.145'
1818
adaptor.connection.port.should == 389
1919
adaptor.connection.base.should == 'dc=intridea, dc=com'
20-
adaptor.connection.instance_variable_get('@auth').should == {:method => :anonymous, :username => nil, :password => nil}
20+
adaptor.connection.instance_variable_get('@auth').should == {:method => :anonymous, :username => nil, :password => nil}
2121
end
22+
2223
it 'should setup ldap connection with simple' do
2324
adaptor = OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', bind_dn: 'bind_dn', password: 'password'})
2425
adaptor.connection.should_not == nil
2526
adaptor.connection.host.should == '192.168.1.145'
2627
adaptor.connection.port.should == 389
2728
adaptor.connection.base.should == 'dc=intridea, dc=com'
28-
adaptor.connection.instance_variable_get('@auth').should == {:method => :simple, :username => 'bind_dn', :password => 'password'}
29-
end
29+
adaptor.connection.instance_variable_get('@auth').should == {:method => :simple, :username => 'bind_dn', :password => 'password'}
30+
end
31+
3032
it 'should setup ldap connection with sasl-md5' do
3133
adaptor = OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', try_sasl: true, sasl_mechanisms: ["DIGEST-MD5"], bind_dn: 'bind_dn', password: 'password'})
3234
adaptor.connection.should_not == nil
3335
adaptor.connection.host.should == '192.168.1.145'
3436
adaptor.connection.port.should == 389
3537
adaptor.connection.base.should == 'dc=intridea, dc=com'
36-
adaptor.connection.instance_variable_get('@auth')[:method].should == :sasl
37-
adaptor.connection.instance_variable_get('@auth')[:mechanism].should == 'DIGEST-MD5'
38-
adaptor.connection.instance_variable_get('@auth')[:initial_credential].should == ''
39-
adaptor.connection.instance_variable_get('@auth')[:challenge_response].should_not be_nil
38+
adaptor.connection.instance_variable_get('@auth')[:method].should == :sasl
39+
adaptor.connection.instance_variable_get('@auth')[:mechanism].should == 'DIGEST-MD5'
40+
adaptor.connection.instance_variable_get('@auth')[:initial_credential].should == ''
41+
adaptor.connection.instance_variable_get('@auth')[:challenge_response].should_not be_nil
4042
end
43+
4144
it 'should setup ldap connection with sasl-gss' do
4245
adaptor = OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', try_sasl: true, sasl_mechanisms: ["GSS-SPNEGO"], bind_dn: 'bind_dn', password: 'password'})
4346
adaptor.connection.should_not == nil
4447
adaptor.connection.host.should == '192.168.1.145'
4548
adaptor.connection.port.should == 389
4649
adaptor.connection.base.should == 'dc=intridea, dc=com'
47-
adaptor.connection.instance_variable_get('@auth')[:method].should == :sasl
48-
adaptor.connection.instance_variable_get('@auth')[:mechanism].should == 'GSS-SPNEGO'
49-
adaptor.connection.instance_variable_get('@auth')[:initial_credential].should =~ /^NTLMSSP/
50-
adaptor.connection.instance_variable_get('@auth')[:challenge_response].should_not be_nil
50+
adaptor.connection.instance_variable_get('@auth')[:method].should == :sasl
51+
adaptor.connection.instance_variable_get('@auth')[:mechanism].should == 'GSS-SPNEGO'
52+
adaptor.connection.instance_variable_get('@auth')[:initial_credential].should =~ /^NTLMSSP/
53+
adaptor.connection.instance_variable_get('@auth')[:challenge_response].should_not be_nil
5154
end
5255
end
53-
56+
5457
describe 'bind_as' do
5558
let(:args) { {:filter => Net::LDAP::Filter.eq('sAMAccountName', 'username'), :password => 'password', :size => 1} }
5659
let(:rs) { Struct.new(:dn).new('new dn') }
60+
5761
it 'should bind simple' do
5862
adaptor = OmniAuth::LDAP::Adaptor.new({host: "192.168.1.126", method: 'plain', base: 'dc=score, dc=local', port: 389, uid: 'sAMAccountName', bind_dn: 'bind_dn', password: 'password'})
5963
adaptor.connection.should_receive(:open).and_yield(adaptor.connection)
60-
adaptor.connection.should_receive(:search).with(args).and_return([rs])
64+
adaptor.connection.should_receive(:search).with(args).and_return([rs])
6165
adaptor.connection.should_receive(:bind).with({:username => 'new dn', :password => args[:password], :method => :simple}).and_return(true)
6266
adaptor.bind_as(args).should == rs
6367
end
68+
6469
it 'should bind sasl' do
6570
adaptor = OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', try_sasl: true, sasl_mechanisms: ["GSS-SPNEGO"], bind_dn: 'bind_dn', password: 'password'})
6671
adaptor.connection.should_receive(:open).and_yield(adaptor.connection)
67-
adaptor.connection.should_receive(:search).with(args).and_return([rs])
72+
adaptor.connection.should_receive(:search).with(args).and_return([rs])
6873
adaptor.connection.should_receive(:bind).and_return(true)
6974
adaptor.bind_as(args).should == rs
7075
end

0 commit comments

Comments
 (0)