Added access controls to test plans. Users must now either be a member of the Testers group, added to a plan via regular expressions, or added explicitly to modify plans and their objects.

This commit is contained in:
ghendricks%novell.com 2007-02-06 00:02:16 +00:00
Родитель 7fad6a15c3
Коммит 23071d7e64
18 изменённых файлов: 726 добавлений и 320 удалений

Просмотреть файл

@ -44,6 +44,17 @@ sub get_param_list {
default => 0,
},
{
name => 'testopia-allow-group-member-deletes',
type => 'b',
default => 0,
},
{
name => 'testopia-default-plan-testers-regexp',
type => 't',
},
# {
# name => 'print-tag-in-case-log',
# type => 'b',

Просмотреть файл

@ -104,7 +104,7 @@ sub init {
my @fields;
my %specialorderjoin;
my %chartfields;
#my ($testergroup) = $dbh->selectrow_array("SELECT id FROM groups WHERE name = ?",undef, 'Testers');
# $chartid is the number of the current chart whose SQL we're constructing
# $row is the current row of the current chart
@ -302,7 +302,30 @@ sub init {
}
}
# Set up tables for access control
unless (Bugzilla->user->in_group('Testers')){
if ($obj eq 'case'){
push(@supptables, "INNER JOIN test_case_plans AS case_plans " .
"ON test_cases.case_id = case_plans.case_id");
push(@supptables, "INNER JOIN test_plans " .
"ON case_plans.plan_id = test_plans.plan_id"); }
elsif ($obj eq 'case_run'){
push(@supptables, "INNER JOIN test_case_runs AS case_runs " .
"ON test_runs.run_id = case_runs.run_id");
push(@supptables, "INNER JOIN test_cases " .
"ON case_runs.case_id = test_cases.case_id");
}
elsif ($obj eq 'run'){
push(@supptables, "INNER JOIN test_plans " .
"ON test_runs.plan_id = test_plans.plan_id");
}
push @supptables, "INNER JOIN test_plan_permissions ON test_plans.plan_id = test_plan_permissions.plan_id";
push @supptables, "INNER JOIN user_group_map AS map_testers ON map_testers.user_id = test_plan_permissions.userid";
push @supptables, "INNER JOIN groups on map_testers.group_id = groups.id";
push @wherepart, "((test_plan_permissions.permissions > 0 AND test_plan_permissions.userid = ". Bugzilla->user->id
.") OR (map_testers.user_id = ". Bugzilla->user->id ." AND groups.name = 'Testers'))";
}
# Set up tables for field sort order
my $order = $cgi->param('order') || '';
if ($order eq 'author') {

Просмотреть файл

@ -740,18 +740,26 @@ sub unlink_plan {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($plan_id) = @_;
my $plan = Bugzilla::Testopia::TestPlan->new($plan_id);
if (scalar @{$self->plans} == 1){
$self->obliterate;
return 1;
}
$dbh->bz_lock_tables('test_case_plans WRITE', 'test_case_runs WRITE',
'test_runs READ', 'test_plans READ');
if (!$self->can_unlink_plan($plan_id)){
$dbh->bz_unlock_tables();
return 0;
foreach my $run (@{$plan->test_runs}){
$dbh->do("DELETE FROM test_case_runs
WHERE case_id = ?
AND run_id = ?", undef, $self->id, $run->id);
}
$dbh->do("DELETE FROM test_case_plans
WHERE plan_id = ?
AND case_id = ?", undef, $plan_id, $self->{'case_id'});
AND case_id = ?",
undef, $plan_id, $self->{'case_id'});
$dbh->bz_unlock_tables();
@ -1262,20 +1270,12 @@ sub can_unlink_plan {
my $self = shift;
my ($plan_id) = @_;
return 0 if (!UserInGroup('managetestplans'));
return 0 if scalar @{$self->plans} < 2;
my $dbh = Bugzilla->dbh;
my ($res) = $dbh->selectrow_array(
"SELECT 1
FROM test_case_runs
INNER JOIN test_runs ON test_case_runs.run_id = test_runs.run_id
WHERE test_runs.plan_id = ?
AND test_case_runs.case_id = ?",
undef, ($plan_id, $self->{'case_id'}));
return !$res;
my $plan = Bugzilla::Testopia::TestPlan->new($plan_id);
return 1 if Bugzilla->user->in_group('admin');
return 1 if Bugzilla->user->in_group('Testers') && Param("testopia-allow-group-member-deletes");
return 1 if $plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 4;
return 1 if $plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 4;
return 0;
}
=head2 obliterate
@ -1315,9 +1315,10 @@ Returns true if the logged in user has rights to edit this test case.
sub canedit {
my $self = shift;
return $self->canview
&& UserInGroup('managetestplans')
|| UserInGroup('edittestcases');
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 2;
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 2;
return 0;
}
=head2 canview
@ -1328,13 +1329,10 @@ Returns true if the logged in user has rights to view this test case.
sub canview {
my $self = shift;
return $self->{'canview'} if exists $self->{'canview'};
my $ret = 1;
foreach my $p (@{$self->plans}){
$ret = 0 unless Bugzilla::Testopia::Util::can_view_product($p->product_id);
}
$self->{'canview'} = $ret;
return $self->{'canview'};
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) > 0;
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) > 0;
return 0;
}
=head2 candelete
@ -1345,26 +1343,36 @@ Returns true if the logged in user has rights to delete this test case.
sub candelete {
my $self = shift;
return 0 unless $self->canedit && Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group("admin");
# Allow plan author to delete if this case is linked only to plans she owns.
return 1 if Bugzilla->user->in_group('admin');
return 0 unless Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group('Testers') && Param("testopia-allow-group-member-deletes");
# Otherwise, check for delete rights on all the plans this is linked to
my $own_all = 1;
foreach my $plan (@{$self->plans}){
if (Bugzilla->user->id != $plan->author->id) {
if (!($plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 4)
|| !($plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 4)) {
$own_all = 0;
last;
}
}
return 1 if $own_all;
# Allow case author to delete if this case is not in any runs.
return 1 if Bugzilla->user->id == $self->author->id &&
$self->get_caserun_count == 0;
return 0;
}
sub get_user_rights {
my $self = shift;
my ($userid, $type) = @_;
my $dbh = Bugzilla->dbh;
my ($perms) = $dbh->selectrow_array(
"SELECT MAX(permissions) FROM test_plan_permissions
LEFT JOIN test_case_plans ON test_plan_permissions.plan_id = test_case_plans.plan_id
INNER JOIN test_cases ON test_case_plans.case_id = test_cases.case_id
WHERE userid = ? AND test_plan_permissions.plan_id = ? AND grant_type = ?",
undef, ($userid, $self->id, $type));
return $perms;
}
###############################
#### Accessors ####
###############################

Просмотреть файл

@ -48,6 +48,7 @@ use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::Constants;
use Bugzilla::Bug;
@ -976,66 +977,11 @@ Returns true if the logged in user has rights to view this case-run.
=cut
sub canview {
my $self = shift;
# return $self->{'canview'} if exists $self->{'canview'};
# my ($case_log_id, $run_id, $plan_id, $current_user_id) = @_;
#
# my $dbh = Bugzilla->dbh;
# my $canview = 0;
# my $current_user_id = Bugzilla->user->id;
# my ($plan_id) = $dbh->selectrow_array("SELECT plan_id FROM test_runs
# WHERE run_id=?",
# undef, $self->{'test_run_id'});
#
# if (0 == &Bugzilla::Param('private-cases-log')) {
# $canview = 1;
# } else {
#
# if (0 == $self->{'isprivate'}) {
# # if !isprivate, then everybody can run it and should be able to see
# # the current status
# $canview = 1;
# } else {
# # check is the current user is a tester:
# if (defined $current_user_id) {
#
# SendSQL("select 1 from test_case_run_testers ".
# "where case_log_id=". $self->{'id'} ." and userid=$current_user_id");
#
# if (FetchOneColumn()) {
# # current user is a tester
# $canview = 1;
# } else {
# # check editors
# SendSQL("select 1 from test_plans where plan_id=$plan_id and editor=$current_user_id");
#
# if (FetchOneColumn()) {
# $canview = 1;
# } else {
# # check watchers
# SendSQL("select 1 from test_plans_watchers where plan_id=$plan_id and userid=$current_user_id");
# if (FetchOneColumn()) {
# $canview = 1;
# } else {
# #check test run manager
# SendSQL("select 1 from test_runs where test_run_id=". $self->{'test_run_id'} ." and manager=$current_user_id");
# $canview = FetchOneColumn()? 1 : 0;
#
# if (0 == $canview) {
# if (UserInGroup('admin')) {
# $canview = 1;
# }
# }
# }
# }
# }
# }
# }
# }
$self->{'canview'} = 1;
return $self->{'canview'};
my $self = shift;
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) > 0;
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) > 0;
return 0;
}
=head2 canedit
@ -1046,10 +992,10 @@ Returns true if the logged in user has rights to edit this case-run.
sub canedit {
my $self = shift;
return !$self->run->stop_date && $self->canview
&& (UserInGroup('managetestplans')
|| UserInGroup('edittestcases')
|| UserInGroup('runtests'));
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 2;
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 2;
return 0;
}
=head2 candelete
@ -1060,9 +1006,11 @@ Returns true if the logged in user has rights to delete this case-run.
sub candelete {
my $self = shift;
return 0 unless $self->canedit && Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group("admin");
return 1 if Bugzilla->user->id == $self->run->manager->id;
return 1 if Bugzilla->user->in_group('admin');
return 0 unless Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group('Testers') && Param("testopia-allow-group-member-deletes");
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 4;
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 4;
return 0;
}

Просмотреть файл

@ -45,6 +45,7 @@ use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::TestRun;
use Bugzilla::Testopia::TestCase;
@ -77,14 +78,14 @@ use base qw(Exporter);
=cut
use constant DB_COLUMNS => qw(
test_plans.plan_id
test_plans.product_id
test_plans.author_id
test_plans.type_id
test_plans.default_product_version
test_plans.name
test_plans.creation_date
test_plans.isactive
plan_id
product_id
author_id
type_id
default_product_version
name
creation_date
isactive
);
use constant NAME_MAX_LENGTH => 255;
@ -183,6 +184,14 @@ sub store {
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
$self->store_text($key, $self->{'author_id'}, $self->text, $timestamp);
$self->{'plan_id'} = $key;
# Add permissions for the plan
$self->add_tester($self->{'author_id'},15);
if (Param('testopia-default-plan-testers-regexp')) {
$self->set_tester_regexp( Param('testopia-default-plan-testers-regexp'), 3);
$self->derive_regexp_testers(Param('testopia-default-plan-testers-regexp'));
}
return $key;
}
@ -595,6 +604,20 @@ sub check_plan_type {
return $type;
}
sub check_tester {
my $self = shift;
my $userid = shift;
my $dbh = Bugzilla->dbh;
my ($exists) = $dbh->selectrow_array(
"SELECT 1
FROM test_plan_permissions
WHERE userid = ? AND plan_id = ?",
undef, ($userid, $self->id));
return $exists;
}
=head2 update_plan_type
Update the given type
@ -772,6 +795,30 @@ sub history {
return $ref;
}
sub copy_permissions {
my $self = shift;
my ($planid) = @_;
my $dbh = Bugzilla->dbh;
my ($regexp, $perms) = $dbh->selectrow_array(
"SELECT user_regexp, permissions
FROM test_plan_permissions_regexp
WHERE plan_id = ?",undef, $self->id);
$dbh->do("INSERT INTO test_plan_permissions_regexp (plan_id, user_regexp, permissions)
VALUES(?,?,?)", undef,($planid, $regexp, $perms));
my $ref = $dbh->selectall_arrayref(
"SELECT userid, permissions
FROM test_plan_permissions
WHERE plan_id = ? AND grant_type = ?",
{'Slice' =>{}}, ($self->id, GRANT_DIRECT));
foreach my $row (@$ref){
$dbh->do("INSERT INTO test_plan_permissions (userid, plan_id, permissions, grant_type)
VALUES(?,?,?,?)", undef, ($row->{'userid'}, $planid, $row->{'permissions'}, GRANT_DIRECT));
}
}
=head2 lookup_type
Takes an ID of the type field and returns the value
@ -846,7 +893,94 @@ sub lookup_product_by_name {
return $value;
}
sub set_tester_regexp {
my $self = shift;
my ($regexp, $permissions) = @_;
my $dbh = Bugzilla->dbh;
my ($is, $oldreg, $oldperms) = $dbh->selectrow_array(
"SELECT 1, user_regexp, permissions
FROM test_plan_permissions_regexp
WHERE plan_id = ?",undef, $self->id);
return unless ($oldreg ne $regexp || $oldperms != $permissions);
if ($is){
$dbh->do("UPDATE test_plan_permissions_regexp
SET user_regexp = ?, permissions = ?
WHERE plan_id = ?", undef, ($regexp, $permissions, $self->id));
}
else {
$dbh->do("INSERT INTO test_plan_permissions_regexp(plan_id, user_regexp, permissions)
VALUES(?,?,?)",
undef, ($self->id, $regexp, $permissions));
}
$self->derive_regexp_testers($regexp);
}
sub derive_regexp_testers {
my $self = shift;
my $regexp = shift;
my $dbh = Bugzilla->dbh;
# Get the permissions of the regexp testers so we can set it later.
my ($permissions) = $dbh->selectrow_array(
"SELECT permissions
FROM test_plan_permissions_regexp
WHERE plan_id = ?", undef, $self->id);
# If something has changed, it is easier to delete everyone and add tham back in
$dbh->do("DELETE FROM test_plan_permissions
WHERE plan_id = ? AND grant_type = ?",
undef, ($self->id, GRANT_REGEXP));
my $sth = $dbh->prepare("SELECT profiles.userid, profiles.login_name, plan_id
FROM profiles
LEFT JOIN test_plan_permissions
ON test_plan_permissions.userid = profiles.userid
AND test_plan_permissions.plan_id = ?
AND grant_type = ?");
my $plan_add = $dbh->prepare("INSERT INTO test_plan_permissions
(userid, plan_id, permissions, grant_type)
VALUES (?,?,?,?)");
my $plan_del = $dbh->prepare("DELETE FROM test_plan_permissions
WHERE user_id = ? AND plan_id = ?
AND grant_type = ?");
$sth->execute($self->id, GRANT_REGEXP);
while (my ($userid, $login, $present) = $sth->fetchrow_array()) {
if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i)){
$plan_add->execute($userid, $self->id, $permissions, GRANT_REGEXP) unless $present;
}
}
}
sub remove_tester {
my $self = shift;
my ($userid) = @_;
my $dbh = Bugzilla->dbh;
$dbh->do("DELETE FROM test_plan_permissions
WHERE userid = ? AND plan_id = ? AND grant_type = ?",
undef, ($userid, $self->id, GRANT_DIRECT));
}
sub add_tester {
my $self = shift;
my ($userid, $perms) = @_;
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_plan_permissions(userid, plan_id, permissions)
VALUES(?,?,?,?)",
undef, ($userid, $self->id, $perms, GRANT_DIRECT));
}
sub update_tester {
my $self = shift;
my ($userid, $perms) = @_;
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_plan_permissions SET permissions = ?
WHERE userid = ? AND plan_id = ? AND grant_type = ?",
undef, ($perms, $userid, $self->id, GRANT_DIRECT));
}
=head2 obliterate
@ -903,7 +1037,11 @@ Returns true if the logged in user has rights to edit this plan
sub canedit {
my $self = shift;
return $self->canview && UserInGroup("managetestplans");
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 2;
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 2;
return 0;
}
=head2 canview
@ -914,8 +1052,10 @@ Returns true if the logged in user has rights to view this plan
sub canview {
my $self = shift;
return 1 if (Bugzilla->user->id == $self->author->id);
return Bugzilla::Testopia::Util::can_view_product($self->product_id);
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) > 0;
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) > 0;
return 0;
}
=head2 candelete
@ -926,12 +1066,34 @@ Returns true if the logged in user has rights to delete this plan
sub candelete {
my $self = shift;
return 0 unless $self->canedit && Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group('admin');
return 0 unless Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group('Testers') && Param("testopia-allow-group-member-deletes");
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 4;
return 1 if $self->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 4;
return 0;
}
sub canadmin {
my $self = shift;
return 1 if Bugzilla->user->in_group("admin");
return 1 if Bugzilla->user->id == $self->author->id;
return 1 if ($self->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 8);
return 1 if ($self->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 8);
return 0;
}
sub get_user_rights {
my $self = shift;
my ($userid, $type) = @_;
my $dbh = Bugzilla->dbh;
my ($perms) = $dbh->selectrow_array(
"SELECT permissions FROM test_plan_permissions
WHERE userid = ? AND plan_id = ? AND grant_type = ?",
undef, ($userid, $self->id, $type));
return $perms;
}
###############################
#### Accessors ####
@ -970,6 +1132,12 @@ Returns the type id of this plan
Returns true if this plan is not archived
=head2 use_product_rights
If true, user access is granted based first on product groups and then on the
plan's access list otherwise, all right associated with this plan are
determined bey the ACL.
=cut
sub id { return $_[0]->{'plan_id'}; }
@ -993,6 +1161,59 @@ sub type {
return $self->{'type'};
}
sub tester_regexp {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
my ($regexp) = $dbh->selectrow_array(
"SELECT user_regexp
FROM test_plan_permissions_regexp
WHERE plan_id = ?", undef, $self->id);
return $regexp;
}
sub tester_regexp_permissions {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
my ($perms) = $dbh->selectrow_array(
"SELECT permissions
FROM test_plan_permissions_regexp
WHERE plan_id = ?", undef, $self->id);
my $p;
$p->{'read'} = 1 & $perms;
$p->{'write'} = 2 & $perms;
$p->{'delete'} = 4 & $perms;
$p->{'admin'} = 8 & $perms;
return $p;
}
sub access_list {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectall_arrayref(
"SELECT tpt.userid, permissions
FROM test_plan_permissions AS tpt
JOIN profiles ON profiles.userid = tpt.userid
WHERE plan_id = ? AND grant_type = ?
ORDER BY profiles.realname", {'Slice' =>{}}, ($self->id, GRANT_DIRECT));
my @rows;
foreach my $row (@$ref){
push @rows, {'user' => Bugzilla::User->new($row->{'userid'}),
'read' => 1 & $row->{'permissions'},
'write' => 2 & $row->{'permissions'},
'delete' => 4 & $row->{'permissions'},
'admin' => 8 & $row->{'permissions'},
};
}
$self->{'access_list'} = \@rows;
return $self->{'access_list'};
}
=head2 attachments
Returns a reference to a list of attachments on this plan

Просмотреть файл

@ -738,10 +738,10 @@ Returns true if the logged in user has rights to edit this test run.
sub canedit {
my $self = shift;
return $self->canview
&& (UserInGroup('managetestplans')
|| UserInGroup('edittestcases')
|| UserInGroup('runtests'));
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 2;
return 1 if $self->plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 2;
return 0;
}
=head2 canview
@ -752,9 +752,11 @@ Returns true if the logged in user has rights to view this test run.
sub canview {
my $self = shift;
return $self->{'canview'} if exists $self->{'canview'};
$self->{'canview'} = Bugzilla::Testopia::Util::can_view_product($self->plan->product_id);
return $self->{'canview'};
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) > 0;
return 1 if $self->plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) > 0;
return 0;
}
=head2 candelete
@ -765,10 +767,11 @@ Returns true if the logged in user has rights to delete this test run.
sub candelete {
my $self = shift;
return 0 unless $self->canedit && Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group("admin");
return 1 if Bugzilla->user->id == $self->manager->id;
return 1 if Bugzilla->user->id == $self->plan->author->id;
return 1 if Bugzilla->user->in_group('admin');
return 0 unless Param("allow-test-deletion");
return 1 if Bugzilla->user->in_group('Testers') && Param("testopia-allow-group-member-deletes");
return 1 if $self->plan->get_user_rights(Bugzilla->user->id, GRANT_REGEXP) & 4;
return 1 if $self->plan->get_user_rights(Bugzilla->user->id, GRANT_DIRECT) & 4;
return 0;
}

Просмотреть файл

@ -29,6 +29,12 @@
"allow-test-deletion" => "If this option is on, users can delete objects including plans and cases",
"testopia-allow-group-member-deletes" => "If this option is on, members of the Testers group will be
allowed to delete test objects",
"testopia-default-plan-testers-regexp" => "This is the default regular expression for granting
access to new test plans",
"print-tag-in-case-log" => 'If this option is on, the entire tag text is printed in a test case ' _
'log entry. Otherwise, only an href to the tag is put there.',

Просмотреть файл

@ -120,5 +120,11 @@
[% ELSIF error == "missing-plans-list" %]
[% title = "No plans selected" %]
You did not select any plans to copy this case to.
[% ELSIF error == "testopia-tester-already-on-list" %]
[% title = "Selected user is already on the list" %]
The user [% login FILTER html %] is already a member of the ACL for this plan.
[% ELSIF error == "testopia-plan-acl-denied" %]
[% title = "Plan Administrator Privileges Required" %]
You must be an administrator of this test plan to modify the access control list.
[% END %]

Просмотреть файл

@ -0,0 +1,106 @@
[%# 1.0@bugzilla.org %]
[%# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Testopia System.
#
# The Initial Developer of the Original Code is Greg Hendricks.
# Portions created by Greg Hendricks are Copyright (C) 2006
# Novell. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
#%]
<link href="testopia/css/default.css" rel="stylesheet" type="text/css" />
[% PROCESS global/header.html.tmpl %]
<h1>Access Control for Plan [% plan.id FILTER none %] - [% plan.name FILTER html %]</h1>
<p>
This page allows plan managers to permit access to a test plan. By default, members of the
<b>Testers</b> group in Bugzilla will have read and write access. Beyond this, users login names
(email addresses) that match a given regular expression will be granted the level of permissions
specified. Lastly, individuals can be granted additional permissions by adding them
explicitly.
</p>
<p>
Delete and Admin rights are always handled by the access control list, unless the
<b>testopia-allow-group-member-deletes</b> parameter is set to <i>on</i>. Doing so will grant
delete rights to the <b>Testers</b> group.
</p>
[% PROCESS testopia/messages.html.tmpl %]
<form action="tr_plan_access.cgi" method="POST">
<input type="hidden" name="plan_id" value="[% plan.id FILTER none %]">
<h3>Access Method</h3>
<table>
<tr class="bz_row_header">
<th>User Regular Expression</th>
<th>Read</th>
<th>Write</th>
<th>Delete</th>
<th>Admin</th>
</tr>
<tr>
<td><input name="userregexp" value="[% plan.tester_regexp FILTER none %]" size="70"><br></td>
<td align="center"><input type="checkbox" name="pr" [% 'checked="checked"' IF plan.tester_regexp_permissions.read %] value="1"></td>
<td align="center"><input type="checkbox" name="pw" [% 'checked="checked"' IF plan.tester_regexp_permissions.write %] value="1"></td>
<td align="center"><input type="checkbox" name="pd" [% 'checked="checked"' IF plan.tester_regexp_permissions.delete %] value="1"></td>
<td align="center"><input type="checkbox" name="pa" [% 'checked="checked"' IF plan.tester_regexp_permissions.admin %] value="1"></td>
</tr>
</table>
<br>
<h3>Access Control List</h3>
<table>
<tr class="bz_row_header">
<th>User</th>
<th>Read</th>
<th>Write</th>
<th>Delete</th>
<th>Admin</th>
</tr>
[% FOREACH row = plan.access_list %]
<tr class="[% loop.count % 2 == 0 ? "bz_row_odd" : "bz_row_even" %]">
<td>[%'*' IF row.user.id == plan.author.id %][% row.user.identity FILTER html %]</td>
<td align="center"><input type="checkbox" name="r[% row.user.id %]" [% 'checked="checked"' IF row.read %] value="1"></td>
<td align="center"><input type="checkbox" name="w[% row.user.id %]" [% 'checked="checked"' IF row.write %] value="1"></td>
<td align="center"><input type="checkbox" name="d[% row.user.id %]" [% 'checked="checked"' IF row.delete %] value="1"></td>
<td align="center"><input type="checkbox" name="a[% row.user.id %]" [% 'checked="checked"' IF row.admin %] value="1"></td>
<td><a href="tr_plan_access.cgi?action=delete&user=[% row.user.id %]&plan_id=[% plan.id FILTER none %]">Remove</a></td>
</tr>
[% END %]
<tr style="background-color:#D8E5EE">
<td style="padding: 3px;"><input style="width:200px;" name="adduser"></td>
<td align="center"><input type="checkbox" name="nr" value="1"></td>
<td align="center"><input type="checkbox" name="nw" value="1"></td>
<td align="center"><input type="checkbox" name="nd" value="1"></td>
<td align="center"><input type="checkbox" name="na" value="1"></td>
<td align="left"><input type="submit" name="action" value="Add User"></td>
</tr>
</table>
<p align="right">
<input type="submit" name="action" value="Apply Changes">
</p>
<dl>
<dt>Read</dt>
<dd>Allows viewing rights to the plan and all test cases, test runs, and test case-runs associated with it.
Test cases linked to more than one plan will not be visible unless the user has view rights on all plans linked</dd>
<dt>Write</dt>
<dd>Implies Read. Allows rights to modify the plan and associated cases, runs, and case-runs.</dd>
<dt>Delete</dt>
<dd>Implies Read and Write. Allows rights to delete the plan and associated cases, runs, and case-runs.</dd>
<dt>Admin</dt>
<dd>Implies Read, Write, and Delete. Allows rights to modify the plan's access controls (this page).</dd>
</dl>
* Plan author
<p>
<a href="tr_show_plan.cgi?plan_id=[% plan.id FILTER none %]">Back</a> to test plan
</p>
[% PROCESS global/footer.html.tmpl %]

Просмотреть файл

@ -34,7 +34,7 @@
[%############################################################################%]
[% PROCESS global/header.html.tmpl
title = "Delete Test Case $case.summary"
title = "Delete Test Case: $case.id - $case.summary"
%]
[% IF NOT deleted %]

Просмотреть файл

@ -0,0 +1,48 @@
[%# 1.0@bugzilla.org %]
[%# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Testopia System.
#
# The Initial Developer of the Original Code is Greg Hendricks.
# Portions created by Greg Hendricks are Copyright (C) 2007
# Novell. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
#%]
[%# INTERFACE:
# ...
#%]
[% PROCESS global/variables.none.tmpl %]
[% PROCESS global/header.html.tmpl
title = "Unlink Test Case: $case.id - $case.summary"
%]
You are about to permanently unlink this test case from test plan<br>
<b>[% plan.id FILTER none %] - [% plan.name FILTER html %]</b>.
<p>
This will remove all history of this case from this plan.
<br>
<span style="font-size:12pt; font-weight:bold; color:#cc0000;">Warning: This action cannot be undone</span>
<br>
<br>
<form action="tr_show_case.cgi">
<input type="hidden" name="case_id" value="[% case.id FILTER none %]" />
<input type="hidden" name="plan_id" value="[% plan.id FILTER none %]" />
<input type="hidden" name="action" value="do_unlink" />
<input type="submit" value="Unlink This Test Case" />
</form>
<a href="tr_show_case.cgi?case_id=[% case.id FILTER none %]">Go back</a>
[% PROCESS global/footer.html.tmpl %]

Просмотреть файл

@ -90,6 +90,12 @@
type="radio" name="copy_tags" value="0" />No</td>
</tr>
<tr>
<td valign="TOP" align="RIGHT">Copy permissions:</td>
<td><input type="radio" name="copy_perms" value="1" checked />Yes <input
type="radio" name="copy_perms" value="0" />No</td>
</tr>
<tr>
<td valign="TOP" align="RIGHT">Maintain Plan and Case Authors:</td>
<td><input type="radio" name="keepauthor" value="1" checked />Yes <input

Просмотреть файл

@ -154,6 +154,10 @@
<tr class="bz_row_data">
<td colspan="3"><div align="right">
<a href="#attributes">Edit Plan Attributes</a>
[% IF plan.canadmin %]
&nbsp;|&nbsp;<a href="tr_plan_access.cgi?plan_id=[% plan.id FILTER none %]">Edit Access Controls</a>
[% END %]
<input type="hidden" name="plan_id" value="[% plan.id FILTER none %]">
<input type="submit" name="action" value="Commit" style="visibility:hidden;">
<input type="submit" name="action" value="[% plan.isactive ? "Archive" : "Unarchive" %]">

Просмотреть файл

@ -1,177 +0,0 @@
textarea {
background-color: #fff;
border: 1px solid #999;
}
.resultsTable {
border-collapse: collapse; /*cellspacing*/
border-spacing: 0px;
}
.resultsTable td { /*only immediate tds of mytable*/
padding: 4px; /*cellpadding*/
}
.resultsTable td a:hover {
text-decoration:underline overline;
}
.resultsTable th {
border-color:#FFF;
background-color:#C0FFC0;
border-style:solid;
border-width: 2px;
padding: 4px;
}
.dlgTable {
border-collapse: collapse; /*cellspacing*/
border-spacing: 0px;
}
.dlgTable td { /*only immediate tds of mytable*/
padding: 4px; /*cellpadding*/
background-color:#EEE;
}
.spacerTable {
border-collapse: collapse; /*cellspacing*/
border-spacing: 0px;
}
.spacerTable td { /*only immediate tds of mytable*/
padding: 16px; /*cellpadding*/
background-color:#FFF;
}
.menuTable {
border-collapse: collapse; /*cellspacing*/
border-spacing: 0px;
}
.menuTable td { /*only immediate tds of mytable*/
padding: 4px; /*cellpadding*/
background-color:#EEE;
border-style:solid;
border-width: 3px;
border-color:#FFF;
}
.evenRow_first {
background-color:#FFF;
}
.evenRow {
background-color:#FFF;
}
.evenRow td{
background-color:#FFF;border-top-style:solid;border-top-width:1px;border-top-color:#CCC;
}
.oddRow_first {
background-color:#EEE;
}
.oddRow {
background-color:#EEE;
}
.oddRow td {
background-color:#EEE;border-top-style:solid;border-top-width:1px;border-top-color:#CCC;
}
.highlightedCell {
border-width:1px;
border-style:solid;
background-color:lightyellow;
}
/* tr_showcaselog */
.short_body {
border-bottom:1px solid #000;
border-left:1px solid #000;
border-right:1px solid #000;
}
.short_head {
border-top:1px solid #000;
border-left:1px solid #000;
border-right:1px solid #000;
}
.ae_dv {
display:none;
margin-left:15px;
border-width:0px;
}
.ae_tb {
width:100%;
border:0;
margin:0;
margin-right:30px;
margin-bottom:10px;
border-collapse:collapse;
border-spacing: 0px;
}
.ae_s {
border-style:solid;
border-width:1px;
border-color:#000;
background-color:#FFFFE0;
}
.cc_i {
float:left;
margin-left:10px;
}
.cc_xx {
clear:both;
margin-right:30px;
padding-top:5px;
}
.cc_trg {
padding-left:5px;
padding-right:5px;
vertical-align:baseline;
}
/* TODO: refactor these two: */
#floatMsg {
text-align:right;
background-color:#FFDD66;
color:black;
padding:4px;
padding-left:20px;
padding-right:20px;
font-family:Arial;
font-weight:bold;
font-size:12px;
display:none;
}
.floatMsg {
text-align:right;
background-color:#FFDD66;
color:black;
padding:4px;
padding-left:20px;
padding-right:20px;
font-family:Arial;
font-weight:bold;
font-size:12px;
display:none;
}
.tr_button {
width:100px;
}
a img {
border: none;
}
h3 {
border-bottom: 1px dotted #000;
}

Просмотреть файл

@ -87,6 +87,9 @@ if (!GroupDoesExist($dbh, "runtests")) {
'Can add, delete and edit test runs.', $adminid);
tr_AssignAdminGrants($dbh, $groupid, $adminid);
}
updateACLs($dbh);
print "Done.\n\n";
##############################################################################
print "Cleaning up Testopia cache ...\n";
@ -295,9 +298,9 @@ sub UpdateDB {
$dbh->bz_drop_table('test_case_group_map');
$dbh->bz_drop_table('test_category_templates');
$dbh->bz_drop_table('test_plan_testers');
$dbh->bz_drop_table('test_plan_group_map');
$dbh->bz_drop_column('test_plans', 'editor_id');
$dbh->bz_add_column('test_case_bugs', 'case_id', {TYPE => 'INT4', UNSIGNED => 1});
$dbh->bz_add_column('test_case_runs', 'environment_id', {TYPE => 'INT4', UNSIGNED => 1, NOTNULL => 1}, 0);
$dbh->bz_add_column('test_case_tags', 'userid', {TYPE => 'INT3', NOTNULL => 1}, 0);
@ -380,7 +383,7 @@ sub UpdateDB {
$dbh->bz_add_index('test_runs', 'test_run_plan_id_run_id_idx', [qw(plan_id run_id)]);
$dbh->bz_add_index('test_runs', 'test_runs_summary_idx', {FIELDS => ['summary'], TYPE => 'FULLTEXT'});
if ($dbh->bz_index_info('test_case_tags', 'case_tags_case_id_idx')->{TYPE} eq '') {
if ($dbh->bz_index_info('test_case_tags', 'case_tags_case_id_idx') && $dbh->bz_index_info('test_case_tags', 'case_tags_case_id_idx')->{TYPE} eq '') {
$dbh->bz_drop_index('test_case_tags', 'case_tags_case_id_idx');
$dbh->bz_add_index('test_case_tags', 'case_tags_case_id_idx', {FIELDS => [qw(tag_id case_id)], TYPE => 'UNIQUE'});
}
@ -399,6 +402,21 @@ sub UpdateDB {
migrateEnvData($dbh);
}
sub updateACLs {
my $dbh = shift;
print "Checking plan ACLs \n";
my $ref = $dbh->selectall_arrayref("SELECT plan_id, author_id FROM test_plans", {'Slice' =>{}});
foreach my $plan (@$ref){
my ($finished) = $dbh->selectrow_array(
"SELECT COUNT(*) FROM test_plan_permissions
WHERE plan_id = ? AND userid = ?",
undef, ($plan->{'plan_id'}, $plan->{'author_id'}));
next if ($finished);
$dbh->do("INSERT INTO test_plan_permissions(userid, plan_id, permissions)
VALUES(?,?,?)",
undef, ($plan->{'author_id'}, $plan->{'plan_id'}, 15));
}
}
sub populateMiscTables {
my ($dbh) = (@_);

Просмотреть файл

@ -0,0 +1,139 @@
#!/usr/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Testopia System.
#
# The Initial Developer of the Original Code is Greg Hendricks.
# Portions created by Greg Hendricks are Copyright (C) 2007
# Novell. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
use strict;
use lib ".";
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::Product;
use vars qw($vars);
use Data::Dumper;
require 'globals.pl';
my $template = Bugzilla->template;
my $cgi = Bugzilla->cgi;
Bugzilla->login();
print $cgi->header;
my $plan_id = trim($cgi->param('plan_id') || '');
my $action = $cgi->param('action') || '';
unless (detaint_natural($plan_id)){
$vars->{'form_action'} = 'tr_plan_access.cgi';
$template->process("testopia/plan/choose.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
my $plan = Bugzilla::Testopia::TestPlan->new($plan_id);
unless ($plan->canadmin){
ThrowUserError('testopia-plan-acl-denied', {plan_id => $plan->id});
}
#print Dumper($plan->access_list);
if ($action eq 'Apply Changes'){
do_update();
display();
}
elsif ($action eq 'Add User'){
do_update();
my $dbh = Bugzilla->dbh;
my $userid = DBNameToIdAndCheck(trim($cgi->param('adduser')));
ThrowUserError('testopia-tester-already-on-list', {'login' => $cgi->param('adduser')})
if ($plan->check_tester($userid));
my $perms = 0;
# The order we check these is important since each permission
# implies the prior ones.
$perms = $cgi->param("nr") ? 1 : $perms;
$perms = $cgi->param("nw") ? 3 : $perms;
$perms = $cgi->param("nd") ? 7 : $perms;
$perms = $cgi->param("na") ? 15 : $perms;
$plan->add_tester($userid, $perms);
display();
}
elsif ($action eq 'delete'){
my $userid = $cgi->param('user');
detaint_natural($userid);
my $user = Bugzilla::User->new($userid);
$plan->remove_tester($user->id);
$vars->{'tr_message'} = $user->login ." Removed from Plan";
display();
}
else{
display();
}
sub do_update {
# We need at least on admin
my $params = join(" ", $cgi->param());
ThrowUserErorr('testopia-no-admins') unless $params =~ /a\d+/;
my $tester_regexp = $cgi->param('userregexp');
trick_taint($tester_regexp);
my $regexp_perms = 0;
# The order we check these is important since each permission
# implies the prior ones.
$regexp_perms = $cgi->param('pr') ? 1 : $regexp_perms;
$regexp_perms = $cgi->param('pw') ? 3 : $regexp_perms;
$regexp_perms = $cgi->param('pd') ? 7 : $regexp_perms;
$regexp_perms = $cgi->param('pa') ? 15 : $regexp_perms;
$plan->set_tester_regexp($tester_regexp, $regexp_perms);
my $dbh = Bugzilla->dbh;
foreach my $row (@{$plan->access_list}){
my $perms = 0;
# The order we check these is important since each permission
# implies the prior ones.
$perms = $cgi->param('r'.$row->{'user'}->id) ? 1 : $perms;
$perms = $cgi->param('w'.$row->{'user'}->id) ? 3 : $perms;
$perms = $cgi->param('d'.$row->{'user'}->id) ? 7 : $perms;
$perms = $cgi->param('a'.$row->{'user'}->id) ? 15 : $perms;
$plan->update_tester($row->{'user'}->id, $perms);
}
}
sub display {
$vars->{'plan'} = $plan;
$vars->{'user'} = Bugzilla->user;
$template->process("testopia/admin/access-list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}

Просмотреть файл

@ -222,12 +222,37 @@ elsif ($action eq 'unlink'){
my $plan_id = $cgi->param('plan_id');
validate_test_id($plan_id, 'plan');
my $case = Bugzilla::Testopia::TestCase->new($case_id);
ThrowUserError("testopia-read-only", {'object' => 'case'})
unless ($case->can_unlink_plan($plan_id));
if (scalar @{$case->plans} == 1){
$vars->{'case'} = $case;
$vars->{'runcount'} = scalar @{$case->runs};
$vars->{'plancount'} = scalar @{$case->plans};
$vars->{'bugcount'} = scalar @{$case->bugs};
$template->process("testopia/case/delete.html.tmpl", $vars) ||
ThrowTemplateError($template->error());
}
else {
$vars->{'plan'} = Bugzilla::Testopia::TestPlan->new($plan_id);
$vars->{'case'} = $case;
$template->process("testopia/case/unlink.html.tmpl", $vars) ||
ThrowTemplateError($template->error());
}
}
elsif ($action eq 'do_unlink'){
Bugzilla->login(LOGIN_REQUIRED);
my $plan_id = $cgi->param('plan_id');
validate_test_id($plan_id, 'plan');
my $case = Bugzilla::Testopia::TestCase->new($case_id);
ThrowUserError("testopia-read-only", {'object' => 'case'})
unless ($case->can_unlink_plan($plan_id));
if ($case->unlink_plan($plan_id)){
$vars->{'tr_message'} = "Test plan successfully unlinked";
}
else {
$vars->{'tr_error'} = "Test plan could not be unlinked. It is used in test runs.";
}
$vars->{'backlink'} = $case;
display($case);
}

Просмотреть файл

@ -135,6 +135,17 @@ elsif ($action eq 'do_clone'){
$newplan->add_tag($newtagid);
}
}
if ($cgi->param('copy_perms')){
$plan->copy_permissions($newplanid);
$newplan->derive_regexp_testers($plan->tester_regexp);
}
else {
# Give the author admin rights
$newplan->add_tester($author, 15);
$newplan->set_tester_regexp( Param('testopia-default-plan-testers-regexp'), 3)
if Param('testopia-default-plan-testers-regexp');
$newplan->derive_regexp_testers(Param('testopia-default-plan-testers-regexp'))
}
if ($cgi->param('copy_cases')){
my @catids;
#TODO: Copy case to the new category
@ -478,13 +489,13 @@ sub display {
my @time = localtime(time());
my $date = sprintf "%04d-%02d-%02d", 1900+$time[5],$time[4]+1,$time[3];
my $filename = "testcases-$date.$format->{extension}";
# print $cgi->multipart_final if ($serverpush && $vars->{'case_table'}->list_count >= 1000);
print $cgi->header(-type => $format->{'ctype'},
-content_disposition => "$disp; filename=$filename");
-content_disposition => "$disp; filename=$filename")
unless ($action eq 'do_clone');
$vars->{'percentage'} = \&percentage;
$template->process($format->{'template'}, $vars) ||
ThrowTemplateError($template->error());
$vars->{'percentage'} = \&percentage;
$template->process($format->{'template'}, $vars) ||
ThrowTemplateError($template->error());
}