-
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 9 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,25 @@ | ||
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) | ||
@adapters[name] | ||
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,3 @@ | ||
require 'net/ldap/auth_adapters/simple' | ||
|
||
Net::LDAP::AuthAdapter.register(:anon, Net::LDAP::AuthAdapters::Simple) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require 'net/ldap/auth_adapters/simple' | ||
|
||
Net::LDAP::AuthAdapter.register(:anonymous, Net::LDAP::AuthAdapters::Simple) | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
require 'net/ldap/auth_adapter' | ||
require 'net/ldap/auth_adapters/sasl' | ||
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. Why do you need sasl here? |
||
|
||
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' | ||
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. This adapter depends on |
||
|
||
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.new(@connection). | ||
bind(:method => :sasl, :mechanism => "GSS-SPNEGO", | ||
:initial_credential => NTLM::Message::Type1.new.serialize, | ||
:challenge_response => nego) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
Net::LDAP::Adapter.register(:gss_spnego, Net::LDAP::AuthAdapters::GSS_SPNEGO) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
require 'net/ldap/auth_adapter' | ||
|
||
module Net | ||
class LDAP | ||
module AuthAdapters | ||
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 | ||
|
||
Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapters::Sasl) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
require 'net/ldap/auth_adapter' | ||
|
||
module Net | ||
class LDAP | ||
module AuthAdapters | ||
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) | ||
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. Calling a private method looks dirty... do you have any idea? 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. I agree, but this is the existing implementation, so let's change this in a separate PR. 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. I got. thanks. |
||
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 | ||
|
||
Net::LDAP::AuthAdapter.register(:simple, Net::LDAP::AuthAdapters::Simple) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -250,130 +250,12 @@ 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" | ||
require "net/ldap/auth_adapters/#{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. This works, but I prefer to have require 'net/ldap/auth_adapter'
require 'net/ldap/auth_adapter/simple' # <-- side note, can we rename the folder and namespace to be singular?
require 'net/ldap/auth_adapter/sasl'
# ...
# Move all of the registration to a single place, decouples the definition of the adapter from it's registration and use
Net::LDAP::AuthAdapter.register([:simple, :anon, :anonymous], Net::LDAP::AuthAdapters::Simple)
Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapters::Sasl) |
||
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 | ||
# | ||
|
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.
Why have both
:anon
and:anonymous
? Is this for backwards compatibility?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.
Ah, I see the previous case statement works with
:simple, :anonymous, :anon