From 65d3dc0ec33fd76229dc02536a74ccac5408876b Mon Sep 17 00:00:00 2001 From: "bugreport%peshkin.net" <> Date: Mon, 23 Sep 2002 00:14:48 +0000 Subject: bug 157756 - Groups_20020716_Branch Tracking : > 55 groups now supported r=bbaetz, gerv --- Bug.pm | 41 +- Bugzilla/Bug.pm | 41 +- Bugzilla/Search.pm | 36 +- CGI.pl | 69 +- attachment.cgi | 1 - bug_form.pl | 108 +-- buglist.cgi | 29 +- checksetup.pl | 794 ++++++++++++++------- contrib/bug_email.pl | 2 +- docs/sgml/administration.sgml | 64 +- docs/xml/administration.xml | 64 +- duplicates.cgi | 7 +- editgroups.cgi | 506 ++++++------- editproducts.cgi | 185 +---- editusers.cgi | 286 ++++---- enter_bug.cgi | 92 +-- globals.pl | 369 ++++++---- index.cgi | 1 - long_list.cgi | 6 +- post_bug.cgi | 28 +- process_bug.cgi | 103 +-- processmail | 35 +- query.cgi | 26 +- sanitycheck.cgi | 90 ++- show_activity.cgi | 5 +- showdependencygraph.cgi | 16 +- showdependencytree.cgi | 23 +- sidebar.cgi | 6 +- .../en/default/account/prefs/permissions.html.tmpl | 34 +- template/en/default/global/useful-links.html.tmpl | 2 +- template/en/default/sidebar.xul.tmpl | 2 +- token.cgi | 2 + userprefs.cgi | 28 +- votes.cgi | 3 +- 34 files changed, 1695 insertions(+), 1409 deletions(-) diff --git a/Bug.pm b/Bug.pm index 7e703d14f..3dadd3cd5 100755 --- a/Bug.pm +++ b/Bug.pm @@ -37,8 +37,8 @@ use Bugzilla::Util; for my $key (qw (bug_id alias product version rep_platform op_sys bug_status resolution priority bug_severity component assigned_to reporter bug_file_loc short_desc target_milestone - qa_contact status_whiteboard creation_ts groupset - delta_ts votes whoid usergroupset comment query error) ){ + qa_contact status_whiteboard creation_ts + delta_ts votes whoid comment query error) ){ $ok_field{$key}++; } @@ -105,10 +105,6 @@ sub initBug { $self->{'whoid'} = $user_id; - &::SendSQL("SELECT groupset FROM profiles WHERE userid=$self->{'whoid'}"); - my $usergroupset = &::FetchOneColumn(); - if (!$usergroupset) { $usergroupset = '0' } - $self->{'usergroupset'} = $usergroupset; my $query = " select @@ -116,7 +112,7 @@ sub initBug { resolution, priority, bug_severity, components.name, assigned_to, reporter, bug_file_loc, short_desc, target_milestone, qa_contact, status_whiteboard, date_format(creation_ts,'%Y-%m-%d %H:%i'), - groupset, delta_ts, sum(votes.count) + delta_ts, sum(votes.count) from bugs left join votes using(bug_id), products, components where bugs.bug_id = $bug_id @@ -124,10 +120,10 @@ sub initBug { AND components.id = bugs.component_id group by bugs.bug_id"; - &::SendSQL(&::SelectVisible($query, $user_id, $usergroupset)); - my @row; + &::SendSQL($query); + my @row = (); - if (@row = &::FetchSQLData()) { + if ((@row = &::FetchSQLData()) && &::CanSeeBug($bug_id, $self->{'whoid'})) { my $count = 0; my %fields; foreach my $field ("bug_id", "alias", "product", "version", "rep_platform", @@ -135,24 +131,21 @@ sub initBug { "bug_severity", "component", "assigned_to", "reporter", "bug_file_loc", "short_desc", "target_milestone", "qa_contact", "status_whiteboard", "creation_ts", - "groupset", "delta_ts", "votes") { + "delta_ts", "votes") { $fields{$field} = shift @row; if ($fields{$field}) { $self->{$field} = $fields{$field}; } $count++; } - } else { - &::SendSQL("select groupset from bugs where bug_id = $bug_id"); - if (@row = &::FetchSQLData()) { + } elsif (@row) { $self->{'bug_id'} = $bug_id; $self->{'error'} = "NotPermitted"; return $self; - } else { + } else { $self->{'bug_id'} = $bug_id; $self->{'error'} = "NotFound"; return $self; - } } $self->{'assigned_to'} = &::DBID_to_name($self->{'assigned_to'}); @@ -356,22 +349,6 @@ sub XML_Footer { return ("\n"); } -sub UserInGroup { - my $self = shift(); - my ($groupname) = (@_); - if ($self->{'usergroupset'} eq "0") { - return 0; - } - &::ConnectToDatabase(); - &::SendSQL("select (bit & $self->{'usergroupset'}) != 0 from groups where name = " - . &::SqlQuote($groupname)); - my $bit = &::FetchOneColumn(); - if ($bit) { - return 1; - } - return 0; -} - sub CanChangeField { my $self = shift(); my ($f, $oldvalue, $newvalue) = (@_); diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 7e703d14f..3dadd3cd5 100755 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -37,8 +37,8 @@ use Bugzilla::Util; for my $key (qw (bug_id alias product version rep_platform op_sys bug_status resolution priority bug_severity component assigned_to reporter bug_file_loc short_desc target_milestone - qa_contact status_whiteboard creation_ts groupset - delta_ts votes whoid usergroupset comment query error) ){ + qa_contact status_whiteboard creation_ts + delta_ts votes whoid comment query error) ){ $ok_field{$key}++; } @@ -105,10 +105,6 @@ sub initBug { $self->{'whoid'} = $user_id; - &::SendSQL("SELECT groupset FROM profiles WHERE userid=$self->{'whoid'}"); - my $usergroupset = &::FetchOneColumn(); - if (!$usergroupset) { $usergroupset = '0' } - $self->{'usergroupset'} = $usergroupset; my $query = " select @@ -116,7 +112,7 @@ sub initBug { resolution, priority, bug_severity, components.name, assigned_to, reporter, bug_file_loc, short_desc, target_milestone, qa_contact, status_whiteboard, date_format(creation_ts,'%Y-%m-%d %H:%i'), - groupset, delta_ts, sum(votes.count) + delta_ts, sum(votes.count) from bugs left join votes using(bug_id), products, components where bugs.bug_id = $bug_id @@ -124,10 +120,10 @@ sub initBug { AND components.id = bugs.component_id group by bugs.bug_id"; - &::SendSQL(&::SelectVisible($query, $user_id, $usergroupset)); - my @row; + &::SendSQL($query); + my @row = (); - if (@row = &::FetchSQLData()) { + if ((@row = &::FetchSQLData()) && &::CanSeeBug($bug_id, $self->{'whoid'})) { my $count = 0; my %fields; foreach my $field ("bug_id", "alias", "product", "version", "rep_platform", @@ -135,24 +131,21 @@ sub initBug { "bug_severity", "component", "assigned_to", "reporter", "bug_file_loc", "short_desc", "target_milestone", "qa_contact", "status_whiteboard", "creation_ts", - "groupset", "delta_ts", "votes") { + "delta_ts", "votes") { $fields{$field} = shift @row; if ($fields{$field}) { $self->{$field} = $fields{$field}; } $count++; } - } else { - &::SendSQL("select groupset from bugs where bug_id = $bug_id"); - if (@row = &::FetchSQLData()) { + } elsif (@row) { $self->{'bug_id'} = $bug_id; $self->{'error'} = "NotPermitted"; return $self; - } else { + } else { $self->{'bug_id'} = $bug_id; $self->{'error'} = "NotFound"; return $self; - } } $self->{'assigned_to'} = &::DBID_to_name($self->{'assigned_to'}); @@ -356,22 +349,6 @@ sub XML_Footer { return ("\n"); } -sub UserInGroup { - my $self = shift(); - my ($groupname) = (@_); - if ($self->{'usergroupset'} eq "0") { - return 0; - } - &::ConnectToDatabase(); - &::SendSQL("select (bit & $self->{'usergroupset'}) != 0 from groups where name = " - . &::SqlQuote($groupname)); - my $bit = &::FetchOneColumn(); - if ($bit) { - return 1; - } - return 0; -} - sub CanChangeField { my $self = shift(); my ($f, $oldvalue, $newvalue) = (@_); diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index 482daca5c..d6e7a9b7f 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -29,7 +29,7 @@ use strict; # The caller MUST require CGI.pl and globals.pl before using this -use vars qw($userid $usergroupset); +use vars qw($userid); package Bugzilla::Search; @@ -117,7 +117,7 @@ sub init { my @legal_fields = ("product", "version", "rep_platform", "op_sys", "bug_status", "resolution", "priority", "bug_severity", "assigned_to", "reporter", "component", - "target_milestone", "groupset"); + "target_milestone", "bug_group"); foreach my $field (keys %F) { if (lsearch(\@legal_fields, $field) != -1) { @@ -322,6 +322,12 @@ sub init { push(@wherepart, "$table.bug_id = bugs.bug_id"); $f = "$table.thetext"; }, + "^bug_group,(?!changed)" => sub { + push(@supptables, "LEFT JOIN bug_group_map bug_group_map_$chartid ON bugs.bug_id = bug_group_map_$chartid.bug_id"); + + push(@supptables, "LEFT JOIN groups groups_$chartid ON groups_$chartid.id = bug_group_map_$chartid.group_id"); + $f = "groups_$chartid.name"; + }, "^attachments\..*," => sub { my $table = "attachments_$chartid"; push(@supptables, "attachments $table"); @@ -747,7 +753,7 @@ sub init { # chart -1 is generated by other code above, not from the user- # submitted form, so we'll blindly accept any values in chart -1 if ((!$chartfields{$f}) && ($chart != -1)) { - my $errstr = "Can't use " . html_quote($f) . " as a field name. " . + my $errstr = "Can't use $f as a field name. " . "If you think you're getting this in error, please copy the " . "entire URL out of the address bar at the top of your browser " . "window and email it to <109679\@bugzilla.org>"; @@ -807,11 +813,27 @@ sub init { $suppseen{$str} = 1; } } - my $query = ("SELECT DISTINCT " . join(', ', @fields) . + my $query = ("SELECT DISTINCT " . + join(', ', @fields) . + ", COUNT(DISTINCT ugmap.group_id) AS cntuseringroups, " . + " COUNT(DISTINCT bgmap.group_id) AS cntbugingroups, " . + " ((COUNT(DISTINCT ccmap.who) AND cclist_accessible) " . + " OR ((bugs.reporter = $::userid) AND bugs.reporter_accessible) " . + " OR bugs.assigned_to = $::userid ) AS canseeanyway " . " FROM $suppstring" . - " WHERE " . join(' AND ', (@wherepart, @andlist))); - - $query = &::SelectVisible($query, $::userid, $::usergroupset); + " LEFT JOIN bug_group_map AS bgmap " . + " ON bgmap.bug_id = bugs.bug_id " . + " LEFT JOIN user_group_map AS ugmap " . + " ON bgmap.group_id = ugmap.group_id " . + " AND ugmap.user_id = $::userid " . + " AND ugmap.isbless = 0" . + " LEFT JOIN cc AS ccmap " . + " ON ccmap.who = $::userid AND ccmap.bug_id = bugs.bug_id " . + " WHERE " . join(' AND ', (@wherepart, @andlist)) . + " GROUP BY bugs.bug_id " . + " HAVING cntuseringroups = cntbugingroups" . + " OR canseeanyway" + ); if ($debug) { print "

" . value_quote($query) . "

\n"; diff --git a/CGI.pl b/CGI.pl index 0c85128d1..70c15c932 100644 --- a/CGI.pl +++ b/CGI.pl @@ -289,11 +289,6 @@ sub ValidateBugID { # converted-from-alias ID. $_[0] = $id; - # Get the values of the usergroupset and userid global variables - # and write them to local variables for use within this function, - # setting those local variables to the default value of zero if - # the global variables are undefined. - # First check that the bug exists SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id"); @@ -303,7 +298,7 @@ sub ValidateBugID { return if $skip_authorization; - return if CanSeeBug($id, $::userid, $::usergroupset); + return if CanSeeBug($id, $::userid); # The user did not pass any of the authorization tests, which means they # are not authorized to see the bug. Display an error and stop execution. @@ -438,30 +433,25 @@ sub PasswordForLogin { } sub quietly_check_login() { - $::usergroupset = '0'; - my $loginok = 0; $::disabledreason = ''; - $::userid = 0; + my $userid = 0; if (defined $::COOKIE{"Bugzilla_login"} && defined $::COOKIE{"Bugzilla_logincookie"}) { - SendSQL("SELECT profiles.userid, profiles.groupset, " . - "profiles.login_name, " . - "profiles.login_name = " . - SqlQuote($::COOKIE{"Bugzilla_login"}) . - " AND logincookies.ipaddr = " . - SqlQuote($ENV{"REMOTE_ADDR"}) . - ", profiles.disabledtext " . + SendSQL("SELECT profiles.userid," . + " profiles.login_name, " . + " profiles.disabledtext " . " FROM profiles, logincookies WHERE logincookies.cookie = " . SqlQuote($::COOKIE{"Bugzilla_logincookie"}) . - " AND profiles.userid = logincookies.userid"); + " AND profiles.userid = logincookies.userid AND" . + " profiles.login_name = " . + SqlQuote($::COOKIE{"Bugzilla_login"}) . + " AND logincookies.ipaddr = " . + SqlQuote($ENV{"REMOTE_ADDR"})); my @row; - if (@row = FetchSQLData()) { - my ($userid, $groupset, $loginname, $ok, $disabledtext) = (@row); - if ($ok) { + if (MoreSQLData()) { + ($userid, my $loginname, my $disabledtext) = FetchSQLData(); + if ($userid > 0) { if ($disabledtext eq '') { - $loginok = 1; - $::userid = $userid; - $::usergroupset = $groupset; $::COOKIE{"Bugzilla_login"} = $loginname; # Makes sure case # is in # canonical form. @@ -469,6 +459,7 @@ sub quietly_check_login() { detaint_natural($::COOKIE{"Bugzilla_logincookie"}); } else { $::disabledreason = $disabledtext; + $userid = 0; } } } @@ -478,13 +469,14 @@ sub quietly_check_login() { my $whoid = DBname_to_id($::FORM{'who'}); delete $::FORM{'who'} unless $whoid; } - if (!$loginok) { + if (!$userid) { delete $::COOKIE{"Bugzilla_login"}; } + $::userid = $userid; + ConfirmGroup($userid); $vars->{'user'} = GetUserInfo($::userid); - - return $loginok; + return $userid; } # Populate a hash with information about this user. @@ -500,10 +492,9 @@ sub GetUserInfo { $user{'login'} = $::COOKIE{"Bugzilla_login"}; $user{'userid'} = $userid; - SendSQL("SELECT mybugslink, realname, groupset, blessgroupset " . + SendSQL("SELECT mybugslink, realname " . "FROM profiles WHERE userid = $userid"); - ($user{'showmybugslink'}, $user{'realname'}, $user{'groupset'}, - $user{'blessgroupset'}) = FetchSQLData(); + ($user{'showmybugslink'}, $user{'realname'}) = FetchSQLData(); SendSQL("SELECT name, query, linkinfooter FROM namedqueries " . "WHERE userid = $userid"); @@ -516,10 +507,15 @@ sub GetUserInfo { $user{'queries'} = \@queries; - SendSQL("select name, (bit & $user{'groupset'}) != 0 from groups"); + $user{'canblessany'} = UserCanBlessAnything(); + + SendSQL("SELECT name FROM groups, user_group_map " . + "WHERE groups.id = user_group_map.group_id " . + "AND user_id = $userid " . + "AND NOT isbless"); while (MoreSQLData()) { - my ($name, $bit) = FetchSQLData(); - $groups{$name} = $bit; + my ($name) = FetchSQLData(); + $groups{$name} = 1; } $user{'groups'} = \%groups; @@ -561,6 +557,7 @@ sub confirm_login { # to a later section. -Joe Robins, 8/3/00 my $enteredlogin = ""; my $realcryptpwd = ""; + my $userid; # If the form contains Bugzilla login and password fields, use Bugzilla's # built-in authentication to authenticate the user (otherwise use LDAP below). @@ -570,7 +567,6 @@ sub confirm_login { CheckEmailSyntax($enteredlogin); # Retrieve the user's ID and crypted password from the database. - my $userid; SendSQL("SELECT userid, cryptpassword FROM profiles WHERE login_name = " . SqlQuote($enteredlogin)); ($userid, $realcryptpwd) = FetchSQLData(); @@ -765,9 +761,9 @@ sub confirm_login { print "Set-Cookie: Bugzilla_logincookie=$logincookie ; path=$cookiepath; expires=Sun, 30-Jun-2029 00:00:00 GMT\n"; } - my $loginok = quietly_check_login(); + $userid = quietly_check_login(); - if ($loginok != 1) { + if (!$userid) { if ($::disabledreason) { my $cookiepath = Param("cookiepath"); print "Set-Cookie: Bugzilla_login= ; path=$cookiepath; expires=Sun, 30-Jun-80 00:00:00 GMT @@ -810,7 +806,8 @@ Content-type: text/html SendSQL("UPDATE logincookies SET lastused = null " . "WHERE cookie = $::COOKIE{'Bugzilla_logincookie'}"); } - return $::userid; + ConfirmGroup($userid); + return $userid; } sub PutHeader { diff --git a/attachment.cgi b/attachment.cgi index 3b29a321e..cfdbd4ea6 100755 --- a/attachment.cgi +++ b/attachment.cgi @@ -50,7 +50,6 @@ require "CGI.pl"; ConnectToDatabase(); # Check whether or not the user is logged in and, if so, set the $::userid -# and $::usergroupset variables. quietly_check_login(); ################################################################################ diff --git a/bug_form.pl b/bug_form.pl index 8d7a2b02e..dfffca9b8 100644 --- a/bug_form.pl +++ b/bug_form.pl @@ -82,7 +82,7 @@ sub show_bug { bug_file_loc, short_desc, target_milestone, qa_contact, status_whiteboard, date_format(creation_ts,'%Y-%m-%d %H:%i'), - groupset, delta_ts, sum(votes.count), delta_ts calc_disp_date + delta_ts, sum(votes.count), delta_ts calc_disp_date FROM bugs LEFT JOIN votes USING(bug_id), products, components WHERE bugs.bug_id = $id AND bugs.product_id = products.id @@ -106,7 +106,7 @@ sub show_bug { "bug_severity", "component", "assigned_to", "reporter", "bug_file_loc", "short_desc", "target_milestone", "qa_contact", "status_whiteboard", "creation_ts", - "groupset", "delta_ts", "votes","calc_disp_date") + "delta_ts", "votes", "calc_disp_date") { $value = shift(@row); if ($field eq "calc_disp_date") { @@ -221,58 +221,68 @@ sub show_bug { # Groups my @groups; - if ($::usergroupset ne '0' || $bug{'groupset'} ne '0') { - my $bug_groupset = $bug{'groupset'}; - SendSQL("SELECT bit, name, description, (bit & $bug_groupset != 0), - (bit & $::usergroupset != 0) FROM groups - WHERE isbuggroup != 0 " . - # Include active groups as well as inactive groups to which - # the bug already belongs. This way the bug can be removed - # from an inactive group but can only be added to active ones. - "AND ((isactive = 1 AND (bit & $::usergroupset != 0)) OR - (bit & $bug_groupset != 0))"); + # For every group, we need to know if there is ANY bug_group_map + # record putting the current bug in that group and if there is ANY + # user_group_map record putting the user in that group. + # The LEFT JOINs are checking for record existence. + # + SendSQL("SELECT DISTINCT groups.id, name, description," . + " bug_group_map.group_id IS NOT NULL," . + " user_group_map.group_id IS NOT NULL," . + " isactive" . + " FROM groups" . + " LEFT JOIN bug_group_map" . + " ON bug_group_map.group_id = groups.id" . + " AND bug_id = $bug{'bug_id'}" . + " LEFT JOIN user_group_map" . + " ON user_group_map.group_id = groups.id" . + " AND user_id = $::userid" . + " AND NOT isbless" . + " WHERE isbuggroup"); + + $user{'inallgroups'} = 1; - $user{'inallgroups'} = 1; + while (MoreSQLData()) { + my ($groupid, $name, $description, $ison, $ingroup, $isactive) + = FetchSQLData(); + + $bug{'inagroup'} = 1 if ($ison); + + # For product groups, we only want to display the checkbox if either + # (1) The bit is already set, or + # (2) The user is in the group, but either: + # (a) The group is a product group for the current product, or + # (b) The group name isn't a product name + # This means that all product groups will be skipped, but + # non-product bug groups will still be displayed. + if($ison || + ($isactive && ($ingroup && (!Param("usebuggroups") || ($name eq $bug{'product'}) || + (!defined $::proddesc{$name}))))) + { + $user{'inallgroups'} &= $ingroup; - while (MoreSQLData()) { - my ($bit, $name, $description, $ison, $ingroup) = FetchSQLData(); - # For product groups, we only want to display the checkbox if either - # (1) The bit is already set, or - # (2) The user is in the group, but either: - # (a) The group is a product group for the current product, or - # (b) The group name isn't a product name - # This means that all product groups will be skipped, but - # non-product bug groups will still be displayed. - if($ison || - ($ingroup && (($name eq $bug{'product'}) || - (!defined $::proddesc{$name})))) - { - $user{'inallgroups'} &= $ingroup; - - push (@groups, { "bit" => $bit, - "ison" => $ison, - "ingroup" => $ingroup, - "description" => $description }); - } + push (@groups, { "bit" => $groupid, + "ison" => $ison, + "ingroup" => $ingroup, + "description" => $description }); } + } - # If the bug is restricted to a group, display checkboxes that allow - # the user to set whether or not the reporter - # and cc list can see the bug even if they are not members of all - # groups to which the bug is restricted. - if ($bug{'groupset'} != 0) { - $bug{'inagroup'} = 1; - - # Determine whether or not the bug is always accessible by the - # reporter, QA contact, and/or users on the cc: list. - SendSQL("SELECT reporter_accessible, cclist_accessible - FROM bugs - WHERE bug_id = $id - "); - ($bug{'reporter_accessible'}, - $bug{'cclist_accessible'}) = FetchSQLData(); - } + # If the bug is restricted to a group, get flags that allow + # the user to set whether or not the reporter + # and cc list can see the bug even if they are not members of all + # groups to which the bug is restricted. + if ($bug{'inagroup'}) { + + # Determine whether or not the bug is always accessible by the + # reporter, QA contact, and/or users on the cc: list. + SendSQL("SELECT reporter_accessible, cclist_accessible + FROM bugs + WHERE bug_id = $id + "); + ($bug{'reporter_accessible'}, + $bug{'cclist_accessible'}) = FetchSQLData(); } $vars->{'groups'} = \@groups; diff --git a/buglist.cgi b/buglist.cgi index 6597fbe3e..728ead4d1 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -207,23 +207,24 @@ sub GetQuip { return $quip; } -sub GetGroupsByGroupSet { - my ($groupset) = @_; +sub GetGroupsByUserId { + my ($userid) = @_; - return if !$groupset; + return if !$userid; SendSQL(" - SELECT bit, name, description, isactive - FROM groups - WHERE (bit & $groupset) != 0 - AND isbuggroup != 0 + SELECT groups.id, name, description, isactive + FROM groups, user_group_map + WHERE user_id = $userid AND NOT isbless + AND user_group_map.group_id = groups.id + AND isbuggroup ORDER BY description "); my @groups; while (MoreSQLData()) { my $group = {}; - ($group->{'bit'}, $group->{'name'}, + ($group->{'id'}, $group->{'name'}, $group->{'description'}, $group->{'isactive'}) = FetchSQLData(); push(@groups, $group); } @@ -379,7 +380,6 @@ sub DefineColumn { # Column: ID Name Title DefineColumn("id" , "bugs.bug_id" , "ID" ); -DefineColumn("groupset" , "bugs.groupset" , "Groupset" ); DefineColumn("opendate" , "bugs.creation_ts" , "Opened" ); DefineColumn("changeddate" , "bugs.delta_ts" , "Changed" ); DefineColumn("severity" , "bugs.bug_severity" , "Severity" ); @@ -437,9 +437,6 @@ else { # and are hard-coded into the display templates. @displaycolumns = grep($_ ne 'id', @displaycolumns); -# IMPORTANT! Never allow the groupset column to be displayed! -@displaycolumns = grep($_ ne 'groupset', @displaycolumns); - # Add the votes column to the list of columns to be displayed # in the bug list if the user is searching for bugs with a certain # number of votes and the votes column is not already on the list. @@ -458,10 +455,8 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) { # Generate the list of columns that will be selected in the SQL query. -# The bug ID and groupset are always selected because bug IDs are always -# displayed and we need the groupset to determine whether or not the bug -# is visible to the user. -my @selectcolumns = ("id", "groupset"); +# The bug ID is always selected because bug IDs are always displayed +my @selectcolumns = ("id"); # Display columns are selected because otherwise we could not display them. push (@selectcolumns, @displaycolumns); @@ -721,7 +716,7 @@ if ($dotweak) { $vars->{'bugstatuses'} = [ keys %$bugstatuses ]; # The groups to which the user belongs. - $vars->{'groups'} = GetGroupsByGroupSet($::usergroupset) if $::usergroupset ne '0'; + $vars->{'groups'} = GetGroupsByUserId($::userid); # If all bugs being changed are in the same product, the user can change # their version and component, so generate a list of products, a list of diff --git a/checksetup.pl b/checksetup.pl index c00301742..b752f9b65 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -1360,7 +1360,6 @@ $table{attachstatusdefs} = # $table{bugs} = 'bug_id mediumint not null auto_increment primary key, - groupset bigint not null, assigned_to mediumint not null, # This is a comment. bug_file_loc text, bug_severity enum($my_severities) not null, @@ -1454,16 +1453,7 @@ $table{dependencies} = index(dependson)'; -# Group bits must be a power of two. Groups are identified by a bit; sets of -# groups are indicated by or-ing these values together. -# -# isbuggroup is nonzero if this is a group that controls access to a set -# of bugs. In otherword, the groupset field in the bugs table should only -# have this group's bit set if isbuggroup is nonzero. -# -# User regexp is which email addresses are initially put into this group. -# This is only used when an email account is created; otherwise, profiles -# may be individually tweaked to add them in and out of groups. +# User regexp is which email addresses are put into this group. # # 2001-04-10 myk@mozilla.org: # isactive determines whether or not a group is active. An inactive group @@ -1473,14 +1463,14 @@ $table{dependencies} = # http://bugzilla.mozilla.org/show_bug.cgi?id=75482 $table{groups} = - 'bit bigint not null, + 'id mediumint not null auto_increment primary key, name varchar(255) not null, description text not null, isbuggroup tinyint not null, + last_changed datetime not null, userregexp tinytext not null, isactive tinyint not null default 1, - unique(bit), unique(name)'; $table{logincookies} = @@ -1511,13 +1501,10 @@ $table{profiles} = login_name varchar(255) not null, cryptpassword varchar(34), realname varchar(255), - groupset bigint not null, disabledtext mediumtext not null, mybugslink tinyint not null default 1, - blessgroupset bigint not null default 0, emailflags mediumtext, - - + refreshed_when datetime not null, unique(login_name)'; @@ -1610,6 +1597,38 @@ $table{tokens} = index(userid)'; +# group membership tables for tracking group and privilege +# +# This table determines the groups that a user belongs to +# directly or due to regexp and which groups can be blessed +# by a user. +# +# isderived: +# if 0 - record was explicitly granted +# if 1 - record was created by evaluating a regexp or group hierarchy +$table{user_group_map} = + 'user_id mediumint not null, + group_id mediumint not null, + isbless tinyint not null default 0, + isderived tinyint not null default 0, + + unique(user_id, group_id, isderived, isbless)'; + +$table{group_group_map} = + 'member_id mediumint not null, + grantor_id mediumint not null, + isbless tinyint not null default 0, + + unique(member_id, grantor_id, isbless)'; + +# This table determines which groups a user must be a member of +# in order to see a bug. +$table{bug_group_map} = + 'bug_id mediumint not null, + group_id mediumint not null, + unique(bug_id, group_id), + index(group_id)'; + # 2002-07-19, davef@tetsubo.com, bug 67950: # Store quips in the db. $table{quips} = @@ -1617,7 +1636,6 @@ $table{quips} = userid mediumint not null default 0, quip text not null'; - ########################################################################### # Create tables ########################################################################### @@ -1692,7 +1710,7 @@ sub GroupDoesExist ($) # # This subroutine checks if a group exist. If not, it will be automatically -# created with the next available bit set +# created with the next available groupid # sub AddGroup { @@ -1701,57 +1719,19 @@ sub AddGroup { return if GroupDoesExist($name); - # get highest bit number - my $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC"); - $sth->execute; - my @row = $sth->fetchrow_array; - - # normalize bits - my $bit; - if (defined $row[0]) { - $bit = $row[0] << 1; - } else { - $bit = 1; - } - - print "Adding group $name ...\n"; - $sth = $dbh->prepare('INSERT INTO groups - (bit, name, description, userregexp, isbuggroup) - VALUES (?, ?, ?, ?, ?)'); - $sth->execute($bit, $name, $desc, $userregexp, 0); - return $bit; -} - - -# -# BugZilla uses --GROUPS-- to assign various rights to its users. -# - -AddGroup 'tweakparams', 'Can tweak operating parameters'; -AddGroup 'editusers', 'Can edit or disable users'; -AddGroup 'creategroups', 'Can create and destroy groups.'; -AddGroup 'editcomponents', 'Can create, destroy, and edit components.'; -AddGroup 'editkeywords', 'Can create, destroy, and edit keywords.'; - -# Add the groupset field here because this code is run before the -# code that updates the database structure. -&AddField('profiles', 'groupset', 'bigint not null'); - -if (!GroupDoesExist("editbugs")) { - my $id = AddGroup('editbugs', 'Can edit all aspects of any bug.', ".*"); - $dbh->do("UPDATE profiles SET groupset = groupset | $id"); -} + my $sth = $dbh->prepare('INSERT INTO groups + (name, description, userregexp, isbuggroup) + VALUES (?, ?, ?, ?)'); + $sth->execute($name, $desc, $userregexp, 0); -if (!GroupDoesExist("canconfirm")) { - my $id = AddGroup('canconfirm', 'Can confirm a bug.', ".*"); - $dbh->do("UPDATE profiles SET groupset = groupset | $id"); + $sth = $dbh->prepare("select last_insert_id()"); + $sth->execute(); + my ($last) = $sth->fetchrow_array(); + return $last; } - - - ########################################################################### # Populate the list of fields. ########################################################################### @@ -1818,9 +1798,9 @@ AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed", AddFDef("longdesc", "Comment", 0); AddFDef("alias", "Alias", 0); AddFDef("everconfirmed", "Ever Confirmed", 0); -AddFDef("groupset", "Groupset", 0); AddFDef("reporter_accessible", "Reporter Accessible", 0); AddFDef("cclist_accessible", "CC Accessible", 0); +AddFDef("bug_group", "Group", 0); # Oops. Bug 163299 $dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'"); @@ -1937,181 +1917,9 @@ CheckEnumField('bugs', 'op_sys', @my_opsys); CheckEnumField('bugs', 'rep_platform', @my_platforms); -########################################################################### -# Create Administrator --ADMIN-- -########################################################################### - -# Prompt the user for the email address and name of an administrator. Create -# that login, if it doesn't exist already, and make it a member of all groups. - -sub bailout { # this is just in case we get interrupted while getting passwd - system("stty","echo"); # re-enable input echoing - exit 1; -} - -$sth = $dbh->prepare(<<_End_Of_SQL_); - SELECT login_name - FROM profiles - WHERE groupset=9223372036854775807 -_End_Of_SQL_ -$sth->execute; -# when we have no admin users, prompt for admin email address and password ... -if ($sth->rows == 0) { - my $login = ""; - my $realname = ""; - my $pass1 = ""; - my $pass2 = "*"; - my $admin_ok = 0; - my $admin_create = 1; - my $mailcheckexp = Param('emailregexp'); - my $mailcheck = Param('emailregexpdesc'); - - print "\nLooks like we don't have an administrator set up yet. Either this is your\n"; - print "first time using Bugzilla, or your administrator's privs might have accidently\n"; - print "gotten deleted at some point.\n"; - while(! $admin_ok ) { - while( $login eq "" ) { - print "Enter the e-mail address of the administrator: "; - $login = $answer{'ADMIN_EMAIL'} - || ($silent && die("cant preload ADMIN_EMAIL")) - || ; - chomp $login; - if(! $login ) { - print "\nYou DO want an administrator, don't you?\n"; - } - unless ($login =~ /$mailcheckexp/) { - print "\nThe login address is invalid:\n"; - print "$mailcheck\n"; - print "You can change this test on the params page once checksetup has successfully\n"; - print "completed.\n\n"; - # Go round, and ask them again - $login = ""; - } - } - $login = $dbh->quote($login); - $sth = $dbh->prepare(<<_End_Of_SQL_); - SELECT login_name - FROM profiles - WHERE login_name=$login -_End_Of_SQL_ - $sth->execute; - if ($sth->rows > 0) { - print "$login already has an account.\n"; - print "Make this user the administrator? [Y/n] "; - my $ok = $answer{'ADMIN_OK'} - || ($silent && die("cant preload ADMIN_OK")) - || ; - chomp $ok; - if ($ok !~ /^n/i) { - $admin_ok = 1; - $admin_create = 0; - } else { - print "OK, well, someone has to be the administrator. Try someone else.\n"; - $login = ""; - } - } else { - print "You entered $login. Is this correct? [Y/n] "; - my $ok = $answer{'ADMIN_OK'} - || ($silent && die("cant preload ADMIN_OK")) - || ; - chomp $ok; - if ($ok !~ /^n/i) { - $admin_ok = 1; - } else { - print "That's okay, typos happen. Give it another shot.\n"; - $login = ""; - } - } - } - - if ($admin_create) { - - while( $realname eq "" ) { - print "Enter the real name of the administrator: "; - $realname = $answer{'ADMIN_REALNAME'} - || ($silent && die("cant preload ADMIN_REALNAME")) - || ; - chomp $realname; - if(! $realname ) { - print "\nReally. We need a full name.\n"; - } - } - - # trap a few interrupts so we can fix the echo if we get aborted. - $SIG{HUP} = \&bailout; - $SIG{INT} = \&bailout; - $SIG{QUIT} = \&bailout; - $SIG{TERM} = \&bailout; - - system("stty","-echo"); # disable input echoing - - while( $pass1 ne $pass2 ) { - while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) { - print "Enter a password for the administrator account: "; - $pass1 = $answer{'ADMIN_PASSWORD'} - || ($silent && die("cant preload ADMIN_PASSWORD")) - || ; - chomp $pass1; - if(! $pass1 ) { - print "\n\nIt's just plain stupid to not have a password. Try again!\n"; - } elsif ( $pass1 !~ /^.{3,16}$/ ) { - print "The password must be 3-16 characters in length."; - } - } - print "\nPlease retype the password to verify: "; - $pass2 = $answer{'ADMIN_PASSWORD'} - || ($silent && die("cant preload ADMIN_PASSWORD")) - || ; - chomp $pass2; - if ($pass1 ne $pass2) { - print "\n\nPasswords don't match. Try again!\n"; - $pass1 = ""; - $pass2 = "*"; - } - } - - # Crypt the administrator's password - my $cryptedpassword = Crypt($pass1); - - system("stty","echo"); # re-enable input echoing - $SIG{HUP} = 'DEFAULT'; # and remove our interrupt hooks - $SIG{INT} = 'DEFAULT'; - $SIG{QUIT} = 'DEFAULT'; - $SIG{TERM} = 'DEFAULT'; - - $realname = $dbh->quote($realname); - $cryptedpassword = $dbh->quote($cryptedpassword); - - $dbh->do(<<_End_Of_SQL_); - INSERT INTO profiles - (login_name, realname, cryptpassword, groupset) - VALUES ($login, $realname, $cryptedpassword, 0x7fffffffffffffff) -_End_Of_SQL_ - } else { - $dbh->do(<<_End_Of_SQL_); - UPDATE profiles - SET groupset=0x7fffffffffffffff - WHERE login_name=$login -_End_Of_SQL_ - } - print "\n$login is now set up as the administrator account.\n"; -} - - - - ########################################################################### # Create initial test product if there are no products present. ########################################################################### - -$sth = $dbh->prepare(<<_End_Of_SQL_); - SELECT userid - FROM profiles - WHERE groupset=9223372036854775807 -_End_Of_SQL_ -$sth->execute; -my ($adminuid) = $sth->fetchrow_array; -if (!$adminuid) { die "No administator!" } # should never get here $sth = $dbh->prepare("SELECT description FROM products"); $sth->execute; unless ($sth->rows) { @@ -2126,19 +1934,20 @@ unless ($sth->rows) { $sth->execute; my ($product_id) = $sth->fetchrow_array; $dbh->do(qq{INSERT INTO versions (value, product_id) VALUES ("other", $product_id)}); + # note: since admin user is not yet known, components gets a 0 for + # initialowner and this is fixed during final checks. $dbh->do("INSERT INTO components (name, product_id, description, initialowner, initialqacontact) VALUES (" . "'TestComponent', $product_id, " . "'This is a test component in the test product database. " . "This ought to be blown away and replaced with real stuff in " . - "a finished installation of bugzilla.', $adminuid, 0)"); + "a finished installation of Bugzilla.', 0, 0)"); $dbh->do(qq{INSERT INTO milestones (product_id, value) VALUES ($product_id,"---")}); } - ########################################################################### # Update the tables to the current definition ########################################################################### @@ -2238,8 +2047,10 @@ sub TableExists ($) # really old fields that were added before checksetup.pl existed # but aren't in very old bugzilla's (like 2.1) # Steve Stock (sstock@iconnect-inc.com) + +# bug 157756 - groupsets replaced by maps +# AddField('bugs', 'groupset', 'bigint not null'); AddField('bugs', 'target_milestone', 'varchar(20) not null default "---"'); -AddField('bugs', 'groupset', 'bigint not null'); AddField('bugs', 'qa_contact', 'mediumint not null'); AddField('bugs', 'status_whiteboard', 'mediumtext not null'); AddField('products', 'disallownew', 'tinyint not null'); @@ -2673,7 +2484,8 @@ if (!GetFieldDef('bugs', 'everconfirmed')) { } AddField('products', 'maxvotesperbug', 'smallint not null default 10000'); AddField('products', 'votestoconfirm', 'smallint not null'); -AddField('profiles', 'blessgroupset', 'bigint not null'); +# bug 157756 - groupsets replaced by maps +# AddField('profiles', 'blessgroupset', 'bigint not null'); # 2000-03-21 Adding a table for target milestones to # database - matthew@zeroknowledge.com @@ -3216,6 +3028,222 @@ if (($fielddef = GetFieldDef("attachments", "creation_ts")) && ChangeFieldType("attachments", "creation_ts", "datetime NOT NULL"); } +# 2002-08-XX - bugreport@peshkin.net - bug 157756 +# +# If the whole groups system is new, but the installation isn't, +# convert all the old groupset groups, etc... +# +# This requires: +# 1) define groups ids in group table +# 2) populate user_group_map with grants from old groupsets and blessgroupsets +# 3) populate bug_group_map with data converted from old bug groupsets +# 4) convert activity logs to use group names instead of numbers +# 5) identify the admin from the old all-ones groupset +# +# ListBits(arg) returns a list of UNKNOWN if the group +# has been deleted for all bits set in arg. When the activity +# records are converted from groupset numbers to lists of +# group names, ListBits is used to fill in a list of references +# to groupset bits for groups that no longer exist. +# +sub ListBits { + my ($num) = @_; + my @res = (); + my $curr = 1; + while (1) { + # Convert a big integer to a list of bits + my $sth = $dbh->prepare("SELECT ($num & ~$curr) > 0, + ($num & $curr), + ($num & ~$curr), + $curr << 1"); + $sth->execute; + my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array; + push @res,"UNKNOWN<$curr>" if ($thisbit); + $curr = $nval; + $num = $remain; + last if (!$more); + } + return @res; +} + +my @admins = (); +# The groups system needs to be converted if groupset exists +if (GetFieldDef("profiles", "groupset")) { + AddField('groups', 'last_changed', 'datetime not null'); + # Some mysql versions will promote any unique key to primary key + # so all unique keys are removed first and then added back in + $dbh->do("ALTER TABLE groups DROP INDEX bit") if GetIndexDef("groups","bit"); + $dbh->do("ALTER TABLE groups DROP INDEX name") if GetIndexDef("groups","name"); + $dbh->do("ALTER TABLE groups DROP PRIMARY KEY"); + AddField('groups', 'id', 'mediumint not null auto_increment primary key'); + $dbh->do("ALTER TABLE groups ADD UNIQUE (name)"); + AddField('profiles', 'refreshed_when', 'datetime not null'); + + # Convert all existing groupset records to map entries before removing + # groupset fields or removing "bit" from groups. + $sth = $dbh->prepare("SELECT bit, id FROM groups + WHERE bit > 0"); + $sth->execute(); + while (my ($bit, $gid) = $sth->fetchrow_array) { + # Create user_group_map membership grants for old groupsets. + # Get each user with the old groupset bit set + my $sth2 = $dbh->prepare("SELECT userid FROM profiles + WHERE (groupset & $bit) != 0"); + $sth2->execute(); + while (my ($uid) = $sth2->fetchrow_array) { + # Check to see if the user is already a member of the group + # and, if not, insert a new record. + my $query = "SELECT user_id FROM user_group_map + WHERE group_id = $gid AND user_id = $uid + AND isbless = 0"; + my $sth3 = $dbh->prepare($query); + $sth3->execute(); + if ( !$sth3->fetchrow_array() ) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES($uid, $gid, 0, 0)"); + } + } + # Create user can bless group grants for old groupsets. + # Get each user with the old blessgroupset bit set + $sth2 = $dbh->prepare("SELECT userid FROM profiles + WHERE (blessgroupset & $bit) != 0"); + $sth2->execute(); + while (my ($uid) = $sth2->fetchrow_array) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES($uid, $gid, 1, 0)"); + } + # Create bug_group_map records for old groupsets. + # Get each bug with the old group bit set. + $sth2 = $dbh->prepare("SELECT bug_id FROM bugs + WHERE (groupset & $bit) != 0"); + $sth2->execute(); + while (my ($bug_id) = $sth2->fetchrow_array) { + # Insert the bug, group pair into the bug_group_map. + $dbh->do("INSERT INTO bug_group_map + (bug_id, group_id) + VALUES($bug_id, $gid)"); + } + } + # Replace old activity log groupset records with lists of names of groups. + # Start by defining the bug_group field and getting its id. + AddFDef("bug_group", "Group", 0); + $sth = $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = " . $dbh->quote('bug_group')); + $sth->execute(); + my ($bgfid) = $sth->fetchrow_array; + # Get the field id for the old groupset field + $sth = $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = " . $dbh->quote('groupset')); + $sth->execute(); + my ($gsid) = $sth->fetchrow_array; + # Get all bugs_activity records from groupset changes + $sth = $dbh->prepare("SELECT bug_id, bug_when, who, added, removed + FROM bugs_activity WHERE fieldid = $gsid"); + $sth->execute(); + while (my ($bug_id, $bug_when, $who, $added, $removed) = $sth->fetchrow_array) { + $added ||= 0; + $removed ||= 0; + # Get names of groups added. + my $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $added) != 0 AND (bit & $removed) = 0"); + $sth2->execute(); + my @logadd = (); + while (my ($n) = $sth2->fetchrow_array) { + push @logadd, $n; + } + # Get names of groups removed. + $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $removed) != 0 AND (bit & $added) = 0"); + $sth2->execute(); + my @logrem = (); + while (my ($n) = $sth2->fetchrow_array) { + push @logrem, $n; + } + # Get list of group bits added that correspond to missing groups. + $sth2 = $dbh->prepare("SELECT ($added & ~BIT_OR(bit)) FROM groups"); + $sth2->execute(); + my ($miss) = $sth2->fetchrow_array; + if ($miss) { + push @logadd, ListBits($miss); + print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id CONTAINS DELETED GROUPS\n"; + } + # Get list of group bits deleted that correspond to missing groups. + $sth2 = $dbh->prepare("SELECT ($removed & ~BIT_OR(bit)) FROM groups"); + $sth2->execute(); + ($miss) = $sth2->fetchrow_array; + if ($miss) { + push @logrem, ListBits($miss); + print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id CONTAINS DELETED GROUPS\n"; + } + my $logr = ""; + my $loga = ""; + $logr = join(", ", @logrem) . '?' if @logrem; + $loga = join(", ", @logadd) . '?' if @logadd; + # Replace to old activity record with the converted data. + $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " . + $dbh->quote($loga) . ", removed = " . + $dbh->quote($logr) . + " WHERE bug_id = $bug_id AND bug_when = " . $dbh->quote($bug_when) . + " AND who = $who AND fieldid = $gsid"); + + } + # Replace groupset changes with group name changes in profiles_activity. + # Get profiles_activity records for groupset. + $sth = $dbh->prepare("SELECT userid, profiles_when, who, newvalue, oldvalue + FROM profiles_activity WHERE fieldid = $gsid"); + $sth->execute(); + while (my ($uid, $uwhen, $uwho, $added, $removed) = $sth->fetchrow_array) { + $added ||= 0; + $removed ||= 0; + # Get names of groups added. + my $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $added) != 0 AND (bit & $removed) = 0"); + $sth2->execute(); + my @logadd = (); + while (my ($n) = $sth2->fetchrow_array) { + push @logadd, $n; + } + # Get names of groups removed. + $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $removed) != 0 AND (bit & $added) = 0"); + $sth2->execute(); + my @logrem = (); + while (my ($n) = $sth2->fetchrow_array) { + push @logrem, $n; + } + my $ladd = ""; + my $lrem = ""; + $ladd = join(", ", @logadd) . '?' if @logadd; + $lrem = join(", ", @logrem) . '?' if @logrem; + # Replace profiles_activity record for groupset change with group list. + $dbh->do("UPDATE profiles_activity SET fieldid = $bgfid, newvalue = " . + $dbh->quote($ladd) . ", oldvalue = " . + $dbh->quote($lrem) . + " WHERE userid = $uid AND profiles_when = " . + $dbh->quote($uwhen) . + " AND who = $uwho AND fieldid = $gsid"); + + } + + # Identify admin group. + my $sth = $dbh->prepare("SELECT id FROM groups + WHERE name = 'admin'"); + $sth->execute(); + my ($adminid) = $sth->fetchrow_array(); + # find existing admins + # Don't lose admins from DBs where Bug 157704 applies + $sth = $dbh->prepare("SELECT userid, (groupset & 65536), login_name FROM profiles + WHERE (groupset | 65536) = 9223372036854775807"); + $sth->execute(); + while ( my ($userid, $iscomplete, $login_name) = $sth->fetchrow_array() ) { + # existing administrators are made members of group "admin" + print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG 157704\n\n" + if (!$iscomplete); + push @admins, $userid; + } + DropField('profiles','groupset'); + DropField('profiles','blessgroupset'); + DropField('bugs','groupset'); + DropField('groups','bit'); + $dbh->do("DELETE FROM fielddefs WHERE name = " . $dbh->quote('groupset')); +} + # If you had to change the --TABLE-- definition in any way, then add your # differential change code *** A B O V E *** this comment. # @@ -3225,9 +3253,291 @@ if (($fielddef = GetFieldDef("attachments", "creation_ts")) && # AddField/DropField/ChangeFieldType/RenameField code above. This would then # be honored by everyone who updates his Bugzilla installation. # + +# +# BugZilla uses --GROUPS-- to assign various rights to its users. +# + +AddGroup('tweakparams', 'Can tweak operating parameters'); +AddGroup('editusers', 'Can edit or disable users'); +AddGroup('creategroups', 'Can create and destroy groups.'); +AddGroup('editcomponents', 'Can create, destroy, and edit components.'); +AddGroup('editkeywords', 'Can create, destroy, and edit keywords.'); +AddGroup('admin', 'Administrators'); + + +if (!GroupDoesExist("editbugs")) { + my $id = AddGroup('editbugs', 'Can edit all aspects of any bug.', ".*"); + my $sth = $dbh->prepare("SELECT userid FROM profiles"); + $sth->execute(); + while (my ($userid) = $sth->fetchrow_array()) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($userid, $id, 0, 0)"); + } +} + +if (!GroupDoesExist("canconfirm")) { + my $id = AddGroup('canconfirm', 'Can confirm a bug.', ".*"); + my $sth = $dbh->prepare("SELECT userid FROM profiles"); + $sth->execute(); + while (my ($userid) = $sth->fetchrow_array()) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($userid, $id, 0, 0)"); + } + +} + + +########################################################################### +# Create Administrator --ADMIN-- +########################################################################### + + +sub bailout { # this is just in case we get interrupted while getting passwd + system("stty","echo"); # re-enable input echoing + exit 1; +} + +if (@admins) { + # Identify admin group. + my $sth = $dbh->prepare("SELECT id FROM groups + WHERE name = 'admin'"); + $sth->execute(); + my ($adminid) = $sth->fetchrow_array(); + foreach my $userid (@admins) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($userid, $adminid, 0, 0)"); + # Existing administrators are made blessers of group "admin" + # but only explitly defined blessers can bless group admin. + # Other groups can be blessed by any admin (by default) or additional + # defined blessers. + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($userid, $adminid, 1, 0)"); + } + $sth = $dbh->prepare("SELECT id FROM groups"); + $sth->execute(); + while ( my ($id) = $sth->fetchrow_array() ) { + # Admins can bless every group. + $dbh->do("INSERT INTO group_group_map + (member_id, grantor_id, isbless) + VALUES ($adminid, $id, 1)"); + # Admins are initially members of every group. + next if ($id == $adminid); + $dbh->do("INSERT INTO group_group_map + (member_id, grantor_id, isbless) + VALUES ($adminid, $id, 0)"); + } +} + + +my @groups = (); +$sth = $dbh->prepare("select id from groups"); +$sth->execute(); +while ( my @row = $sth->fetchrow_array() ) { + push (@groups, $row[0]); +} + +# Prompt the user for the email address and name of an administrator. Create +# that login, if it doesn't exist already, and make it a member of all groups. + +$sth = $dbh->prepare("SELECT user_id FROM groups, user_group_map" . + " WHERE name = 'admin' AND id = group_id"); +$sth->execute; +# when we have no admin users, prompt for admin email address and password ... +if ($sth->rows == 0) { + my $login = ""; + my $realname = ""; + my $pass1 = ""; + my $pass2 = "*"; + my $admin_ok = 0; + my $admin_create = 1; + my $mailcheckexp = ""; + my $mailcheck = ""; + + # Here we look to see what the emailregexp is set to so we can + # check the email addy they enter. Bug 96675. If they have no + # params (likely but not always the case), we use the default. + if (-e "data/params") { + require "data/params"; # if they have a params file, use that + } + if (Param('emailregexp')) { + $mailcheckexp = Param('emailregexp'); + $mailcheck = Param('emailregexpdesc'); + } else { + $mailcheckexp = '^[^@]+@[^@]+\\.[^@]+$'; + $mailcheck = 'A legal address must contain exactly one \'@\', + and at least one \'.\' after the @.'; + } + + print "\nLooks like we don't have an administrator set up yet. Either this is your\n"; + print "first time using Bugzilla, or your administrator's privileges might have accidently\n"; + print "been deleted.\n"; + while(! $admin_ok ) { + while( $login eq "" ) { + print "Enter the e-mail address of the administrator: "; + $login = $answer{'ADMIN_EMAIL'} + || ($silent && die("cant preload ADMIN_EMAIL")) + || ; + chomp $login; + if(! $login ) { + print "\nYou DO want an administrator, don't you?\n"; + } + unless ($login =~ /$mailcheckexp/) { + print "\nThe login address is invalid:\n"; + print "$mailcheck\n"; + print "You can change this test on the params page once checksetup has successfully\n"; + print "completed.\n\n"; + # Go round, and ask them again + $login = ""; + } + } + $login = $dbh->quote($login); + $sth = $dbh->prepare("SELECT login_name FROM profiles" . + " WHERE login_name=$login"); + $sth->execute; + if ($sth->rows > 0) { + print "$login already has an account.\n"; + print "Make this user the administrator? [Y/n] "; + my $ok = $answer{'ADMIN_OK'} + || ($silent && die("cant preload ADMIN_OK")) + || ; + chomp $ok; + if ($ok !~ /^n/i) { + $admin_ok = 1; + $admin_create = 0; + } else { + print "OK, well, someone has to be the administrator. Try someone else.\n"; + $login = ""; + } + } else { + print "You entered $login. Is this correct? [Y/n] "; + my $ok = $answer{'ADMIN_OK'} + || ($silent && die("cant preload ADMIN_OK")) + || ; + chomp $ok; + if ($ok !~ /^n/i) { + $admin_ok = 1; + } else { + print "That's okay, typos happen. Give it another shot.\n"; + $login = ""; + } + } + } + + if ($admin_create) { + + while( $realname eq "" ) { + print "Enter the real name of the administrator: "; + $realname = $answer{'ADMIN_REALNAME'} + || ($silent && die("cant preload ADMIN_REALNAME")) + || ; + chomp $realname; + if(! $realname ) { + print "\nReally. We need a full name.\n"; + } + } + + # trap a few interrupts so we can fix the echo if we get aborted. + $SIG{HUP} = \&bailout; + $SIG{INT} = \&bailout; + $SIG{QUIT} = \&bailout; + $SIG{TERM} = \&bailout; + + system("stty","-echo"); # disable input echoing + + while( $pass1 ne $pass2 ) { + while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) { + print "Enter a password for the administrator account: "; + $pass1 = $answer{'ADMIN_PASSWORD'} + || ($silent && die("cant preload ADMIN_PASSWORD")) + || ; + chomp $pass1; + if(! $pass1 ) { + print "\n\nIt's just plain stupid to not have a password. Try again!\n"; + } elsif ( $pass1 !~ /^.{3,16}$/ ) { + print "The password must be 3-16 characters in length."; + } + } + print "\nPlease retype the password to verify: "; + $pass2 = $answer{'ADMIN_PASSWORD'} + || ($silent && die("cant preload ADMIN_PASSWORD")) + || ; + chomp $pass2; + if ($pass1 ne $pass2) { + print "\n\nPasswords don't match. Try again!\n"; + $pass1 = ""; + $pass2 = "*"; + } + } + + # Crypt the administrator's password + my $cryptedpassword = Crypt($pass1); + + system("stty","echo"); # re-enable input echoing + $SIG{HUP} = 'DEFAULT'; # and remove our interrupt hooks + $SIG{INT} = 'DEFAULT'; + $SIG{QUIT} = 'DEFAULT'; + $SIG{TERM} = 'DEFAULT'; + + $realname = $dbh->quote($realname); + $cryptedpassword = $dbh->quote($cryptedpassword); + + $dbh->do("INSERT INTO profiles (login_name, realname, cryptpassword)" . + " VALUES ($login, $realname, $cryptedpassword)"); + } + # Put the admin in each group if not already + my $query = "select userid from profiles where login_name = $login"; + $sth = $dbh->prepare($query); + $sth->execute(); + my ($userid) = $sth->fetchrow_array(); + + foreach my $group (@groups) { + my $query = "SELECT user_id FROM user_group_map + WHERE group_id = $group AND user_id = $userid + AND isbless = 0"; + $sth = $dbh->prepare($query); + $sth->execute(); + if ( !$sth->fetchrow_array() ) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($userid, $group, 0, 0)"); + } + } + # the admin also gets an explicit bless capability for the admin group + my $sth = $dbh->prepare("SELECT id FROM groups + WHERE name = 'admin'"); + $sth->execute(); + my ($id) = $sth->fetchrow_array(); + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($userid, $id, 1, 0)"); + foreach my $group ( @groups ) { + $dbh->do("INSERT INTO group_group_map + (member_id, grantor_id, isbless) + VALUES ($id, $group, 1)"); + } + + print "\n$login is now set up as an administrator account.\n"; +} + + + # # Final checks... +$sth = $dbh->prepare("SELECT user_id FROM groups, user_group_map" . + " WHERE groups.name = 'admin'" . + " AND groups.id = user_group_map.group_id"); +$sth->execute; +my ($adminuid) = $sth->fetchrow_array; +if (!$adminuid) { die "No administrator!" } # should never get here +# when test product was created, admin was unknown +$dbh->do("UPDATE components SET initialowner = $adminuid WHERE initialowner = 0"); + unlink "data/versioncache"; print "Reminder: Bugzilla now requires version 8.7 or later of sendmail.\n" unless $silent; diff --git a/contrib/bug_email.pl b/contrib/bug_email.pl index fb2bbec4d..bf442502c 100755 --- a/contrib/bug_email.pl +++ b/contrib/bug_email.pl @@ -37,7 +37,7 @@ # # You need to work with bug_email.pl the MIME::Parser installed. # -# $Id: bug_email.pl,v 1.13 2002/08/26 06:17:21 bbaetz%student.usyd.edu.au Exp $ +# $Id: bug_email.pl,v 1.14 2002/09/22 17:15:03 bugreport%peshkin.net Exp $ ############################################################### # 02/12/2000 (SML) diff --git a/docs/sgml/administration.sgml b/docs/sgml/administration.sgml index a82a659bf..a0ff3e174 100644 --- a/docs/sgml/administration.sgml +++ b/docs/sgml/administration.sgml @@ -212,33 +212,11 @@ you for this username and password. - If you wish to add more administrative users, you must use the - MySQL interface. Run "mysql" from the command line, and use these - commands: - - - mysql> - use bugs; - - - - mysql> - - - update profiles set groupset=0x7ffffffffffffff where login_name = - "(user's login name)"; - - - + If you wish to add more administrative users, add them to + the "admin" group and, optionally, add edit the tweakparams, editusers, + creategroups, editcomponents, and editkeywords groups to add the + entire admin group to those groups. - - Yes, that is - fourteen - - f - - 's. A whole lot of f-ing going on if you want to create a new - administator. @@ -698,10 +676,22 @@ - Fill out the "New Name", "New Description", and - "New User RegExp" fields. "New User RegExp" allows you to automatically + Fill out the "Group", "Description", and + "User RegExp" fields. "New User RegExp" allows you to automatically place all users who fulfill the Regular Expression into the new group. When you have finished, click "Add". + + The User Regexp is a perl regexp and, if not anchored, will match + any part of an address. So, if you do not want to grant access + into 'mycompany.com' to 'badperson@mycompany.com.hacker.net', use + '@mycompany\.com$' as the regexp. + + + + After you add your new group, edit the new group. On the + edit page, you can specify other groups that should be included + in this group and which groups should be permitted to add and delete + users from this group. @@ -712,17 +702,6 @@ Turn on "usebuggroups" and "usebuggroupsentry" in the "Edit Parameters" screen. - - XXX is this still true? - "usebuggroupsentry" has the capacity to prevent the - administrative user from directly altering bugs because of - conflicting group permissions. If you plan on using - "usebuggroupsentry", you should plan on restricting - administrative account usage to administrative duties only. In - other words, manage bugs with an unpriveleged user account, and - manage users, groups, Products, etc. with the administrative - account. - @@ -734,13 +713,6 @@ - - Bugzilla currently has a limit of 64 groups per installation. If - you have more than about 50 products, you should consider - running multiple Bugzillas. Ask in the newsgroup for other - suggestions for working around this restriction. - - Note that group permissions are such that you need to be a member of all the groups a bug is in, for whatever diff --git a/docs/xml/administration.xml b/docs/xml/administration.xml index a82a659bf..a0ff3e174 100644 --- a/docs/xml/administration.xml +++ b/docs/xml/administration.xml @@ -212,33 +212,11 @@ you for this username and password. - If you wish to add more administrative users, you must use the - MySQL interface. Run "mysql" from the command line, and use these - commands: - - - mysql> - use bugs; - - - - mysql> - - - update profiles set groupset=0x7ffffffffffffff where login_name = - "(user's login name)"; - - - + If you wish to add more administrative users, add them to + the "admin" group and, optionally, add edit the tweakparams, editusers, + creategroups, editcomponents, and editkeywords groups to add the + entire admin group to those groups. - - Yes, that is - fourteen - - f - - 's. A whole lot of f-ing going on if you want to create a new - administator. @@ -698,10 +676,22 @@ - Fill out the "New Name", "New Description", and - "New User RegExp" fields. "New User RegExp" allows you to automatically + Fill out the "Group", "Description", and + "User RegExp" fields. "New User RegExp" allows you to automatically place all users who fulfill the Regular Expression into the new group. When you have finished, click "Add". + + The User Regexp is a perl regexp and, if not anchored, will match + any part of an address. So, if you do not want to grant access + into 'mycompany.com' to 'badperson@mycompany.com.hacker.net', use + '@mycompany\.com$' as the regexp. + + + + After you add your new group, edit the new group. On the + edit page, you can specify other groups that should be included + in this group and which groups should be permitted to add and delete + users from this group. @@ -712,17 +702,6 @@ Turn on "usebuggroups" and "usebuggroupsentry" in the "Edit Parameters" screen. - - XXX is this still true? - "usebuggroupsentry" has the capacity to prevent the - administrative user from directly altering bugs because of - conflicting group permissions. If you plan on using - "usebuggroupsentry", you should plan on restricting - administrative account usage to administrative duties only. In - other words, manage bugs with an unpriveleged user account, and - manage users, groups, Products, etc. with the administrative - account. - @@ -734,13 +713,6 @@ - - Bugzilla currently has a limit of 64 groups per installation. If - you have more than about 50 products, you should consider - running multiple Bugzillas. Ask in the newsgroup for other - suggestions for working around this restriction. - - Note that group permissions are such that you need to be a member of all the groups a bug is in, for whatever diff --git a/duplicates.cgi b/duplicates.cgi index 3eeab3fb5..0e27f077e 100755 --- a/duplicates.cgi +++ b/duplicates.cgi @@ -40,7 +40,7 @@ GetVersionTable(); quietly_check_login(); -use vars qw (%FORM $userid $usergroupset @legal_product); +use vars qw (%FORM $userid @legal_product); my %dbmcount; my %count; @@ -160,9 +160,7 @@ if (scalar(%count)) { # Limit to a single product if requested $query .= (" AND bugs.product_id = " . $product_id) if $product_id; - SendSQL(SelectVisible($query, - $userid, - $usergroupset)); + SendSQL($query); while (MoreSQLData()) { # Note: maximum row count is dealt with in the template. @@ -170,6 +168,7 @@ if (scalar(%count)) { my ($id, $component, $bug_severity, $op_sys, $target_milestone, $short_desc, $bug_status, $resolution) = FetchSQLData(); + next if (!CanSeeBug($id, $::userid)); # Limit to open bugs only if requested next if $openonly && ($resolution ne ""); diff --git a/editgroups.cgi b/editgroups.cgi index 5bcd4f61c..9ecda4138 100755 --- a/editgroups.cgi +++ b/editgroups.cgi @@ -19,6 +19,7 @@ # Rights Reserved. # # Contributor(s): Dave Miller +# Joel Peshkin # Jacob Steenhagen # Code derived from editowners.cgi and editusers.cgi @@ -99,36 +100,36 @@ sub PutTrailer (@) unless ($action) { PutHeader("Edit Groups","Edit Groups","This lets you edit the groups available to put users in."); - print "
\n"; print "\n"; print ""; - print ""; print ""; print ""; print ""; - print ""; + print ""; + print ""; print ""; print "\n"; - SendSQL("SELECT bit,name,description,userregexp,isactive " . + SendSQL("SELECT id,name,description,userregexp,isactive,isbuggroup " . "FROM groups " . - "WHERE isbuggroup != 0 " . - "ORDER BY bit"); + "ORDER BY isbuggroup, name"); while (MoreSQLData()) { - my ($bit, $name, $desc, $regexp, $isactive) = FetchSQLData(); + my ($groupid, $name, $desc, $regexp, $isactive, $isbuggroup) = FetchSQLData(); print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; } print "\n"; @@ -136,62 +137,135 @@ unless ($action) { print "\n"; print "\n"; print "
BitNameDescriptionUser RegExpActiveUse For BugsTypeAction
$bit\n"; - print "\n"; - print "\n"; - print "\n"; - print "Delete
$name$desc$regexp "; + print "X" if $isactive; + print "    "; + print (($isbuggroup == 0 ) ? "system" : "user"); + print "  + Edit"; + print " | Delete" if ($isbuggroup != 0); + print "
Add Group
\n"; - print ""; - print "\n"; - print "

"; print "Name is what is used with the UserInGroup() function in any customized cgi files you write that use a given group. It can also be used by -people submitting bugs by email to limit a bug to a certain groupset.

"; +people submitting bugs by email to limit a bug to a certain set of groups.

"; print "Description is what will be shown in the bug reports to members of the group where they can choose whether the bug will be restricted to others in the same group.

"; print "User RegExp is optional, and if filled in, will automatically grant membership to this group to anyone creating a new account with an -email address that matches this regular expression.

"; - print "The Active flag determines whether or not the group is active. -If you deactivate a group it will no longer be possible for users to add bugs -to that group, although bugs already in the group will remain in the group. -Deactivating a group is a much less drastic way to stop a group from growing -than deleting the group would be.

"; - print "In addition, the following groups that determine user privileges -exist. You can only edit the User rexexp on these groups. You should also take -care not to duplicate the Names of any of them in your user groups.

"; - print "Also please note that both of the Submit Changes buttons on this page -will submit the changes in both tables. There are two buttons simply for the -sake of convience.

"; +email address that matches this perl regular expression. Do not forget the trailing \'\$\'. Example \'\@mycompany\\.com\$\'

"; + print "The Use For Bugs flag determines whether or not the group is eligible to be used for bugs. +If you remove this flag, it will no longer be possible for users to add bugs +to this group, although bugs already in the group will remain in the group. +Doing so is a much less drastic way to stop a group from growing +than deleting the group as well as a way to maintain lists of users without cluttering the lists of groups used for bug restrictions.

"; + print "The Type field identifies system groups.

"; - print "\n"; - print ""; - print ""; - print ""; - print ""; - print ""; - print "\n"; + PutFooter(); + exit; +} - SendSQL("SELECT bit,name,description,userregexp " . - "FROM groups " . - "WHERE isbuggroup = 0 " . - "ORDER BY bit"); +# +# +# action='changeform' -> present form for altering an existing group +# +# (next action will be 'postchanges') +# + +if ($action eq 'changeform') { + PutHeader("Change Group"); + + my $gid = trim($::FORM{group} || ''); + unless ($gid) { + ShowError("No group specified.
" . + "Click the Back button and try again."); + PutFooter(); + exit; + } + + SendSQL("SELECT id, name, description, userregexp, isactive, isbuggroup + FROM groups WHERE id=" . SqlQuote($gid)); + my ($group_id, $name, $description, $rexp, $isactive, $isbuggroup) + = FetchSQLData(); + + print "\n"; + print "
BitNameDescriptionUser RegExp
"; + print " + "; + if ($isbuggroup == 1) { + print " + "; + } + print "
Group:"; + if ($isbuggroup == 0) { + print "$name"; + } else { + print " + "; + } + print "
Description:"; + if ($isbuggroup == 0) { + print "$description"; + } else { + print " + "; + } + print "
User Regexp:"; + print " +
Use For Bugs: + + +
+
+ Users become members of this group in one of three ways: +
+ - by being explicity included when the user is edited +
+ - by matching the user regexp above +
+ - by being a member of one of the groups included in this group + by checking the boxes + below.

\n"; + + print ""; + print ""; + print ""; + print ""; + + # For each group, we use left joins to establish the existance of + # a record making that group a member of this group + # and the existance of a record permitting that group to bless + # this one + SendSQL("SELECT groups.id, groups.name, groups.description," . + " group_group_map.member_id IS NOT NULL," . + " B.member_id IS NOT NULL" . + " FROM groups" . + " LEFT JOIN group_group_map" . + " ON group_group_map.member_id = groups.id" . + " AND group_group_map.grantor_id = $group_id" . + " AND group_group_map.isbless = 0" . + " LEFT JOIN group_group_map as B" . + " ON B.member_id = groups.id" . + " AND B.grantor_id = $group_id" . + " AND B.isbless" . + " WHERE groups.id != $group_id ORDER by name"); while (MoreSQLData()) { - my ($bit, $name, $desc, $regexp) = FetchSQLData(); - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; + my ($grpid, $grpnam, $grpdesc, $grpmember, $blessmember) = FetchSQLData(); + my $grpchecked = $grpmember ? "CHECKED" : ""; + my $blesschecked = $blessmember ? "CHECKED" : ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; } - print "
Members of these groups can grant membership to this group
|Members of these groups are included in this group
||
$bit$name$desc
"; + print ""; + print "$grpnam$grpdesc

\n"; - print "\n"; - print "

\n"; + print "
"; + print "\n"; + print "\n"; + print "\n"; + print ""; - PutFooter(); + + + PutTrailer("Back to group list"); exit; } @@ -209,7 +283,7 @@ if ($action eq 'add') { print "New Name"; print "New Description"; print "New User RegExp"; - print "Active"; + print "Use For Bugs"; print ""; print "\n"; print "\n"; @@ -223,17 +297,17 @@ if ($action eq 'add') { print "

"; print "Name is what is used with the UserInGroup() function in any customized cgi files you write that use a given group. It can also be used by -people submitting bugs by email to limit a bug to a certain groupset. It +people submitting bugs by email to limit a bug to a certain set of groups. It may not contain any spaces.

"; print "Description is what will be shown in the bug reports to members of the group where they can choose whether the bug will be restricted to others in the same group.

"; - print "The Active flag determines whether or not the group is active. -If you deactivate a group it will no longer be possible for users to add bugs -to that group, although bugs already in the group will remain in the group. -Deactivating a group is a much less drastic way to stop a group from growing + print "The Use For Bugs flag determines whether or not the group is eligible to be used for bugs. +If you clear this, it will no longer be possible for users to add bugs +to this group, although bugs already in the group will remain in the group. +Doing so is a much less drastic way to stop a group from growing than deleting the group would be. Note: If you are creating a group, you -probably want it to be active, in which case you should leave this checked.

"; +probably want it to be usable for bugs, in which case you should leave this checked.

"; print "User RegExp is optional, and if filled in, will automatically grant membership to this group to anyone creating a new account with an email address that matches this regular expression.

"; @@ -287,62 +361,30 @@ if ($action eq 'new') { exit; } - # Major hack for bit values... perl can't handle 64-bit ints, so I can't - # just do the math to get the next available bit number, gotta handle - # them as strings... also, we're actually only going to allow 63 bits - # because that's all that opblessgroupset masks for (the high bit is off - # to avoid signing issues). - - my @bitvals = ('1','2','4','8','16','32','64','128','256','512','1024', - '2048','4096','8192','16384','32768', - - '65536','131072','262144','524288','1048576','2097152', - '4194304','8388608','16777216','33554432','67108864', - '134217728','268435456','536870912','1073741824', - '2147483648', - - '4294967296','8589934592','17179869184','34359738368', - '68719476736','137438953472','274877906944', - '549755813888','1099511627776','2199023255552', - '4398046511104','8796093022208','17592186044416', - '35184372088832','70368744177664','140737488355328', - - '281474976710656','562949953421312','1125899906842624', - '2251799813685248','4503599627370496','9007199254740992', - '18014398509481984','36028797018963968','72057594037927936', - '144115188075855872','288230376151711744', - '576460752303423488','1152921504606846976', - '2305843009213693952','4611686018427387904'); - - # First the next available bit - my $bit = ""; - foreach (@bitvals) { - if ($bit eq "") { - SendSQL("SELECT bit FROM groups WHERE bit=" . SqlQuote($_)); - if (!FetchOneColumn()) { $bit = $_; } - } - } - if ($bit eq "") { - ShowError("Sorry, you already have the maximum number of groups " . - "defined.

You must delete a group first before you " . - "can add any more."); - PutTrailer("Back to the group list"); + if (!eval {qr/$regexp/}) { + ShowError("The regular expression you entered is invalid. " . + "Please click the Back button and try again."); + PutFooter(); exit; } # Add the new group SendSQL("INSERT INTO groups ( " . - "bit, name, description, isbuggroup, userregexp, isactive" . + "name, description, isbuggroup, userregexp, isactive, last_changed " . " ) VALUES ( " . - $bit . "," . - SqlQuote($name) . "," . - SqlQuote($desc) . "," . + SqlQuote($name) . ", " . + SqlQuote($desc) . ", " . "1," . - SqlQuote($regexp) . "," . - $isactive . ")" ); - + SqlQuote($regexp) . ", " . + $isactive . ", NOW())" ); + SendSQL("SELECT last_insert_id()"); + my $gid = FetchOneColumn(); + my $admin = GroupNameToId('admin'); + SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless) + VALUES ($admin, $gid, 0)"); + SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless) + VALUES ($admin, $gid, 1)"); print "OK, done.

\n"; - print "Your new group was assigned bit #$bit.

"; PutTrailer("Add another group", "Back to the group list"); exit; @@ -356,14 +398,14 @@ if ($action eq 'new') { if ($action eq 'del') { PutHeader("Delete group"); - my $bit = trim($::FORM{group} || ''); - unless ($bit) { + my $gid = trim($::FORM{group} || ''); + unless ($gid) { ShowError("No group specified.
" . "Click the Back button and try again."); PutFooter(); exit; } - SendSQL("SELECT bit FROM groups WHERE bit=" . SqlQuote($bit)); + SendSQL("SELECT id FROM groups WHERE id=" . SqlQuote($gid)); if (!FetchOneColumn()) { ShowError("That group doesn't exist.
" . "Click the Back button and try again."); @@ -372,17 +414,17 @@ if ($action eq 'del') { } SendSQL("SELECT name,description " . "FROM groups " . - "WHERE bit = " . SqlQuote($bit)); + "WHERE id = " . SqlQuote($gid)); my ($name, $desc) = FetchSQLData(); print "\n"; print ""; - print ""; + print ""; print ""; print ""; print "\n"; print "\n"; - print "\n"; + print "\n"; print "\n"; print "\n"; print "\n"; @@ -390,26 +432,26 @@ if ($action eq 'del') { print "\n"; my $cantdelete = 0; - SendSQL("SELECT login_name FROM profiles WHERE " . - "(groupset & $bit) OR (blessgroupset & $bit)"); + SendSQL("SELECT user_id FROM user_group_map + WHERE group_id = $gid AND isbless = 0"); if (!FetchOneColumn()) {} else { $cantdelete = 1; print " One or more users belong to this group. You cannot delete this group while there are users in it.
-Show me which users. - Remove all users from +Show me which users. - Remove all users from this group for me

"; } - SendSQL("SELECT bug_id FROM bugs WHERE (groupset & $bit)"); + SendSQL("SELECT bug_id FROM bug_group_map WHERE group_id = $gid"); + my $buglist=""; if (MoreSQLData()) { - $cantdelete = 1; - my $buglist = "0"; - while (MoreSQLData()) { - my ($bug) = FetchSQLData(); - $buglist .= "," . $bug; - } + $cantdelete = 1; + my $buglist = "0"; + while (MoreSQLData()) { + my ($bug) = FetchSQLData(); + $buglist .= "," . $bug; + } print " One or more bug reports are visible only to this group. You cannot delete this group while any bugs are using it.
@@ -440,7 +482,7 @@ You cannot delete this group while it is tied to a product.
} print "

\n"; print "\n"; - print "\n"; + print "\n"; print ""; PutTrailer("No, go back to the group list"); @@ -453,8 +495,8 @@ You cannot delete this group while it is tied to a product.
if ($action eq 'delete') { PutHeader("Deleting group"); - my $bit = trim($::FORM{group} || ''); - unless ($bit) { + my $gid = trim($::FORM{group} || ''); + unless ($gid) { ShowError("No group specified.
" . "Click the Back button and try again."); PutFooter(); @@ -462,27 +504,19 @@ if ($action eq 'delete') { } SendSQL("SELECT name " . "FROM groups " . - "WHERE bit = " . SqlQuote($bit)); + "WHERE group_id = " . SqlQuote($gid)); my ($name) = FetchSQLData(); my $cantdelete = 0; - my $opblessgroupset = '9223372036854775807'; # This is all 64 bits. - SendSQL("SELECT userid FROM profiles " . - "WHERE (groupset & $opblessgroupset)=$opblessgroupset"); - my @opusers = (); - while (MoreSQLData()) { - my ($userid) = FetchSQLData(); - push @opusers, $userid; # cache a list of the users with admin powers - } - SendSQL("SELECT login_name FROM profiles WHERE " . - "(groupset & $bit)=$bit OR (blessgroupset & $bit)=$bit"); + SendSQL("SELECT user_id FROM user_group_map + WHERE group_id = $gid AND isbless = 0"); if (FetchOneColumn()) { if (!defined $::FORM{'removeusers'}) { $cantdelete = 1; } } - SendSQL("SELECT bug_id FROM bugs WHERE (groupset & $bit)=$bit"); + SendSQL("SELECT bug_id FROM bug_group_map WHERE group_id = $gid"); if (FetchOneColumn()) { if (!defined $::FORM{'removebugs'}) { $cantdelete = 1; @@ -496,141 +530,123 @@ if ($action eq 'delete') { } if ($cantdelete == 1) { - ShowError("This group cannot be deleted because there are child " . - "records in the database which refer to it. All child records " . + ShowError("This group cannot be deleted because there are " . + "records in the database which refer to it. All such records " . "must be removed or altered to remove the reference to this " . "group before the group can be deleted."); - print "" . + print "" . "View the list of which records are affected
"; PutTrailer("Back to group list"); exit; } - SendSQL("SELECT login_name,groupset,blessgroupset FROM profiles WHERE " . - "(groupset & $bit) OR (blessgroupset & $bit)"); - if (FetchOneColumn()) { - SendSQL("UPDATE profiles SET groupset=(groupset-$bit) " . - "WHERE (groupset & $bit)"); - print "All users have been removed from group $bit.
"; - SendSQL("UPDATE profiles SET blessgroupset=(blessgroupset-$bit) " . - "WHERE (blessgroupset & $bit)"); - print "All users with authority to add users to group $bit have " . - "had that authority removed.
"; - } - SendSQL("SELECT bug_id FROM bugs WHERE (groupset & $bit)"); - if (FetchOneColumn()) { - SendSQL("UPDATE bugs SET groupset=(groupset-$bit), delta_ts=delta_ts " . - "WHERE (groupset & $bit)"); - print "All bugs have had group bit $bit cleared. Any of these " . - "bugs that were not also in another group are now " . - "publicly visible.
"; - } - SendSQL("DELETE FROM groups WHERE bit=$bit"); - print "Group $bit has been deleted.
"; - - foreach my $userid (@opusers) { - SendSQL("UPDATE profiles SET groupset=$opblessgroupset " . - "WHERE userid=$userid"); - print "Group bits restored for " . DBID_to_name($userid) . - " (maintainer)
\n"; - } + SendSQL("DELETE FROM user_group_map WHERE group_id = $gid"); + SendSQL("DELETE FROM group_group_map WHERE grantor_id = $gid"); + SendSQL("DELETE FROM bug_group_map WHERE group_id = $gid"); + SendSQL("DELETE FROM groups WHERE id = $gid"); + print "Group $gid has been deleted.
"; + PutTrailer("Back to group list"); exit; } # -# action='update' -> update the groups +# action='postchanges' -> update the groups # -if ($action eq 'update') { - PutHeader("Updating groups"); - +if ($action eq 'postchanges') { + PutHeader("Updating group hierarchy"); + my $gid = trim($::FORM{group} || ''); + unless ($gid) { + ShowError("No group specified.
" . + "Click the Back button and try again."); + PutFooter(); + exit; + } + SendSQL("SELECT isbuggroup FROM groups WHERE id = $gid"); + my ($isbuggroup) = FetchSQLData(); my $chgs = 0; - - foreach my $b (grep(/^name-\d*$/, keys %::FORM)) { - if ($::FORM{$b}) { - my $v = substr($b, 5); - -# print "Old: '" . $::FORM{"oldname-$v"} . "', '" . $::FORM{"olddesc-$v"} . -# "', '" . $::FORM{"oldregexp-$v"} . "'
"; -# print "New: '" . $::FORM{"name-$v"} . "', '" . $::FORM{"desc-$v"} . -# "', '" . $::FORM{"regexp-$v"} . "'
"; - - if ($::FORM{"oldname-$v"} ne $::FORM{"name-$v"}) { - $chgs = 1; - SendSQL("SELECT name FROM groups WHERE name=" . - SqlQuote($::FORM{"name-$v"})); - if (!FetchOneColumn()) { - SendSQL("SELECT name FROM groups WHERE name=" . - SqlQuote($::FORM{"oldname-$v"}) . - " && isbuggroup = 0"); - if (FetchOneColumn()) { - ShowError("You cannot update the name of a " . - "system group. Skipping $v"); - } else { - SendSQL("UPDATE groups SET name=" . - SqlQuote($::FORM{"name-$v"}) . - " WHERE bit=" . SqlQuote($v)); - print "Group $v name updated.
\n"; - } - } else { - ShowError("Duplicate name '" . $::FORM{"name-$v"} . - "' specified for group $v.
" . - "Update of group $v name skipped."); - } - } - if ($::FORM{"olddesc-$v"} ne $::FORM{"desc-$v"}) { + if (($isbuggroup == 1) && ($::FORM{"oldname"} ne $::FORM{"name"})) { + $chgs = 1; + SendSQL("UPDATE groups SET name = " . + SqlQuote($::FORM{"name"}) . " WHERE id = $gid"); + } + if (($isbuggroup == 1) && ($::FORM{"olddesc"} ne $::FORM{"desc"})) { + $chgs = 1; + SendSQL("UPDATE groups SET description = " . + SqlQuote($::FORM{"desc"}) . " WHERE id = $gid"); + } + if ($::FORM{"oldrexp"} ne $::FORM{"rexp"}) { + $chgs = 1; + if (!eval {qr/$::FORM{"rexp"}/}) { + ShowError("The regular expression you entered is invalid. " . + "Please click the Back button and try again."); + PutFooter(); + exit; + } + SendSQL("UPDATE groups SET userregexp = " . + SqlQuote($::FORM{"rexp"}) . " WHERE id = $gid"); + } + if (($isbuggroup == 1) && ($::FORM{"oldisactive"} ne $::FORM{"isactive"})) { + $chgs = 1; + SendSQL("UPDATE groups SET isactive = " . + SqlQuote($::FORM{"isactive"}) . " WHERE id = $gid"); + } + + print "Checking...."; + foreach my $b (grep(/^oldgrp-\d*$/, keys %::FORM)) { + if (defined($::FORM{$b})) { + my $v = substr($b, 7); + my $grp = $::FORM{"grp-$v"} || 0; + if ($::FORM{"oldgrp-$v"} != $grp) { $chgs = 1; - SendSQL("SELECT description FROM groups WHERE description=" . - SqlQuote($::FORM{"desc-$v"})); - if (!FetchOneColumn()) { - SendSQL("UPDATE groups SET description=" . - SqlQuote($::FORM{"desc-$v"}) . - " WHERE bit=" . SqlQuote($v)); - print "Group $v description updated.
\n"; + print "changed"; + if ($grp != 0) { + print " set "; + SendSQL("INSERT INTO group_group_map + (member_id, grantor_id, isbless) + VALUES ($v, $gid, 0)"); } else { - ShowError("Duplicate description '" . $::FORM{"desc-$v"} . - "' specified for group $v.
" . - "Update of group $v description skipped."); + print " cleared "; + SendSQL("DELETE FROM group_group_map + WHERE member_id = $v AND grantor_id = $gid + AND isbless = 0"); } } - if ($::FORM{"oldregexp-$v"} ne $::FORM{"regexp-$v"}) { - $chgs = 1; - SendSQL("UPDATE groups SET userregexp=" . - SqlQuote($::FORM{"regexp-$v"}) . - " WHERE bit=" . SqlQuote($v)); - print "Group $v user regexp updated.
\n"; - } - # convert an undefined value in the inactive field to zero - # (this occurs when the inactive checkbox is not checked - # and the browser does not send the field to the server) - my $isactive = $::FORM{"isactive-$v"} || 0; - if ($::FORM{"oldisactive-$v"} != $isactive) { + + my $bless = $::FORM{"bless-$v"} || 0; + if ($::FORM{"oldbless-$v"} != $bless) { $chgs = 1; - if ($isactive == 0 || $isactive == 1) { - SendSQL("UPDATE groups SET isactive=$isactive" . - " WHERE bit=" . SqlQuote($v)); - print "Group $v active flag updated.
\n"; + print "changed"; + if ($bless != 0) { + print " set "; + SendSQL("INSERT INTO group_group_map + (member_id, grantor_id, isbless) + VALUES ($v, $gid, 1)"); } else { - ShowError("The value '" . $isactive . - "' is not a valid value for the active flag.
" . - "There may be a problem with Bugzilla or a bug in your browser.
" . - "Update of active flag for group $v skipped."); + print " cleared "; + SendSQL("DELETE FROM group_group_map + WHERE member_id = $v AND grantor_id = $gid + AND isbless = 1"); } } + } } if (!$chgs) { print "You didn't change anything!
\n"; print "If you really meant it, hit the Back button and try again.

\n"; } else { + SendSQL("UPDATE groups SET last_changed = NOW() WHERE id = $gid"); print "Done.

\n"; } PutTrailer("Back to the group list"); exit; } + + # # No valid action found # diff --git a/editproducts.cgi b/editproducts.cgi index 5b4c3c249..18ad4216d 100755 --- a/editproducts.cgi +++ b/editproducts.cgi @@ -79,9 +79,9 @@ sub CheckProduct ($) # Displays the form to edit a products parameters # -sub EmitFormElements ($$$$$$$$$) +sub EmitFormElements ($$$$$$$$) { - my ($product, $description, $milestoneurl, $userregexp, $disallownew, + my ($product, $description, $milestoneurl, $disallownew, $votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone) = @_; @@ -110,13 +110,6 @@ sub EmitFormElements ($$$$$$$$$) print qq{\n}; } - # Added -JMR, 2/16/00 - if (Param("usebuggroups")) { - $userregexp = value_quote($userregexp); - print "

\n"; - print " \n"; - print " \n"; - } print "\n"; print " \n"; @@ -263,7 +256,7 @@ if ($action eq 'add') { print "\n"; print "
BitIdNameDescription
$bit$gid$name$desc
User Regexp for Bug Group:
Closed for bug entry:
\n"; - EmitFormElements('', '', '', '', 0, 0, 10000, 0, "---"); + EmitFormElements('', '', '', 0, 0, 10000, 0, "---"); print "\n"; print " \n"; @@ -315,7 +308,6 @@ if ($action eq 'new') { my $description = trim($::FORM{description} || ''); my $milestoneurl = trim($::FORM{milestoneurl} || ''); - my $userregexp = trim($::FORM{userregexp} || ''); my $disallownew = 0; $disallownew = 1 if $::FORM{disallownew}; my $votesperuser = $::FORM{votesperuser}; @@ -351,48 +343,20 @@ if ($action eq 'new') { # If we're using bug groups, then we need to create a group for this # product as well. -JMR, 2/16/00 if(Param("usebuggroups")) { - # First we need to figure out the bit for this group. We'll simply - # use the next highest bit available. We'll use a minimum bit of 256, - # to leave room for a few more Bugzilla operation groups at the bottom. - SendSQL("SELECT MAX(bit) FROM groups"); - my $bit = FetchOneColumn(); - if($bit < 256) { - $bit = 256; - } else { - $bit = $bit * 2; - } - # Next we insert into the groups table SendSQL("INSERT INTO groups " . - "(bit, name, description, isbuggroup, userregexp) " . + "(name, description, isbuggroup, last_changed) " . "VALUES (" . - $bit . ", " . SqlQuote($product) . ", " . - SqlQuote($product . " Bugs Access") . ", " . - "1, " . - SqlQuote($userregexp) . ")"); + SqlQuote("Access to bugs in the $product product") . ", 1, NOW())"); + SendSQL("SELECT last_insert_id()"); + my $gid = FetchOneColumn(); + my $admin = GroupNameToId('admin'); + SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless) + VALUES ($admin, $gid, 0)"); + SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless) + VALUES ($admin, $gid, 1)"); - # And last, we need to add any existing users that match the regexp - # to the group. - # There may be a better way to do this in MySql, but I need to compare - # the login_names to this regexp, and the only way I can think of to - # do that is to get the list of login_names, and then update them - # one by one if they match. Furthermore, I need to do it with two - # separate loops, since opening a new SQL statement to do the update - # seems to clobber the previous one. - - # Modified, 7/17/00, Joe Robins - # If the userregexp is left empty, then no users should be added to - # the bug group. As is, it was adding all users, since they all - # matched the empty pattern. - # In addition, I've replaced the rigamarole I was going through to - # find matching users with a much simpler statement that lets the - # mySQL database do the work. - unless($userregexp eq "") { - SendSQL("UPDATE profiles ". - "SET groupset = groupset | " . $bit . " " . - "WHERE LOWER(login_name) REGEXP LOWER(" . SqlQuote($userregexp) . ")"); - } } # Make versioncache flush @@ -444,22 +408,6 @@ if ($action eq 'del') { print " \n"; } - # Added -JMR, 2/16/00 - if(Param('usebuggroups')) { - # Get the regexp for this product. - SendSQL("SELECT userregexp - FROM groups - WHERE name=" . SqlQuote($product)); - my $userregexp = FetchOneColumn(); - if(!defined $userregexp) { - $userregexp = "undefined"; - } elsif ($userregexp eq "") { - $userregexp = "blank"; - } - print "\n"; - print " \n"; - print " \n"; - } print "\n"; print " \n"; @@ -637,30 +585,6 @@ if ($action eq 'delete') { WHERE id=$product_id"); print "Product '$product' deleted.
\n"; - # Added -JMR, 2/16/00 - if (Param("usebuggroups")) { - # We need to get the bit of the group from the table, then update the - # groupsets of members of that group and remove the group. - SendSQL("SELECT bit, description FROM groups " . - "WHERE name = " . SqlQuote($product)); - my ($bit, $group_desc) = FetchSQLData(); - - # Make sure there is a group before we try to do any deleting... - if($bit) { - # I'm kludging a bit so that I don't break superuser access; - # I'm merely checking to make sure that the groupset is not - # the superuser groupset in doing this update... - SendSQL("UPDATE profiles " . - "SET groupset = groupset - $bit " . - "WHERE (groupset & $bit) " . - "AND (groupset != 9223372036854710271)"); - print "Users dropped from group '$group_desc'.
\n"; - - SendSQL("DELETE FROM groups " . - "WHERE bit = $bit"); - print "Group '$group_desc' deleted.
\n"; - } - } SendSQL("UNLOCK TABLES"); @@ -690,18 +614,10 @@ if ($action eq 'edit') { $votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone) = FetchSQLData(); - my $userregexp = ''; - if(Param("usebuggroups")) { - SendSQL("SELECT userregexp - FROM groups - WHERE name=" . SqlQuote($product)); - $userregexp = FetchOneColumn() || ""; - } - print "\n"; print "
Version:$milestonelink
User Regexp for Bug Group:$userregexp
Closed for bugs:
\n"; - EmitFormElements($product, $description, $milestoneurl, $userregexp, + EmitFormElements($product, $description, $milestoneurl, $disallownew, $votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone); @@ -787,10 +703,6 @@ if ($action eq 'edit') { value_quote($description) . "\">\n"; print "\n"; - if(Param("usebuggroups")) { - print "\n"; - } print "\n"; print "\n"; print "\n"; @@ -826,8 +738,6 @@ if ($action eq 'update') { my $milestoneurlold = trim($::FORM{milestoneurlold} || ''); my $votesperuser = trim($::FORM{votesperuser} || 0); my $votesperuserold = trim($::FORM{votesperuserold} || 0); - my $userregexp = trim($::FORM{userregexp} || ''); - my $userregexpold = trim($::FORM{userregexpold} || ''); my $maxvotesperbug = trim($::FORM{maxvotesperbug} || 0); my $maxvotesperbugold = trim($::FORM{maxvotesperbugold} || 0); my $votestoconfirm = trim($::FORM{votestoconfirm} || 0); @@ -883,68 +793,6 @@ if ($action eq 'update') { print "Updated mile stone URL.
\n"; } - # Added -JMR, 2/16/00 - if (Param("usebuggroups") && $userregexp ne $userregexpold) { - # This will take a little bit of work here, since there may not be - # an existing bug group for this product, and we will also have to - # update users groupsets. - # First we find out if there's an existing group for this product, and - # get its bit if there is. - SendSQL("SELECT bit " . - "FROM groups " . - "WHERE name = " . SqlQuote($productold)); - my $bit = FetchOneColumn(); - if($bit) { - # Group exists, so we do an update statement. - SendSQL("UPDATE groups " . - "SET userregexp = " . SqlQuote($userregexp) . " " . - "WHERE name = " . SqlQuote($productold)); - print "Updated user regexp for bug group.
\n"; - } else { - # Group doesn't exist. Let's make it, the same way as we make a - # group for a new product above. - SendSQL("SELECT MAX(bit) FROM groups"); - my $tmp_bit = FetchOneColumn(); - if($tmp_bit < 256) { - $bit = 256; - } else { - $bit = $tmp_bit * 2; - } - SendSQL("INSERT INTO groups " . - "(bit, name, description, isbuggroup, userregexp) " . - "values (" . $bit . ", " . - SqlQuote($productold) . ", " . - SqlQuote($productold . " Bugs Access") . ", " . - "1, " . - SqlQuote($userregexp) . ")"); - print "Created bug group.
\n"; - } - - # And now we have to update the profiles again to add any users who - # match the new regexp to the group. I'll do this the same way as - # when I create a new group above. Note that I'm not taking out - # users who matched the old regexp and not the new one; that would - # be insanely messy. Use the group administration page for that - # instead. - SendSQL("SELECT login_name FROM profiles"); - my @login_list = (); - my $this_login; - while($this_login = FetchOneColumn()) { - push @login_list, $this_login; - } - my $updated_profiles = 0; - foreach $this_login (@login_list) { - if($this_login =~ /$userregexp/) { - SendSQL("UPDATE profiles " . - "SET groupset = groupset | " . $bit . " " . - "WHERE login_name = " . SqlQuote($this_login)); - $updated_profiles = 1; - } - } - if($updated_profiles) { - print "Added users matching regexp to group.
\n"; - } - } if ($votesperuser ne $votesperuserold) { SendSQL("UPDATE products @@ -1012,9 +860,10 @@ if ($action eq 'update') { # update it so it will match in the future. If there is no group, this # update statement will do nothing, so no harm done. -JMR, 3/8/00 SendSQL("UPDATE groups " . - "SET name=$qp, " . - "description=".SqlQuote($product." Bugs Access")." ". - "WHERE name=$qpold"); + "SET name = $qp, " . + "description = " . + SqlQuote("Access to bugs in the $product product") . + " WHERE name = $qpold"); print "Updated product name.
\n"; } diff --git a/editusers.cgi b/editusers.cgi index 4586345af..cc6be6665 100755 --- a/editusers.cgi +++ b/editusers.cgi @@ -22,6 +22,7 @@ # Dave Miller # Joe Robins # Dan Mosedale +# Joel Peshkin # # Direct any questions on this source code to # @@ -39,11 +40,9 @@ require "globals.pl"; sub sillyness { my $zz; $zz = $::userid; - $zz = $::superusergroupset; } my $editall; -my $opblessgroupset = '9223372036854775807'; # This is all 64 bits. @@ -97,9 +96,9 @@ sub EmitElement ($$) # Displays the form to edit a user parameters # -sub EmitFormElements ($$$$$) +sub EmitFormElements ($$$$) { - my ($user, $realname, $groupset, $blessgroupset, $disabledtext) = @_; + my ($user_id, $user, $realname, $disabledtext) = @_; print " \n"; EmitElement("user", $user); @@ -134,11 +133,15 @@ sub EmitFormElements ($$$$$) if($user ne "") { print "\n"; - - print "\n"; } @@ -238,9 +241,7 @@ print "Content-type: text/html\n\n"; $editall = UserInGroup("editusers"); if (!$editall) { - SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $::userid"); - $opblessgroupset = FetchOneColumn(); - if (!$opblessgroupset) { + if (!UserCanBlessAnything()) { PutHeader("Not allowed"); print "Sorry, you aren't a member of the 'editusers' group, and you\n"; print "don't have permissions to put people in or out of any group.\n"; @@ -316,6 +317,10 @@ if ($action eq 'list') { } elsif (exists $::FORM{'query'}) { $query = "SELECT login_name,realname,disabledtext " . "FROM profiles WHERE " . $::FORM{'query'} . " ORDER BY login_name"; + } elsif (exists $::FORM{'group'}) { + $query = "SELECT DISTINCT login_name,realname,disabledtext " . + "FROM profiles, user_group_map WHERE profiles.userid = user_group_map.user_id + AND group_id=" . $::FORM{'group'} . " ORDER BY login_name"; } else { die "Missing parameters"; } @@ -396,7 +401,7 @@ if ($action eq 'add') { print "\n"; print "
Login name:
Group Access:"; - SendSQL("SELECT bit,name,description,bit & $groupset != 0, " . - " bit & $blessgroupset " . + SendSQL("SELECT groups.id, groups.name, groups.description, " . + "COUNT(user_id), " . + "MAX(isderived) " . "FROM groups " . - "WHERE bit & $opblessgroupset != 0 AND isbuggroup " . - "ORDER BY name"); + "LEFT JOIN user_group_map " . + "ON user_group_map.group_id = groups.id " . + "AND isbless = 0 " . + "AND user_id = $user_id " . + "GROUP BY groups.name "); if (MoreSQLData()) { if ($editall) { print "\n"; @@ -146,50 +149,50 @@ sub EmitFormElements ($$$$$) } print "\n"; while (MoreSQLData()) { - my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData(); - print "\n"; + my ($groupid, $name, $description, $member, $isderived) = FetchSQLData(); + next if (!$editall && !UserCanBlessGroup($name)); + $isderived = $isderived || 0; + my $checked = $member - $isderived; + PushGlobalSQLState(); + SendSQL("SELECT user_id " . + "FROM user_group_map " . + "WHERE isbless = 1 " . + "AND user_id = $user_id " . + "AND group_id = $groupid"); + my ($blchecked) = FetchSQLData() ? 1 : 0; + SendSQL("SELECT grantor_id FROM user_group_map, + group_group_map + WHERE $groupid = grantor_id + AND user_group_map.user_id = $user_id + AND user_group_map.isbless = 0 + AND group_group_map.isbless = 1 + AND user_group_map.group_id = member_id"); + my $derivedbless = FetchOneColumn(); + PopGlobalSQLState(); + print "\n"; + print "\n"; + print "\n"; if ($editall) { $blchecked = ($blchecked) ? "CHECKED" : ""; - print ""; + print "\n"; } $checked = ($checked) ? "CHECKED" : ""; - print ""; - print "\n"; - } - } - print "
Can turn this bit on for other usersUser is a member of these groups
"; + print "[" if $derivedbless; + print ""; + print "]" if $derivedbless; + print "" . ucfirst($name) . ": $description
Privileges:"; - SendSQL("SELECT bit,name,description,bit & $groupset != 0, " . - " bit & $blessgroupset " . - "FROM groups " . - "WHERE bit & $opblessgroupset != 0 AND !isbuggroup " . - "ORDER BY name"); - if (MoreSQLData()) { - if ($editall) { - print "\n"; - print "\n\n"; - } - print "\n"; - while (MoreSQLData()) { - my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData(); - print "\n"; - if ($editall) { - $blchecked = ($blchecked) ? "CHECKED" : ""; - print ""; + print "\n"; } - $checked = ($checked) ? "CHECKED" : ""; - print ""; - print "\n"; } } - } else { - print "\n"; print "
Can turn this bit on for other users
|User has these privileges
"; + print '[' if ($isderived); + print ""; + print ']' if ($isderived); + print ""; + print ucfirst($name) . ": $description" . ucfirst($name) . ": $description
Groups and
Privileges:
"; - print "\n"; - } + print "
The new user will be inserted into groups " . - "based on their userregexps.
To change the group " . - "permissions for this user, you must edit the account after ". - "creating it.
\n"; - EmitFormElements('', '', 0, 0, ''); + EmitFormElements(0, '', '', ''); print "
\n


\n"; print "\n"; @@ -465,48 +470,22 @@ if ($action eq 'new') { exit; } - # For new users, we use the regexps from the groups table to determine - # their initial group membership. - # We also keep a list of groups the user was added to for display on the - # confirmation page. - my $bits = "0"; - my @grouplist = (); - SendSQL("select bit, name, userregexp from groups where userregexp != ''"); - while (MoreSQLData()) { - my @row = FetchSQLData(); - if ($user =~ m/$row[2]/i) { - $bits .= "+ $row[0]"; # Silly hack to let MySQL do the math, - # not Perl, since we're dealing with 64 - # bit ints here, and I don't *think* Perl - # does that. - push(@grouplist, $row[1]); - } - } - # Add the new user SendSQL("INSERT INTO profiles ( " . - "login_name, cryptpassword, realname, groupset, " . + "login_name, cryptpassword, realname, " . "disabledtext" . " ) VALUES ( " . SqlQuote($user) . "," . SqlQuote(Crypt($password)) . "," . SqlQuote($realname) . "," . - $bits . "," . SqlQuote($disabledtext) . ")" ); #+++ send e-mail away print "OK, done.
\n"; - if($#grouplist > -1) { - print "New user added to these groups based on group regexps:\n"; - print "
    \n"; - foreach (@grouplist) { - print "
  • $_
  • \n"; - } - print "
\n"; - } else { - print "New user not added to any groups.

\n"; - } + SendSQL("SELECT last_insert_id()"); + my ($newuserid) = FetchSQLData(); + DeriveGroup($newuserid); print "To change ${user}'s permissions, go back and edit this user"; print "

\n"; PutTrailer($localtrailer, @@ -538,9 +517,9 @@ if ($action eq 'del') { CheckUser($user); # display some data about the user - SendSQL("SELECT realname, groupset FROM profiles + SendSQL("SELECT userid, realname FROM profiles WHERE login_name=" . SqlQuote($user)); - my ($realname, $groupset) = + my ($thisuserid, $realname) = FetchSQLData(); $realname = ($realname ? html_quote($realname) : "missing"); @@ -561,9 +540,11 @@ if ($action eq 'del') { print " Group set:\n"; print " "; SendSQL("SELECT name - FROM groups - WHERE bit & $groupset = bit - ORDER BY isbuggroup, name"); + FROM groups, user_group_map + WHERE groups.id = user_group_map.group_id + AND user_group_map.user_id = $thisuserid + AND isbless = 0 + ORDER BY name"); my $found = 0; while ( MoreSQLData() ) { my ($name) = FetchSQLData(); @@ -697,27 +678,34 @@ if ($action eq 'edit') { CheckUser($user); # get data of user - SendSQL("SELECT realname, groupset, blessgroupset, disabledtext + SendSQL("SELECT userid, realname, disabledtext FROM profiles WHERE login_name=" . SqlQuote($user)); - my ($realname, $groupset, $blessgroupset, - $disabledtext) = FetchSQLData(); + my ($thisuserid, $realname, $disabledtext) = FetchSQLData(); + if ($thisuserid > 0) { + DeriveGroup($thisuserid); + } print "\n"; print "\n"; - EmitFormElements($user, $realname, $groupset, $blessgroupset, $disabledtext); + EmitFormElements($thisuserid, $user, $realname, $disabledtext); print "
\n"; - print "\n"; print "\n"; - print "\n"; - print "\n"; print "\n"; print "\n"; print "\n"; + print "
User is a member of any groups shown with grey bars and + marked with brackets surrounding the membership checkbox as a + result of a regular expression match + or membership in another group. + User can bless any group + marked with brackets surrounding the bless checkbox as a + result of membership in another group. +
"; print ""; @@ -740,69 +728,67 @@ if ($action eq 'update') { my $password = $::FORM{password} || ''; my $disabledtext = trim($::FORM{disabledtext} || ''); my $disabledtextold = trim($::FORM{disabledtextold} || ''); - my $groupsetold = trim($::FORM{groupsetold} || '0'); - my $blessgroupsetold = trim($::FORM{blessgroupsetold} || '0'); - my $groupset = "0"; - foreach (keys %::FORM) { - next unless /^bit_/; - #print "$_=$::FORM{$_}
\n"; - detaint_natural($::FORM{$_}) || die "Groupset field tampered with"; - $groupset .= " + $::FORM{$_}"; + CheckUser($userold); + SendSQL("SELECT userid FROM profiles + WHERE login_name=" . SqlQuote($userold)); + my ($thisuserid) = FetchSQLData(); + + my @grpadd = (); + my @grpdel = (); + my $chggrp = 0; + SendSQL("SELECT id, name FROM groups"); + while (my ($groupid, $name) = FetchSQLData()) { + if ($::FORM{"oldgroup_$groupid"} != ($::FORM{"group_$groupid"} ? 1 : 0)) { + # group membership changed + PushGlobalSQLState(); + $chggrp = 1; + SendSQL("DELETE FROM user_group_map + WHERE user_id = $thisuserid + AND group_id = $groupid + AND isbless = 0 + AND isderived = 0"); + if ($::FORM{"group_$groupid"}) { + SendSQL("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($thisuserid, $groupid, 0, 0)"); + print "Added user to group $name
\n"; + push(@grpadd, $name); + } else { + print "Dropped user from group $name
\n"; + push(@grpdel, $name); + } + PopGlobalSQLState(); + } + if ($editall && ($::FORM{"oldbless_$groupid"} != ($::FORM{"bless_$groupid"} ? 1 : 0))) { + # group membership changed + PushGlobalSQLState(); + SendSQL("DELETE FROM user_group_map + WHERE user_id = $thisuserid + AND group_id = $groupid + AND isbless = 1 + AND isderived = 0"); + if ($::FORM{"bless_$groupid"}) { + SendSQL("INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES ($thisuserid, $groupid, 1, 0)"); + print "Granted user permission to bless group $name
\n"; + } else { + print "Revoked user's permission to bless group $name
\n"; + } + PopGlobalSQLState(); + + } } - my $blessgroupset = "0"; - foreach (keys %::FORM) { - next unless /^blbit_/; - #print "$_=$::FORM{$_}
\n"; - detaint_natural($::FORM{$_}) || die "Blessgroupset field tampered with"; - $blessgroupset .= " + $::FORM{$_}"; + my $fieldid = GetFieldID("bug_group"); + if ($chggrp) { + SendSQL("INSERT INTO profiles_activity " . + "(userid, who, profiles_when, fieldid, oldvalue, newvalue) " . + "VALUES " . "($thisuserid, $::userid, now(), $fieldid, " . + SqlQuote(join(", ",@grpdel)) . ", " . + SqlQuote(join(", ",@grpadd)) . ")"); } - CheckUser($userold); - - # Note that the order of this tests is important. If you change - # them, be sure to test for WHERE='$product' or WHERE='$productold' - - if ($groupset ne $groupsetold) { - SendSQL("SELECT groupset FROM profiles WHERE login_name=" . - SqlQuote($userold)); - $groupsetold = FetchOneColumn(); - # Updated, 5/7/00, Joe Robins - # We don't want to change the groupset of a superuser. - if($groupsetold eq $::superusergroupset) { - print "Cannot change permissions of superuser.\n"; - } else { - SendSQL("UPDATE profiles - SET groupset = - groupset - (groupset & $opblessgroupset) + - (($groupset) & $opblessgroupset) - WHERE login_name=" . SqlQuote($userold)); - - # I'm paranoid that someone who I give the ability to bless people - # will start misusing it. Let's log who blesses who (even though - # nothing actually uses this log right now). - my $fieldid = GetFieldID("groupset"); - SendSQL("SELECT userid, groupset FROM profiles WHERE login_name=" . - SqlQuote($userold)); - my $u; - ($u, $groupset) = (FetchSQLData()); - if ($groupset ne $groupsetold) { - SendSQL("INSERT INTO profiles_activity " . - "(userid,who,profiles_when,fieldid,oldvalue,newvalue) " . - "VALUES " . - "($u, $::userid, now(), $fieldid, " . - " $groupsetold, $groupset)"); - } - print "Updated permissions.\n"; - } - } - - if ($editall && $blessgroupset ne $blessgroupsetold) { - SendSQL("UPDATE profiles - SET blessgroupset=" . $blessgroupset . " - WHERE login_name=" . SqlQuote($userold)); - print "Updated ability to tweak permissions of other users.\n"; - } # Update the database with the user's new password if they changed it. if ( !Param('useLDAP') && $editall && $password ) { diff --git a/enter_bug.cgi b/enter_bug.cgi index a779aa85d..a7733ec3e 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -48,6 +48,7 @@ use vars qw( @legal_platform @legal_priority @legal_severity + $userid %MFORM %versions ); @@ -326,60 +327,58 @@ $default{'bug_status'} = $status[0]; # Select whether to restrict this bug to the product's bug group or not, # if the usebuggroups parameter is set, and if this product has a bug group. -if ($::usergroupset ne '0') { - # First we get the bit and description for the group. - my $group_bit = '0'; - - if(Param("usebuggroups") && GroupExists($product)) { - SendSQL("SELECT bit FROM groups ". - "WHERE name = " . SqlQuote($product) . " " . - "AND isbuggroup != 0"); - ($group_bit) = FetchSQLData(); - } - - SendSQL("SELECT bit, name, description FROM groups " . - "WHERE bit & $::usergroupset != 0 " . - "AND isbuggroup != 0 AND isactive = 1 ORDER BY description"); - - my @groups; - - while (MoreSQLData()) { - my ($bit, $prodname, $description) = FetchSQLData(); - # Don't want to include product groups other than this product. - next unless($prodname eq $product || - !defined($::proddesc{$prodname})); +# First we get the bit and description for the group. +my $group_id = '0'; - my $check; +if(Param("usebuggroups")) { + ($group_id) = GroupExists($product); +} - # If this is the group for this product, make it checked. - if(formvalue("maketemplate") eq - "Remember values as bookmarkable template") - { - # If this is a bookmarked template, then we only want to set the - # bit for those bits set in the template. - $check = formvalue("bit-$bit", 0); - } - else { - # $group_bit will only have a non-zero value if we're using - # bug groups and have one for this product. - # If $group_bit is 0, it won't match the current group, so compare - # it to the current bit instead of checking for non-zero. - $check = ($group_bit == $bit); - } +SendSQL("SELECT DISTINCT groups.id, groups.name, groups.description " . + "FROM groups, user_group_map " . + "WHERE user_group_map.group_id = groups.id " . + "AND user_group_map.user_id = $::userid " . + "AND isbless = 0 " . + "AND isbuggroup = 1 AND isactive = 1 ORDER BY description"); - my $group = - { - 'bit' => $bit , - 'checked' => $check , - 'description' => $description - }; +my @groups; - push @groups, $group; +while (MoreSQLData()) { + my ($id, $prodname, $description) = FetchSQLData(); + # Don't want to include product groups other than this product. + next unless(!Param("usebuggroups") || $prodname eq $product || + !defined($::proddesc{$prodname})); + + my $check; + + # If this is the group for this product, make it checked. + if(formvalue("maketemplate") eq + "Remember values as bookmarkable template") + { + # If this is a bookmarked template, then we only want to set the + # bit for those bits set in the template. + $check = formvalue("bit-$id", 0); + } + else { + # $group_bit will only have a non-zero value if we're using + # bug groups and have one for this product. + # If $group_bit is 0, it won't match the current group, so compare + # it to the current bit instead of checking for non-zero. + $check = ($group_id == $id); } - $vars->{'group'} = \@groups; + my $group = + { + 'bit' => $id , + 'checked' => $check , + 'description' => $description + }; + + push @groups, $group; } +$vars->{'group'} = \@groups; + $vars->{'default'} = \%default; my $format = @@ -388,3 +387,4 @@ my $format = print "Content-type: $format->{'ctype'}\n\n"; $template->process($format->{'template'}, $vars) || ThrowTemplateError($template->error()); + diff --git a/globals.pl b/globals.pl index 4570a5658..242c8a5f6 100644 --- a/globals.pl +++ b/globals.pl @@ -22,6 +22,7 @@ # Jacob Steenhagen # Bradley Baetz # Christopher Aillon +# Joel Peshkin # Contains some global variables and routines used throughout bugzilla. @@ -57,7 +58,6 @@ sub globals_pl_sillyness { $zz = @main::milestoneurl; $zz = %main::proddesc; $zz = @main::prodmaxvotes; - $zz = $main::superusergroupset; $zz = $main::template; $zz = $main::userid; $zz = $main::vars; @@ -102,10 +102,6 @@ $::defaultqueryname = "(Default query)"; $::unconfirmedstate = "UNCONFIRMED"; $::dbwritesallowed = 1; -# Adding a global variable for the value of the superuser groupset. -# Joe Robins, 7/5/00 -$::superusergroupset = "9223372036854775807"; - #sub die_with_dignity { # my ($err_msg) = @_; # print $err_msg; @@ -583,30 +579,14 @@ sub InsertNewUser { my $password = GenerateRandomPassword(); my $cryptpassword = Crypt($password); - # Determine what groups the user should be in by default - # and add them to those groups. - PushGlobalSQLState(); - SendSQL("select bit, userregexp from groups where userregexp != ''"); - my $groupset = "0"; - while (MoreSQLData()) { - my @row = FetchSQLData(); - # Modified -Joe Robins, 2/17/00 - # Making this case insensitive, since usernames are email addresses, - # and could be any case. - if ($username =~ m/$row[1]/i) { - $groupset .= "+ $row[0]"; # Silly hack to let MySQL do the math, - # not Perl, since we're dealing with 64 - # bit ints here, and I don't *think* Perl - # does that. - } - } # Insert the new user record into the database. $username = SqlQuote($username); $realname = SqlQuote($realname); $cryptpassword = SqlQuote($cryptpassword); - SendSQL("INSERT INTO profiles (login_name, realname, cryptpassword, groupset) - VALUES ($username, $realname, $cryptpassword, $groupset)"); + PushGlobalSQLState(); + SendSQL("INSERT INTO profiles (login_name, realname, cryptpassword) + VALUES ($username, $realname, $cryptpassword)"); PopGlobalSQLState(); # Return the password to the calling code so it can be included @@ -650,98 +630,47 @@ sub GenerateRandomPassword { return $password; } -sub SelectVisible { - my ($query, $userid, $usergroupset) = @_; - - # Run the SQL $query with the additional restriction that - # the bugs can be seen by $userid. $usergroupset is provided - # as an optimisation when this is already known, eg from CGI.pl - # If not present, it will be obtained from the db. - # Assumes that 'bugs' is mentioned as a table name. You should - # also make sure that bug_id is qualified bugs.bug_id! - # Your query must have a WHERE clause. This is unlikely to be a problem. - - # Also, note that mySQL requires aliases for tables to be locked, as well - # This means that if you change the name from selectVisible_cc (or add - # additional tables), you will need to update anywhere which does a - # LOCK TABLE, and then calls routines which call this - - $usergroupset = 0 unless $userid; - - unless (defined($usergroupset)) { - PushGlobalSQLState(); - SendSQL("SELECT groupset FROM profiles WHERE userid = $userid"); - $usergroupset = FetchOneColumn(); - PopGlobalSQLState(); - } - - # Users are authorized to access bugs if they are a member of all - # groups to which the bug is restricted. User group membership and - # bug restrictions are stored as bits within bitsets, so authorization - # can be determined by comparing the intersection of the user's - # bitset with the bug's bitset. If the result matches the bug's bitset - # the user is a member of all groups to which the bug is restricted - # and is authorized to access the bug. - - # A user is also authorized to access a bug if she is the reporter, - # or member of the cc: list of the bug and the bug allows users in those - # roles to see the bug. The boolean fields reporter_accessible and - # cclist_accessible identify whether or not those roles can see the bug. - - # Bit arithmetic is performed by MySQL instead of Perl because bitset - # fields in the database are 64 bits wide (BIGINT), and Perl installations - # may or may not support integers larger than 32 bits. Using bitsets - # and doing bitset arithmetic is probably not cross-database compatible, - # however, so these mechanisms are likely to change in the future. - - my $replace = " "; - - if ($userid) { - $replace .= "LEFT JOIN cc selectVisible_cc ON - bugs.bug_id = selectVisible_cc.bug_id AND - selectVisible_cc.who = $userid " - } - - $replace .= "WHERE ((bugs.groupset & $usergroupset) = bugs.groupset "; - - if ($userid) { - # There is a mysql bug affecting v3.22 and 3.23 (at least), where this will - # cause all rows to be returned! We work arround this by adding an not isnull - # test to the JOINed cc table. See http://lists.mysql.com/cgi-ez/ezmlm-cgi?9:mss:11417 - # Its needed, even though it shouldn't be - $replace .= "OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid)" . - " OR (bugs.cclist_accessible = 1 AND selectVisible_cc.who = $userid AND not isnull(selectVisible_cc.who))" . - " OR (bugs.assigned_to = $userid)"; - if (Param("useqacontact")) { - $replace .= " OR (bugs.qa_contact = $userid)"; - } - } - - $replace .= ") AND "; - - $query =~ s/\sWHERE\s/$replace/i; - - return $query; -} - sub CanSeeBug { - # Note that we pass in the usergroupset, since this is known - # in most cases (ie viewing bugs). Maybe make this an optional - # parameter? - my ($id, $userid, $usergroupset) = @_; + my ($id, $userid) = @_; # Query the database for the bug, retrieving a boolean value that # represents whether or not the user is authorized to access the bug. + # if no groups are found --> user is permitted to access + # if no user is found for any group --> user is not permitted to access + my $query = "SELECT bugs.bug_id, reporter, assigned_to, qa_contact," . + " reporter_accessible, cclist_accessible," . + " cc.who IS NOT NULL," . + " COUNT(bug_group_map.group_id) as cntbugingroups," . + " COUNT(DISTINCT(user_group_map.group_id)) as cntuseringroups" . + " FROM bugs" . + " LEFT JOIN cc ON bugs.bug_id = cc.bug_id" . + " AND cc.who = $userid" . + " LEFT JOIN bug_group_map ON bugs.bug_id = bug_group_map.bug_id" . + " LEFT JOIN user_group_map ON" . + " user_group_map.group_id = bug_group_map.group_id" . + " AND user_group_map.isbless = 0" . + " AND user_group_map.user_id = $userid" . + " WHERE bugs.bug_id = $id GROUP BY bugs.bug_id"; PushGlobalSQLState(); - SendSQL(SelectVisible("SELECT bugs.bug_id FROM bugs WHERE bugs.bug_id = $id", - $userid, $usergroupset)); - - my $ret = defined(FetchSQLData()); + SendSQL($query); + my ($found_id, $reporter, $assigned_to, $qa_contact, + $rep_access, $cc_access, + $found_cc, $found_groups, $found_members) + = FetchSQLData(); PopGlobalSQLState(); - - return $ret; + return ( + ($found_groups == 0) + || (($userid > 0) && + ( + ($assigned_to == $userid) + || ($qa_contact == $userid) + || (($reporter == $userid) && $rep_access) + || ($found_cc && $cc_access) + || ($found_groups == $found_members) + )) + ); } sub ValidatePassword { @@ -799,6 +728,94 @@ sub Crypt { return $cryptedpassword; } +# ConfirmGroup(userid) is called prior to any activity that relies +# on user_group_map to ensure that derived group permissions are up-to-date. +# Permissions must be rederived if ANY groups have a last_changed newer +# than the profiles.refreshed_when value. +sub ConfirmGroup { + my ($user) = (@_); + PushGlobalSQLState(); + SendSQL("SELECT userid FROM profiles, groups WHERE userid = $user " . + "AND profiles.refreshed_when <= groups.last_changed "); + my $ret = FetchSQLData(); + PopGlobalSQLState(); + if ($ret) { + DeriveGroup($user); + } +} + +# DeriveGroup removes and rederives all derived group permissions for +# the specified user. +sub DeriveGroup { + my ($user) = (@_); + PushGlobalSQLState(); + + SendSQL("LOCK TABLES profiles WRITE, user_group_map WRITE, group_group_map READ, groups READ"); + + # avoid races, we are only as up to date as the BEGINNING of this process + SendSQL("SELECT login_name, NOW() FROM profiles WHERE userid = $user"); + my ($login, $starttime) = FetchSQLData(); + + # first remove any old derived stuff for this user + SendSQL("DELETE FROM user_group_map WHERE user_id = $user " . + "AND isderived = 1"); + + my %groupidsadded = (); + # add derived records for any matching regexps + SendSQL("SELECT id, userregexp FROM groups WHERE userregexp != ''"); + while (MoreSQLData()) { + my ($groupid, $rexp) = FetchSQLData(); + if ($login =~ m/$rexp/i) { + PushGlobalSQLState(); + $groupidsadded{$groupid} = 1; + SendSQL("INSERT INTO user_group_map " . + "(user_id, group_id, isbless, isderived) " . + "VALUES ($user, $groupid, 0, 1)"); + PopGlobalSQLState(); + + } + } + + # Get a list of the groups of which the user is a member. + my %groupidschecked = (); + my @groupidstocheck = (); + SendSQL("SELECT group_id FROM user_group_map WHERE user_id = $user + AND NOT isbless"); + while (MoreSQLData()) { + my ($groupid) = FetchSQLData(); + push(@groupidstocheck,$groupid); + } + + # Each group needs to be checked for inherited memberships once. + while (@groupidstocheck) { + my $group = shift @groupidstocheck; + if (!defined($groupidschecked{"$group"})) { + $groupidschecked{"$group"} = 1; + SendSQL("SELECT grantor_id FROM group_group_map WHERE" + . " member_id = $group AND NOT isbless"); + while (MoreSQLData()) { + my ($groupid) = FetchSQLData(); + if (!defined($groupidschecked{"$groupid"})) { + push(@groupidstocheck,$groupid); + } + if (!$groupidsadded{$groupid}) { + $groupidsadded{$groupid} = 1; + PushGlobalSQLState(); + SendSQL("INSERT INTO user_group_map" + . " (user_id, group_id, isbless, isderived)" + . " VALUES ($user, $groupid, 0, 1)"); + PopGlobalSQLState(); + } + } + } + } + + SendSQL("UPDATE profiles SET refreshed_when = " . + SqlQuote($starttime) . "WHERE userid = $user"); + SendSQL("UNLOCK TABLES"); + PopGlobalSQLState(); +}; + sub DBID_to_real_or_loginname { my ($id) = (@_); @@ -860,6 +877,9 @@ sub DBNameToIdAndCheck { ThrowUserError("invalid_username"); } + + + sub get_product_id { my ($prod) = @_; PushGlobalSQLState(); @@ -1015,12 +1035,12 @@ sub GetBugLink { # is saved off rather than overwritten PushGlobalSQLState(); - SendSQL("SELECT bugs.bug_status, resolution, short_desc, groupset " . + SendSQL("SELECT bugs.bug_status, resolution, short_desc " . "FROM bugs WHERE bugs.bug_id = $bug_num"); # If the bug exists, save its data off for use later in the sub if (MoreSQLData()) { - my ($bug_state, $bug_res, $bug_desc, $bug_grp) = FetchSQLData(); + my ($bug_state, $bug_res, $bug_desc) = FetchSQLData(); # Initialize these variables to be "" so that we don't get warnings # if we don't change them below (which is highly likely). my ($pre, $title, $post) = ("", "", ""); @@ -1035,7 +1055,7 @@ sub GetBugLink { $title .= " $bug_res"; $post = ""; } - if ($bug_grp == 0 || CanSeeBug($bug_num, $::userid, $::usergroupset)) { + if (CanSeeBug($bug_num, $::userid)) { $title .= " - $bug_desc"; } $::buglink{$bug_num} = [$pre, value_quote($title), $post]; @@ -1186,15 +1206,100 @@ sub SqlQuote { return "'$str'"; } + +# UserInGroup returns information aboout the current user if no second +# parameter is specified sub UserInGroup { - return $::vars->{'user'}{'groups'}{$_[0]}; + my ($groupname, $userid) = (@_); + if (!$userid) { + return $::vars->{'user'}{'groups'}{$_[0]}; + } + PushGlobalSQLState(); + $userid ||= $::userid; + SendSQL("SELECT groups.id FROM groups, user_group_map + WHERE groups.id = user_group_map.group_id + AND user_group_map.user_id = $userid + AND isbless = 0 + AND groups.name = " . SqlQuote($groupname)); + my $result = FetchOneColumn(); + PopGlobalSQLState(); + return defined($result); +} + +sub UserCanBlessGroup { + my ($groupname) = (@_); + PushGlobalSQLState(); + # check if user explicitly can bless group + SendSQL("SELECT groups.id FROM groups, user_group_map + WHERE groups.id = user_group_map.group_id + AND user_group_map.user_id = $::userid + AND isbless = 1 + AND groups.name = " . SqlQuote($groupname)); + my $result = FetchOneColumn(); + PopGlobalSQLState(); + if ($result) { + return 1; + } + PushGlobalSQLState(); + # check if user is a member of a group that can bless this group + # this group does not count + SendSQL("SELECT groups.id FROM groups, user_group_map, + group_group_map + WHERE groups.id = grantor_id + AND user_group_map.user_id = $::userid + AND user_group_map.isbless = 0 + AND group_group_map.isbless = 1 + AND user_group_map.group_id = member_id + AND groups.name = " . SqlQuote($groupname)); + $result = FetchOneColumn(); + PopGlobalSQLState(); + return $result; +} + +sub UserCanBlessAnything { + PushGlobalSQLState(); + # check if user explicitly can bless a group + SendSQL("SELECT group_id FROM user_group_map + WHERE user_id = $::userid AND isbless = 1"); + my $result = FetchOneColumn(); + PopGlobalSQLState(); + if ($result) { + return 1; + } + PushGlobalSQLState(); + # check if user is a member of a group that can bless this group + SendSQL("SELECT groups.id FROM groups, user_group_map, + group_group_map + WHERE groups.id = grantor_id + AND user_group_map.user_id = $::userid + AND group_group_map.isbless = 1 + AND user_group_map.group_id = member_id"); + $result = FetchOneColumn(); + PopGlobalSQLState(); + if ($result) { + return 1; + } + return 0; } sub BugInGroup { my ($bugid, $groupname) = (@_); - my $groupbit = GroupNameToBit($groupname); PushGlobalSQLState(); - SendSQL("SELECT (bugs.groupset & $groupbit) != 0 FROM bugs WHERE bugs.bug_id = $bugid"); + SendSQL("SELECT bug_group_map.bug_id != 0 FROM bug_group_map, groups + WHERE bug_group_map.bug_id = $bugid + AND bug_group_map.group_id = groups.id + AND groups.name = " . SqlQuote($groupname)); + my $bugingroup = FetchOneColumn(); + PopGlobalSQLState(); + return $bugingroup; +} + +sub BugInGroupId { + my ($bugid, $groupid) = (@_); + PushGlobalSQLState(); + SendSQL("SELECT bug_id != 0 FROM bug_group_map + WHERE bug_id = $bugid + AND group_id = $groupid"); my $bugingroup = FetchOneColumn(); PopGlobalSQLState(); return $bugingroup; @@ -1203,32 +1308,39 @@ sub BugInGroup { sub GroupExists { my ($groupname) = (@_); PushGlobalSQLState(); - SendSQL("select count(*) from groups where name=" . SqlQuote($groupname)); - my $count = FetchOneColumn(); + SendSQL("SELECT id FROM groups WHERE name=" . SqlQuote($groupname)); + my $id = FetchOneColumn(); PopGlobalSQLState(); - return $count; + return $id; } -# Given the name of an existing group, returns the bit associated with it. -# If the group does not exist, returns 0. -# !!! Remove this function when the new group system is implemented! -sub GroupNameToBit { +sub GroupNameToId { my ($groupname) = (@_); PushGlobalSQLState(); - SendSQL("SELECT bit FROM groups WHERE name = " . SqlQuote($groupname)); - my $bit = FetchOneColumn() || 0; + SendSQL("SELECT id FROM groups WHERE name=" . SqlQuote($groupname)); + my $id = FetchOneColumn(); PopGlobalSQLState(); - return $bit; + return $id; } +sub GroupIdToName { + my ($groupid) = (@_); + PushGlobalSQLState(); + SendSQL("SELECT name FROM groups WHERE id = $groupid"); + my $name = FetchOneColumn(); + PopGlobalSQLState(); + return $name; +} + + # Determines whether or not a group is active by checking # the "isactive" column for the group in the "groups" table. -# Note: This function selects groups by bit rather than by name. +# Note: This function selects groups by id rather than by name. sub GroupIsActive { - my ($groupbit) = (@_); - $groupbit ||= 0; + my ($groupid) = (@_); + $groupid ||= 0; PushGlobalSQLState(); - SendSQL("select isactive from groups where bit=$groupbit"); + SendSQL("SELECT isactive FROM groups WHERE id=$groupid"); my $isactive = FetchOneColumn(); PopGlobalSQLState(); return $isactive; @@ -1561,4 +1673,5 @@ $::vars = 'VERSION' => $Bugzilla::Config::VERSION, }; + 1; diff --git a/index.cgi b/index.cgi index 42f493fcb..7e9fb5aab 100755 --- a/index.cgi +++ b/index.cgi @@ -45,7 +45,6 @@ use vars qw( ConnectToDatabase(); # Check whether or not the user is logged in and, if so, set the $::userid -# and $::usergroupset variables. quietly_check_login(); ############################################################################### diff --git a/long_list.cgi b/long_list.cgi index 6acee0332..6df8a8bad 100755 --- a/long_list.cgi +++ b/long_list.cgi @@ -26,7 +26,7 @@ use lib qw(.); require "CGI.pl"; -use vars qw($userid $usergroupset @legal_keywords %FORM); +use vars qw($userid @legal_keywords %FORM); # Use global template variables. use vars qw($template $vars); @@ -69,8 +69,8 @@ my @bugs; foreach my $bug_id (split(/[:,]/, $buglist)) { detaint_natural($bug_id) || next; - SendSQL(SelectVisible("$generic_query AND bugs.bug_id = $bug_id", - $::userid, $::usergroupset)); + CanSeeBug($bug_id, $::userid) || next; + SendSQL("$generic_query AND bugs.bug_id = $bug_id"); my %bug; my @row = FetchSQLData(); diff --git a/post_bug.cgi b/post_bug.cgi index 430ae37a8..22aa0e6bd 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -34,7 +34,6 @@ require "bug_form.pl"; sub sillyness { my $zz; $zz = $::buffer; - $zz = $::usergroupset; $zz = %::COOKIE; $zz = %::components; $zz = %::versions; @@ -242,7 +241,7 @@ if ($::FORM{'keywords'} && UserInGroup("editbugs")) { # Build up SQL string to add bug. my $sql = "INSERT INTO bugs " . - "(" . join(",", @used_fields) . ", reporter, creation_ts, groupset) " . + "(" . join(",", @used_fields) . ", reporter, creation_ts) " . "VALUES ("; foreach my $field (@used_fields) { @@ -255,14 +254,15 @@ $comment = trim($comment); # OK except for the fact that it causes e-mail to be suppressed. $comment = $comment ? $comment : " "; -$sql .= "$::userid, now(), (0"; +$sql .= "$::userid, now() )"; # Groups +my @groupstoadd = (); foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) { if ($::FORM{$b}) { my $v = substr($b, 4); $v =~ /^(\d+)$/ - || ThrowCodeError("group_bit_invalid", "abort"); + || ThrowCodeError("group_id_invalid", "abort"); if (!GroupIsActive($v)) { # Prevent the user from adding the bug to an inactive group. # Should only happen if there is a bug in Bugzilla or the user @@ -271,18 +271,22 @@ foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) { $vars->{'bit'} = $v; ThrowCodeError("inactive_group", "abort"); } - $sql .= " + $v"; # Carefully written so that the math is - # done by MySQL, which can handle 64-bit math, - # and not by Perl, which I *think* can not. + SendSQL("SELECT user_id FROM user_group_map + WHERE user_id = $::userid + AND group_id = $v + AND isbless = 0"); + my ($member) = FetchSQLData(); + if ($member) { + push(@groupstoadd, $v) + } } } -$sql .= ") & $::usergroupset)\n"; # Lock tables before inserting records for the new bug into the database # if we are using a shadow database to prevent shadow database corruption # when two bugs get created at the same time. -SendSQL("LOCK TABLES bugs WRITE, longdescs WRITE, cc WRITE, profiles READ") if Param("shadowdb"); +SendSQL("LOCK TABLES bugs WRITE, bug_group_map WRITE, longdescs WRITE, cc WRITE, profiles READ") if Param("shadowdb"); # Add the bug report to the DB. SendSQL($sql); @@ -291,6 +295,12 @@ SendSQL($sql); SendSQL("select LAST_INSERT_ID()"); my $id = FetchOneColumn(); +# Add the group restrictions +foreach my $grouptoadd (@groupstoadd) { + SendSQL("INSERT INTO bug_group_map (bug_id, group_id) + VALUES ($id, $grouptoadd)"); +} + # Add the comment SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext) VALUES ($id, $::userid, now(), " . SqlQuote($comment) . ")"); diff --git a/process_bug.cgi b/process_bug.cgi index f62285ffb..3468a9790 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -47,7 +47,6 @@ use vars qw(%versions %settable_resolution %target_milestone %legal_severity - %superusergroupset $next_bug); ConnectToDatabase(); @@ -143,7 +142,7 @@ if ( Param("usetargetmilestone") ) { # # This function checks if there is a comment required for a specific # function and tests, if the comment was given. -# If comments are required for functions is defined by params. +# If comments are required for functions is defined by params. # sub CheckonComment( $ ) { my ($function) = (@_); @@ -410,10 +409,8 @@ sub DuplicateUserConfirm { SendSQL("SELECT reporter FROM bugs WHERE bug_id = " . SqlQuote($dupe)); my $reporter = FetchOneColumn(); - SendSQL("SELECT profiles.groupset FROM profiles WHERE profiles.userid =".SqlQuote($reporter)); - my $reportergroupset = FetchOneColumn(); - if (CanSeeBug($original, $reporter, $reportergroupset)) { + if (CanSeeBug($original, $reporter)) { $::FORM{'confirm_add_duplicate'} = "1"; return; } @@ -460,9 +457,9 @@ if (defined $::FORM{'id'}) { CheckFormFieldDefined(\%::FORM, 'longdesclength'); } -my $action = ''; +my $action = ''; if (defined $::FORM{action}) { - $action = trim($::FORM{action}); + $action = trim($::FORM{action}); } if (Param("move-enabled") && $action eq Param("move-button-text")) { $::FORM{'buglist'} = join (":", @idlist); @@ -564,33 +561,27 @@ sub ChangeResolution { # operations # If the form element isn't present, or the user isn't in the group, leave # it as-is -if($::usergroupset ne '0') { - my $groupAdd = "0"; - my $groupDel = "0"; - - SendSQL("SELECT bit, isactive FROM groups WHERE " . - "isbuggroup != 0 AND bit & $::usergroupset != 0 ORDER BY bit"); - while (my ($b, $isactive) = FetchSQLData()) { - # The multiple change page may not show all groups a bug is in - # (eg product groups when listing more than one product) - # Only consider groups which were present on the form. We can't do this - # for single bug changes because non-checked checkboxes aren't present. - # All the checkboxes should be shown in that case, though, so its not - # an issue there - if ($::FORM{'id'} || exists $::FORM{"bit-$b"}) { - if (!$::FORM{"bit-$b"}) { - $groupDel .= "+$b"; - } elsif ($::FORM{"bit-$b"} == 1 && $isactive) { - $groupAdd .= "+$b"; - } +my @groupAdd = (); +my @groupDel = (); + +SendSQL("SELECT groups.id, isactive FROM groups, user_group_map WHERE " . + "groups.id = user_group_map.group_id AND " . + "user_group_map.user_id = $::userid AND " . + "isbless = 0 AND isbuggroup = 1"); +while (my ($b, $isactive) = FetchSQLData()) { + # The multiple change page may not show all groups a bug is in + # (eg product groups when listing more than one product) + # Only consider groups which were present on the form. We can't do this + # for single bug changes because non-checked checkboxes aren't present. + # All the checkboxes should be shown in that case, though, so its not + # an issue there + if ($::FORM{'id'} || exists $::FORM{"bit-$b"}) { + if (!$::FORM{"bit-$b"}) { + push(@groupDel, $b); + } elsif ($::FORM{"bit-$b"} == 1 && $isactive) { + push(@groupAdd, $b); } } - if ($groupAdd ne "0" || $groupDel ne "0") { - DoComma(); - # mysql < 3.23.5 doesn't support the ~ operator, even though - # the docs say that it does - $::query .= "groupset = ((groupset & ($::superusergroupset - ($groupDel))) | ($groupAdd))"; - } } foreach my $field ("rep_platform", "priority", "bug_severity", @@ -708,9 +699,9 @@ if (defined $::FORM{'qa_contact'}) { # and cc list can see the bug even if they are not members of all groups # to which the bug is restricted. if ( $::FORM{'id'} ) { - SendSQL("SELECT groupset FROM bugs WHERE bug_id = $::FORM{'id'}"); - my ($groupset) = FetchSQLData(); - if ( $groupset ) { + SendSQL("SELECT group_id FROM bug_group_map WHERE bug_id = $::FORM{'id'}"); + my ($havegroup) = FetchSQLData(); + if ( $havegroup ) { DoComma(); $::FORM{'reporter_accessible'} = $::FORM{'reporter_accessible'} ? '1' : '0'; $::query .= "reporter_accessible = $::FORM{'reporter_accessible'}"; @@ -1047,6 +1038,8 @@ foreach my $id (@idlist) { "profiles $write, dependencies $write, votes $write, " . "products READ, components READ, " . "keywords $write, longdescs $write, fielddefs $write, " . + "bug_group_map $write, " . + "user_group_map READ, " . "keyworddefs READ, groups READ, attachments READ"); my @oldvalues = SnapShotBug($id); my %oldhash; @@ -1206,9 +1199,29 @@ foreach my $id (@idlist) { if ($::comma ne "") { SendSQL($query); } + my @groupAddNames = (); + foreach my $grouptoadd (@groupAdd) { + if (!BugInGroupId($id, $grouptoadd)) { + push(@groupAddNames, GroupIdToName($grouptoadd)); + SendSQL("INSERT INTO bug_group_map (bug_id, group_id) + VALUES ($id, $grouptoadd)"); + } + } + my @groupDelNames = (); + foreach my $grouptodel (@groupDel) { + if (BugInGroupId($id, $grouptodel)) { + push(@groupDelNames, GroupIdToName($grouptodel)); + } + SendSQL("DELETE FROM bug_group_map + WHERE bug_id = $id AND group_id = $grouptodel"); + } SendSQL("select now()"); $timestamp = FetchOneColumn(); - + + my $groupDelNames = join(',', @groupDelNames); + my $groupAddNames = join(',', @groupAddNames); + + LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames); if (defined $::FORM{'comment'}) { AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'}, $::FORM{'commentprivacy'}); @@ -1322,7 +1335,7 @@ foreach my $id (@idlist) { # the user wants to add the bug to the new product's group; ($::FORM{'addtonewgroup'} eq 'yes' || ($::FORM{'addtonewgroup'} eq 'yesifinold' - && GroupNameToBit($oldhash{'product'}) & $oldhash{'groupset'})) + && BugInGroup($id, $oldhash{'product'}))) # the new product is associated with a group; && GroupExists($::FORM{'product'}) @@ -1344,14 +1357,16 @@ foreach my $id (@idlist) { && (UserInGroup($::FORM{'product'}) || !Param('usebuggroupsentry')) # the associated group is active, indicating it can accept new bugs; - && GroupIsActive(GroupNameToBit($::FORM{'product'})) + && GroupIsActive(GroupNameToId($::FORM{'product'})) ) { # Add the bug to the group associated with its new product. - my $groupbit = GroupNameToBit($::FORM{'product'}); - SendSQL("UPDATE bugs SET groupset = groupset + $groupbit WHERE bug_id = $id"); + my $groupid = GroupNameToId($::FORM{'product'}); + if (!BugInGroupId($id, $groupid)) { + SendSQL("INSERT INTO bug_group_map (bug_id, group_id) VALUES ($id, $groupid)"); + } } - if ( + if ( # the old product is associated with a group; GroupExists($oldhash{'product'}) @@ -1359,8 +1374,8 @@ foreach my $id (@idlist) { && BugInGroup($id, $oldhash{'product'}) ) { # Remove the bug from the group associated with its old product. - my $groupbit = GroupNameToBit($oldhash{'product'}); - SendSQL("UPDATE bugs SET groupset = groupset - $groupbit WHERE bug_id = $id"); + my $groupid = GroupNameToId($oldhash{'product'}); + SendSQL("DELETE FROM bug_group_map WHERE bug_id = $id AND group_id = $groupid"); } } @@ -1523,7 +1538,7 @@ if ($::COOKIE{"BUGLIST"} && $::FORM{'id'}) { my $cur = lsearch(\@bugs, $::FORM{"id"}); if ($cur >= 0 && $cur < $#bugs) { my $next_bug = $bugs[$cur + 1]; - if (detaint_natural($next_bug) && CanSeeBug($next_bug)) { + if (detaint_natural($next_bug) && CanSeeBug($next_bug, $::userid)) { $::FORM{'id'} = $next_bug; $vars->{'next_id'} = $next_bug; diff --git a/processmail b/processmail index 45aaacc77..a47297597 100755 --- a/processmail +++ b/processmail @@ -632,14 +632,17 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { } - SendSQL("SELECT userid, groupset " . + SendSQL("SELECT userid, (refreshed_when > " . SqlQuote($::last_changed) . ") " . "FROM profiles WHERE login_name = " . SqlQuote($person)); - my ($userid, $groupset) = (FetchSQLData()); + my ($userid, $current) = (FetchSQLData()); $seen{$person} = 1; detaint_natural($userid); - detaint_natural($groupset); + + if (!$current) { + DeriveGroup($userid); + } # if this person doesn't have permission to see info on this bug, # return. @@ -649,19 +652,13 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { # see the action of restricting the bug itself; the bug will just # quietly disappear from their radar. # - return unless CanSeeBug($id, $userid, $groupset); + return unless CanSeeBug($id, $userid); + # Drop any non-insiders if the comment is private - if (Param("insidergroup") && ($anyprivate != 0)) { - ConnectToDatabase(); - PushGlobalSQLState(); - SendSQL("select (bit & $groupset ) != 0 from groups where name = " . SqlQuote(Param("insidergroup"))); - my $bit = FetchOneColumn(); - PopGlobalSQLState(); - if (!$bit) { - return; - } - } + return if (Param("insidergroup") && + ($anyprivate != 0) && + (!UserInGroup(Param("insidergroup"), $userid))); # We shouldn't send changedmail if this is a dependency mail, and any of # the depending bugs is not visible to the user. @@ -669,7 +666,7 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { my $save_id = $dep_id; detaint_natural($dep_id) || warn("Unexpected Error: \@depbugs contains a non-numeric value: '$save_id'") && return; - return unless CanSeeBug($dep_id, $userid, $groupset); + return unless CanSeeBug($dep_id, $userid); } my %mailhead = %defmailhead; @@ -781,6 +778,14 @@ if (open(FID, "= 0 && $ARGV[0] eq "regenerate") { print "Regenerating is no longer required or supported\n"; exit; diff --git a/query.cgi b/query.cgi index e038d98ae..cd679e8e8 100755 --- a/query.cgi +++ b/query.cgi @@ -50,19 +50,19 @@ use vars qw( ); ConnectToDatabase(); - +my $userid = 0; if (defined $::FORM{"GoAheadAndLogIn"}) { # We got here from a login page, probably from relogin.cgi. We better # make sure the password is legit. - confirm_login(); + $userid = confirm_login(); } else { - quietly_check_login(); + $userid = quietly_check_login(); } # Backwards compatibility hack -- if there are any of the old QUERY_* # cookies around, and we are logged in, then move them into the database # and nuke the cookie. This is required for Bugzilla 2.8 and earlier. -if ($::userid) { +if ($userid) { my @oldquerycookies; foreach my $i (keys %::COOKIE) { if ($i =~ /^QUERY_(.*)$/) { @@ -79,12 +79,12 @@ if ($::userid) { if ($value) { my $qname = SqlQuote($name); SendSQL("SELECT query FROM namedqueries " . - "WHERE userid = $::userid AND name = $qname"); + "WHERE userid = $userid AND name = $qname"); my $query = FetchOneColumn(); if (!$query) { SendSQL("REPLACE INTO namedqueries " . "(userid, name, query) VALUES " . - "($::userid, $qname, " . SqlQuote($value) . ")"); + "($userid, $qname, " . SqlQuote($value) . ")"); } } print "Set-Cookie: $cookiename= ; path=" . Param("cookiepath") . @@ -94,17 +94,17 @@ if ($::userid) { } if ($::FORM{'nukedefaultquery'}) { - if ($::userid) { + if ($userid) { SendSQL("DELETE FROM namedqueries " . - "WHERE userid = $::userid AND name = '$::defaultqueryname'"); + "WHERE userid = $userid AND name = '$::defaultqueryname'"); } $::buffer = ""; } my $userdefaultquery; -if ($::userid) { +if ($userid) { SendSQL("SELECT query FROM namedqueries " . - "WHERE userid = $::userid AND name = '$::defaultqueryname'"); + "WHERE userid = $userid AND name = '$::defaultqueryname'"); $userdefaultquery = FetchOneColumn(); } @@ -285,7 +285,7 @@ $vars->{'rep_platform'} = \@::legal_platform; $vars->{'op_sys'} = \@::legal_opsys; $vars->{'priority'} = \@::legal_priority; $vars->{'bug_severity'} = \@::legal_severity; -$vars->{'userid'} = $::userid; +$vars->{'userid'} = $userid; # Boolean charts my @fields; @@ -332,10 +332,10 @@ for (my $chart = 0; $::FORM{"field$chart-0-0"}; $chart++) { $default{'charts'} = \@charts; # Named queries -if ($::userid) { +if ($userid) { my @namedqueries; SendSQL("SELECT name FROM namedqueries " . - "WHERE userid = $::userid AND name != '$::defaultqueryname' " . + "WHERE userid = $userid AND name != '$::defaultqueryname' " . "ORDER BY name"); while (MoreSQLData()) { push(@namedqueries, FetchOneColumn()); diff --git a/sanitycheck.cgi b/sanitycheck.cgi index 451ad23da..da71163cc 100755 --- a/sanitycheck.cgi +++ b/sanitycheck.cgi @@ -109,6 +109,17 @@ sub CrossCheck { } } +sub DateCheck { + my $table = shift @_; + my $field = shift @_; + Status("Checking dates in $table.$field"); + SendSQL("SELECT COUNT( $field ) FROM $table WHERE $field > NOW()"); + my $c = FetchOneColumn(); + if ($c) { + Alert("Found $c dates in future"); + } +} + my @badbugs; @@ -139,6 +150,57 @@ if (exists $::FORM{'rebuildvotecache'}) { Status("Vote cache has been rebuilt."); } +if (exists $::FORM{'rederivegroups'}) { + Status("OK, All users' inherited permissions will be rechecked when " . + "they next access Bugzilla."); + SendSQL("UPDATE groups SET last_changed = NOW() LIMIT 1"); +} + +# rederivegroupsnow is REALLY only for testing. +if (exists $::FORM{'rederivegroupsnow'}) { + Status("OK, now rederiving groups."); + SendSQL("SELECT userid FROM profiles"); + while ((my $id) = FetchSQLData()) { + DeriveGroup($id); + Status("Group $id"); + } +} + +if (exists $::FORM{'cleangroupsnow'}) { + Status("OK, now cleaning stale groups."); + # Only users that were out of date already long ago should be cleaned + # and the cleaning is done with tables locked. This is require in order + # to keep another session from proceeding with permission checks + # after the groups have been cleaned unless it first had an opportunity + # to get the groups up to date. + # If any page starts taking longer than one hour to load, this interval + # should be revised. + SendSQL("SELECT MAX(last_changed) FROM groups WHERE last_changed < NOW() - INTERVAL 1 HOUR"); + (my $cutoff) = FetchSQLData(); + Status("Cutoff is $cutoff"); + SendSQL("SELECT COUNT(*) FROM user_group_map"); + (my $before) = FetchSQLData(); + SendSQL("LOCK TABLES user_group_map WRITE, profiles WRITE"); + SendSQL("SELECT userid FROM profiles " . + "WHERE refreshed_when > 0 " . + "AND refreshed_when < " . SqlQuote($cutoff) . + " LIMIT 1000"); + my $count = 0; + while ((my $id) = FetchSQLData()) { + $count++; + PushGlobalSQLState(); + SendSQL("DELETE FROM user_group_map WHERE " . + "user_id = $id AND isderived = 1 AND isbless = 0"); + SendSQL("UPDATE profiles SET refreshed_when = 0 WHERE userid = $id"); + PopGlobalSQLState(); + } + SendSQL("UNLOCK TABLES"); + SendSQL("SELECT COUNT(*) FROM user_group_map"); + (my $after) = FetchSQLData(); + Status("Cleaned table for $count users " . + "- reduced from $before records to $after records"); +} + print "OK, now running sanity checks.

\n"; # This one goes first, because if this is wrong, then the below tests @@ -178,6 +240,7 @@ CrossCheck("attachstatusdefs", "id", CrossCheck("bugs", "bug_id", ["bugs_activity", "bug_id"], + ["bug_group_map", "bug_id"], ["attachments", "bug_id"], ["cc", "bug_id"], ["longdescs", "bug_id"], @@ -188,6 +251,12 @@ CrossCheck("bugs", "bug_id", ["duplicates", "dupe_of", "dupe"], ["duplicates", "dupe", "dupe_of"]); +CrossCheck("groups", "id", + ["bug_group_map", "group_id"], + ["group_group_map", "grantor_id"], + ["group_group_map", "member_id"], + ["user_group_map", "group_id"]); + CrossCheck("profiles", "userid", ["bugs", "reporter", "bug_id"], ["bugs", "assigned_to", "bug_id"], @@ -203,6 +272,7 @@ CrossCheck("profiles", "userid", ["watch", "watched"], ["tokens", "userid"], ["components", "initialowner", "name"], + ["user_group_map", "user_id"], ["components", "initialqacontact", "name", ["0"]]); CrossCheck("products", "id", @@ -212,25 +282,9 @@ CrossCheck("products", "id", ["versions", "product_id", "value"], ["attachstatusdefs", "product_id", "name"]); -########################################################################### -# Perform group checks -########################################################################### +DateCheck("groups", "last_changed"); +DateCheck("profiles", "refreshed_when"); -Status("Checking groups"); -SendSQL("select bit from groups where bit != pow(2, round(log(bit) / log(2)))"); -while (my $bit = FetchOneColumn()) { - Alert("Illegal bit number found in group table: $bit"); -} - -SendSQL("select sum(bit) from groups where isbuggroup != 0"); -my $buggroupset = FetchOneColumn(); -if (!defined $buggroupset || $buggroupset eq "") { - $buggroupset = 0; -} -SendSQL("select bug_id, groupset from bugs where groupset & $buggroupset != groupset"); -while (@row = FetchSQLData()) { - Alert("Bad groupset $row[1] found in bug " . BugLink($row[0])); -} ########################################################################### # Perform product specific field checks diff --git a/show_activity.cgi b/show_activity.cgi index f6d9b75c9..14b4149ba 100755 --- a/show_activity.cgi +++ b/show_activity.cgi @@ -35,10 +35,7 @@ ConnectToDatabase(); # Begin Data/Security Validation ############################################################################### -# Check whether or not the user is currently logged in. This function -# sets the value of $::usergroupset, the binary number that records -# the set of groups to which the user belongs and which we can use -# to determine whether or not the user is authorized to access this bug. +# Check whether or not the user is currently logged in. quietly_check_login(); # Make sure the bug ID is a positive integer representing an existing diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi index e5885d180..021150bf0 100755 --- a/showdependencygraph.cgi +++ b/showdependencygraph.cgi @@ -31,7 +31,7 @@ ConnectToDatabase(); quietly_check_login(); -use vars qw($template $vars $userid $usergroupset); +use vars qw($template $vars $userid); my %seen; my %edgesdone; @@ -128,13 +128,13 @@ foreach my $k (keys(%seen)) { my $summary = ""; my $stat; if ($::FORM{'showsummary'}) { - SendSQL(SelectVisible("SELECT bug_status, short_desc FROM bugs " . - "WHERE bugs.bug_id = $k", - $::userid, - $::usergroupset)); - ($stat, $summary) = FetchSQLData(); - $stat = "NEW" if !defined $stat; - $summary = "" if !defined $summary; + if (CanSeeBug($k, $::userid)) { + SendSQL("SELECT bug_status, short_desc FROM bugs " . + "WHERE bugs.bug_id = $k"); + ($stat, $summary) = FetchSQLData(); + $stat = "NEW" if !defined $stat; + $summary = "" if !defined $summary; + } } else { SendSQL("SELECT bug_status FROM bugs WHERE bug_id = $k"); $stat = FetchOneColumn(); diff --git a/showdependencytree.cgi b/showdependencytree.cgi index 7917f00fe..e2e9d52b0 100755 --- a/showdependencytree.cgi +++ b/showdependencytree.cgi @@ -39,7 +39,6 @@ quietly_check_login(); # More warning suppression silliness. $::userid = $::userid; -$::usergroupset = $::usergroupset; ################################################################################ # Data/Security Validation # @@ -144,7 +143,9 @@ sub GetBug { # and returns it to the calling code. my ($id) = @_; - SendSQL(SelectVisible("SELECT 1, + my $bug = {}; + if (CanSeeBug($id, $::userid)) { + SendSQL("SELECT 1, bug_status, short_desc, $milestone_column, @@ -152,18 +153,16 @@ sub GetBug { assignee.login_name FROM bugs, profiles AS assignee WHERE bugs.bug_id = $id - AND bugs.assigned_to = assignee.userid", - $::userid, - $::usergroupset)); + AND bugs.assigned_to = assignee.userid"); - my $bug = {}; - ($bug->{'exists'}, - $bug->{'status'}, - $bug->{'summary'}, - $bug->{'milestone'}, - $bug->{'assignee_id'}, - $bug->{'assignee_email'}) = FetchSQLData(); + ($bug->{'exists'}, + $bug->{'status'}, + $bug->{'summary'}, + $bug->{'milestone'}, + $bug->{'assignee_id'}, + $bug->{'assignee_email'}) = FetchSQLData(); + } $bug->{'open'} = IsOpenedState($bug->{'status'}); $bug->{'dependencies'} = []; diff --git a/sidebar.cgi b/sidebar.cgi index d3692b16d..ec021ea1c 100755 --- a/sidebar.cgi +++ b/sidebar.cgi @@ -36,11 +36,11 @@ quietly_check_login(); $vars->{'username'} = $::COOKIE{'Bugzilla_login'} || ''; if (defined $::COOKIE{'Bugzilla_login'}) { - SendSQL("SELECT mybugslink, userid, blessgroupset FROM profiles " . + SendSQL("SELECT mybugslink, userid FROM profiles " . "WHERE login_name = " . SqlQuote($::COOKIE{'Bugzilla_login'})); - my ($mybugslink, $userid, $blessgroupset) = (FetchSQLData()); + my ($mybugslink, $userid) = (FetchSQLData()); $vars->{'userid'} = $userid; - $vars->{'blessgroupset'} = $blessgroupset; + $vars->{'canblessanything'} = UserCanBlessAnything(); if ($mybugslink) { my $mybugstemplate = Param("mybugstemplate"); my %substs = ( 'userid' => url_quote($::COOKIE{'Bugzilla_login'}) ); diff --git a/template/en/default/account/prefs/permissions.html.tmpl b/template/en/default/account/prefs/permissions.html.tmpl index 15bca6deb..064220d6e 100644 --- a/template/en/default/account/prefs/permissions.html.tmpl +++ b/template/en/default/account/prefs/permissions.html.tmpl @@ -20,36 +20,48 @@ #%] [%# INTERFACE: - # has_bits: array of strings. May be empty. - # Descriptions of the permission bits the user has. - # set_bits: array of strings. May be empty. - # Descriptions of the permission bits the user can set for + # has_bits: array of hashes. May be empty. + # name => Names of the permissions the user has. + # desc => Descriptions of the permissions the user has. + # set_bits: array of hashes. May be empty. + # name => Names of the permissions the user can set for + # other people. + # desc => Descriptions of the permissions the user can set for # other people. #%] - +
diff --git a/template/en/default/global/useful-links.html.tmpl b/template/en/default/global/useful-links.html.tmpl index 987ca370b..1e5b09df1 100644 --- a/template/en/default/global/useful-links.html.tmpl +++ b/template/en/default/global/useful-links.html.tmpl @@ -65,7 +65,7 @@ [% ', parameters' IF user.groups.tweakparams %] [% ', users' IF user.groups.editusers - || (user.blessgroupset > 0) %] + || user.canblessany %] [% ', products' IF user.groups.editcomponents %] [% ', attachment statuses' diff --git a/template/en/default/sidebar.xul.tmpl b/template/en/default/sidebar.xul.tmpl index ad7158b13..21a07c998 100644 --- a/template/en/default/sidebar.xul.tmpl +++ b/template/en/default/sidebar.xul.tmpl @@ -73,7 +73,7 @@ function normal_keypress_handler( aEvent ) { [%- IF UserInGroup('tweakparams') %] [%- END %] - [%- IF UserInGroup('editusers') || blessgroupset %] + [%- IF UserInGroup('editusers') || canblessany %] [%- END %] [%- IF UserInGroup('editcomponents') %] diff --git a/token.cgi b/token.cgi index 43ee1b04e..86e51939d 100755 --- a/token.cgi +++ b/token.cgi @@ -265,6 +265,7 @@ sub changeEmail { SendSQL("DELETE FROM tokens WHERE userid = $userid AND tokentype = 'emailnew'"); SendSQL("UNLOCK TABLES"); + DeriveGroup($userid); # Return HTTP response headers. print "Content-Type: text/html\n\n"; @@ -300,6 +301,7 @@ sub cancelChangeEmail { SET login_name = $quotedoldemail WHERE userid = $userid"); SendSQL("UNLOCK TABLES"); + DeriveGroup($userid); $vars->{'message'} .= " Your old account settings have been reinstated."; } diff --git a/userprefs.cgi b/userprefs.cgi index 808aebf40..369c681ca 100755 --- a/userprefs.cgi +++ b/userprefs.cgi @@ -33,7 +33,6 @@ use RelationSet; sub sillyness { my $zz; $zz = $::defaultqueryname; - $zz = $::usergroupset; } # Use global template variables. @@ -331,21 +330,22 @@ sub SaveFooter { sub DoPermissions { my (@has_bits, @set_bits); - SendSQL("SELECT description FROM groups " . - "WHERE bit & $::usergroupset != 0 " . - "ORDER BY bit"); + SendSQL("SELECT DISTINCT name, description FROM groups, user_group_map " . + "WHERE user_group_map.group_id = groups.id " . + "AND user_id = $::userid " . + "AND isbless = 0 " . + "ORDER BY name"); while (MoreSQLData()) { - push(@has_bits, FetchSQLData()); + my ($nam, $desc) = FetchSQLData(); + push(@has_bits, {"desc" => $desc, "name" => $nam}); } - - SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $userid"); - my $blessgroupset = FetchOneColumn(); - if ($blessgroupset) { - SendSQL("SELECT description FROM groups " . - "WHERE bit & $blessgroupset != 0 " . - "ORDER BY bit"); - while (MoreSQLData()) { - push(@set_bits, FetchSQLData()); + my @set_ids = (); + SendSQL("SELECT DISTINCT name, description FROM groups " . + "ORDER BY name"); + while (MoreSQLData()) { + my ($nam, $desc) = FetchSQLData(); + if (UserCanBlessGroup($nam)) { + push(@set_bits, {"desc" => $desc, "name" => $nam}); } } diff --git a/votes.cgi b/votes.cgi index 88e303971..3bfe11682 100755 --- a/votes.cgi +++ b/votes.cgi @@ -28,7 +28,6 @@ use lib "."; require "CGI.pl"; -use vars qw($usergroupset); # Use global template variables use vars qw($template $vars); @@ -188,7 +187,7 @@ sub show_user { # and they can see there are votes 'missing', but not on what bug # they are. This seems a reasonable compromise; the alternative is # to lie in the totals. - next if !CanSeeBug($id, $who, $usergroupset); + next if !CanSeeBug($id, $who); push (@bugs, { id => $id, summary => $summary, -- cgit v1.2.3-24-g4f1b
[% IF has_bits.size %] You have the following permission bits set on your account: -
    +

    +
    + [% FOREACH bit_description = has_bits %] -
  • [% bit_description %]
  • + + + + [% END %] - +
    [% bit_description.name %][% bit_description.desc %]
    [% ELSE %] There are no permission bits set on your account. [% END %] [% IF set_bits.size %] +
    And you can turn on or off the following bits for other users:

    -

      + [% FOREACH bit_description = set_bits %] -
    • [% bit_description %]
    • + + + + [% END %] - +
      [% bit_description.name %][% bit_description.desc %]

      [% END %]