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..ca3136c 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -173,6 +173,69 @@ module Vmpooler JSON.pretty_generate(result) end + get "#{api_prefix}/token/:token/?" do + content_type :json + + result = { 'ok' => false } + + Vmpooler::API.settings.config[:auth] ? status(401) : status(404) + + result[params[:token]] = Vmpooler::API.settings.redis.hgetall('vmpooler__token__' + params[:token]) + + 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) + end + + delete "#{api_prefix}/token/:token/?" do + content_type :json + + result = { 'ok' => false } + + Vmpooler::API.settings.config[:auth] ? status(401) : status(404) + + if Vmpooler::API.settings.config[:auth] and Vmpooler::API.settings.redis.del('vmpooler__token__' + params[:token]) + status(200) + result['ok'] = true + end + + JSON.pretty_generate(result) + end + + post "#{api_prefix}/token" do + content_type :json + + result = { 'ok' => false } + + Vmpooler::API.settings.config[:auth] ? status(401) : status(404) + + 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 @@ -229,13 +292,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 @@ -291,12 +354,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 @@ -310,15 +373,14 @@ module Vmpooler get "#{api_prefix}/vm/:hostname/?" do content_type :json - result = {} + result = { 'ok' => false } - status 404 - result['ok'] = false + status(404) 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]) @@ -352,10 +414,9 @@ module Vmpooler delete "#{api_prefix}/vm/:hostname/?" do content_type :json - result = {} + result = { 'ok' => false } - status 404 - result['ok'] = false + status(404) params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) @@ -364,7 +425,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 @@ -377,10 +438,9 @@ module Vmpooler failure = false - result = {} + result = { 'ok' => false } - status 404 - result['ok'] = false + status(404) params[:hostname] = hostname_shorten(params[:hostname], Vmpooler::API.settings.config[:config]['domain']) @@ -388,7 +448,7 @@ module Vmpooler begin jdata = JSON.parse(request.body.read) rescue - status 400 + status(400) return JSON.pretty_generate(result) end @@ -409,7 +469,7 @@ module Vmpooler end if failure - status 400 + status(400) else jdata.each do |param, arg| case param @@ -424,7 +484,7 @@ module Vmpooler end end - status 200 + status(200) result['ok'] = true end end 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