require 'json'
require 'securerandom'
require 'debci/app'
require 'debci/test_handler'
require 'debci/html_helpers'
require 'debci/validators'
require 'omniauth'
require 'omniauth-gitlab'
module Debci
class SelfService < Debci::App
include Debci::TestHandler
include Debci::HTMLHelpers
include Debci::Validators::APTSource
use OmniAuth::Builder do
if development?
provider :developer,
fields: [:name],
uid_field: :name,
callback_path: '/user/auth/developer/callback'
end
provider :gitlab, Debci.config.salsa_client_id, Debci.config.salsa_client_secret,
redirect_url: "#{Debci.config.url_base}/user/auth/gitlab/callback",
scope: "read_user",
client_options: {
site: 'https://salsa.debian.org/api/v4/'
}
end
OmniAuth.config.on_failure = proc do |env|
OmniAuth::FailureEndpoint.new(env).redirect_to_failure
end
OmniAuth.config.logger.level = Logger::UNKNOWN
set :views, "#{File.dirname(__FILE__)}/html/templates"
configure do
set :suites, Debci.config.suite_list
set :archs, Debci.config.arch_list
end
enable :sessions
set :session_secret, Debci.config.session_secret || SecureRandom.hex(64)
before do
authenticate! unless request.path =~ %r{/user/[^/]+/jobs/?$} || request.path == '/user/login' || request.path =~ %r{/user/auth/\w*}
@user = session[:user]
end
def authenticate!
return unless session[:user].nil?
session[:original_url] = request.path
redirect('/user/login')
halt
end
get '/' do
redirect("/user/#{@user.username}")
end
get '/login' do
erb :login, locals: { csrf: request.env['rack.session']['csrf'] }
end
get '/auth/gitlab/callback' do
uid = request.env['omniauth.auth']['uid']
username = request.env['omniauth.auth']['info']['username']
login_callback(uid, username)
end
if development?
post '/auth/developer/callback' do
username = request.env['rack.request.form_hash']['name']
user = Debci::User.find_by(username: username)
uid = user&.uid || username
login_callback(uid, username)
end
end
get '/auth/failure' do
halt(403, erb("<h2>Authentication Failed</h2><h4>Reason: </h4><pre>#{params[:message]}</pre>"))
end
get '/logout' do
session[:user] = nil
redirect '/'
end
get '/:user' do
redirect("/user/#{params[:user]}/jobs") unless @user.username == params[:user]
erb :self_service
end
get '/:user/test' do
redirect("/user/#{params[:user]}/jobs") unless @user.username == params[:user]
erb :self_service_test
end
post '/:user/test/submit' do
trigger = params[:trigger] || ''
package = params[:package] || ''
suite = params[:suite] || ''
archs = (params[:arch] || []).reject(&:empty?)
pin_packages = (params[:pin_packages] || '').split(/\n+|\r+/).reject(&:empty?)
is_private = params[:is_private] == "true"
= (params[:extra_apt_sources] || []).reject(&:empty?)
begin
validate_form_submission(package, suite, archs, )
test_obj = {
'trigger' => trigger,
'package' => package,
'is_private' => is_private
}
test_obj['extra-apt-sources'] = []
.each do ||
test_obj['extra-apt-sources'].push(Debci..find().entry)
end
test_obj['pin-packages'] = []
pin_packages.each do |pin_package|
pin_package = pin_package.split(/,\s*/)
test_obj['pin-packages'].push(pin_package)
end
test_request = {
'archs' => archs,
'suite' => suite,
'tests' => [test_obj]
}
if params[:export]
content_type :json
[200, [test_request].to_json]
else
archs.each do |arch|
request_tests(test_request['tests'], suite, arch, @user)
end
@success = true
[201, erb(:self_service_test)]
end
rescue InvalidRequest => error
@error_msg = error
halt(400, erb(:self_service_test))
end
end
get '/:user/retry/:run_id' do
if @user
run_id = params[:run_id]
redirect "user/#{@user.username}/retry/#{run_id}" if params[:user] == ":user"
@original_job = get_job_to_retry(run_id)
@same_jobs = get_same_pending_jobs(@original_job).count
erb :retry
else
[403, erb(:cant_retry)]
end
end
post '/:user/retry/:run_id' do
run_id = params[:run_id]
j = get_job_to_retry(run_id)
job = Debci::Job.create!(
package: j.package,
suite: j.suite,
arch: j.arch,
requestor: j.requestor,
trigger: j.trigger,
pin_packages: j.pin_packages,
is_private: j.is_private,
extra_apt_sources: j.
)
self.enqueue(job)
201
end
get '/:user/getkey' do
erb :getkey
end
post '/:user/getkey' do
if @user
key = Debci::Key.reset!(@user)
['Content-Type'] = 'text/plain'
[201, key.key]
else
403
end
end
class InvalidRequest < RuntimeError
end
def validate_form_submission(package, suite, archs, )
raise InvalidRequest.new('Please enter a valid package name') unless valid_package_name?(package)
raise InvalidRequest.new('Please select a suite') if suite == ''
raise InvalidRequest.new('Please select an architecture') if archs.empty?
= ()
raise InvalidRequest.new("Please enter valid extra apt sources: Invalid apt sources: #{}") unless .empty?
end
post '/:user/test/upload' do
begin
raise InvalidRequest.new("Please select a JSON file to upload") if params[:tests].nil?
test_requests = JSON.parse(File.read(params[:tests][:tempfile]))
errors = validate_batch_test(test_requests)
raise InvalidRequest.new(errors.join("; ")) unless errors.empty?
request_batch_tests(test_requests, @user)
rescue JSON::ParserError => error
halt(400, "Invalid JSON: #{error}")
rescue InvalidRequest => error
@error_msg = error
halt(400, erb(:self_service_test))
else
@success = true
[201, erb(:self_service_test)]
end
end
get '/:user/jobs/?' do
arch_filter = params[:arch]
suite_filter = params[:suite]
package_filter = params[:package] || ''
trigger_filter = params[:trigger] || ''
user = Debci::User.find_by(username: params[:user])
query = {
requestor: user
}
disable_private_filter = (session[:user] != user)
is_private_filter = disable_private_filter ? false : params[:is_private]
query[:is_private] = is_private_filter unless is_private_filter.nil?
query[:arch] = arch_filter if arch_filter
query[:suite] = suite_filter if suite_filter
@history = Debci::Job.where(query)
unless package_filter.empty?
pkgs = Debci::Package.where('name LIKE :query', query: package_filter.tr('*', '%')).pluck(:id)
@history = @history.where(package_id: pkgs)
end
unless trigger_filter.empty?
@history = @history.where(
'trigger LIKE :query',
query: "%#{trigger_filter}%",
)
end
@history = @history.order('date DESC')
results = get_page_params(@history, params[:page], 20)
query_params = {}
params.each do |key, val|
next if [:user, :page].include?(key.to_sym)
case val
when Array then query_params["#{key}[]"] = val
else
query_params[key] = val
end
end
erb :self_service_history, locals: { query_params: query_params, arch_filter: arch_filter, suite_filter: suite_filter, package_filter: package_filter, trigger_filter: trigger_filter,
is_private_filter: is_private_filter, disable_private_filter: disable_private_filter, results: results }
end
get '/:user/test/publish' do
run_ids = (params[:run_ids] || [])
@jobs = Debci::Job.where(requestor: @user, run_id: run_ids, is_private: true)
erb :publish
end
post '/:user/test/publish' do
run_ids = (params[:run_ids] || []).split(',')
Debci::Job.where(requestor: @user, run_id: run_ids).update(is_private: false)
@success = true
[200, erb(:publish)]
end
def get_job_to_retry(run_id)
begin
job = Debci::Job.find(run_id)
rescue ActiveRecord::RecordNotFound
halt(400, "Job ID not known: #{run_id}")
end
halt(403, "Package #{job.package.name} is in the REJECT list and cannot be retried") if Debci.reject_list.include?(job.package, suite: job.suite, arch: job.arch)
job
end
def get_same_pending_jobs(job)
Debci::Job.pending.where(
package_id: job.package_id,
suite: job.suite,
arch: job.arch,
requestor: job.requestor,
trigger: job.trigger,
is_private: job.is_private
).select { |j| Set.new(j.pin_packages) == Set.new(job.pin_packages) }
.select { |j| Set.new(j.) == Set.new(job.) }
end
def login_callback(uid, username)
user = Debci::User.find_or_create_by!(uid: uid) do |c|
c.username = username
end
user.update(username: username) if user.username != username
session[:user] = user
original_url = session[:original_url]
session.delete(:original_url)
redirect(original_url || "/user/#{user.username}")
end
end
end