Skip to content

Commit b020065

Browse files
committed
Fixes #83 - Pull in omniauth-ldap
1 parent edcf069 commit b020065

File tree

3 files changed

+251
-1
lines changed

3 files changed

+251
-1
lines changed

lib/omniauth-ldap/adaptor.rb

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# taken from https://github.com/omniauth/omniauth-ldap/blob/master/lib/omniauth-ldap/adaptor.rb
2+
#this code borrowed pieces from activeldap and net-ldap
3+
require 'rack'
4+
require 'net/ldap'
5+
require 'net/ntlm'
6+
require 'sasl'
7+
require 'kconv'
8+
module OmniAuth
9+
module LDAP
10+
class Adaptor
11+
class LdapError < StandardError; end
12+
class ConfigurationError < StandardError; end
13+
class AuthenticationError < StandardError; end
14+
class ConnectionError < StandardError; end
15+
16+
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous, :filter]
17+
18+
# A list of needed keys. Possible alternatives are specified using sub-lists.
19+
MUST_HAVE_KEYS = [:host, :port, :method, [:uid, :filter], :base]
20+
21+
METHOD = {
22+
:ssl => :simple_tls,
23+
:tls => :start_tls,
24+
:plain => nil,
25+
}
26+
27+
attr_accessor :bind_dn, :password
28+
attr_reader :connection, :uid, :base, :auth, :filter
29+
def self.validate(configuration={})
30+
message = []
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
37+
end
38+
raise ArgumentError.new(message.join(",") +" MUST be provided") unless message.empty?
39+
end
40+
def initialize(configuration={})
41+
Adaptor.validate(configuration)
42+
@configuration = configuration.dup
43+
@configuration[:allow_anonymous] ||= false
44+
@logger = @configuration.delete(:logger)
45+
VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
46+
instance_variable_set("@#{name}", @configuration[name])
47+
end
48+
method = ensure_method(@method)
49+
config = {
50+
:host => @host,
51+
:port => @port,
52+
:base => @base
53+
}
54+
@bind_method = @try_sasl ? :sasl : (@allow_anonymous||!@bind_dn||!@password ? :anonymous : :simple)
55+
56+
57+
@auth = sasl_auths({:username => @bind_dn, :password => @password}).first if @bind_method == :sasl
58+
@auth ||= { :method => @bind_method,
59+
:username => @bind_dn,
60+
:password => @password
61+
}
62+
config[:auth] = @auth
63+
config[:encryption] = method
64+
@connection = Net::LDAP.new(config)
65+
end
66+
67+
#:base => "dc=yourcompany, dc=com",
68+
# :filter => "(mail=#{user})",
69+
# :password => psw
70+
def bind_as(args = {})
71+
result = false
72+
@connection.open do |me|
73+
rs = me.search args
74+
if rs and rs.first and dn = rs.first.dn
75+
password = args[:password]
76+
method = args[:method] || @method
77+
password = password.call if password.respond_to?(:call)
78+
if method == 'sasl'
79+
result = rs.first if me.bind(sasl_auths({:username => dn, :password => password}).first)
80+
else
81+
result = rs.first if me.bind(:method => :simple, :username => dn,
82+
:password => password)
83+
end
84+
end
85+
end
86+
result
87+
end
88+
89+
private
90+
def ensure_method(method)
91+
method ||= "plain"
92+
normalized_method = method.to_s.downcase.to_sym
93+
return METHOD[normalized_method] if METHOD.has_key?(normalized_method)
94+
95+
available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ")
96+
format = "%s is not one of the available connect methods: %s"
97+
raise ConfigurationError, format % [method.inspect, available_methods]
98+
end
99+
100+
def sasl_auths(options={})
101+
auths = []
102+
sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
103+
sasl_mechanisms.each do |mechanism|
104+
normalized_mechanism = mechanism.downcase.gsub(/-/, '_')
105+
sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}"
106+
next unless respond_to?(sasl_bind_setup, true)
107+
initial_credential, challenge_response = send(sasl_bind_setup, options)
108+
auths << {
109+
:method => :sasl,
110+
:initial_credential => initial_credential,
111+
:mechanism => mechanism,
112+
:challenge_response => challenge_response
113+
}
114+
end
115+
auths
116+
end
117+
118+
def sasl_bind_setup_digest_md5(options)
119+
bind_dn = options[:username]
120+
initial_credential = ""
121+
challenge_response = Proc.new do |cred|
122+
pref = SASL::Preferences.new :digest_uri => "ldap/#{@host}", :username => bind_dn, :has_password? => true, :password => options[:password]
123+
sasl = SASL.new("DIGEST-MD5", pref)
124+
response = sasl.receive("challenge", cred)
125+
response[1]
126+
end
127+
[initial_credential, challenge_response]
128+
end
129+
130+
def sasl_bind_setup_gss_spnego(options)
131+
bind_dn = options[:username]
132+
psw = options[:password]
133+
raise LdapError.new( "invalid binding information" ) unless (bind_dn && psw)
134+
135+
nego = proc {|challenge|
136+
t2_msg = Net::NTLM::Message.parse( challenge )
137+
bind_dn, domain = bind_dn.split('\\').reverse
138+
t2_msg.target_name = Net::NTLM::encode_utf16le(domain) if domain
139+
t3_msg = t2_msg.response( {:user => bind_dn, :password => psw}, {:ntlmv2 => true} )
140+
t3_msg.serialize
141+
}
142+
[Net::NTLM::Message::Type1.new.serialize, nego]
143+
end
144+
145+
end
146+
end
147+
end

lib/omniauth/strategies/ldap.rb

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# taken from https://github.com/omniauth/omniauth-ldap/blob/master/lib/omniauth/strategies/ldap.rb
2+
require 'omniauth'
3+
4+
module OmniAuth
5+
module Strategies
6+
class LDAP
7+
include OmniAuth::Strategy
8+
@@config = {
9+
'name' => 'cn',
10+
'first_name' => 'givenName',
11+
'last_name' => 'sn',
12+
'email' => ['mail', "email", 'userPrincipalName'],
13+
'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'],
14+
'mobile' => ['mobile', 'mobileTelephoneNumber'],
15+
'nickname' => ['uid', 'userid', 'sAMAccountName'],
16+
'title' => 'title',
17+
'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]},
18+
'uid' => 'dn',
19+
'url' => ['wwwhomepage'],
20+
'image' => 'jpegPhoto',
21+
'description' => 'description'
22+
}
23+
option :title, "LDAP Authentication" #default title for authentication form
24+
option :port, 389
25+
option :method, :plain
26+
option :uid, 'sAMAccountName'
27+
option :name_proc, lambda {|n| n}
28+
29+
def request_phase
30+
OmniAuth::LDAP::Adaptor.validate @options
31+
f = OmniAuth::Form.new(:title => (options[:title] || "LDAP Authentication"), :url => callback_path)
32+
f.text_field 'Login', 'username'
33+
f.password_field 'Password', 'password'
34+
f.button "Sign In"
35+
f.to_response
36+
end
37+
38+
def callback_phase
39+
@adaptor = OmniAuth::LDAP::Adaptor.new @options
40+
41+
return fail!(:missing_credentials) if missing_credentials?
42+
begin
43+
@ldap_user_info = @adaptor.bind_as(:filter => filter(@adaptor), :size => 1, :password => request['password'])
44+
return fail!(:invalid_credentials) if !@ldap_user_info
45+
46+
@user_info = self.class.map_user(@@config, @ldap_user_info)
47+
super
48+
rescue Exception => e
49+
return fail!(:ldap_error, e)
50+
end
51+
end
52+
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+
61+
uid {
62+
@user_info["uid"]
63+
}
64+
info {
65+
@user_info
66+
}
67+
extra {
68+
{ :raw_info => @ldap_user_info }
69+
}
70+
71+
def self.map_user(mapper, object)
72+
user = {}
73+
mapper.each do |key, value|
74+
case value
75+
when String
76+
user[key] = object[value.downcase.to_sym].first if object.respond_to? value.downcase.to_sym
77+
when Array
78+
value.each {|v| (user[key] = object[v.downcase.to_sym].first; break;) if object.respond_to? v.downcase.to_sym}
79+
when Hash
80+
value.map do |key1, value1|
81+
pattern = key1.dup
82+
value1.each_with_index do |v,i|
83+
part = ''; v.collect(&:downcase).collect(&:to_sym).each {|v1| (part = object[v1].first; break;) if object.respond_to? v1}
84+
pattern.gsub!("%#{i}",part||'')
85+
end
86+
user[key] = pattern
87+
end
88+
end
89+
end
90+
user
91+
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?
98+
end
99+
end
100+
end
101+
102+
OmniAuth.config.add_camelization 'ldap', 'LDAP'

plugin.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
gem 'pyu-ruby-sasl', '0.0.3.3', require: false
99
gem 'rubyntlm', '0.3.4', require: false
1010
gem 'net-ldap', '0.17.1'
11-
gem 'omniauth-ldap', '1.0.5'
1211

1312
require 'yaml'
13+
require_relative 'lib/omniauth-ldap/adaptor'
14+
require_relative 'lib/omniauth/strategies/ldap'
1415
require_relative 'lib/ldap_user'
1516

1617
class ::LDAPAuthenticator < ::Auth::Authenticator

0 commit comments

Comments
 (0)