From e5a8ee5c1754f3144ae7ac2e48b20ca5615d5775 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 27 Aug 2024 02:57:16 +0200 Subject: [PATCH] web: require permissions for /api/push --- src/lib/Hydra/Controller/API.pm | 24 ++++++++++++++++++------ t/Hydra/Controller/API/checks.t | 28 ++++++++++++++++++++++++++-- t/Hydra/Controller/Project/delete.t | 12 +++--------- t/lib/HydraTestContext.pm | 6 ++++-- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/lib/Hydra/Controller/API.pm b/src/lib/Hydra/Controller/API.pm index 3105072d..1281bd89 100644 --- a/src/lib/Hydra/Controller/API.pm +++ b/src/lib/Hydra/Controller/API.pm @@ -242,23 +242,35 @@ sub push : Chained('api') PathPart('push') Args(0) { $c->{stash}->{json}->{jobsetsTriggered} = []; my $force = exists $c->request->query_params->{force}; - my @jobsets = split /,/, ($c->request->query_params->{jobsets} // ""); - foreach my $s (@jobsets) { + my @jobsetNames = split /,/, ($c->request->query_params->{jobsets} // ""); + my @jobsets; + + foreach my $s (@jobsetNames) { my ($p, $j) = parseJobsetName($s); my $jobset = $c->model('DB::Jobsets')->find($p, $j); - next unless defined $jobset && ($force || ($jobset->project->enabled && $jobset->enabled)); - triggerJobset($self, $c, $jobset, $force); + push @jobsets, $jobset if defined $jobset; } my @repos = split /,/, ($c->request->query_params->{repos} // ""); foreach my $r (@repos) { - triggerJobset($self, $c, $_, $force) foreach $c->model('DB::Jobsets')->search( + foreach ($c->model('DB::Jobsets')->search( { 'project.enabled' => 1, 'me.enabled' => 1 }, { join => 'project', where => \ [ 'exists (select 1 from JobsetInputAlts where project = me.project and jobset = me.name and value = ?)', [ 'value', $r ] ], order_by => 'me.id DESC' - }); + })) { + push @jobsets, $_; + } + } + + foreach my $jobset (@jobsets) { + requireRestartPrivileges($c, $jobset->project); + } + + foreach my $jobset (@jobsets) { + next unless defined $jobset && ($force || ($jobset->project->enabled && $jobset->enabled)); + triggerJobset($self, $c, $jobset, $force); } $self->status_ok( diff --git a/t/Hydra/Controller/API/checks.t b/t/Hydra/Controller/API/checks.t index c2edf904..9454ea30 100644 --- a/t/Hydra/Controller/API/checks.t +++ b/t/Hydra/Controller/API/checks.t @@ -35,6 +35,17 @@ my $queuedBuilds = $ctx->makeAndEvaluateJobset( build => 0 ); +# Login and save cookie for future requests +my $req = request(POST '/login', + Referer => 'http://localhost/', + Content => { + username => 'root', + password => 'rootPassword' + } +); +is($req->code, 302, "Logging in gets a 302"); +my $cookie = $req->header("set-cookie"); + subtest "/api/queue" => sub { my $response = request(GET '/api/queue?nr=1'); ok($response->is_success, "The API enpdoint showing the queue returns 200."); @@ -102,7 +113,7 @@ subtest "/api/nrbuilds" => sub { }; subtest "/api/push" => sub { - subtest "with a specific jobset" => sub { + subtest "without authentication" => sub { my $build = $finishedBuilds->{"one_job"}; my $jobset = $build->jobset; my $projectName = $jobset->project->name; @@ -110,6 +121,18 @@ subtest "/api/push" => sub { is($jobset->forceeval, undef, "The existing jobset is not set to be forced to eval"); my $response = request(GET "/api/push?jobsets=$projectName:$jobsetName&force=1"); + is($response->code, 403, "The API enpdoint for triggering jobsets requires authentication."); + }; + + subtest "with a specific jobset" => sub { + my $build = $finishedBuilds->{"one_job"}; + my $jobset = $build->jobset; + my $projectName = $jobset->project->name; + my $jobsetName = $jobset->name; + is($jobset->forceeval, undef, "The existing jobset is not set to be forced to eval"); + + my $response = request(GET "/api/push?jobsets=$projectName:$jobsetName&force=1", + Cookie => $cookie); ok($response->is_success, "The API enpdoint for triggering jobsets returns 200."); my $data = is_json($response); @@ -128,7 +151,8 @@ subtest "/api/push" => sub { print STDERR $repo; - my $response = request(GET "/api/push?repos=$repo&force=1"); + my $response = request(GET "/api/push?repos=$repo&force=1", + Cookie => $cookie); ok($response->is_success, "The API enpdoint for triggering jobsets returns 200."); my $data = is_json($response); diff --git a/t/Hydra/Controller/Project/delete.t b/t/Hydra/Controller/Project/delete.t index f162a0f6..d5f258e5 100644 --- a/t/Hydra/Controller/Project/delete.t +++ b/t/Hydra/Controller/Project/delete.t @@ -11,20 +11,14 @@ my $ctx = test_context(); Catalyst::Test->import('Hydra'); -my $user = $ctx->db()->resultset('Users')->create({ - username => 'alice', - emailaddress => 'root@invalid.org', - password => '!' -}); -$user->setPassword('foobar'); -$user->userroles->update_or_create({ role => 'admin' }); +$ctx->db(); # Ensure DB initialization. # Login and save cookie for future requests my $req = request(POST '/login', Referer => 'http://localhost/', Content => { - username => 'alice', - password => 'foobar' + username => 'root', + password => 'rootPassword' } ); is($req->code, 302, "Logging in gets a 302"); diff --git a/t/lib/HydraTestContext.pm b/t/lib/HydraTestContext.pm index d1de2212..45fdd398 100644 --- a/t/lib/HydraTestContext.pm +++ b/t/lib/HydraTestContext.pm @@ -115,11 +115,13 @@ sub db { $self->{_db} = Hydra::Model::DB->new(); if (!(defined $setup && $setup == 0)) { - $self->{_db}->resultset('Users')->create({ + my $user = $self->{_db}->resultset('Users')->create({ username => "root", emailaddress => 'root@invalid.org', - password => '' + password => '!' }); + $user->setPassword('rootPassword'); + $user->userroles->update_or_create({ role => 'admin' }); } }