From 979572b060f3ea51c974d7d24c41c6406f230a36 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Tue, 27 Aug 2024 17:00:00 +0200 Subject: [PATCH] Create eval-jobset role and guard /api/push route --- doc/manual/src/configuration.md | 3 ++- src/lib/Hydra/Config.pm | 2 ++ src/lib/Hydra/Controller/API.pm | 7 ++++++- src/lib/Hydra/Helper/CatalystUtils.pm | 22 ++++++++++++++++++++++ src/root/user.tt | 1 + t/Hydra/Config/ldap_role_map.t | 2 ++ t/Hydra/Controller/API/checks.t | 23 +++++++++++++++++++++-- t/Hydra/Controller/User/ldap-legacy.t | 3 ++- t/Hydra/Controller/User/ldap.t | 5 ++++- 9 files changed, 62 insertions(+), 6 deletions(-) diff --git a/doc/manual/src/configuration.md b/doc/manual/src/configuration.md index 4954040c6..d370312a7 100644 --- a/doc/manual/src/configuration.md +++ b/doc/manual/src/configuration.md @@ -208,7 +208,8 @@ Example configuration: # Make all users in the hydra_admin group Hydra admins hydra_admin = admin - # Allow all users in the dev group to restart jobs and cancel builds + # Allow all users in the dev group to eval jobsets, restart jobs and cancel builds + dev = eval-jobset dev = restart-jobs dev = cancel-build diff --git a/src/lib/Hydra/Config.pm b/src/lib/Hydra/Config.pm index af686fca7..6aae5a5e5 100644 --- a/src/lib/Hydra/Config.pm +++ b/src/lib/Hydra/Config.pm @@ -95,6 +95,7 @@ sub get_legacy_ldap_config { "hydra_bump-to-front" => [ "bump-to-front" ], "hydra_cancel-build" => [ "cancel-build" ], "hydra_create-projects" => [ "create-projects" ], + "hydra_eval-jobset" => [ "eval-jobset" ], "hydra_restart-jobs" => [ "restart-jobs" ], }, }; @@ -159,6 +160,7 @@ sub valid_roles { "bump-to-front", "cancel-build", "create-projects", + "eval-jobset", "restart-jobs", ]; } diff --git a/src/lib/Hydra/Controller/API.pm b/src/lib/Hydra/Controller/API.pm index cd0055517..9f8b7cba2 100644 --- a/src/lib/Hydra/Controller/API.pm +++ b/src/lib/Hydra/Controller/API.pm @@ -248,19 +248,24 @@ sub push : Chained('api') PathPart('push') Args(0) { foreach my $s (@jobsets) { my ($p, $j) = parseJobsetName($s); my $jobset = $c->model('DB::Jobsets')->find($p, $j); + requireEvalJobsetPrivileges($c, $jobset->project); next unless defined $jobset && ($force || ($jobset->project->enabled && $jobset->enabled)); triggerJobset($self, $c, $jobset, $force); } my @repos = split /,/, ($c->request->query_params->{repos} // ""); foreach my $r (@repos) { - triggerJobset($self, $c, $_, $force) foreach $c->model('DB::Jobsets')->search( + my @jobsets = $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' }); + foreach my $jobset (@jobsets) { + requireEvalJobsetPrivileges($c, $jobset->project); + triggerJobset($self, $c, $jobset, $force) + } } $self->status_ok( diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm index 15d50b1a1..3e568ce39 100644 --- a/src/lib/Hydra/Helper/CatalystUtils.pm +++ b/src/lib/Hydra/Helper/CatalystUtils.pm @@ -15,6 +15,7 @@ our @EXPORT = qw( forceLogin requireUser requireProjectOwner requireRestartPrivileges requireAdmin requirePost isAdmin isProjectOwner requireBumpPrivileges requireCancelBuildPrivileges + requireEvalJobsetPrivileges trim getLatestFinishedEval getFirstEval paramToList @@ -186,6 +187,27 @@ sub isProjectOwner { defined $c->model('DB::ProjectMembers')->find({ project => $project, userName => $c->user->username })); } +sub hasEvalJobsetRole { + my ($c) = @_; + return $c->user_exists && $c->check_user_roles("eval-jobset"); +} + +sub mayEvalJobset { + my ($c, $project) = $_; + return + $c->user_exists && + (isAdmin($c) || + hasEvalJobsetRole($c) || + isProjectOwner($c, $project)); +} + +sub requireEvalJobsetPrivileges { + my ($c, $project) = @_; + requireUser($c); + accessDenied($c, "Only the project members, administrators, and accounts with eval-jobset privileges can perform this operation.") + unless mayEvalJobset($c, $project); +} + sub hasCancelBuildRole { my ($c) = @_; return $c->user_exists && $c->check_user_roles('cancel-build'); diff --git a/src/root/user.tt b/src/root/user.tt index 76f858504..04eb6e683 100644 --- a/src/root/user.tt +++ b/src/root/user.tt @@ -91,6 +91,7 @@ [% INCLUDE roleoption mutable=mutable role="restart-jobs" %] [% INCLUDE roleoption mutable=mutable role="bump-to-front" %] [% INCLUDE roleoption mutable=mutable role="cancel-build" %] + [% INCLUDE roleoption mutable=mutable role="eval-jobset" %]

diff --git a/t/Hydra/Config/ldap_role_map.t b/t/Hydra/Config/ldap_role_map.t index cb1adf46d..9287c7822 100644 --- a/t/Hydra/Config/ldap_role_map.t +++ b/t/Hydra/Config/ldap_role_map.t @@ -57,6 +57,7 @@ subtest "getLDAPConfig" => sub { "hydra_cancel-build" => [ "cancel-build" ], "hydra_create-projects" => [ "create-projects" ], "hydra_restart-jobs" => [ "restart-jobs" ], + "hydra_eval-jobset" => [ "eval-jobset" ], } }, "The empty file and set env var make legacy mode active." @@ -177,6 +178,7 @@ subtest "get_legacy_ldap_config" => sub { "hydra_cancel-build" => [ "cancel-build" ], "hydra_create-projects" => [ "create-projects" ], "hydra_restart-jobs" => [ "restart-jobs" ], + "hydra_eval-jobset" => [ "eval-jobset" ], } }, "Legacy, default role maps are applied." diff --git a/t/Hydra/Controller/API/checks.t b/t/Hydra/Controller/API/checks.t index f0f51f1cf..783bec797 100644 --- a/t/Hydra/Controller/API/checks.t +++ b/t/Hydra/Controller/API/checks.t @@ -25,6 +25,25 @@ my $ctx = test_context(); Catalyst::Test->import('Hydra'); +my $db = Hydra::Model::DB->new; +hydra_setup($db); + +# Create a user to log in to +my $user = $db->resultset('Users')->create({ username => 'alice', emailaddress => 'alice@example.com', password => '!' }); +$user->setPassword('foobar'); +$user->userroles->update_or_create({ role => 'admin' }); + +# Login and save cookie for future requests +my $req = request(POST '/login', + Referer => 'http://localhost/', + Content => { + username => 'alice', + password => 'foobar' + } +); +is($req->code, 302); +my $cookie = $req->header("set-cookie"); + my $finishedBuilds = $ctx->makeAndEvaluateJobset( expression => "one-job.nix", build => 1 @@ -109,7 +128,7 @@ subtest "/api/push" => sub { my $jobsetName = $jobset->name; is($jobset->forceeval, undef, "The existing jobset is not set to be forced to eval"); - my $response = request(POST "/api/push?jobsets=$projectName:$jobsetName&force=1"); + my $response = request(POST "/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 +147,7 @@ subtest "/api/push" => sub { print STDERR $repo; - my $response = request(POST "/api/push?repos=$repo&force=1"); + my $response = request(POST "/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/User/ldap-legacy.t b/t/Hydra/Controller/User/ldap-legacy.t index 9cb197c0e..19f0c6bf8 100644 --- a/t/Hydra/Controller/User/ldap-legacy.t +++ b/t/Hydra/Controller/User/ldap-legacy.t @@ -24,6 +24,7 @@ $ldap->add_group("hydra_create-projects", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_restart-jobs", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_bump-to-front", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_cancel-build", $users->{"many_roles"}->{"username"}); +$ldap->add_group("hydra_eval-jobset", $users->{"many_roles"}->{"username"}); my $hydra_ldap_config = "${\$ldap->tmpdir()}/hydra_ldap_config.yaml"; LDAPContext::write_file($hydra_ldap_config, < sub { unrelated => [], admin => ["admin"], not_admin => [], - many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build" ], + many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build", "eval-jobset" ], ); for my $username (keys %users_to_roles) { my $user = $users->{$username}; diff --git a/t/Hydra/Controller/User/ldap.t b/t/Hydra/Controller/User/ldap.t index 175b66aab..050fde233 100644 --- a/t/Hydra/Controller/User/ldap.t +++ b/t/Hydra/Controller/User/ldap.t @@ -24,6 +24,7 @@ $ldap->add_group("hydra_create-projects", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_restart-jobs", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_bump-to-front", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_cancel-build", $users->{"many_roles"}->{"username"}); +$ldap->add_group("hydra_eval-jobset", $users->{"many_roles"}->{"username"}); my $ctx = test_context( @@ -76,10 +77,12 @@ my $ctx = test_context( hydra_cancel-build = cancel-build hydra_bump-to-front = bump-to-front hydra_restart-jobs = restart-jobs + hydra_eval-jobset = eval-jobset hydra_one_group_many_roles = create-projects hydra_one_group_many_roles = cancel-build hydra_one_group_many_roles = bump-to-front + hydra_one_group_many-roles = eval-jobset CFG @@ -92,7 +95,7 @@ subtest "Valid login attempts" => sub { unrelated => [], admin => ["admin"], not_admin => [], - many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build" ], + many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build", "eval-jobset" ], many_roles_one_group => [ "create-projects", "bump-to-front", "cancel-build" ], ); for my $username (keys %users_to_roles) {