-
Notifications
You must be signed in to change notification settings - Fork 251
Define auth adapters #226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define auth adapters #226
Changes from 14 commits
197d460
b57a283
9c7b1af
2546e35
069ad98
585ae82
ac729dd
91db1ba
ab20ad2
60edf55
b56450d
86e4ba1
fbb1951
9bf1f30
8be5224
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
module Net | ||
class LDAP | ||
class AuthAdapter | ||
def self.register(names, adapter) | ||
names = Array(names) | ||
@adapters ||= {} | ||
names.each do |name| | ||
@adapters[name] = adapter | ||
end | ||
end | ||
|
||
def self.[](name) | ||
a = @adapters[name] | ||
if a.nil? | ||
raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{name})" | ||
end | ||
return a | ||
end | ||
|
||
def initialize(conn) | ||
@connection = conn | ||
end | ||
|
||
def bind | ||
raise "bind method must be overwritten" | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
require 'net/ldap/auth_adapter' | ||
require 'net/ldap/auth_adapter/sasl' | ||
|
||
module Net | ||
class LDAP | ||
module AuthAdapers | ||
#-- | ||
# PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET. | ||
# Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to | ||
# integrate it without introducing an external dependency. | ||
# | ||
# This authentication method is accessed by calling #bind with a :method | ||
# parameter of :gss_spnego. It requires :username and :password | ||
# attributes, just like the :simple authentication method. It performs a | ||
# GSS-SPNEGO authentication with the server, which is presumed to be a | ||
# Microsoft Active Directory. | ||
#++ | ||
class GSS_SPNEGO < Net::LDAP::AuthAdapter | ||
def bind(auth) | ||
require 'ntlm' | ||
|
||
user, psw = [auth[:username] || auth[:dn], auth[:password]] | ||
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) | ||
|
||
nego = proc { |challenge| | ||
t2_msg = NTLM::Message.parse(challenge) | ||
t3_msg = t2_msg.response({ :user => user, :password => psw }, | ||
{ :ntlmv2 => true }) | ||
t3_msg.serialize | ||
} | ||
|
||
Net::LDAP::AuthAdapter::Sasl.new(@connection). | ||
bind(:method => :sasl, :mechanism => "GSS-SPNEGO", | ||
:initial_credential => NTLM::Message::Type1.new.serialize, | ||
:challenge_response => nego) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
require 'net/ldap/auth_adapter' | ||
|
||
module Net | ||
class LDAP | ||
class AuthAdapter | ||
class Sasl < Net::LDAP::AuthAdapter | ||
#-- | ||
# Required parameters: :mechanism, :initial_credential and | ||
# :challenge_response | ||
# | ||
# Mechanism is a string value that will be passed in the SASL-packet's | ||
# "mechanism" field. | ||
# | ||
# Initial credential is most likely a string. It's passed in the initial | ||
# BindRequest that goes to the server. In some protocols, it may be empty. | ||
# | ||
# Challenge-response is a Ruby proc that takes a single parameter and | ||
# returns an object that will typically be a string. The | ||
# challenge-response block is called when the server returns a | ||
# BindResponse with a result code of 14 (saslBindInProgress). The | ||
# challenge-response block receives a parameter containing the data | ||
# returned by the server in the saslServerCreds field of the LDAP | ||
# BindResponse packet. The challenge-response block may be called multiple | ||
# times during the course of a SASL authentication, and each time it must | ||
# return a value that will be passed back to the server as the credential | ||
# data in the next BindRequest packet. | ||
#++ | ||
def bind(auth) | ||
mech, cred, chall = auth[:mechanism], auth[:initial_credential], | ||
auth[:challenge_response] | ||
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall) | ||
|
||
message_id = @connection.next_msgid | ||
|
||
n = 0 | ||
loop { | ||
sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3) | ||
request = [ | ||
Net::LDAP::Connection::LdapVersion.to_ber, "".to_ber, sasl | ||
].to_ber_appsequence(Net::LDAP::PDU::BindRequest) | ||
|
||
@connection.send(:write, request, nil, message_id) | ||
pdu = @connection.queued_read(message_id) | ||
|
||
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult | ||
raise Net::LDAP::NoBindResultError, "no bind result" | ||
end | ||
|
||
return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress | ||
raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges) | ||
|
||
cred = chall.call(pdu.result_server_sasl_creds) | ||
} | ||
|
||
raise Net::LDAP::SASLChallengeOverflowError, "why are we here?" | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
require 'net/ldap/auth_adapter' | ||
|
||
module Net | ||
class LDAP | ||
class AuthAdapter | ||
class Simple < AuthAdapter | ||
def bind(auth) | ||
user, psw = if auth[:method] == :simple | ||
[auth[:username] || auth[:dn], auth[:password]] | ||
else | ||
["", ""] | ||
end | ||
|
||
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) | ||
|
||
message_id = @connection.next_msgid | ||
request = [ | ||
Net::LDAP::Connection::LdapVersion.to_ber, user.to_ber, | ||
psw.to_ber_contextspecific(0) | ||
].to_ber_appsequence(Net::LDAP::PDU::BindRequest) | ||
|
||
@connection.send(:write, request, nil, message_id) | ||
pdu = @connection.queued_read(message_id) | ||
|
||
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult | ||
raise Net::LDAP::NoBindResultError, "no bind result" | ||
end | ||
|
||
pdu | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -250,130 +250,11 @@ def next_msgid | |
def bind(auth) | ||
instrument "bind.net_ldap_connection" do |payload| | ||
payload[:method] = meth = auth[:method] | ||
if [:simple, :anonymous, :anon].include?(meth) | ||
bind_simple auth | ||
elsif meth == :sasl | ||
bind_sasl(auth) | ||
elsif meth == :gss_spnego | ||
bind_gss_spnego(auth) | ||
else | ||
raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{meth})" | ||
end | ||
end | ||
end | ||
|
||
#-- | ||
# Implements a simple user/psw authentication. Accessed by calling #bind | ||
# with a method of :simple or :anonymous. | ||
#++ | ||
def bind_simple(auth) | ||
user, psw = if auth[:method] == :simple | ||
[auth[:username] || auth[:dn], auth[:password]] | ||
else | ||
["", ""] | ||
end | ||
|
||
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) | ||
|
||
message_id = next_msgid | ||
request = [ | ||
LdapVersion.to_ber, user.to_ber, | ||
psw.to_ber_contextspecific(0) | ||
].to_ber_appsequence(Net::LDAP::PDU::BindRequest) | ||
|
||
write(request, nil, message_id) | ||
pdu = queued_read(message_id) | ||
|
||
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult | ||
raise Net::LDAP::NoBindResultError, "no bind result" | ||
adapter = Net::LDAP::AuthAdapter[meth] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should test and handle the case when an adaptor is not found. |
||
adapter.new(self).bind(auth) | ||
end | ||
|
||
pdu | ||
end | ||
|
||
#-- | ||
# Required parameters: :mechanism, :initial_credential and | ||
# :challenge_response | ||
# | ||
# Mechanism is a string value that will be passed in the SASL-packet's | ||
# "mechanism" field. | ||
# | ||
# Initial credential is most likely a string. It's passed in the initial | ||
# BindRequest that goes to the server. In some protocols, it may be empty. | ||
# | ||
# Challenge-response is a Ruby proc that takes a single parameter and | ||
# returns an object that will typically be a string. The | ||
# challenge-response block is called when the server returns a | ||
# BindResponse with a result code of 14 (saslBindInProgress). The | ||
# challenge-response block receives a parameter containing the data | ||
# returned by the server in the saslServerCreds field of the LDAP | ||
# BindResponse packet. The challenge-response block may be called multiple | ||
# times during the course of a SASL authentication, and each time it must | ||
# return a value that will be passed back to the server as the credential | ||
# data in the next BindRequest packet. | ||
#++ | ||
def bind_sasl(auth) | ||
mech, cred, chall = auth[:mechanism], auth[:initial_credential], | ||
auth[:challenge_response] | ||
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall) | ||
|
||
message_id = next_msgid | ||
|
||
n = 0 | ||
loop { | ||
sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3) | ||
request = [ | ||
LdapVersion.to_ber, "".to_ber, sasl | ||
].to_ber_appsequence(Net::LDAP::PDU::BindRequest) | ||
|
||
write(request, nil, message_id) | ||
pdu = queued_read(message_id) | ||
|
||
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult | ||
raise Net::LDAP::NoBindResultError, "no bind result" | ||
end | ||
|
||
return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress | ||
raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges) | ||
|
||
cred = chall.call(pdu.result_server_sasl_creds) | ||
} | ||
|
||
raise Net::LDAP::SASLChallengeOverflowError, "why are we here?" | ||
end | ||
private :bind_sasl | ||
|
||
#-- | ||
# PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET. | ||
# Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to | ||
# integrate it without introducing an external dependency. | ||
# | ||
# This authentication method is accessed by calling #bind with a :method | ||
# parameter of :gss_spnego. It requires :username and :password | ||
# attributes, just like the :simple authentication method. It performs a | ||
# GSS-SPNEGO authentication with the server, which is presumed to be a | ||
# Microsoft Active Directory. | ||
#++ | ||
def bind_gss_spnego(auth) | ||
require 'ntlm' | ||
|
||
user, psw = [auth[:username] || auth[:dn], auth[:password]] | ||
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) | ||
|
||
nego = proc { |challenge| | ||
t2_msg = NTLM::Message.parse(challenge) | ||
t3_msg = t2_msg.response({ :user => user, :password => psw }, | ||
{ :ntlmv2 => true }) | ||
t3_msg.serialize | ||
} | ||
|
||
bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO", | ||
:initial_credential => NTLM::Message::Type1.new.serialize, | ||
:challenge_response => nego) | ||
end | ||
private :bind_gss_spnego | ||
|
||
|
||
#-- | ||
# Allow the caller to specify a sort control | ||
# | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
require 'test_helper' | ||
|
||
class TestAuthAdapter < Test::Unit::TestCase | ||
def test_undefined_auth_adapter | ||
flexmock(TCPSocket).should_receive(:new).ordered.with('ldap.example.com', 379).once.and_return(nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's too bad that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for your feedback. I had no idea so I referred to |
||
conn = Net::LDAP::Connection.new(host: 'ldap.example.com', port: 379) | ||
assert_raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (foo)" do | ||
conn.bind(method: :foo) | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we leave this adapter out of this PR since it's not one of the existing adapters we currently support? After this merges, we can create a separate gem and link to it from the README.