(POOLER-113) Add support for multiple LDAP search bases

This commit updates vmpooler to support setting an array of search bases
in addition to a single base provided as a string. Without this change
it is not possible to specify multiple search bases to use with the LDAP
authentication provider. Additionally, test coverage is added to
the authentication helper method.
This commit is contained in:
kirby@puppetlabs.com 2018-06-25 13:56:55 -07:00
parent de813943e9
commit 9fa27af8e5
7 changed files with 218 additions and 71 deletions

View file

@ -54,36 +54,64 @@ module Vmpooler
return false return false
end end
def authenticate_ldap(port, host, user_object, base, username_str, password_str)
require 'rubygems'
require 'net/ldap'
ldap = Net::LDAP.new(
:host => host,
:port => port,
:encryption => {
:method => :start_tls,
:tls_options => { :ssl_version => 'TLSv1' }
},
:base => base,
:auth => {
:method => :simple,
:username => "#{user_object}=#{username_str},#{base}",
:password => password_str
}
)
return true if ldap.bind
return false
end
def authenticate(auth, username_str, password_str) def authenticate(auth, username_str, password_str)
case auth['provider'] case auth['provider']
when 'dummy' when 'dummy'
return (username_str != password_str) return (username_str != password_str)
when 'ldap' when 'ldap'
require 'rubygems' ldap_base = auth[:ldap]['base']
require 'net/ldap' ldap_port = auth[:ldap]['port'] || 389
ldap = Net::LDAP.new( if ldap_base.is_a? Array
:host => auth[:ldap]['host'], ldap_base.each do |search_base|
:port => auth[:ldap]['port'] || 389, result = authenticate_ldap(
:encryption => { ldap_port,
:method => :start_tls, auth[:ldap]['host'],
:tls_options => { :ssl_version => 'TLSv1' } auth[:ldap]['user_object'],
}, search_base,
:base => auth[:ldap]['base'], username_str,
:auth => { password_str,
:method => :simple,
:username => "#{auth[:ldap]['user_object']}=#{username_str},#{auth[:ldap]['base']}",
:password => password_str
}
) )
return true if result == true
if ldap.bind
return true
end end
else
result = authenticate_ldap(
ldap_port,
auth[:ldap]['host'],
auth[:ldap]['user_object'],
ldap_base,
username_str,
password_str,
)
return result
end end
return false return false
end end
end
def export_tags(backend, hostname, tags) def export_tags(backend, hostname, tags)
tags.each_pair do |tag, value| tags.each_pair do |tag, value|

View file

@ -1,16 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'rack/test' require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do describe Vmpooler::API::V1 do
include Rack::Test::Methods include Rack::Test::Methods

View file

@ -1,16 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'rack/test' require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
def has_set_tag?(vm, tag, value) def has_set_tag?(vm, tag, value)
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}") value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
end end

View file

@ -1,16 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'rack/test' require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
def has_set_tag?(vm, tag, value) def has_set_tag?(vm, tag, value)
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}") value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
end end

View file

@ -1,16 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'rack/test' require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do describe Vmpooler::API::V1 do
include Rack::Test::Methods include Rack::Test::Methods

View file

@ -1,16 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'rack/test' require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do describe Vmpooler::API::V1 do
include Rack::Test::Methods include Rack::Test::Methods

View file

@ -1,4 +1,5 @@
require 'spec_helper' require 'spec_helper'
require 'net/ldap'
# A class for testing purposes that includes the Helpers. # A class for testing purposes that includes the Helpers.
# this is impersonating V1's `helpers do include Helpers end` # this is impersonating V1's `helpers do include Helpers end`
@ -238,4 +239,172 @@ describe Vmpooler::API::Helpers do
end end
end end
describe '#authenticate' do
let(:username_str) { 'admin' }
let(:password_str) { 's3cr3t' }
context 'with dummy provider' do
let(:auth) {
{
'provider': 'dummy'
}
}
it 'should return true' do
expect(subject).to receive(:authenticate).with(auth, username_str, password_str).and_return(true)
subject.authenticate(auth, username_str, password_str)
end
end
context 'with ldap provider' do
let(:host) { 'ldap.example.com' }
let(:base) { 'ou=user,dc=test,dc=com' }
let(:user_object) { 'uid' }
let(:auth) {
{
'provider' => 'ldap',
ldap: {
'host' => host,
'base' => base,
'user_object' => user_object
}
}
}
let(:default_port) { 389 }
it 'should attempt ldap authentication' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base, username_str, password_str)
subject.authenticate(auth, username_str, password_str)
end
it 'should return true when authentication is successful' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base, username_str, password_str).and_return(true)
expect(subject.authenticate(auth, username_str, password_str)).to be true
end
it 'should return false when authentication fails' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base, username_str, password_str).and_return(false)
expect(subject.authenticate(auth, username_str, password_str)).to be false
end
context 'with an alternate port' do
let(:alternate_port) { 636 }
before(:each) do
auth[:ldap]['port'] = alternate_port
end
it 'should specify the alternate port when authenticating' do
expect(subject).to receive(:authenticate_ldap).with(alternate_port, host, user_object, base, username_str, password_str)
subject.authenticate(auth, username_str, password_str)
end
end
context 'with multiple search bases' do
let(:base) {
[
'ou=user,dc=test,dc=com',
'ou=service,ou=user,dc=test,dc=com'
]
}
before(:each) do
auth[:ldap]['base'] = base
end
it 'should attempt to bind with each base' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str)
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str)
subject.authenticate(auth, username_str, password_str)
end
it 'should not search the second base when the first binds' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(true)
expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str)
subject.authenticate(auth, username_str, password_str)
end
it 'should search the second base when the first bind fails' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(false)
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str)
subject.authenticate(auth, username_str, password_str)
end
it 'should return true when any bind succeeds' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(false)
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str).and_return(true)
expect(subject.authenticate(auth, username_str, password_str)).to be true
end
it 'should return false when all bind attempts fail' do
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(false)
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str).and_return(false)
expect(subject.authenticate(auth, username_str, password_str)).to be false
end
end
end
context 'with unknown provider' do
let(:auth) {
{
'provider': 'mystery'
}
}
it 'should return false' do
expect(subject).to receive(:authenticate).with(auth, username_str, password_str).and_return(false)
subject.authenticate(auth, username_str, password_str)
end
end
end
describe '#authenticate_ldap' do
let(:port) { 389 }
let(:host) { 'ldap.example.com' }
let(:user_object) { 'uid' }
let(:base) { 'ou=users,dc=example,dc=com' }
let(:username_str) { 'admin' }
let(:password_str) { 's3cr3t' }
let(:ldap) { double('ldap') }
it 'should create a new ldap connection' do
allow(ldap).to receive(:bind)
expect(Net::LDAP).to receive(:new).with(
:host => host,
:port => port,
:encryption => {
:method => :start_tls,
:tls_options => { :ssl_version => 'TLSv1' }
},
:base => base,
:auth => {
:method => :simple,
:username => "#{user_object}=#{username_str},#{base}",
:password => password_str
}
).and_return(ldap)
subject.authenticate_ldap(port, host, user_object, base, username_str, password_str)
end
it 'should return true when a bind is successful' do
expect(Net::LDAP).to receive(:new).and_return(ldap)
expect(ldap).to receive(:bind).and_return(true)
expect(subject.authenticate_ldap(port, host, user_object, base, username_str, password_str)).to be true
end
it 'should return false when a bind fails' do
expect(Net::LDAP).to receive(:new).and_return(ldap)
expect(ldap).to receive(:bind).and_return(false)
expect(subject.authenticate_ldap(port, host, user_object, base, username_str, password_str)).to be false
end
end
end end