From dff2eb9580e033789ceeec0139f93b1473aa25f6 Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Thu, 16 Apr 2015 10:56:22 -0700 Subject: [PATCH 1/6] Add basic [LDAP] authentication and /token routes --- lib/vmpooler/api/helpers.rb | 29 ++++++++++++++++ lib/vmpooler/api/reroute.rb | 12 +++++++ lib/vmpooler/api/v1.rb | 66 +++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/lib/vmpooler/api/helpers.rb b/lib/vmpooler/api/helpers.rb index 5ce6d7e..61bdbcd 100644 --- a/lib/vmpooler/api/helpers.rb +++ b/lib/vmpooler/api/helpers.rb @@ -4,6 +4,35 @@ module Vmpooler module Helpers + def authenticate(auth, username_str, password_str) + case auth['provider'] + when 'ldap' + require 'rubygems' + require 'net/ldap' + + 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 + end + end + + return false + end + def mean(list) s = list.map(&:to_f).reduce(:+).to_f (s > 0 && list.length > 0) ? s / list.length.to_f : 0 diff --git a/lib/vmpooler/api/reroute.rb b/lib/vmpooler/api/reroute.rb index 72832f5..0592de4 100644 --- a/lib/vmpooler/api/reroute.rb +++ b/lib/vmpooler/api/reroute.rb @@ -11,6 +11,18 @@ module Vmpooler call env.merge("PATH_INFO" => "/api/v#{api_version}/summary") end + post '/token/?' do + call env.merge("PATH_INFO" => "/api/v#{api_version}/token") + end + + get '/token/:token/?' do + call env.merge("PATH_INFO" => "/api/v#{api_version}/token/#{params[:token]}") + end + + delete '/token/:token/?' do + call env.merge("PATH_INFO" => "/api/v#{api_version}/token/#{params[:token]}") + end + get '/vm/?' do call env.merge("PATH_INFO" => "/api/v#{api_version}/vm") end diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index ae8ed8e..cf3d361 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -173,6 +173,72 @@ module Vmpooler JSON.pretty_generate(result) end + get "#{api_prefix}/token/:token/?" do + content_type :json + + result = {} + + Vmpooler::API.settings.config[:auth] ? status(401) : status(404) + result['ok'] = false + + if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.exists('vmpooler__token__' + params[:token]) + status(200) + result['ok'] = true + + result[params[:token]] = Vmpooler::API.settings.redis.hgetall('vmpooler__token__' + params[:token]) + end + + JSON.pretty_generate(result) + end + + delete "#{api_prefix}/token/:token/?" do + content_type :json + + result = {} + + Vmpooler::API.settings.config[:auth] ? status(401) : status(404) + result['ok'] = false + + if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.exists('vmpooler__token__' + params[:token]) + status(200) + result['ok'] = true + + Vmpooler::API.settings.redis.del('vmpooler__token__' + params[:token]) + end + + JSON.pretty_generate(result) + end + + post "#{api_prefix}/token" do + content_type :json + + result = {} + + Vmpooler::API.settings.config[:auth] ? status(401) : status(404) + result['ok'] = false + + jdata = JSON.parse(request.body.read) + + if Vmpooler::API.settings.config[:auth] and jdata['username'] and jdata['password'] + if authenticate( + Vmpooler::API.settings.config[:auth], + jdata['username'].to_s, + jdata['password'].to_s + ) + status(200) + result['ok'] = true + + o = [('a'..'z'), ('0'..'9')].map(&:to_a).flatten + result['token'] = o[rand(25)] + (0...31).map { o[rand(o.length)] }.join + + Vmpooler::API.settings.redis.hset('vmpooler__token__' + result['token'], 'user', jdata['username'].to_s) + Vmpooler::API.settings.redis.hset('vmpooler__token__' + result['token'], 'timestamp', Time.now) + end + end + + JSON.pretty_generate(result) + end + get "#{api_prefix}/vm/?" do content_type :json From 9897a9add15b2d8e01f752944a104ef2110485ea Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Thu, 16 Apr 2015 10:57:57 -0700 Subject: [PATCH 2/6] Syntax fixup for status() method --- lib/vmpooler/api/v1.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index cf3d361..c17cc2f 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -295,13 +295,13 @@ module Vmpooler else result[key]['ok'] = false ## - status 503 + status(503) result['ok'] = false end end end else - status 503 + status(503) result['ok'] = false end @@ -357,12 +357,12 @@ module Vmpooler else result[template]['ok'] = false ## - status 503 + status(503) result['ok'] = false end end else - status 503 + status(503) result['ok'] = false end @@ -378,13 +378,13 @@ module Vmpooler result = {} - status 404 + status(404) result['ok'] = false params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) if Vmpooler::API.settings.redis.exists('vmpooler__vm__' + params[:hostname]) - status 200 + status(200) result['ok'] = true rdata = Vmpooler::API.settings.redis.hgetall('vmpooler__vm__' + params[:hostname]) @@ -420,7 +420,7 @@ module Vmpooler result = {} - status 404 + status(404) result['ok'] = false params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) @@ -430,7 +430,7 @@ module Vmpooler Vmpooler::API.settings.redis.srem('vmpooler__running__' + pool['name'], params[:hostname]) Vmpooler::API.settings.redis.sadd('vmpooler__completed__' + pool['name'], params[:hostname]) - status 200 + status(200) result['ok'] = true end end @@ -445,7 +445,7 @@ module Vmpooler result = {} - status 404 + status(404) result['ok'] = false params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) @@ -454,7 +454,7 @@ module Vmpooler begin jdata = JSON.parse(request.body.read) rescue - status 400 + status(400) return JSON.pretty_generate(result) end @@ -475,7 +475,7 @@ module Vmpooler end if failure - status 400 + status(400) else jdata.each do |param, arg| case param @@ -490,7 +490,7 @@ module Vmpooler end end - status 200 + status(200) result['ok'] = true end end From 69ea4972e685d49931c165385841608d8aaf4e6a Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Fri, 17 Apr 2015 09:16:37 -0700 Subject: [PATCH 3/6] Syntax clean-up --- lib/vmpooler/api/v1.rb | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index c17cc2f..5ba5b3e 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -176,10 +176,9 @@ module Vmpooler get "#{api_prefix}/token/:token/?" do content_type :json - result = {} + result = { 'ok' => false } Vmpooler::API.settings.config[:auth] ? status(401) : status(404) - result['ok'] = false if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.exists('vmpooler__token__' + params[:token]) status(200) @@ -194,10 +193,9 @@ module Vmpooler delete "#{api_prefix}/token/:token/?" do content_type :json - result = {} + result = { 'ok' => false } Vmpooler::API.settings.config[:auth] ? status(401) : status(404) - result['ok'] = false if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.exists('vmpooler__token__' + params[:token]) status(200) @@ -212,10 +210,9 @@ module Vmpooler post "#{api_prefix}/token" do content_type :json - result = {} + result = { 'ok' => false } Vmpooler::API.settings.config[:auth] ? status(401) : status(404) - result['ok'] = false jdata = JSON.parse(request.body.read) @@ -376,10 +373,9 @@ module Vmpooler get "#{api_prefix}/vm/:hostname/?" do content_type :json - result = {} + result = { 'ok' => false } status(404) - result['ok'] = false params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) @@ -418,10 +414,9 @@ module Vmpooler delete "#{api_prefix}/vm/:hostname/?" do content_type :json - result = {} + result = { 'ok' => false } status(404) - result['ok'] = false params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) @@ -443,10 +438,9 @@ module Vmpooler failure = false - result = {} + result = { 'ok' => false } status(404) - result['ok'] = false params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) From 76ca9793406d46bbd22a4fc34dbe49d7939cdb7d Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 17 Apr 2015 14:53:29 -0700 Subject: [PATCH 4/6] (QENG-2156) Add spec tests for tokens Add basic spec tests for new routes. --- spec/vmpooler/api/v1_spec.rb | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 spec/vmpooler/api/v1_spec.rb diff --git a/spec/vmpooler/api/v1_spec.rb b/spec/vmpooler/api/v1_spec.rb new file mode 100644 index 0000000..d2e6403 --- /dev/null +++ b/spec/vmpooler/api/v1_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' +require 'rack/test' + +describe Vmpooler::API::V1 do + include Rack::Test::Methods + + def app() + # because of how Vmpooler::API.settings are used + # we need to test the whole thing... + Vmpooler::API + end + + describe 'tokens' do + let(:redis) { double('redis') } + let(:config) { + { + config: {'site_name' => 'test pooler'}, + auth: {exists: 1} + } + } + let(:prefix) { '/api/v1' } + + before do + $config = config + + app.settings.set :config, config + app.settings.set :redis, redis + app.settings.set :environment, :test + end + + describe 'GET /token/:token' do + + context 'valid tokens' do + before do + allow(redis).to receive(:exists).and_return 1 + allow(redis).to receive(:hgetall).and_return 'atoken' + end + + it 'sets key to passed value (:token)' do + get "#{prefix}/token/this" + + expect(last_response).to be_ok + expect(last_response.body).to eq(JSON.pretty_generate({'ok' => true, 'this' => 'atoken'})) + expect(last_response.header['Content-Type']).to eq('application/json') + + get "#{prefix}/token/that" + + expect(last_response).to be_ok + expect(last_response.body).to eq(JSON.pretty_generate({'ok' => true, 'that' => 'atoken'})) + expect(last_response.header['Content-Type']).to eq('application/json') + end + end + + end + + describe 'DELETE /token/:token' do + + context 'valid tokens' do + before do + allow(redis).to receive(:exists).with(String).and_return 1 + allow(redis).to receive(:del) + end + + it 'deletes the key' do + expect(redis).to receive(:del) + + delete "#{prefix}/token/this" + + expect(last_response).to be_ok + expect(last_response.header['Content-Type']).to eq('application/json') + expect(last_response.body).to eq(JSON.pretty_generate({'ok' => true})) + end + end + end + + end + +end \ No newline at end of file From fb891c9265ac326301d05f765e89f3153af66779 Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Mon, 20 Apr 2015 10:35:29 -0700 Subject: [PATCH 5/6] Restructure 'GET /token/:token' --- lib/vmpooler/api/v1.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index 5ba5b3e..1fbdb43 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -180,11 +180,11 @@ module Vmpooler Vmpooler::API.settings.config[:auth] ? status(401) : status(404) - if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.exists('vmpooler__token__' + params[:token]) + result[params[:token]] = Vmpooler::API.settings.redis.hgetall('vmpooler__token__' + params[:token]) + + if Vmpooler::API.settings.config[:auth] and not result[params[:token]].nil? status(200) result['ok'] = true - - result[params[:token]] = Vmpooler::API.settings.redis.hgetall('vmpooler__token__' + params[:token]) end JSON.pretty_generate(result) From 662584dd7fccfae1e42f18133ff549b66f5ae1f8 Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Mon, 20 Apr 2015 10:37:29 -0700 Subject: [PATCH 6/6] Restructure 'DELETE /token/:token' --- lib/vmpooler/api/v1.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index 1fbdb43..ca3136c 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -182,9 +182,11 @@ module Vmpooler result[params[:token]] = Vmpooler::API.settings.redis.hgetall('vmpooler__token__' + params[:token]) - if Vmpooler::API.settings.config[:auth] and not result[params[:token]].nil? + if Vmpooler::API.settings.config[:auth] and result[params[:token]]['timestamp'] status(200) result['ok'] = true + else + result.delete(params[:token]) end JSON.pretty_generate(result) @@ -197,11 +199,9 @@ module Vmpooler Vmpooler::API.settings.config[:auth] ? status(401) : status(404) - if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.exists('vmpooler__token__' + params[:token]) + if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.del('vmpooler__token__' + params[:token]) status(200) result['ok'] = true - - Vmpooler::API.settings.redis.del('vmpooler__token__' + params[:token]) end JSON.pretty_generate(result)