Merge pull request #267 from mattkirby/multiple_ldap_search

(POOLER-113) Add support for multiple LDAP search bases
This commit is contained in:
mchllweeks 2018-06-27 15:26:49 -07:00 committed by GitHub
commit 8be578493a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 239 additions and 84 deletions

View file

@ -4,6 +4,7 @@ module Vmpooler
require 'date'
require 'json'
require 'open-uri'
require 'net/ldap'
require 'rbvmomi'
require 'redis'
require 'sinatra/base'

View file

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

View file

@ -1,16 +1,6 @@
require 'spec_helper'
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
include Rack::Test::Methods

View file

@ -1,16 +1,6 @@
require 'spec_helper'
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)
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
end

View file

@ -1,16 +1,6 @@
require 'spec_helper'
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
include Rack::Test::Methods
@ -39,7 +29,15 @@ describe Vmpooler::API::V1 do
end
context '(auth configured)' do
let(:config) { { auth: true } }
let(:config) {
{
auth: {
'provider' => 'dummy'
}
}
}
let(:username_str) { 'admin' }
let(:password_str) { 's3cr3t' }
it 'returns a 401 if not authed' do
get "#{prefix}/token"
@ -69,7 +67,13 @@ describe Vmpooler::API::V1 do
end
context '(auth configured)' do
let(:config) { { auth: true } }
let(:config) {
{
auth: {
'provider' => 'dummy'
}
}
}
it 'returns a 401 if not authed' do
post "#{prefix}/token"
@ -146,7 +150,13 @@ describe Vmpooler::API::V1 do
end
context '(auth configured)' do
let(:config) { { auth: true } }
let(:config) {
{
auth: {
'provider' => 'dummy'
}
}
}
it 'returns a 401 if not authed' do
delete "#{prefix}/token/this"

View file

@ -1,16 +1,6 @@
require 'spec_helper'
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)
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
end

View file

@ -1,16 +1,6 @@
require 'spec_helper'
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
include Rack::Test::Methods

View file

@ -1,16 +1,6 @@
require 'spec_helper'
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
include Rack::Test::Methods

View file

@ -1,4 +1,5 @@
require 'spec_helper'
require 'net/ldap'
# A class for testing purposes that includes the Helpers.
# this is impersonating V1's `helpers do include Helpers end`
@ -238,4 +239,172 @@ describe Vmpooler::API::Helpers do
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