* Role-based access control. Only admins can create projects. Only

admins or project owners can edit or delete a project.
This commit is contained in:
Eelco Dolstra 2008-11-26 23:25:24 +00:00
parent 161e836d0c
commit 2748cfac07
22 changed files with 163 additions and 47 deletions

View file

@ -13,6 +13,8 @@ name Hydra
<store> <store>
class DBIx::Class class DBIx::Class
user_class DB::Users user_class DB::Users
role_relation userroles
role_field role
</store> </store>
</dbic> </dbic>
</realms> </realms>

View file

@ -11,6 +11,7 @@ use Catalyst qw/-Debug
Static::Simple Static::Simple
StackTrace StackTrace
Authentication Authentication
Authorization::Roles
Session Session
Session::Store::FastMmap Session::Store::FastMmap
Session::State::Cookie Session::State::Cookie

View file

@ -74,7 +74,7 @@ sub login :Local {
? $c->flash->{afterLogin} ? $c->flash->{afterLogin}
: $c->uri_for('/')); : $c->uri_for('/'));
return; return;
} }
$c->stash->{errorMsg} = "Bad username or password."; $c->stash->{errorMsg} = "Bad username or password.";
} }
@ -98,7 +98,6 @@ sub requireLogin {
sub queue :Local { sub queue :Local {
my ($self, $c) = @_; my ($self, $c) = @_;
return requireLogin($c) if !$c->user_exists;
$c->stash->{template} = 'queue.tt'; $c->stash->{template} = 'queue.tt';
$c->stash->{queue} = [$c->model('DB::Builds')->search( $c->stash->{queue} = [$c->model('DB::Builds')->search(
{finished => 0}, {join => 'schedulingInfo', order_by => ["priority DESC", "timestamp"]})]; {finished => 0}, {join => 'schedulingInfo', order_by => ["priority DESC", "timestamp"]})];
@ -118,6 +117,8 @@ sub updateProject {
$project->displayname($displayName); $project->displayname($displayName);
$project->description(trim $c->request->params->{description}); $project->description(trim $c->request->params->{description});
$project->enabled(trim($c->request->params->{enabled}) eq "1" ? 1 : 0); $project->enabled(trim($c->request->params->{enabled}) eq "1" ? 1 : 0);
$project->owner(trim($c->request->params->{owner}))
if $c->check_user_roles('admin');
$project->update; $project->update;
@ -236,21 +237,35 @@ sub project :Local {
$subcommand = "" unless defined $subcommand; $subcommand = "" unless defined $subcommand;
if ($subcommand eq "edit") { if ($subcommand ne "") {
$c->stash->{edit} = 1;
} elsif ($subcommand eq "submit" && $isPosted) { return requireLogin($c) if !$c->user_exists;
$c->model('DB')->schema->txn_do(sub {
updateProject($c, $project); if (!$c->check_user_roles('admin') && $c->user->username ne $project->owner) {
}); return error($c, "Only the project owner or the administrator can perform this operation.");
return $c->res->redirect($c->uri_for("/project", trim $c->request->params->{name})); }
} elsif ($subcommand eq "delete" && $isPosted) {
$c->model('DB')->schema->txn_do(sub { if ($subcommand eq "edit") {
$project->delete; $c->stash->{edit} = 1;
}); }
return $c->res->redirect($c->uri_for("/"));
} elsif ($subcommand eq "") { elsif ($subcommand eq "submit" && $isPosted) {
} else { $c->model('DB')->schema->txn_do(sub {
return error($c, "Unknown subcommand $subcommand."); updateProject($c, $project);
});
return $c->res->redirect($c->uri_for("/project", trim $c->request->params->{name}));
}
elsif ($subcommand eq "delete" && $isPosted) {
$c->model('DB')->schema->txn_do(sub {
$project->delete;
});
return $c->res->redirect($c->uri_for("/"));
}
else {
return error($c, "Unknown subcommand $subcommand.");
}
} }
$c->stash->{curProject} = $project; $c->stash->{curProject} = $project;
@ -283,6 +298,12 @@ sub project :Local {
sub createproject :Local { sub createproject :Local {
my ($self, $c, $subcommand) = @_; my ($self, $c, $subcommand) = @_;
return requireLogin($c) if !$c->user_exists;
if (!$c->check_user_roles('admin')) {
return error($c, "Only administrators can create projects.");
}
if (defined $subcommand && $subcommand eq "submit") { if (defined $subcommand && $subcommand eq "submit") {
eval { eval {
my $projectName = $c->request->params->{name}; my $projectName = $c->request->params->{name};

View file

@ -8,8 +8,8 @@ use base 'DBIx::Class::Schema';
__PACKAGE__->load_classes; __PACKAGE__->load_classes;
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jK/9VMZBot2RJwtlHA6QIg # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OcrPIHyQBUa+kF79Ltf95g
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -36,8 +36,8 @@ __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
__PACKAGE__->belongs_to("dependency", "Hydra::Schema::Builds", { id => "dependency" }); __PACKAGE__->belongs_to("dependency", "Hydra::Schema::Builds", { id => "dependency" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:LRcAsbLWbetVw+DCDnv/9w # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BEl4PIMuykTwqyl7La0pKQ
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -33,8 +33,8 @@ __PACKAGE__->set_primary_key("build", "productnr");
__PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" }); __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Pu6gWxltfVJJ+9DBiC9bYg # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:uEkpbb6hgGe47sDE7KtLDQ
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -29,8 +29,8 @@ __PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" }); __PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:X5GXZRLAaCMl8OKBGjtztw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CfJnTtjRXGV5dD/MWbrJxA
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -70,8 +70,8 @@ __PACKAGE__->has_many(
); );
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:h32zqOEGcpXQy7pshiWVMA # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:AFGyXbj7hMxpQxjzgpvrCw
__PACKAGE__->has_many(dependents => 'Hydra::Schema::Buildinputs', 'dependency'); __PACKAGE__->has_many(dependents => 'Hydra::Schema::Buildinputs', 'dependency');

View file

@ -23,8 +23,8 @@ __PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" }); __PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:egegc7kFKTt9cEGuomi0cQ # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:O6brsCdVF4TfvtmI9R+TOA
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -35,8 +35,8 @@ __PACKAGE__->set_primary_key("id", "stepnr");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" }); __PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:kFD90OFRM1aqVVCBCh/geA # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:klPmbTcngdzKN+Dzhj8gvw
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -22,8 +22,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("srcpath", "sha256hash"); __PACKAGE__->set_primary_key("srcpath", "sha256hash");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:r/3GaLIIWaX1fh8kfuQp+w # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vGVYmR4k3kezEwiCGSXZWQ
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -20,8 +20,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("uri", "revision"); __PACKAGE__->set_primary_key("uri", "revision");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yTp1XcBSQ+6OJvVLugRh1w # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ORooKeTpZBPOQCgosHLGeg
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -33,8 +33,8 @@ __PACKAGE__->belongs_to(
); );
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lYdNLENxLW2mtZ2w+jou8w # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZeFpiIuYHvaFqRSppuUpoA
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -43,8 +43,8 @@ __PACKAGE__->has_many(
); );
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Bk/vLWpBjR3ZU0p1KN7KfA # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/PmcpU0eiLZT+dlUZYyTaQ
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -50,8 +50,8 @@ __PACKAGE__->has_many(
); );
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9xvvQg/H0oibycB6B45V5A # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EGgAWXbhcEC0uBobJMfpUw
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -16,6 +16,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 0, size => undef }, { data_type => "text", is_nullable => 0, size => undef },
"enabled", "enabled",
{ data_type => "integer", is_nullable => 0, size => undef }, { data_type => "integer", is_nullable => 0, size => undef },
"owner",
{ data_type => "text", is_nullable => 0, size => undef },
); );
__PACKAGE__->set_primary_key("name"); __PACKAGE__->set_primary_key("name");
__PACKAGE__->has_many( __PACKAGE__->has_many(
@ -30,8 +32,8 @@ __PACKAGE__->has_many(
); );
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3YMBhMqCjtpUjoTx4JLTOw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jdyfk3vHisJRyE+VNR6dNA
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -16,8 +16,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("system"); __PACKAGE__->set_primary_key("system");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SSKVFeg7ieeLJcF+s1uWWw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:N/yG0cEhf0Y9Ve9YkdwRfA
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -0,0 +1,25 @@
package Hydra::Schema::Userroles;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("UserRoles");
__PACKAGE__->add_columns(
"username",
{ data_type => "text", is_nullable => 0, size => undef },
"role",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("username", "role");
__PACKAGE__->belongs_to("username", "Hydra::Schema::Users", { username => "username" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:f16iU2I/Htdo7mXHvAdwyQ
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View file

@ -18,10 +18,15 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 0, size => undef }, { data_type => "text", is_nullable => 0, size => undef },
); );
__PACKAGE__->set_primary_key("username"); __PACKAGE__->set_primary_key("username");
__PACKAGE__->has_many(
"userroles",
"Hydra::Schema::Userroles",
{ "foreign.username" => "self.username" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-26 20:02:52 # Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 00:07:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BgF6FK+9d7+cc72sp6pfCQ # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wwRBfogrkKN2QdgmFjcUlA
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration

39
src/Hydra/root/login.tt Normal file
View file

@ -0,0 +1,39 @@
[% WRAPPER layout.tt title="Login to Hydra" %]
[% PROCESS common.tt %]
<h1>Login</h1>
[% IF c.user_exists %]
<p>
You are already logged in as <tt>[% c.user.username %]</tt>.
You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
</p>
[% ELSE %]
[% IF errorMsg %]
<p class="error">Error: [% errorMsg %]</p>
[% END %]
<form method="post" action="[% c.uri_for('/login') %]">
<table class="layoutTable">
<tr>
<td>Username:</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" /></td>
</tr>
<tr colspan="2">
<td>
<input type="submit" name="login" value="Login" />
</td>
</tr>
</table>
</form>
[% END %]
[% END %]

View file

@ -163,6 +163,10 @@
<th>Description:</th> <th>Description:</th>
<td>[% INCLUDE maybeEditString param="description" value=curProject.description %]</td> <td>[% INCLUDE maybeEditString param="description" value=curProject.description %]</td>
</tr> </tr>
<tr>
<th>Owner:</th>
<td><tt>[% INCLUDE maybeEditString param="owner" value=curProject.owner edit=(edit && c.check_user_roles('admin')) %]</tt></td>
</tr>
<tr> <tr>
<th>Enabled:</th> <th>Enabled:</th>
<td> <td>

View file

@ -154,6 +154,8 @@ create table Projects (
displayName text not null, -- display name (e.g. "PatchELF") displayName text not null, -- display name (e.g. "PatchELF")
description text, description text,
enabled integer not null default 1 enabled integer not null default 1
owner text not null,
foreign key (owner) references Users(userName) -- ignored by sqlite
); );
@ -269,3 +271,18 @@ create table Users (
emailAddress text not null, emailAddress text not null,
password text not null -- sha256 hash password text not null -- sha256 hash
); );
create table UserRoles (
userName text not null,
role text not null,
primary key (userName, role),
foreign key (userName) references Users(userName) -- ignored by sqlite
);
create trigger cascadeUserDelete
before delete on Users
for each row begin
delete from UserRoles where userName = old.userName;
end;