From 9e7ad08c56aacbe889b614ee2394b58b108c9ca2 Mon Sep 17 00:00:00 2001 From: Simon Green Date: Wed, 18 Dec 2013 20:47:13 +1000 Subject: Bug 452525 - Allow the option of "OR" groups ("any of the groups" instead of "all of the groups") r=gerv, a=sgreen --- Bugzilla/Config.pm | 3 + Bugzilla/Config/GroupSecurity.pm | 6 + Bugzilla/Search.pm | 20 +++- Bugzilla/User.pm | 241 +++++++++++++++++++++++++++------------ 4 files changed, 192 insertions(+), 78 deletions(-) (limited to 'Bugzilla') 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 + +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 Determines if the user can see the specified bug. @@ -2846,8 +2943,6 @@ L =item extern_id -=item visible_bugs - =item UPDATE_COLUMNS =back -- cgit v1.2.3-24-g4f1b