diff options
author | bugreport%peshkin.net <> | 2002-09-23 02:14:48 +0200 |
---|---|---|
committer | bugreport%peshkin.net <> | 2002-09-23 02:14:48 +0200 |
commit | 65d3dc0ec33fd76229dc02536a74ccac5408876b (patch) | |
tree | bcacbb27e99c73f9548e92408fadb2e369f0543b | |
parent | cf9b4ba2e757925eeb18bb63411ae30c8600c643 (diff) | |
download | bugzilla-65d3dc0ec33fd76229dc02536a74ccac5408876b.tar.gz bugzilla-65d3dc0ec33fd76229dc02536a74ccac5408876b.tar.xz |
bug 157756 - Groups_20020716_Branch Tracking : > 55 groups now supported
r=bbaetz, gerv
34 files changed, 1695 insertions, 1409 deletions
@@ -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 ("</bugzilla>\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 ("</bugzilla>\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 "<p><code>" . value_quote($query) . "</code></p>\n"; @@ -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'"); @@ -1938,180 +1918,8 @@ 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")) - || <STDIN>; - 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")) - || <STDIN>; - 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")) - || <STDIN>; - 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")) - || <STDIN>; - 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")) - || <STDIN>; - 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")) - || <STDIN>; - 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<n> 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")) + || <STDIN>; + 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")) + || <STDIN>; + 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")) + || <STDIN>; + 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")) + || <STDIN>; + 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")) + || <STDIN>; + 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")) + || <STDIN>; + 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.</para> <tip> - <para>If you wish to add more administrative users, you must use the - MySQL interface. Run "mysql" from the command line, and use these - commands: - <simplelist> - <member> - <prompt>mysql></prompt> - <command>use bugs;</command> - </member> - - <member> - <prompt>mysql></prompt> - - <command> - update profiles set groupset=0x7ffffffffffffff where login_name = - "(user's login name)"; - </command> - </member> - </simplelist> + <para>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. </para> - - <para>Yes, that is - <emphasis>fourteen</emphasis> - - <quote>f</quote> - - 's. A whole lot of f-ing going on if you want to create a new - administator.</para> </tip> </section> @@ -698,10 +676,22 @@ </listitem> <listitem> - <para>Fill out the "New Name", "New Description", and - "New User RegExp" fields. "New User RegExp" allows you to automatically + <para>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".</para> + <warning> + <para>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.</para> + </warning> + </listitem> + <listitem> + <para>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.</para> </listitem> </orderedlist> @@ -712,17 +702,6 @@ <para>Turn on "usebuggroups" and "usebuggroupsentry" in the "Edit Parameters" screen.</para> - <warning> - <para>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.</para> - </warning> </listitem> <listitem> @@ -734,13 +713,6 @@ </listitem> </orderedlist> - <warning> - <para>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.</para> - </warning> - <para> Note that group permissions are such that you need to be a member of <emphasis>all</emphasis> 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.</para> <tip> - <para>If you wish to add more administrative users, you must use the - MySQL interface. Run "mysql" from the command line, and use these - commands: - <simplelist> - <member> - <prompt>mysql></prompt> - <command>use bugs;</command> - </member> - - <member> - <prompt>mysql></prompt> - - <command> - update profiles set groupset=0x7ffffffffffffff where login_name = - "(user's login name)"; - </command> - </member> - </simplelist> + <para>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. </para> - - <para>Yes, that is - <emphasis>fourteen</emphasis> - - <quote>f</quote> - - 's. A whole lot of f-ing going on if you want to create a new - administator.</para> </tip> </section> @@ -698,10 +676,22 @@ </listitem> <listitem> - <para>Fill out the "New Name", "New Description", and - "New User RegExp" fields. "New User RegExp" allows you to automatically + <para>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".</para> + <warning> + <para>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.</para> + </warning> + </listitem> + <listitem> + <para>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.</para> </listitem> </orderedlist> @@ -712,17 +702,6 @@ <para>Turn on "usebuggroups" and "usebuggroupsentry" in the "Edit Parameters" screen.</para> - <warning> - <para>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.</para> - </warning> </listitem> <listitem> @@ -734,13 +713,6 @@ </listitem> </orderedlist> - <warning> - <para>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.</para> - </warning> - <para> Note that group permissions are such that you need to be a member of <emphasis>all</emphasis> 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 <justdave@syndicomm.com> +# Joel Peshkin <bugreport@peshkin.net> # Jacob Steenhagen <jake@bugzilla.org> # 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 "<form method=post action=editgroups.cgi>\n"; print "<table border=1>\n"; print "<tr>"; - print "<th>Bit</th>"; print "<th>Name</th>"; print "<th>Description</th>"; print "<th>User RegExp</th>"; - print "<th>Active</th>"; + print "<th>Use For Bugs</th>"; + print "<th>Type</th>"; print "<th>Action</th>"; print "</tr>\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 "<tr>\n"; - print "<td valign=middle>$bit</td>\n"; - print "<td><input size=20 name=\"name-$bit\" value=\"$name\">\n"; - print "<input type=hidden name=\"oldname-$bit\" value=\"$name\"></td>\n"; - print "<td><input size=40 name=\"desc-$bit\" value=\"$desc\">\n"; - print "<input type=hidden name=\"olddesc-$bit\" value=\"$desc\"></td>\n"; - print "<td><input size=30 name=\"regexp-$bit\" value=\"$regexp\">\n"; - print "<input type=hidden name=\"oldregexp-$bit\" value=\"$regexp\"></td>\n"; - print "<td><input type=\"checkbox\" name=\"isactive-$bit\" value=\"1\"" . ($isactive ? " checked" : "") . ">\n"; - print "<input type=hidden name=\"oldisactive-$bit\" value=\"$isactive\"></td>\n"; - print "<td align=center valign=middle><a href=\"editgroups.cgi?action=del&group=$bit\">Delete</a></td>\n"; - print "</tr>\n"; + print "<td>$name</td>\n"; + print "<td>$desc</td>\n"; + print "<td>$regexp </td>\n"; + print "<td align=center>"; + print "X" if $isactive; + print " </td>\n"; + print "<td>   "; + print (($isbuggroup == 0 ) ? "system" : "user"); + print " </td>\n"; + print "<td align=center valign=middle> + <a href=\"editgroups.cgi?action=changeform&group=$groupid\">Edit</a>"; + print " | <a href=\"editgroups.cgi?action=del&group=$groupid\">Delete</a>" if ($isbuggroup != 0); + print "</td></tr>\n"; } print "<tr>\n"; @@ -136,62 +137,135 @@ unless ($action) { print "<td><a href=\"editgroups.cgi?action=add\">Add Group</a></td>\n"; print "</tr>\n"; print "</table>\n"; - print "<input type=hidden name=\"action\" value=\"update\">"; - print "<input type=submit value=\"Submit changes\">\n"; - print "<p>"; print "<b>Name</b> 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. <p>"; +people submitting bugs by email to limit a bug to a certain set of groups. <p>"; print "<b>Description</b> 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.<p>"; print "<b>User RegExp</b> 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.<p>"; - print "The <b>Active</b> 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.<p>"; - 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.<p>"; - 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.<p>"; +email address that matches this perl regular expression. Do not forget the trailing \'\$\'. Example \'\@mycompany\\.com\$\'<p>"; + print "The <b>Use For Bugs</b> 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.<p>"; + print "The <b>Type</b> field identifies system groups.<p>"; - print "<table border=1>\n"; - print "<tr>"; - print "<th>Bit</th>"; - print "<th>Name</th>"; - print "<th>Description</th>"; - print "<th>User RegExp</th>"; - print "</tr>\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.<BR>" . + "Click the <b>Back</b> 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 "<FORM METHOD=POST ACTION=editgroups.cgi>\n"; + print "<TABLE BORDER=1 CELLPADDING=4>"; + print "<TR><TH>Group:</TH><TD>"; + if ($isbuggroup == 0) { + print "$name"; + } else { + print "<INPUT TYPE=HIDDEN NAME=\"oldname\" VALUE=$name> + <INPUT SIZE=60 NAME=\"name\" VALUE=\"$name\">"; + } + print "</TD></TR><TR><TH>Description:</TH><TD>"; + if ($isbuggroup == 0) { + print "$description"; + } else { + print "<INPUT TYPE=HIDDEN NAME=\"olddesc\" VALUE=\"$description\"> + <INPUT SIZE=70 NAME=\"desc\" VALUE=\"$description\">"; + } + print "</TD></TR><TR> + <TH>User Regexp:</TH><TD>"; + print "<INPUT TYPE=HIDDEN NAME=\"oldrexp\" VALUE=\"$rexp\"> + <INPUT SIZE=40 NAME=\"rexp\" VALUE=\"$rexp\"></TD></TR>"; + if ($isbuggroup == 1) { + print "<TR><TH>Use For Bugs:</TH><TD> + <INPUT TYPE=checkbox NAME =\"isactive\" VALUE=1 " . (($isactive == 1) ? "CHECKED" : "") . "> + <INPUT TYPE=HIDDEN NAME=\"oldisactive\" VALUE=$isactive> + </TD> + </TR>"; + } + print "</TABLE> + <BR> + Users become members of this group in one of three ways: + <BR> + - by being explicity included when the user is edited + <BR> + - by matching the user regexp above + <BR> + - by being a member of one of the groups included in this group + by checking the boxes + below. <P>\n"; + + print "<TABLE>"; + print "<TR><TD COLSPAN=4>Members of these groups can grant membership to this group</TD></TR>"; + print "<TR><TD ALIGN=CENTER>|</TD><TD COLSPAN=3>Members of these groups are included in this group</TD></TR>"; + print "<TR><TD ALIGN=CENTER>|</TD><TD ALIGN=CENTER>|</TD><TD COLSPAN=2></TD><TR>"; + + # 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 "<tr>\n"; - print "<td>$bit</td>\n"; - print "<td>$name</td>\n"; - print "<input type=hidden name=\"name-$bit\" value=\"$name\">\n"; - print "<input type=hidden name=\"oldname-$bit\" value=\"$name\">\n"; - print "<td>$desc</td>\n"; - print "<td><input type=text size=30 name=\"regexp-$bit\" value=\"$regexp\"></td>\n"; - print "<input type=hidden name=\"oldregexp-$bit\" value=\"$regexp\">\n"; - print "</tr>\n"; + my ($grpid, $grpnam, $grpdesc, $grpmember, $blessmember) = FetchSQLData(); + my $grpchecked = $grpmember ? "CHECKED" : ""; + my $blesschecked = $blessmember ? "CHECKED" : ""; + print "<TR>"; + print "<TD><INPUT TYPE=checkbox NAME=\"bless-$grpid\" $blesschecked VALUE=1>"; + print "<INPUT TYPE=HIDDEN NAME=\"oldbless-$grpid\" VALUE=$blessmember></TD>"; + print "<TD><INPUT TYPE=checkbox NAME=\"grp-$grpid\" $grpchecked VALUE=1>"; + print "<INPUT TYPE=HIDDEN NAME=\"oldgrp-$grpid\" VALUE=$grpmember></TD>"; + print "<TD><B>$grpnam</B></TD>"; + print "<TD>$grpdesc</TD>"; + print "</TR>\n"; } - print "</table><p>\n"; - print "<input type=submit value=\"Submit changes\">\n"; - print "</form>\n"; + print "</TABLE><BR>"; + print "<INPUT TYPE=SUBMIT VALUE=\"Submit\">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"postchanges\">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"group\" VALUE=$gid>\n"; + print "</FORM>"; - PutFooter(); + + + PutTrailer("<a href=editgroups.cgi>Back to group list</a>"); exit; } @@ -209,7 +283,7 @@ if ($action eq 'add') { print "<th>New Name</th>"; print "<th>New Description</th>"; print "<th>New User RegExp</th>"; - print "<th>Active</th>"; + print "<th>Use For Bugs</th>"; print "</tr><tr>"; print "<td><input size=20 name=\"name\"></td>\n"; print "<td><input size=40 name=\"desc\"></td>\n"; @@ -223,17 +297,17 @@ if ($action eq 'add') { print "<p>"; print "<b>Name</b> 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.<p>"; print "<b>Description</b> 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.<p>"; - print "The <b>Active</b> 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 <b>Use For Bugs</b> 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. <b>Note: If you are creating a group, you -probably want it to be active, in which case you should leave this checked.</b><p>"; +probably want it to be usable for bugs, in which case you should leave this checked.</b><p>"; print "<b>User RegExp</b> 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.<p>"; @@ -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.<BR><BR>You must delete a group first before you " . - "can add any more.</B>"); - PutTrailer("<a href=editgroups.cgi>Back to the group list</a>"); + if (!eval {qr/$regexp/}) { + ShowError("The regular expression you entered is invalid. " . + "Please click the <b>Back</b> 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.<p>\n"; - print "Your new group was assigned bit #$bit.<p>"; PutTrailer("<a href=\"editgroups.cgi?action=add\">Add another group</a>", "<a href=\"editgroups.cgi\">Back to the group list</a>"); 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.<BR>" . "Click the <b>Back</b> 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.<BR>" . "Click the <b>Back</b> 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 "<table border=1>\n"; print "<tr>"; - print "<th>Bit</th>"; + print "<th>Id</th>"; print "<th>Name</th>"; print "<th>Description</th>"; print "</tr>\n"; print "<tr>\n"; - print "<td>$bit</td>\n"; + print "<td>$gid</td>\n"; print "<td>$name</td>\n"; print "<td>$desc</td>\n"; print "</tr>\n"; @@ -390,26 +432,26 @@ if ($action eq 'del') { print "<FORM METHOD=POST ACTION=editgroups.cgi>\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 " <B>One or more users belong to this group. You cannot delete this group while there are users in it.</B><BR> -<A HREF=\"editusers.cgi?action=list&query=" . -url_quote("(groupset & $bit) OR (blessgroupset & $bit)") . "\">Show me which users.</A> - <INPUT TYPE=CHECKBOX NAME=\"removeusers\">Remove all users from +<A HREF=\"editusers.cgi?action=list&group=$gid\">Show me which users.</A> - <INPUT TYPE=CHECKBOX NAME=\"removeusers\">Remove all users from this group for me<P> "; } - 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 " <B>One or more bug reports are visible only to this group. You cannot delete this group while any bugs are using it.</B><BR> @@ -440,7 +482,7 @@ You cannot delete this group while it is tied to a product.</B><BR> } print "<P><INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n"; - print "<INPUT TYPE=HIDDEN NAME=\"group\" VALUE=\"$bit\">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"group\" VALUE=\"$gid\">\n"; print "</FORM>"; PutTrailer("<a href=editgroups.cgi>No, go back to the group list</a>"); @@ -453,8 +495,8 @@ You cannot delete this group while it is tied to a product.</B><BR> 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.<BR>" . "Click the <b>Back</b> 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 "<A HREF=\"editgroups.cgi?action=del&group=$bit\">" . + print "<A HREF=\"editgroups.cgi?action=del&group=$gid\">" . "View the list of which records are affected</A><BR>"; PutTrailer("<a href=editgroups.cgi>Back to group list</a>"); 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.<BR>"; - 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.<BR>"; - } - 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.<BR>"; - } - SendSQL("DELETE FROM groups WHERE bit=$bit"); - print "<B>Group $bit has been deleted.</B><BR>"; - - foreach my $userid (@opusers) { - SendSQL("UPDATE profiles SET groupset=$opblessgroupset " . - "WHERE userid=$userid"); - print "Group bits restored for " . DBID_to_name($userid) . - " (maintainer)<BR>\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 "<B>Group $gid has been deleted.</B><BR>"; + PutTrailer("<a href=editgroups.cgi>Back to group list</a>"); 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.<BR>" . + "Click the <b>Back</b> 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"} . "'<br>"; -# print "New: '" . $::FORM{"name-$v"} . "', '" . $::FORM{"desc-$v"} . -# "', '" . $::FORM{"regexp-$v"} . "'<br>"; - - 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.<br>\n"; - } - } else { - ShowError("Duplicate name '" . $::FORM{"name-$v"} . - "' specified for group $v.<BR>" . - "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 <b>Back</b> 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.<br>\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.<BR>" . - "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.<br>\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.<br>\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.<BR>" . - "There may be a problem with Bugzilla or a bug in your browser.<br>" . - "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!<BR>\n"; print "If you really meant it, hit the <B>Back</B> button and try again.<p>\n"; } else { + SendSQL("UPDATE groups SET last_changed = NOW() WHERE id = $gid"); print "Done.<p>\n"; } PutTrailer("<a href=editgroups.cgi>Back to the group list</a>"); 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{<INPUT TYPE=HIDDEN NAME="defaultmilestone" VALUE="$defaultmilestone">\n}; } - # Added -JMR, 2/16/00 - if (Param("usebuggroups")) { - $userregexp = value_quote($userregexp); - print "</TR><TR>\n"; - print " <TH ALIGN=\"right\">User Regexp for Bug Group:</TH>\n"; - print " <TD><INPUT TYPE=TEXT SIZE=64 MAXLENGTH=255 NAME=\"userregexp\" VALUE=\"$userregexp\"></TD>\n"; - } print "</TR><TR>\n"; print " <TH ALIGN=\"right\">Closed for bug entry:</TH>\n"; @@ -263,7 +256,7 @@ if ($action eq 'add') { print "<FORM METHOD=POST ACTION=editproducts.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; - EmitFormElements('', '', '', '', 0, 0, 10000, 0, "---"); + EmitFormElements('', '', '', 0, 0, 10000, 0, "---"); print "</TR><TR>\n"; print " <TH ALIGN=\"right\">Version:</TH>\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 " <TD VALIGN=\"top\">$milestonelink</TD>\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 = "<FONT COLOR=\"red\">undefined</FONT>"; - } elsif ($userregexp eq "") { - $userregexp = "<FONT COLOR=\"blue\">blank</FONT>"; - } - print "</TR><TR>\n"; - print " <TD VALIGN=\"top\">User Regexp for Bug Group:</TD>\n"; - print " <TD VALIGN=\"top\">$userregexp</TD>\n"; - } print "</TR><TR>\n"; print " <TD VALIGN=\"top\">Closed for bugs:</TD>\n"; @@ -637,30 +585,6 @@ if ($action eq 'delete') { WHERE id=$product_id"); print "Product '$product' deleted.<BR>\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'.<BR>\n"; - - SendSQL("DELETE FROM groups " . - "WHERE bit = $bit"); - print "Group '$group_desc' deleted.<BR>\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 "<FORM METHOD=POST ACTION=editproducts.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\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 "<INPUT TYPE=HIDDEN NAME=\"milestoneurlold\" VALUE=\"" . value_quote($milestoneurl) . "\">\n"; - if(Param("usebuggroups")) { - print "<INPUT TYPE=HIDDEN NAME=\"userregexpold\" VALUE=\"" . - value_quote($userregexp) . "\">\n"; - } print "<INPUT TYPE=HIDDEN NAME=\"disallownewold\" VALUE=\"$disallownew\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"votesperuserold\" VALUE=\"$votesperuser\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"maxvotesperbugold\" VALUE=\"$maxvotesperbug\">\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.<BR>\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.<BR>\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.<BR>\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.<BR>\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.<BR>\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 <justdave@syndicomm.com> # Joe Robins <jmrobins@tgix.com> # Dan Mosedale <dmose@mozilla.org> +# Joel Peshkin <bugreport@peshkin.net> # # 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 " <TH ALIGN=\"right\">Login name:</TH>\n"; EmitElement("user", $user); @@ -134,11 +133,15 @@ sub EmitFormElements ($$$$$) if($user ne "") { print "</TR><TR><TH VALIGN=TOP ALIGN=RIGHT>Group Access:</TH><TD><TABLE><TR>"; - 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 "<TD COLSPAN=3 ALIGN=LEFT><B>Can turn this bit on for other users</B></TD>\n"; @@ -146,50 +149,50 @@ sub EmitFormElements ($$$$$) } print "<TD COLSPAN=2 ALIGN=LEFT><B>User is a member of these groups</B></TD>\n"; while (MoreSQLData()) { - my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData(); - print "</TR><TR>\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 "</TR><TR"; + print ' bgcolor=#cccccc' if ($isderived); + print ">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"oldgroup_$groupid\" VALUE=\"$checked\">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"oldbless_$groupid\" VALUE=\"$blchecked\">\n"; if ($editall) { $blchecked = ($blchecked) ? "CHECKED" : ""; - print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"blbit_$name\" $blchecked VALUE=\"$bit\"></TD>"; + print "<TD ALIGN=CENTER>"; + print "[" if $derivedbless; + print "<INPUT TYPE=CHECKBOX NAME=\"bless_$groupid\" $blchecked VALUE=\"$groupid\">"; + print "]" if $derivedbless; + print "</TD>\n"; } $checked = ($checked) ? "CHECKED" : ""; - print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"bit_$name\" $checked VALUE=\"$bit\"></TD>"; - print "<TD><B>" . ucfirst($name) . "</B>: $description</TD>\n"; - } - } - print "</TR></TABLE></TD>\n"; - - print "</TR><TR><TH VALIGN=TOP ALIGN=RIGHT>Privileges:</TH><TD><TABLE><TR>"; - 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 "<TD COLSPAN=3 ALIGN=LEFT><B>Can turn this bit on for other users</B></TD>\n"; - print "</TR><TR>\n<TD ALIGN=CENTER><B>|</B></TD>\n"; - } - print "<TD COLSPAN=2 ALIGN=LEFT><B>User has these privileges</B></TD>\n"; - while (MoreSQLData()) { - my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData(); - print "</TR><TR>\n"; - if ($editall) { - $blchecked = ($blchecked) ? "CHECKED" : ""; - print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"blbit_$name\" $blchecked VALUE=\"$bit\"></TD>"; + print "<TD ALIGN=CENTER>"; + print '[' if ($isderived); + print "<INPUT TYPE=CHECKBOX NAME=\"group_$groupid\" $checked VALUE=\"$groupid\">"; + print ']' if ($isderived); + print "</TD><TD><B>"; + print ucfirst($name) . "</B>: $description</TD>\n"; } - $checked = ($checked) ? "CHECKED" : ""; - print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"bit_$name\" $checked VALUE=\"$bit\"></TD>"; - print "<TD><B>" . ucfirst($name) . "</B>: $description</TD>\n"; } } - } else { - print "</TR><TR><TH ALIGN=RIGHT>Groups and<br>Privileges:</TH><TD><TABLE><TR>"; - print "<TD COLSPAN=3>The new user will be inserted into groups " . - "based on their userregexps.<BR>To change the group " . - "permissions for this user, you must edit the account after ". - "creating it.</TD>\n"; - } + print "</TR></TABLE></TD>\n"; print "</TR></TABLE></TD>\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 "<FORM METHOD=POST ACTION=editusers.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; - EmitFormElements('', '', 0, 0, ''); + EmitFormElements(0, '', '', ''); print "</TR></TABLE>\n<HR>\n"; print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\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.<br>\n"; - if($#grouplist > -1) { - print "New user added to these groups based on group regexps:\n"; - print "<ul>\n"; - foreach (@grouplist) { - print "<li>$_</li>\n"; - } - print "</ul>\n"; - } else { - print "New user not added to any groups.<br><br>\n"; - } + SendSQL("SELECT last_insert_id()"); + my ($newuserid) = FetchSQLData(); + DeriveGroup($newuserid); print "To change ${user}'s permissions, go back and <a href=\"editusers.cgi?action=edit&user=" . url_quote($user)."\">edit this user</A>"; print "<p>\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) : "<FONT COLOR=\"red\">missing</FONT>"); @@ -561,9 +540,11 @@ if ($action eq 'del') { print " <TD VALIGN=\"top\">Group set:</TD>\n"; print " <TD VALIGN=\"top\">"; 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 "<FORM METHOD=POST ACTION=editusers.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; - EmitFormElements($user, $realname, $groupset, $blessgroupset, $disabledtext); + EmitFormElements($thisuserid, $user, $realname, $disabledtext); print "</TR></TABLE>\n"; - print "<INPUT TYPE=HIDDEN NAME=\"userold\" VALUE=\"$user\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"realnameold\" VALUE=\"$realname\">\n"; - print "<INPUT TYPE=HIDDEN NAME=\"groupsetold\" VALUE=\"$groupset\">\n"; - print "<INPUT TYPE=HIDDEN NAME=\"blessgroupsetold\" VALUE=\"$blessgroupset\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"disabledtextold\" VALUE=\"" . value_quote($disabledtext) . "\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n"; print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n"; + print "<BR>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. + <BR>"; print "</FORM>"; @@ -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{$_}<br>\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<BR>\n"; + push(@grpadd, $name); + } else { + print "Dropped user from group $name<BR>\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<BR>\n"; + } else { + print "Revoked user's permission to bless group $name<BR>\n"; + } + PopGlobalSQLState(); + + } } - my $blessgroupset = "0"; - foreach (keys %::FORM) { - next unless /^blbit_/; - #print "$_=$::FORM{$_}<br>\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 <jake@bugzilla.org> # Bradley Baetz <bbaetz@cs.mcgill.ca> # Christopher Aillon <christopher@aillon.com> +# Joel Peshkin <bugreport@peshkin.net> # 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 = "</strike>"; } - 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; @@ -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, "<data/nomail")) { close FID; } +# Since any email recipients must be rederived if the user has not +# been rederived since the most recent group change, figure out when that +# is once and determine the need to rederive users using the same DB access +# that gets the user's email address each time a person is processed. +# +SendSQL("SELECT MAX(last_changed) FROM groups"); +($::last_changed) = FetchSQLData(); + if ($#ARGV >= 0 && $ARGV[0] eq "regenerate") { print "Regenerating is no longer required or supported\n"; exit; @@ -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.<p>\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. #%] -<table> +<table align="center"> <tr> <td> [% IF has_bits.size %] You have the following permission bits set on your account: - <ul> + <p> + <br> + <table align="center"> [% FOREACH bit_description = has_bits %] - <li>[% bit_description %]</li> + <tr> + <td>[% bit_description.name %]</td> + <td>[% bit_description.desc %]</td> + </tr> [% END %] - </ul> + </table> [% ELSE %] There are no permission bits set on your account. [% END %] [% IF set_bits.size %] + <br> And you can turn on or off the following bits for <a href="editusers.cgi">other users</a>: <p> - <ul> + <table align="center"> [% FOREACH bit_description = set_bits %] - <li>[% bit_description %]</li> + <tr> + <td>[% bit_description.name %]</td> + <td>[% bit_description.desc %]</td> + </tr> [% END %] - </ul> + </table> </p> [% END %] </td> 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 @@ [% ', <a href="editparams.cgi">parameters</a>' IF user.groups.tweakparams %] [% ', <a href="editusers.cgi">users</a>' IF user.groups.editusers - || (user.blessgroupset > 0) %] + || user.canblessany %] [% ', <a href="editproducts.cgi">products</a>' IF user.groups.editcomponents %] [% ', <a href="editattachstatuses.cgi"> attachment statuses</a>' 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') %] <text class="text-link" onclick="load_relative_url('editparams.cgi')" value="edit params"/> [%- END %] - [%- IF UserInGroup('editusers') || blessgroupset %] + [%- IF UserInGroup('editusers') || canblessany %] <text class="text-link" onclick="load_relative_url('editusers.cgi')" value="edit users"/> [%- END %] [%- IF UserInGroup('editcomponents') %] @@ -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}); } } @@ -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, |