diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm
index 3d490e06..22bfd98e 100644
--- a/src/lib/Hydra/Controller/Build.pm
+++ b/src/lib/Hydra/Controller/Build.pm
@@ -504,7 +504,7 @@ sub restart : Chained('buildChain') PathPart Args(0) {
 sub cancel : Chained('buildChain') PathPart Args(0) {
     my ($self, $c) = @_;
     my $build = $c->stash->{build};
-    requireProjectOwner($c, $build->project);
+    requireCancelBuildPrivileges($c, $build->project);
     my $n = cancelBuilds($c->model('DB')->schema, $c->model('DB::Builds')->search({ id => $build->id }));
     error($c, "This build cannot be cancelled.") if $n != 1;
     $c->flash->{successMsg} = "Build has been cancelled.";
@@ -540,7 +540,7 @@ sub bump : Chained('buildChain') PathPart('bump') {
 
     my $build = $c->stash->{build};
 
-    requireProjectOwner($c, $build->project); # FIXME: require admin?
+    requireBumpPrivileges($c, $build->project);
 
     $c->model('DB')->schema->txn_do(sub {
         $build->update({globalpriority => time()});
diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm
index 77a4385f..62c655e7 100644
--- a/src/lib/Hydra/Controller/JobsetEval.pm
+++ b/src/lib/Hydra/Controller/JobsetEval.pm
@@ -179,7 +179,7 @@ sub create_jobset : Chained('evalChain') PathPart('create-jobset') Args(0) {
 
 sub cancel : Chained('evalChain') PathPart('cancel') Args(0) {
     my ($self, $c) = @_;
-    requireProjectOwner($c, $c->stash->{eval}->project);
+    requireCancelBuildPrivileges($c, $c->stash->{eval}->project);
     my $n = cancelBuilds($c->model('DB')->schema, $c->stash->{eval}->builds);
     $c->flash->{successMsg} = "$n builds have been cancelled.";
     $c->res->redirect($c->uri_for($c->controller('JobsetEval')->action_for('view'), $c->req->captures));
@@ -210,7 +210,7 @@ sub restart_failed : Chained('evalChain') PathPart('restart-failed') Args(0) {
 
 sub bump : Chained('evalChain') PathPart('bump') Args(0) {
     my ($self, $c) = @_;
-    requireProjectOwner($c, $c->stash->{eval}->project); # FIXME: require admin?
+    requireBumpPrivileges($c, $c->stash->{eval}->project); # FIXME: require admin?
     my $builds = $c->stash->{eval}->builds->search({ finished => 0 });
     my $n = $builds->count();
     $c->model('DB')->schema->txn_do(sub {
diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm
index b9019638..d363f966 100644
--- a/src/lib/Hydra/Helper/CatalystUtils.pm
+++ b/src/lib/Hydra/Helper/CatalystUtils.pm
@@ -13,6 +13,8 @@ our @EXPORT = qw(
     searchBuildsAndEvalsForJobset
     error notFound gone accessDenied
     forceLogin requireUser requireProjectOwner requireRestartPrivileges requireAdmin requirePost isAdmin isProjectOwner
+    requireBumpPrivileges
+    requireCancelBuildPrivileges
     trim
     getLatestFinishedEval getFirstEval
     paramToList
@@ -181,6 +183,48 @@ sub isProjectOwner {
          defined $c->model('DB::ProjectMembers')->find({ project => $project, userName => $c->user->username }));
 }
 
+sub hasCancelBuildRole {
+    my ($c) = @_;
+    return $c->user_exists && $c->check_user_roles('cancel-build');
+}
+
+sub mayCancelBuild {
+    my ($c, $project) = @_;
+    return
+        $c->user_exists &&
+        (isAdmin($c) ||
+         hasCancelBuildRole($c) ||
+         isProjectOwner($c, $project));
+}
+
+sub requireCancelBuildPrivileges {
+    my ($c, $project) = @_;
+    requireUser($c);
+    accessDenied($c, "Only the project members, administrators, and accounts with cancel-build privileges can perform this operation.")
+        unless mayCancelBuild($c, $project);
+}
+
+sub hasBumpJobsRole {
+    my ($c) = @_;
+    return $c->user_exists && $c->check_user_roles('bump-to-front');
+}
+
+sub mayBumpJobs {
+    my ($c, $project) = @_;
+    return
+        $c->user_exists &&
+        (isAdmin($c) ||
+         hasBumpJobsRole($c) ||
+         isProjectOwner($c, $project));
+}
+
+sub requireBumpPrivileges {
+    my ($c, $project) = @_;
+    requireUser($c);
+    accessDenied($c, "Only the project members, administrators, and accounts with bump-to-front privileges can perform this operation.")
+        unless mayBumpJobs($c, $project);
+}
+
 sub hasRestartJobsRole {
     my ($c) = @_;
     return $c->user_exists && $c->check_user_roles('restart-jobs');
diff --git a/src/root/user.tt b/src/root/user.tt
index e95ee689..d489d338 100644
--- a/src/root/user.tt
+++ b/src/root/user.tt
@@ -81,6 +81,8 @@
             [% INCLUDE roleoption role="admin" %]
             [% INCLUDE roleoption role="create-projects" %]
             [% INCLUDE roleoption role="restart-jobs" %]
+            [% INCLUDE roleoption role="bump-to-front" %]
+            [% INCLUDE roleoption role="cancel-build" %]
           </select>
         </div>
       </div>