diff --git a/lib/vmpooler/api/helpers.rb b/lib/vmpooler/api/helpers.rb index f76cbce..7575b6d 100644 --- a/lib/vmpooler/api/helpers.rb +++ b/lib/vmpooler/api/helpers.rb @@ -54,35 +54,63 @@ module Vmpooler return false 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) 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) diff --git a/spec/integration/api/v1/config_spec.rb b/spec/integration/api/v1/config_spec.rb index abcfdb1..e5136d7 100644 --- a/spec/integration/api/v1/config_spec.rb +++ b/spec/integration/api/v1/config_spec.rb @@ -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 diff --git a/spec/integration/api/v1/status_spec.rb b/spec/integration/api/v1/status_spec.rb index 2f2f1ba..3ec6cfc 100644 --- a/spec/integration/api/v1/status_spec.rb +++ b/spec/integration/api/v1/status_spec.rb @@ -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 diff --git a/spec/integration/api/v1/vm_hostname_spec.rb b/spec/integration/api/v1/vm_hostname_spec.rb index f5dce5b..f8ade07 100644 --- a/spec/integration/api/v1/vm_hostname_spec.rb +++ b/spec/integration/api/v1/vm_hostname_spec.rb @@ -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 diff --git a/spec/integration/api/v1/vm_spec.rb b/spec/integration/api/v1/vm_spec.rb index 0c14561..387ecd0 100644 --- a/spec/integration/api/v1/vm_spec.rb +++ b/spec/integration/api/v1/vm_spec.rb @@ -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 diff --git a/spec/integration/api/v1/vm_template_spec.rb b/spec/integration/api/v1/vm_template_spec.rb index aaabf4a..72fef36 100644 --- a/spec/integration/api/v1/vm_template_spec.rb +++ b/spec/integration/api/v1/vm_template_spec.rb @@ -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 diff --git a/spec/unit/api/helpers_spec.rb b/spec/unit/api/helpers_spec.rb index 7e75045..4b36542 100644 --- a/spec/unit/api/helpers_spec.rb +++ b/spec/unit/api/helpers_spec.rb @@ -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