summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorlpsolit%gmail.com <>2006-11-11 01:51:27 +0100
committerlpsolit%gmail.com <>2006-11-11 01:51:27 +0100
commit0d3a72b793725118641c4d7abf511b4fc98f7aef (patch)
treec5a7b0fbcb401fdc3a083bb94b0b6345962d0bac /Bugzilla
parentbd49bafdb5915a8e738dcbb82c88e8ffdf65a769 (diff)
downloadbugzilla-0d3a72b793725118641c4d7abf511b4fc98f7aef.tar.gz
bugzilla-0d3a72b793725118641c4d7abf511b4fc98f7aef.tar.xz
Bug 189627: Implement per-product privileges - Patch by Frédéric Buclin <LpSolit@gmail.com> r=mkanat a=myk
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Attachment.pm23
-rwxr-xr-xBugzilla/Bug.pm35
-rw-r--r--Bugzilla/Component.pm20
-rw-r--r--Bugzilla/Constants.pm4
-rw-r--r--Bugzilla/DB/Schema.pm6
-rw-r--r--Bugzilla/Install/DB.pm8
-rw-r--r--Bugzilla/Product.pm5
-rw-r--r--Bugzilla/User.pm139
8 files changed, 170 insertions, 70 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index ebf8c8de8..c1b3a8a28 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -609,19 +609,22 @@ sub validate_content_type {
=pod
-=item C<validate_can_edit()>
+=item C<validate_can_edit($attachment, $product_id)>
Description: validates if the user is allowed to view and edit the attachment.
Only the submitter or someone with editbugs privs can edit it.
Only the submitter and users in the insider group can view
private attachments.
+Params: $attachment - the attachment object being edited.
+ $product_id - the product ID the attachment belongs to.
+
Returns: 1 on success. Else an error is thrown.
=cut
sub validate_can_edit {
- my $attachment = shift;
+ my ($attachment, $product_id) = @_;
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
@@ -634,27 +637,27 @@ sub validate_can_edit {
}
# Users with editbugs privs can edit all attachments.
- return if $user->in_group('editbugs');
+ return if $user->in_group('editbugs', $product_id);
# If we come here, then this attachment cannot be seen by the user.
ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
}
-=item C<validate_obsolete($bug_id)>
+=item C<validate_obsolete($bug)>
Description: validates if attachments the user wants to mark as obsolete
really belong to the given bug and are not already obsolete.
Moreover, a user cannot mark an attachment as obsolete if
he cannot view it (due to restrictions on it).
-Params: $bug_id - The bug ID obsolete attachments should belong to.
+Params: $bug - The bug object obsolete attachments should belong to.
Returns: 1 on success. Else an error is thrown.
=cut
sub validate_obsolete {
- my ($class, $bug_id) = @_;
+ my ($class, $bug) = @_;
my $cgi = Bugzilla->cgi;
# Make sure the attachment id is valid and the user has permissions to view
@@ -674,12 +677,12 @@ sub validate_obsolete {
ThrowUserError('invalid_attach_id', $vars) unless $attachment;
# Check that the user can view and edit this attachment.
- $attachment->validate_can_edit;
+ $attachment->validate_can_edit($bug->product_id);
$vars->{'description'} = $attachment->description;
- if ($attachment->bug_id != $bug_id) {
- $vars->{'my_bug_id'} = $bug_id;
+ if ($attachment->bug_id != $bug->bug_id) {
+ $vars->{'my_bug_id'} = $bug->bug_id;
$vars->{'attach_bug_id'} = $attachment->bug_id;
ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
}
@@ -758,7 +761,7 @@ sub insert_attachment_for_bug {
# Check attachments the user tries to mark as obsolete.
my @obsolete_attachments;
if ($cgi->param('obsolete')) {
- @obsolete_attachments = $class->validate_obsolete($bug->bug_id);
+ @obsolete_attachments = $class->validate_obsolete($bug);
}
# The order of these function calls is important, as Flag::validate
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 18f8316a9..e9c8bf2e9 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -121,7 +121,6 @@ sub VALIDATORS {
commentprivacy => \&_check_commentprivacy,
deadline => \&_check_deadline,
estimated_time => \&_check_estimated_time,
- keywords => \&_check_keywords,
op_sys => \&_check_op_sys,
priority => \&_check_priority,
product => \&_check_product,
@@ -354,6 +353,8 @@ sub run_create_validators {
$params->{version} = $class->_check_version($product, $params->{version});
+ $params->{keywords} = $class->_check_keywords($product, $params->{keywords});
+
$params->{groups} = $class->_check_groups($product,
$params->{groups});
@@ -381,7 +382,7 @@ sub run_create_validators {
$params->{assigned_to}, $params->{qa_contact});
($params->{dependson}, $params->{blocked}) =
- $class->_check_dependencies($params->{dependson}, $params->{blocked});
+ $class->_check_dependencies($product, $params->{dependson}, $params->{blocked});
# You can't set these fields on bug creation (or sometimes ever).
delete $params->{resolution};
@@ -480,7 +481,7 @@ sub _check_assigned_to {
$name = trim($name);
# Default assignee is the component owner.
my $id;
- if (!$user->in_group("editbugs") || !$name) {
+ if (!$user->in_group('editbugs', $component->product_id) || !$name) {
$id = $component->default_assignee->id;
} else {
$id = login_to_id($name, THROW_ERROR);
@@ -508,7 +509,8 @@ sub _check_bug_status {
my @valid_statuses = VALID_ENTRY_STATUS;
- if ($user->in_group('editbugs') || $user->in_group('canconfirm')) {
+ if ($user->in_group('editbugs', $product->id)
+ || $user->in_group('canconfirm', $product->id)) {
# Default to NEW if the user with privs hasn't selected another status.
$status ||= 'NEW';
}
@@ -599,10 +601,10 @@ sub _check_deadline {
# Takes two comma/space-separated strings and returns arrayrefs
# of valid bug IDs.
sub _check_dependencies {
- my ($invocant, $depends_on, $blocks) = @_;
+ my ($invocant, $product, $depends_on, $blocks) = @_;
# Only editbugs users can set dependencies on bug entry.
- return ([], []) unless Bugzilla->user->in_group('editbugs');
+ return ([], []) unless Bugzilla->user->in_group('editbugs', $product->id);
$depends_on ||= '';
$blocks ||= '';
@@ -676,9 +678,10 @@ sub _check_groups {
}
sub _check_keywords {
- my ($invocant, $keyword_string) = @_;
+ my ($invocant, $product, $keyword_string) = @_;
$keyword_string = trim($keyword_string);
- return [] if (!$keyword_string || !Bugzilla->user->in_group('editbugs'));
+ return [] if (!$keyword_string
+ || !Bugzilla->user->in_group('editbugs', $product->id));
my %keywords;
foreach my $keyword (split(/[\s,]+/, $keyword_string)) {
@@ -801,7 +804,7 @@ sub _check_qa_contact {
$name = trim($name);
my $id;
- if (!$user->in_group("editbugs") || !$name) {
+ if (!$user->in_group('editbugs', $component->product_id) || !$name) {
# We want to insert NULL into the database if we get a 0.
$id = $component->default_qa_contact->id || undef;
} else {
@@ -1207,14 +1210,16 @@ sub user {
my $user = Bugzilla->user;
my $canmove = Bugzilla->params->{'move-enabled'} && $user->is_mover;
- my $unknown_privileges = $user->in_group("editbugs");
+ my $prod_id = $self->{'product_id'};
+
+ my $unknown_privileges = $user->in_group('editbugs', $prod_id);
my $canedit = $unknown_privileges
|| $user->id == $self->{assigned_to_id}
|| (Bugzilla->params->{'useqacontact'}
&& $self->{'qa_contact_id'}
&& $user->id == $self->{qa_contact_id});
my $canconfirm = $unknown_privileges
- || $user->in_group("canconfirm");
+ || $user->in_group('canconfirm', $prod_id);
my $isreporter = $user->id
&& $user->id == $self->{reporter_id};
@@ -1824,19 +1829,19 @@ sub check_can_change_field {
# $PrivilegesRequired = 2 : the assignee or an empowered user;
# $PrivilegesRequired = 3 : an empowered user.
- # Allow anyone with "editbugs" privs to change anything.
- if ($user->in_group('editbugs')) {
+ # Allow anyone with (product-specific) "editbugs" privs to change anything.
+ if ($user->in_group('editbugs', $self->{'product_id'})) {
return 1;
}
- # *Only* users with "canconfirm" privs can confirm bugs.
+ # *Only* users with (product-specific) "canconfirm" privs can confirm bugs.
if ($field eq 'canconfirm'
|| ($field eq 'bug_status'
&& $oldvalue eq 'UNCONFIRMED'
&& is_open_state($newvalue)))
{
$$PrivilegesRequired = 3;
- return $user->in_group('canconfirm');
+ return $user->in_group('canconfirm', $self->{'product_id'});
}
# Make sure that a valid bug ID has been given.
diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm
index 4b9856feb..657d0f728 100644
--- a/Bugzilla/Component.pm
+++ b/Bugzilla/Component.pm
@@ -171,6 +171,15 @@ sub initial_cc {
return $self->{'initial_cc'};
}
+sub product {
+ my $self = shift;
+ if (!defined $self->{'product'}) {
+ require Bugzilla::Product; # We cannot |use| it.
+ $self->{'product'} = new Bugzilla::Product($self->product_id);
+ }
+ return $self->{'product'};
+}
+
###############################
#### Accessors ####
###############################
@@ -229,7 +238,8 @@ Bugzilla::Component - Bugzilla product component class.
my $product_id = $component->product_id;
my $default_assignee = $component->default_assignee;
my $default_qa_contact = $component->default_qa_contact;
- my $initial_cc = $component->initial_cc
+ my $initial_cc = $component->initial_cc;
+ my $product = $component->product;
my $bug_flag_types = $component->flag_types->{'bug'};
my $attach_flag_types = $component->flag_types->{'attachment'};
@@ -305,6 +315,14 @@ Initial CC List.
Returns: Two references to an array of flagtype objects.
+=item C<product()>
+
+ Description: Returns the product the component belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Product object.
+
=back
=head1 SUBROUTINES
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 9aefea429..7fb95e8f2 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -101,6 +101,7 @@ use File::Basename;
FULLTEXT_BUGLIST_LIMIT
ADMIN_GROUP_NAME
+ PER_PRODUCT_PRIVILEGES
SENDMAIL_EXE
SENDMAIL_PATH
@@ -289,6 +290,9 @@ use constant FULLTEXT_BUGLIST_LIMIT => 200;
# Default administration group name.
use constant ADMIN_GROUP_NAME => 'admin';
+# Privileges which can be per-product.
+use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
+
# Path to sendmail.exe (Windows only)
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
# Paths to search for the sendmail binary (non-Windows)
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 4235be5ad..6846691e2 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -777,6 +777,12 @@ use constant ABSTRACT_SCHEMA => {
membercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
othercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
canedit => {TYPE => 'BOOLEAN', NOTNULL => 1},
+ editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
+ editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
+ canconfirm => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
],
INDEXES => [
group_control_map_product_id_idx =>
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 863ce9bfa..9592976b6 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -501,6 +501,14 @@ sub update_table_definitions {
$dbh->bz_alter_column('longdescs', 'thetext',
{ TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
+ # 2006-10-20 LpSolit@gmail.com - Bug 189627
+ $dbh->bz_add_column('group_control_map', 'editcomponents',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_column('group_control_map', 'editbugs',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_column('group_control_map', 'canconfirm',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm
index 6f65eae29..4edc7ef85 100644
--- a/Bugzilla/Product.pm
+++ b/Bugzilla/Product.pm
@@ -85,7 +85,10 @@ sub group_controls {
group_control_map.entry,
group_control_map.membercontrol,
group_control_map.othercontrol,
- group_control_map.canedit
+ group_control_map.canedit,
+ group_control_map.editcomponents,
+ group_control_map.editbugs,
+ group_control_map.canconfirm
FROM groups
LEFT JOIN group_control_map
ON groups.id = group_control_map.group_id
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index b3bce9087..ff61034dd 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -482,8 +482,32 @@ sub bless_groups {
}
sub in_group {
- my ($self, $group) = @_;
- return exists $self->groups->{$group} ? 1 : 0;
+ my ($self, $group, $product_id) = @_;
+ if (exists $self->groups->{$group}) {
+ return 1;
+ }
+ elsif ($product_id && detaint_natural($product_id)) {
+ # Make sure $group exists on a per-product basis.
+ return 0 unless (grep {$_ eq $group} PER_PRODUCT_PRIVILEGES);
+
+ $self->{"product_$product_id"} = {} unless exists $self->{"product_$product_id"};
+ if (!defined $self->{"product_$product_id"}->{$group}) {
+ my $dbh = Bugzilla->dbh;
+ my $in_group = $dbh->selectrow_array(
+ "SELECT 1
+ FROM group_control_map
+ WHERE product_id = ?
+ AND $group != 0
+ AND group_id IN (" . $self->groups_as_string . ") " .
+ $dbh->sql_limit(1),
+ undef, $product_id);
+
+ $self->{"product_$product_id"}->{$group} = $in_group ? 1 : 0;
+ }
+ return $self->{"product_$product_id"}->{$group};
+ }
+ # If we come here, then the user is not in the requested group.
+ return 0;
}
sub in_group_id {
@@ -492,6 +516,26 @@ sub in_group_id {
return exists $j{$id} ? 1 : 0;
}
+sub get_products_by_permission {
+ my ($self, $group) = @_;
+ # Make sure $group exists on a per-product basis.
+ return [] unless (grep {$_ eq $group} PER_PRODUCT_PRIVILEGES);
+
+ my $product_ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT DISTINCT product_id
+ FROM group_control_map
+ WHERE $group != 0
+ AND group_id IN(" . $self->groups_as_string . ")");
+
+ # No need to go further if the user has no "special" privs.
+ return [] unless scalar(@$product_ids);
+
+ # We will restrict the list to products the user can see.
+ my $selectable_products = $self->get_selectable_products;
+ my @products = grep {lsearch($product_ids, $_->id) > -1} @$selectable_products;
+ return \@products;
+}
+
sub can_see_user {
my ($self, $otherUser) = @_;
my $query;
@@ -667,15 +711,15 @@ sub can_enter_product {
}
# It could be closed for bug entry...
elsif ($product->disallow_new) {
- ThrowUserError('product_disabled', {product => $product->name});
+ ThrowUserError('product_disabled', {product => $product});
}
# It could have no components...
elsif (!@{$product->components}) {
- ThrowUserError('missing_component', {product => $product->name});
+ ThrowUserError('missing_component', {product => $product});
}
# It could have no versions...
elsif (!@{$product->versions}) {
- ThrowUserError ('missing_version', {product => $product->name});
+ ThrowUserError ('missing_version', {product => $product});
}
die "can_enter_product reached an unreachable location.";
@@ -726,6 +770,20 @@ sub get_accessible_products {
return [ values %products ];
}
+sub check_can_admin_product {
+ my ($self, $product_name) = @_;
+
+ # First make sure the product name is valid.
+ my $product = Bugzilla::Product::check_product($product_name);
+
+ ($self->in_group('editcomponents', $product->id)
+ && $self->can_see_product($product->name))
+ || ThrowUserError('product_access_denied', {product => $product->name});
+
+ # Return the validated product object.
+ return $product;
+}
+
sub can_request_flag {
my ($self, $flag_type) = @_;
@@ -861,23 +919,23 @@ sub derive_regexp_groups {
sub product_responsibilities {
my $self = shift;
+ my $dbh = Bugzilla->dbh;
return $self->{'product_resp'} if defined $self->{'product_resp'};
return [] unless $self->id;
- my $h = Bugzilla->dbh->selectall_arrayref(
- qq{SELECT products.name AS productname,
- components.name AS componentname,
- initialowner,
- initialqacontact
- FROM products, components
- WHERE products.id = components.product_id
- AND ? IN (initialowner, initialqacontact)
- },
- {'Slice' => {}}, $self->id);
- $self->{'product_resp'} = $h;
-
- return $h;
+ my $comp_ids = $dbh->selectcol_arrayref('SELECT id FROM components
+ WHERE initialowner = ?
+ OR initialqacontact = ?',
+ undef, ($self->id, $self->id));
+
+ # We cannot |use| it, because Component.pm already |use|s User.pm.
+ require Bugzilla::Component;
+ my @components;
+ push(@components, new Bugzilla::Component($_)) foreach (@$comp_ids);
+
+ $self->{'product_resp'} = \@components;
+ return $self->{'product_resp'};
}
sub can_bless {
@@ -1797,9 +1855,11 @@ Returns a string containing a comma-separated list of numeric group ids. If
the user is not a member of any groups, returns "-1". This is most often used
within an SQL IN() function.
-=item C<in_group>
+=item C<in_group($group_name, $product_id)>
-Determines whether or not a user is in the given group by name.
+Determines whether or not a user is in the given group by name.
+If $product_id is given, it also checks for local privileges for
+this product.
=item C<in_group_id>
@@ -1814,6 +1874,12 @@ The arrayref consists of the groups the user can bless, taking into account
that having editusers permissions means that you can bless all groups, and
that you need to be aware of a group in order to bless a group.
+=item C<get_products_by_permission($group)>
+
+Returns a list of product objects for which the user has $group privileges
+and which he can access.
+$group must be one of the groups defined in PER_PRODUCT_PRIVILEGES.
+
=item C<can_see_user(user)>
Returns 1 if the specified user account exists and is visible to the user,
@@ -1888,6 +1954,14 @@ method should be called in such a case to force reresolution of these groups.
Returns: an array of product objects.
+=item C<check_can_admin_product($product_name)>
+
+ Description: Checks whether the user is allowed to administrate the product.
+
+ Params: $product_name - a product name.
+
+ Returns: On success, a product object. On failure, an error is thrown.
+
=item C<can_request_flag($flag_type)>
Description: Checks whether the user can request flags of the given type.
@@ -1937,29 +2011,8 @@ list).
=item C<product_responsibilities>
-Retrieve user's product responsibilities as a list of hashes.
-One hash per Bugzilla component the user has a responsibility for.
-These are the hash keys:
-
-=over
-
-=item productname
-
-Name of the product.
-
-=item componentname
-
-Name of the component.
-
-=item initialowner
-
-User ID of default assignee.
-
-=item initialqacontact
-
-User ID of default QA contact.
-
-=back
+Retrieve user's product responsibilities as a list of component objects.
+Each object is a component the user has a responsibility for.
=item C<can_bless>