aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Config.pm3
-rw-r--r--Bugzilla/Config/GroupSecurity.pm6
-rw-r--r--Bugzilla/Search.pm20
-rw-r--r--Bugzilla/User.pm241
-rwxr-xr-xrequest.cgi21
-rw-r--r--template/en/default/admin/params/groupsecurity.html.tmpl13
-rw-r--r--template/en/default/admin/products/groupcontrol/edit.html.tmpl14
-rw-r--r--template/en/default/bug/create/create.html.tmpl2
-rw-r--r--template/en/default/bug/edit.html.tmpl5
9 files changed, 228 insertions, 97 deletions
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
index 69becbf95..5d9a8061c 100644
--- a/Bugzilla/Config.pm
+++ b/Bugzilla/Config.pm
@@ -196,6 +196,9 @@ sub update_params {
$param->{'utf8'} = 1 if $new_install;
+ # Bug 452525: OR based groups are on by default for new installations
+ $param->{'or_groups'} = 1 if $new_install;
+
# --- REMOVE OLD PARAMS ---
my %oldparams;
diff --git a/Bugzilla/Config/GroupSecurity.pm b/Bugzilla/Config/GroupSecurity.pm
index 1d3878415..076f841fd 100644
--- a/Bugzilla/Config/GroupSecurity.pm
+++ b/Bugzilla/Config/GroupSecurity.pm
@@ -83,6 +83,12 @@ sub get_param_list {
name => 'strict_isolation',
type => 'b',
default => 0
+ },
+
+ {
+ name => 'or_groups',
+ type => 'b',
+ default => 0
} );
return @param_list;
}
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index e89a9b361..e546be6d9 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -1228,9 +1228,12 @@ sub _standard_joins {
push(@joins, $security_join);
if ($user->id) {
- $security_join->{extra} =
- ["NOT (" . $user->groups_in_sql('security_map.group_id') . ")"];
-
+ # See also _standard_joins for the other half of the below statement
+ if (!Bugzilla->params->{'or_groups'}) {
+ $security_join->{extra} =
+ ["NOT (" . $user->groups_in_sql('security_map.group_id') . ")"];
+ }
+
my $security_cc_join = {
table => 'cc',
as => 'security_cc',
@@ -1304,10 +1307,17 @@ sub _standard_where {
# until their group controls are set. So if a bug has a NULL creation_ts,
# it shouldn't show up in searches at all.
my @where = ('bugs.creation_ts IS NOT NULL');
-
- my $security_term = 'security_map.group_id IS NULL';
my $user = $self->_user;
+ my $security_term = '';
+ # See also _standard_joins for the other half of the below statement
+ if (Bugzilla->params->{'or_groups'}) {
+ $security_term .= " (security_map.group_id IS NULL OR security_map.group_id IN (" . $user->groups_as_string . "))";
+ }
+ else {
+ $security_term = 'security_map.group_id IS NULL';
+ }
+
if ($user->id) {
my $userid = $user->id;
# This indentation makes the resulting SQL more readable.
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index 5c875918f..1bd6c0b19 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -875,15 +875,34 @@ sub can_edit_product {
my ($self, $prod_id) = @_;
my $dbh = Bugzilla->dbh;
- my $has_external_groups =
- $dbh->selectrow_array('SELECT 1
- FROM group_control_map
- WHERE product_id = ?
- AND canedit != 0
- AND group_id NOT IN(' . $self->groups_as_string . ')',
- undef, $prod_id);
+ if (Bugzilla->params->{'or_groups'}) {
+ my $groups = $self->groups_as_string;
+ # For or-groups, we check if there are any can_edit groups for the
+ # product, and if the user is in any of them. If there are none or
+ # the user is in at least one of them, they can edit the product
+ my ($cnt_can_edit, $cnt_group_member) = $dbh->selectrow_array(
+ "SELECT SUM(p.cnt_can_edit),
+ SUM(p.cnt_group_member)
+ FROM (SELECT CASE WHEN canedit = 1 THEN 1 ELSE 0 END AS cnt_can_edit,
+ CASE WHEN canedit = 1 AND group_id IN ($groups) THEN 1 ELSE 0 END AS cnt_group_member
+ FROM group_control_map
+ WHERE product_id = $prod_id) AS p");
+ return ($cnt_can_edit == 0 or $cnt_group_member > 0);
+ }
+ else {
+ # For and-groups, a user needs to be in all canedit groups. Therefore
+ # if the user is not in a can_edit group for the product, they cannot
+ # edit the product.
+ my $has_external_groups =
+ $dbh->selectrow_array('SELECT 1
+ FROM group_control_map
+ WHERE product_id = ?
+ AND canedit != 0
+ AND group_id NOT IN(' . $self->groups_as_string . ')',
+ undef, $prod_id);
- return !$has_external_groups;
+ return !$has_external_groups;
+ }
}
sub can_see_bug {
@@ -904,9 +923,6 @@ sub visible_bugs {
my @check_ids = grep(!exists $visible_cache->{$_}, @bug_ids);
if (@check_ids) {
- my $dbh = Bugzilla->dbh;
- my $user_id = $self->id;
-
foreach my $id (@check_ids) {
my $orig_id = $id;
detaint_natural($id)
@@ -914,56 +930,113 @@ sub visible_bugs {
function => 'Bugzilla::User->visible_bugs'});
}
- my $sth;
- # Speed up the can_see_bug case.
- if (scalar(@check_ids) == 1) {
- $sth = $self->{_sth_one_visible_bug};
- }
- $sth ||= $dbh->prepare(
- # This checks for groups that the bug is in that the user
- # *isn't* in. Then, in the Perl code below, we check if
- # the user can otherwise access the bug (for example, by being
- # the assignee or QA Contact).
- #
- # The DISTINCT exists because the bug could be in *several*
- # groups that the user isn't in, but they will all return the
- # same result for bug_group_map.bug_id (so DISTINCT filters
- # out duplicate rows).
- "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
- reporter_accessible, cclist_accessible, cc.who,
- bug_group_map.bug_id
- FROM bugs
- LEFT JOIN cc
- ON cc.bug_id = bugs.bug_id
- AND cc.who = $user_id
- LEFT JOIN bug_group_map
- ON bugs.bug_id = bug_group_map.bug_id
- AND bug_group_map.group_id NOT IN ("
- . $self->groups_as_string . ')
- WHERE bugs.bug_id IN (' . join(',', ('?') x @check_ids) . ')
- AND creation_ts IS NOT NULL ');
- if (scalar(@check_ids) == 1) {
- $self->{_sth_one_visible_bug} = $sth;
- }
-
- $sth->execute(@check_ids);
- my $use_qa_contact = Bugzilla->params->{'useqacontact'};
- while (my $row = $sth->fetchrow_arrayref) {
- my ($bug_id, $reporter, $owner, $qacontact, $reporter_access,
- $cclist_access, $isoncclist, $missinggroup) = @$row;
- $visible_cache->{$bug_id} ||=
- ((($reporter == $user_id) && $reporter_access)
- || ($use_qa_contact
- && $qacontact && ($qacontact == $user_id))
- || ($owner == $user_id)
- || ($isoncclist && $cclist_access)
- || !$missinggroup) ? 1 : 0;
- }
+ Bugzilla->params->{'or_groups'}
+ ? $self->_visible_bugs_check_or(\@check_ids)
+ : $self->_visible_bugs_check_and(\@check_ids);
}
return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
}
+sub _visible_bugs_check_or {
+ my ($self, $check_ids) = @_;
+ my $visible_cache = $self->{_visible_bugs_cache};
+ my $dbh = Bugzilla->dbh;
+ my $user_id = $self->id;
+
+ my $sth;
+ # Speed up the can_see_bug case.
+ if (scalar(@$check_ids) == 1) {
+ $sth = $self->{_sth_one_visible_bug};
+ }
+ my $query = qq{
+ SELECT DISTINCT bugs.bug_id
+ FROM bugs
+ LEFT JOIN bug_group_map AS security_map ON bugs.bug_id = security_map.bug_id
+ LEFT JOIN cc AS security_cc ON bugs.bug_id = security_cc.bug_id AND security_cc.who = $user_id
+ WHERE bugs.bug_id IN (} . join(',', ('?') x @$check_ids) . qq{)
+ AND ((security_map.group_id IS NULL OR security_map.group_id IN (} . $self->groups_as_string . qq{))
+ OR (bugs.reporter_accessible = 1 AND bugs.reporter = $user_id)
+ OR (bugs.cclist_accessible = 1 AND security_cc.who IS NOT NULL)
+ OR bugs.assigned_to = $user_id
+ };
+
+ if (Bugzilla->params->{'useqacontact'}) {
+ $query .= " OR bugs.qa_contact = $user_id";
+ }
+ $query .= ')';
+
+ $sth ||= $dbh->prepare($query);
+ if (scalar(@$check_ids) == 1) {
+ $self->{_sth_one_visible_bug} = $sth;
+ }
+
+ # Set all bugs as non visible
+ foreach my $bug_id (@$check_ids) {
+ $visible_cache->{$bug_id} = 0;
+ }
+
+ # Now get the bugs the user can see
+ my $visible_bug_ids = $dbh->selectcol_arrayref($sth, undef, @$check_ids);
+ foreach my $bug_id (@$visible_bug_ids) {
+ $visible_cache->{$bug_id} = 1;
+ }
+}
+
+sub _visible_bugs_check_and {
+ my ($self, $check_ids) = @_;
+ my $visible_cache = $self->{_visible_bugs_cache};
+ my $dbh = Bugzilla->dbh;
+ my $user_id = $self->id;
+
+ my $sth;
+ # Speed up the can_see_bug case.
+ if (scalar(@$check_ids) == 1) {
+ $sth = $self->{_sth_one_visible_bug};
+ }
+ $sth ||= $dbh->prepare(
+ # This checks for groups that the bug is in that the user
+ # *isn't* in. Then, in the Perl code below, we check if
+ # the user can otherwise access the bug (for example, by being
+ # the assignee or QA Contact).
+ #
+ # The DISTINCT exists because the bug could be in *several*
+ # groups that the user isn't in, but they will all return the
+ # same result for bug_group_map.bug_id (so DISTINCT filters
+ # out duplicate rows).
+ "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
+ reporter_accessible, cclist_accessible, cc.who,
+ bug_group_map.bug_id
+ FROM bugs
+ LEFT JOIN cc
+ ON cc.bug_id = bugs.bug_id
+ AND cc.who = $user_id
+ LEFT JOIN bug_group_map
+ ON bugs.bug_id = bug_group_map.bug_id
+ AND bug_group_map.group_id NOT IN ("
+ . $self->groups_as_string . ')
+ WHERE bugs.bug_id IN (' . join(',', ('?') x @$check_ids) . ')
+ AND creation_ts IS NOT NULL ');
+ if (scalar(@$check_ids) == 1) {
+ $self->{_sth_one_visible_bug} = $sth;
+ }
+
+ $sth->execute(@$check_ids);
+ my $use_qa_contact = Bugzilla->params->{'useqacontact'};
+ while (my $row = $sth->fetchrow_arrayref) {
+ my ($bug_id, $reporter, $owner, $qacontact, $reporter_access,
+ $cclist_access, $isoncclist, $missinggroup) = @$row;
+ $visible_cache->{$bug_id} ||=
+ ((($reporter == $user_id) && $reporter_access)
+ || ($use_qa_contact
+ && $qacontact && ($qacontact == $user_id))
+ || ($owner == $user_id)
+ || ($isoncclist && $cclist_access)
+ || !$missinggroup) ? 1 : 0;
+ }
+
+}
+
sub clear_product_cache {
my $self = shift;
delete $self->{enterable_products};
@@ -983,14 +1056,25 @@ sub get_selectable_products {
my $class_restricted = Bugzilla->params->{'useclassification'} && $class_id;
if (!defined $self->{selectable_products}) {
- my $query = "SELECT id " .
- " FROM products " .
- "LEFT JOIN group_control_map " .
- "ON group_control_map.product_id = products.id " .
- " AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY .
- " AND group_id NOT IN(" . $self->groups_as_string . ") " .
- " WHERE group_id IS NULL " .
- "ORDER BY name";
+ my $query =
+ Bugzilla->params->{'or_groups'}
+ ? "SELECT id
+ FROM products
+ WHERE id NOT IN (
+ SELECT product_id
+ FROM group_control_map
+ WHERE group_control_map.membercontrol = " . CONTROLMAPMANDATORY . "
+ AND group_id NOT IN (" . $self->groups_as_string . ")
+ )
+ ORDER BY name"
+ : "SELECT id
+ FROM products
+ LEFT JOIN group_control_map
+ ON group_control_map.product_id = products.id
+ AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY . "
+ AND group_id NOT IN(" . $self->groups_as_string . ")
+ WHERE group_id IS NULL
+ ORDER BY name";
my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query);
$self->{selectable_products} = Bugzilla::Product->new_from_list($prod_ids);
@@ -1084,14 +1168,21 @@ sub get_enterable_products {
}
# All products which the user has "Entry" access to.
- my $enterable_ids = $dbh->selectcol_arrayref(
+ my $query =
'SELECT products.id FROM products
- LEFT JOIN group_control_map
- ON group_control_map.product_id = products.id
- AND group_control_map.entry != 0
- AND group_id NOT IN (' . $self->groups_as_string . ')
- WHERE group_id IS NULL
- AND products.isactive = 1');
+ LEFT JOIN group_control_map
+ ON group_control_map.product_id = products.id
+ AND group_control_map.entry != 0';
+
+ if (Bugzilla->params->{'or_groups'}) {
+ $query .= " WHERE (group_id IN (" . $self->groups_as_string . ")" .
+ " OR group_id IS NULL)";
+ } else {
+ $query .= " AND group_id NOT IN (" . $self->groups_as_string . ")" .
+ " WHERE group_id IS NULL"
+ }
+ $query .= " AND products.isactive = 1";
+ my $enterable_ids = $dbh->selectcol_arrayref($query);
if (scalar @$enterable_ids) {
# And all of these products must have at least one component
@@ -2476,6 +2567,12 @@ Returns 1 if the specified user account exists and is visible to the user,
Determines if, given a product id, the user can edit bugs in this product
at all.
+=item C<visible_bugs($bugs)>
+
+Description: Determines if a list of bugs are visible to the user.
+Params: C<$bugs> - An arrayref of Bugzilla::Bug objects or bug ids
+Returns: An arrayref of the bug ids that the user can see
+
=item C<can_see_bug(bug_id)>
Determines if the user can see the specified bug.
@@ -2846,8 +2943,6 @@ L<Bugzilla|Bugzilla>
=item extern_id
-=item visible_bugs
-
=item UPDATE_COLUMNS
=back
diff --git a/request.cgi b/request.cgi
index 118935092..b771cee37 100755
--- a/request.cgi
+++ b/request.cgi
@@ -118,20 +118,27 @@ sub queue {
ON bugs.product_id = products.id
INNER JOIN components
ON bugs.component_id = components.id
- LEFT JOIN bug_group_map AS bgmap
- ON bgmap.bug_id = bugs.bug_id
- AND bgmap.group_id NOT IN (" .
- $user->groups_as_string . ")
LEFT JOIN bug_group_map AS privs
ON privs.bug_id = bugs.bug_id
LEFT JOIN cc AS ccmap
ON ccmap.who = $userid
AND ccmap.bug_id = bugs.bug_id
- " .
+ LEFT JOIN bug_group_map AS bgmap
+ ON bgmap.bug_id = bugs.bug_id
+ ";
+
+ if (Bugzilla->params->{or_groups}) {
+ $query .= " AND bgmap.group_id IN (" . $user->groups_as_string . ")";
+ $query .= " WHERE (privs.group_id IS NULL OR bgmap.group_id IS NOT NULL OR";
+ }
+ else {
+ $query .= " AND bgmap.group_id NOT IN (" . $user->groups_as_string . ")";
+ $query .= " WHERE (bgmap.group_id IS NULL OR";
+ }
# Weed out bug the user does not have access to
- " WHERE ((bgmap.group_id IS NULL) OR
- (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
+ $query .=
+ " (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
(bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR
(bugs.assigned_to = $userid) " .
(Bugzilla->params->{'useqacontact'} ? "OR
diff --git a/template/en/default/admin/params/groupsecurity.html.tmpl b/template/en/default/admin/params/groupsecurity.html.tmpl
index 4f0b4919b..c9dbf5b4b 100644
--- a/template/en/default/admin/params/groupsecurity.html.tmpl
+++ b/template/en/default/admin/params/groupsecurity.html.tmpl
@@ -37,13 +37,20 @@
usevisibilitygroups => "Do you wish to restrict visibility of users to members of " _
"specific groups?",
-
+
strict_isolation => "Don't allow users to be assigned to, " _
"be qa-contacts on, " _
"be added to CC list, " _
"or make or remove dependencies " _
"involving any bug that is in a product on which that " _
- "user is forbidden to edit.",
-
+ "user is forbidden to edit.",
+
+ or_groups => "Define the visibility of a $terms.bug which is in multiple " _
+ "groups. If this is on (recommended), a user only needs to " _
+ "be a member of one of the $terms.bug's groups in order to " _
+ "view it. If it is off, a user needs to be a member of all " _
+ "the $terms.bug's groups. Note that in either case, if the " _
+ "user has a role on the $terms.bug (e.g. reporter) that may " _
+ "also affect their permissions."
}
%]
diff --git a/template/en/default/admin/products/groupcontrol/edit.html.tmpl b/template/en/default/admin/products/groupcontrol/edit.html.tmpl
index 889647e7e..1ba600271 100644
--- a/template/en/default/admin/products/groupcontrol/edit.html.tmpl
+++ b/template/en/default/admin/products/groupcontrol/edit.html.tmpl
@@ -129,15 +129,17 @@ product.
</p>
<p>
If any group has <b>Entry</b> selected, then this product will
-restrict [% terms.bug %] entry to only those users who are members of all the
-groups with entry selected.
+restrict [% terms.bug %] entry to only those users who are members of
+[%+ IF Param('or_groups') %]at least one of[% ELSE %]all[% END %] the groups
+with entry selected.
</p>
<p>
If any group has <b>Canedit</b> selected, then this product
-will be read-only for any users who are not members of all of
-the groups with Canedit selected. ONLY users who are members of
-all the canedit groups will be able to edit. This is an additional
-restriction that further restricts what can be edited by a user.
+will be read-only for any users who are not members of
+[%+ IF Param('or_groups') %]one[% ELSE %]all[% END %] of the groups with
+Canedit selected. ONLY users who are members of
+[%+ IF Param('or_groups') %]at least one of[% ELSE %]all[% END %] the canedit groups
+will be able to edit. This is an additional restriction that further restricts what can be edited by a user.
</p>
<p>
The following settings control let you choose privileges on a <b>per-product basis</b>.
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 3d150bf89..f4c60ad24 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -634,7 +634,7 @@ TUI_hide_default('attachment_text_field');
<td colspan="3">
<br>
<strong>
- Only users in all of the selected groups can view this
+ Only users in [%+ IF Param('or_groups') %]at least one[% ELSE %]all[% END %] of the selected groups can view this
[%+ terms.bug %]:
</strong>
<br>
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index 202a981ea..0ddc7cc06 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -651,8 +651,9 @@
[% IF NOT emitted_description %]
[% emitted_description = 1 %]
<div id="bz_restrict_group_visibility_help">
- <b>Only users in all of the selected groups can view this
- [%+ terms.bug %]:</b>
+ <b>Only users in
+ [%+ IF Param('or_groups') %]at least one[% ELSE %]all[% END %]
+ of the selected groups can view this [% terms.bug %]:</b>
<p class="instructions">
Unchecking all boxes makes this a more public [% terms.bug %].
</p>