]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
bug 157756 - Groups_20020716_Branch Tracking : > 55 groups now supported
authorbugreport%peshkin.net <>
Mon, 23 Sep 2002 00:14:48 +0000 (00:14 +0000)
committerbugreport%peshkin.net <>
Mon, 23 Sep 2002 00:14:48 +0000 (00:14 +0000)
r=bbaetz, gerv

34 files changed:
Bug.pm
Bugzilla/Bug.pm
Bugzilla/Search.pm
CGI.pl
attachment.cgi
bug_form.pl
buglist.cgi
checksetup.pl
contrib/bug_email.pl
docs/sgml/administration.sgml
docs/xml/administration.xml
duplicates.cgi
editgroups.cgi
editproducts.cgi
editusers.cgi
enter_bug.cgi
globals.pl
index.cgi
long_list.cgi
post_bug.cgi
process_bug.cgi
processmail
query.cgi
sanitycheck.cgi
show_activity.cgi
showdependencygraph.cgi
showdependencytree.cgi
sidebar.cgi
template/en/default/account/prefs/permissions.html.tmpl
template/en/default/global/useful-links.html.tmpl
template/en/default/sidebar.xul.tmpl
token.cgi
userprefs.cgi
votes.cgi

diff --git a/Bug.pm b/Bug.pm
index 7e703d14f8fc16abc431e736d07a7e6c75611447..3dadd3cd5fa7c462edbea48dab15bac4c122dcb7 100755 (executable)
--- a/Bug.pm
+++ b/Bug.pm
@@ -37,8 +37,8 @@ use Bugzilla::Util;
 for my $key (qw (bug_id alias product version rep_platform op_sys bug_status 
                 resolution priority bug_severity component assigned_to
                 reporter bug_file_loc short_desc target_milestone 
-                qa_contact status_whiteboard creation_ts groupset 
-                delta_ts votes whoid usergroupset comment query error) ){
+                qa_contact status_whiteboard creation_ts 
+                delta_ts votes whoid comment query error) ){
     $ok_field{$key}++;
     }
 
@@ -105,10 +105,6 @@ sub initBug  {
 
 
   $self->{'whoid'} = $user_id;
-  &::SendSQL("SELECT groupset FROM profiles WHERE userid=$self->{'whoid'}");
-  my $usergroupset = &::FetchOneColumn();
-  if (!$usergroupset) { $usergroupset = '0' }
-  $self->{'usergroupset'} = $usergroupset;
 
   my $query = "
     select
@@ -116,7 +112,7 @@ sub initBug  {
       resolution, priority, bug_severity, components.name, assigned_to, reporter,
       bug_file_loc, short_desc, target_milestone, qa_contact,
       status_whiteboard, date_format(creation_ts,'%Y-%m-%d %H:%i'),
-      groupset, delta_ts, sum(votes.count)
+      delta_ts, sum(votes.count)
     from bugs left join votes using(bug_id),
       products, components
     where bugs.bug_id = $bug_id
@@ -124,10 +120,10 @@ sub initBug  {
       AND components.id = bugs.component_id
     group by bugs.bug_id";
 
-  &::SendSQL(&::SelectVisible($query, $user_id, $usergroupset));
-  my @row;
+  &::SendSQL($query);
+  my @row = ();
 
-  if (@row = &::FetchSQLData()) {
+  if ((@row = &::FetchSQLData()) && &::CanSeeBug($bug_id, $self->{'whoid'})) {
     my $count = 0;
     my %fields;
     foreach my $field ("bug_id", "alias", "product", "version", "rep_platform",
@@ -135,24 +131,21 @@ sub initBug  {
                        "bug_severity", "component", "assigned_to", "reporter",
                        "bug_file_loc", "short_desc", "target_milestone",
                        "qa_contact", "status_whiteboard", "creation_ts",
-                       "groupset", "delta_ts", "votes") {
+                       "delta_ts", "votes") {
         $fields{$field} = shift @row;
         if ($fields{$field}) {
             $self->{$field} = $fields{$field};
         }
         $count++;
     }
-  } else {
-    &::SendSQL("select groupset from bugs where bug_id = $bug_id");
-    if (@row = &::FetchSQLData()) {
+  } elsif (@row) {
       $self->{'bug_id'} = $bug_id;
       $self->{'error'} = "NotPermitted";
       return $self;
-    } else {
+  } else {
       $self->{'bug_id'} = $bug_id;
       $self->{'error'} = "NotFound";
       return $self;
-    }
   }
 
   $self->{'assigned_to'} = &::DBID_to_name($self->{'assigned_to'});
@@ -356,22 +349,6 @@ sub XML_Footer {
   return ("</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) = (@_);
index 7e703d14f8fc16abc431e736d07a7e6c75611447..3dadd3cd5fa7c462edbea48dab15bac4c122dcb7 100755 (executable)
@@ -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) = (@_);
index 482daca5c7d08938599f35e5d4de24a5f1ffee50..d6e7a9b7f6649898e6c0c959210a0786364d3552 100644 (file)
@@ -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";
diff --git a/CGI.pl b/CGI.pl
index 0c85128d14a904514095087b46b8c6321dd54140..70c15c932af50aa398317b692b804d8d4556360e 100644 (file)
--- a/CGI.pl
+++ b/CGI.pl
@@ -289,11 +289,6 @@ sub ValidateBugID {
     # converted-from-alias ID.
     $_[0] = $id;
     
-    # Get the values of the usergroupset and userid global variables
-    # and write them to local variables for use within this function,
-    # setting those local variables to the default value of zero if
-    # the global variables are undefined.
-
     # First check that the bug exists
     SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id");
 
@@ -303,7 +298,7 @@ sub ValidateBugID {
 
     return if $skip_authorization;
     
-    return if CanSeeBug($id, $::userid, $::usergroupset);
+    return if CanSeeBug($id, $::userid);
 
     # The user did not pass any of the authorization tests, which means they
     # are not authorized to see the bug.  Display an error and stop execution.
@@ -438,30 +433,25 @@ sub PasswordForLogin {
 }
 
 sub quietly_check_login() {
-    $::usergroupset = '0';
-    my $loginok = 0;
     $::disabledreason = '';
-    $::userid = 0;
+    my $userid = 0;
     if (defined $::COOKIE{"Bugzilla_login"} &&
         defined $::COOKIE{"Bugzilla_logincookie"}) {
-        SendSQL("SELECT profiles.userid, profiles.groupset, " .
-                "profiles.login_name, " .
-                "profiles.login_name = " .
-                SqlQuote($::COOKIE{"Bugzilla_login"}) .
-                " AND logincookies.ipaddr = " .
-                SqlQuote($ENV{"REMOTE_ADDR"}) .
-                ", profiles.disabledtext " .
+        SendSQL("SELECT profiles.userid," .
+                " profiles.login_name, " .
+                " profiles.disabledtext " .
                 " FROM profiles, logincookies WHERE logincookies.cookie = " .
                 SqlQuote($::COOKIE{"Bugzilla_logincookie"}) .
-                " AND profiles.userid = logincookies.userid");
+                " AND profiles.userid = logincookies.userid AND" .
+                " profiles.login_name = " .
+                SqlQuote($::COOKIE{"Bugzilla_login"}) .
+                " AND logincookies.ipaddr = " .
+                SqlQuote($ENV{"REMOTE_ADDR"}));
         my @row;
-        if (@row = FetchSQLData()) {
-            my ($userid, $groupset, $loginname, $ok, $disabledtext) = (@row);
-            if ($ok) {
+        if (MoreSQLData()) {
+            ($userid, my $loginname, my $disabledtext) = FetchSQLData();
+            if ($userid > 0) {
                 if ($disabledtext eq '') {
-                    $loginok = 1;
-                    $::userid = $userid;
-                    $::usergroupset = $groupset;
                     $::COOKIE{"Bugzilla_login"} = $loginname; # Makes sure case
                                                               # is in
                                                               # canonical form.
@@ -469,6 +459,7 @@ sub quietly_check_login() {
                     detaint_natural($::COOKIE{"Bugzilla_logincookie"});
                 } else {
                     $::disabledreason = $disabledtext;
+                    $userid = 0;
                 }
             }
         }
@@ -478,13 +469,14 @@ sub quietly_check_login() {
         my $whoid = DBname_to_id($::FORM{'who'});
         delete $::FORM{'who'} unless $whoid;
     }
-    if (!$loginok) {
+    if (!$userid) {
         delete $::COOKIE{"Bugzilla_login"};
     }
                     
+    $::userid = $userid;
+    ConfirmGroup($userid);
     $vars->{'user'} = GetUserInfo($::userid);
-    
-    return $loginok;
+    return $userid;
 }
 
 # Populate a hash with information about this user. 
@@ -500,10 +492,9 @@ sub GetUserInfo {
     $user{'login'} = $::COOKIE{"Bugzilla_login"};
     $user{'userid'} = $userid;
     
-    SendSQL("SELECT mybugslink, realname, groupset, blessgroupset " . 
+    SendSQL("SELECT mybugslink, realname " . 
             "FROM profiles WHERE userid = $userid");
-    ($user{'showmybugslink'}, $user{'realname'}, $user{'groupset'},
-                                       $user{'blessgroupset'}) = FetchSQLData();
+    ($user{'showmybugslink'}, $user{'realname'}) = FetchSQLData();
 
     SendSQL("SELECT name, query, linkinfooter FROM namedqueries " .
             "WHERE userid = $userid");
@@ -516,10 +507,15 @@ sub GetUserInfo {
 
     $user{'queries'} = \@queries;
 
-    SendSQL("select name, (bit & $user{'groupset'}) != 0 from groups");
+    $user{'canblessany'} = UserCanBlessAnything();
+
+    SendSQL("SELECT name FROM groups, user_group_map " .
+            "WHERE groups.id = user_group_map.group_id " .
+            "AND user_id = $userid " .
+            "AND NOT isbless");
     while (MoreSQLData()) {
-        my ($name, $bit) = FetchSQLData();    
-        $groups{$name} = $bit;
+        my ($name) = FetchSQLData();    
+        $groups{$name} = 1;
     }
 
     $user{'groups'} = \%groups;
@@ -561,6 +557,7 @@ sub confirm_login {
     # to a later section.  -Joe Robins, 8/3/00
     my $enteredlogin = "";
     my $realcryptpwd = "";
+    my $userid;
 
     # If the form contains Bugzilla login and password fields, use Bugzilla's 
     # built-in authentication to authenticate the user (otherwise use LDAP below).
@@ -570,7 +567,6 @@ sub confirm_login {
         CheckEmailSyntax($enteredlogin);
 
         # Retrieve the user's ID and crypted password from the database.
-        my $userid;
         SendSQL("SELECT userid, cryptpassword FROM profiles 
                  WHERE login_name = " . SqlQuote($enteredlogin));
         ($userid, $realcryptpwd) = FetchSQLData();
@@ -765,9 +761,9 @@ sub confirm_login {
        print "Set-Cookie: Bugzilla_logincookie=$logincookie ; path=$cookiepath; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
     }
 
-    my $loginok = quietly_check_login();
+    $userid = quietly_check_login();
 
-    if ($loginok != 1) {
+    if (!$userid) {
         if ($::disabledreason) {
             my $cookiepath = Param("cookiepath");
             print "Set-Cookie: Bugzilla_login= ; path=$cookiepath; expires=Sun, 30-Jun-80 00:00:00 GMT
@@ -810,7 +806,8 @@ Content-type: text/html
         SendSQL("UPDATE logincookies SET lastused = null " .
                 "WHERE cookie = $::COOKIE{'Bugzilla_logincookie'}");
     }
-    return $::userid;
+    ConfirmGroup($userid);
+    return $userid;
 }
 
 sub PutHeader {
index 3b29a321e0d45bc315a59899c7b26084fe633099..cfdbd4ea6e6b72c5ee00b60df80acc4ecbdbecb4 100755 (executable)
@@ -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();
 
 ################################################################################
index 8d7a2b02ef58489c1081e4449344859cca513ab2..dfffca9b8ecc7b0742b996e920f197b090d9c123 100644 (file)
@@ -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;
 
index 6597fbe3ea0863b0e6f804215bdfb3150fda2e58..728ead4d1e76dfe9b601f42192c288e793c6329f 100755 (executable)
@@ -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
index c00301742c01af3fdf884887be91046449cdaaa5..b752f9b6564b6fe3ba04b98f5470bd6acebaa10c 100755 (executable)
@@ -1360,7 +1360,6 @@ $table{attachstatusdefs} =
 #
 $table{bugs} =
    'bug_id mediumint not null auto_increment primary key,
-    groupset bigint not null,
     assigned_to mediumint not null, # This is a comment.
     bug_file_loc text,
     bug_severity enum($my_severities) not null,
@@ -1454,16 +1453,7 @@ $table{dependencies} =
     index(dependson)';
 
 
-# Group bits must be a power of two. Groups are identified by a bit; sets of
-# groups are indicated by or-ing these values together.
-#
-# isbuggroup is nonzero if this is a group that controls access to a set
-# of bugs.  In otherword, the groupset field in the bugs table should only
-# have this group's bit set if isbuggroup is nonzero.
-#
-# User regexp is which email addresses are initially put into this group.
-# This is only used when an email account is created; otherwise, profiles
-# may be individually tweaked to add them in and out of groups.
+# User regexp is which email addresses are put into this group.
 #
 # 2001-04-10 myk@mozilla.org:
 # isactive determines whether or not a group is active.  An inactive group
@@ -1473,14 +1463,14 @@ $table{dependencies} =
 # http://bugzilla.mozilla.org/show_bug.cgi?id=75482
 
 $table{groups} =
-   'bit bigint not null,
+   'id mediumint not null auto_increment primary key,
     name varchar(255) not null,
     description text not null,
     isbuggroup tinyint not null,
+    last_changed datetime not null,
     userregexp tinytext not null,
     isactive tinyint not null default 1,
 
-    unique(bit),
     unique(name)';
 
 $table{logincookies} =
@@ -1511,13 +1501,10 @@ $table{profiles} =
     login_name varchar(255) not null,
     cryptpassword varchar(34),
     realname varchar(255),
-    groupset bigint not null,
     disabledtext mediumtext not null,
     mybugslink tinyint not null default 1,
-    blessgroupset bigint not null default 0,
     emailflags mediumtext,
-
-
+    refreshed_when datetime not null,
     unique(login_name)';
 
 
@@ -1610,6 +1597,38 @@ $table{tokens} =
 
      index(userid)';
 
+# group membership tables for tracking group and privilege 
+# 
+# This table determines the groups that a user belongs to
+# directly or due to regexp and which groups can be blessed
+# by a user. 
+#
+# isderived: 
+# if 0 - record was explicitly granted
+# if 1 - record was created by evaluating a regexp or group hierarchy
+$table{user_group_map} =
+    'user_id mediumint not null,
+     group_id mediumint not null,
+     isbless tinyint not null default 0,
+     isderived tinyint not null default 0,
+
+     unique(user_id, group_id, isderived, isbless)';
+
+$table{group_group_map} =
+    'member_id mediumint not null,
+     grantor_id mediumint not null,
+     isbless tinyint not null default 0,
+
+     unique(member_id, grantor_id, isbless)';
+
+# This table determines which groups a user must be a member of
+# in order to see a bug.
+$table{bug_group_map} =
+    'bug_id mediumint not null,
+     group_id mediumint not null,
+     unique(bug_id, group_id),
+     index(group_id)';
+
 # 2002-07-19, davef@tetsubo.com, bug 67950:
 # Store quips in the db.
 $table{quips} =
@@ -1617,7 +1636,6 @@ $table{quips} =
      userid mediumint not null default 0, 
      quip text not null';
 
-
 ###########################################################################
 # Create tables
 ###########################################################################
@@ -1692,7 +1710,7 @@ sub GroupDoesExist ($)
 
 #
 # This subroutine checks if a group exist. If not, it will be automatically
-# created with the next available bit set
+# created with the next available groupid
 #
 
 sub AddGroup {
@@ -1701,57 +1719,19 @@ sub AddGroup {
 
     return if GroupDoesExist($name);
     
-    # get highest bit number
-    my $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC");
-    $sth->execute;
-    my @row = $sth->fetchrow_array;
-
-    # normalize bits
-    my $bit;
-    if (defined $row[0]) {
-        $bit = $row[0] << 1;
-    } else {
-        $bit = 1;
-    }
-
-   
     print "Adding group $name ...\n";
-    $sth = $dbh->prepare('INSERT INTO groups
-                          (bit, name, description, userregexp, isbuggroup)
-                          VALUES (?, ?, ?, ?, ?)');
-    $sth->execute($bit, $name, $desc, $userregexp, 0);
-    return $bit;
-}
-
-
-#
-# BugZilla uses --GROUPS-- to assign various rights to its users. 
-#
-
-AddGroup 'tweakparams',      'Can tweak operating parameters';
-AddGroup 'editusers',      'Can edit or disable users';
-AddGroup 'creategroups',     'Can create and destroy groups.';
-AddGroup 'editcomponents',   'Can create, destroy, and edit components.';
-AddGroup 'editkeywords',   'Can create, destroy, and edit keywords.';
-
-# Add the groupset field here because this code is run before the
-# code that updates the database structure.
-&AddField('profiles', 'groupset', 'bigint not null');
-
-if (!GroupDoesExist("editbugs")) {
-    my $id = AddGroup('editbugs',  'Can edit all aspects of any bug.', ".*");
-    $dbh->do("UPDATE profiles SET groupset = groupset | $id");
-}
+    my $sth = $dbh->prepare('INSERT INTO groups
+                          (name, description, userregexp, isbuggroup)
+                          VALUES (?, ?, ?, ?)');
+    $sth->execute($name, $desc, $userregexp, 0);
 
-if (!GroupDoesExist("canconfirm")) {
-    my $id = AddGroup('canconfirm',  'Can confirm a bug.', ".*");
-    $dbh->do("UPDATE profiles SET groupset = groupset | $id");
+    $sth = $dbh->prepare("select last_insert_id()");
+    $sth->execute();
+    my ($last) = $sth->fetchrow_array();
+    return $last;
 }
 
 
-
-
-
 ###########################################################################
 # Populate the list of fields.
 ###########################################################################
@@ -1818,9 +1798,9 @@ AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed",
 AddFDef("longdesc", "Comment", 0);
 AddFDef("alias", "Alias", 0);
 AddFDef("everconfirmed", "Ever Confirmed", 0);
-AddFDef("groupset", "Groupset", 0);
 AddFDef("reporter_accessible", "Reporter Accessible", 0);
 AddFDef("cclist_accessible", "CC Accessible", 0);
+AddFDef("bug_group", "Group", 0);
 
 # Oops. Bug 163299
 $dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
@@ -1937,181 +1917,9 @@ CheckEnumField('bugs', 'op_sys',       @my_opsys);
 CheckEnumField('bugs', 'rep_platform', @my_platforms);
 
 
-###########################################################################
-# Create Administrator  --ADMIN--
-###########################################################################
-
-#  Prompt the user for the email address and name of an administrator.  Create
-#  that login, if it doesn't exist already, and make it a member of all groups.
-
-sub bailout {   # this is just in case we get interrupted while getting passwd
-    system("stty","echo"); # re-enable input echoing
-    exit 1;
-}
-
-$sth = $dbh->prepare(<<_End_Of_SQL_);
-  SELECT login_name
-  FROM profiles
-  WHERE groupset=9223372036854775807
-_End_Of_SQL_
-$sth->execute;
-# when we have no admin users, prompt for admin email address and password ...
-if ($sth->rows == 0) {
-  my $login = "";
-  my $realname = "";
-  my $pass1 = "";
-  my $pass2 = "*";
-  my $admin_ok = 0;
-  my $admin_create = 1;
-  my $mailcheckexp = Param('emailregexp');
-  my $mailcheck    = Param('emailregexpdesc');
-
-  print "\nLooks like we don't have an administrator set up yet.  Either this is your\n";
-  print "first time using Bugzilla, or your administrator's privs might have accidently\n";
-  print "gotten deleted at some point.\n";
-  while(! $admin_ok ) {
-    while( $login eq "" ) {
-      print "Enter the e-mail address of the administrator: ";
-      $login = $answer{'ADMIN_EMAIL'} 
-          || ($silent && die("cant preload ADMIN_EMAIL")) 
-          || <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;
index fb2bbec4d876e0767baea829ab22e006189d6a6e..bf442502c3c484f6db384a8830da524357e95f17 100755 (executable)
@@ -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)
index a82a659bf3f71ca1809feb366cd347e25b4b3b20..a0ff3e174bc75c6c4455f4ee3ab32c460eddae5c 100644 (file)
       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&gt;</prompt>
-            <command>use bugs;</command>
-          </member>
-
-          <member>
-            <prompt>mysql&gt;</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>
 
       </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>
 
         <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>
       </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
index a82a659bf3f71ca1809feb366cd347e25b4b3b20..a0ff3e174bc75c6c4455f4ee3ab32c460eddae5c 100644 (file)
       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&gt;</prompt>
-            <command>use bugs;</command>
-          </member>
-
-          <member>
-            <prompt>mysql&gt;</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>
 
       </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>
 
         <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>
       </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
index 3eeab3fb5dc62143fa0b65309850a963c04810ff..0e27f077e4ecdbb45faba471f64c1590ee51c24b 100755 (executable)
@@ -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 "");
 
index 5bcd4f61cdfb06e2d09220b0ab733e15fe4ee464..9ecda413839fdb40d58a2d270fdab8ce3e22f04c 100755 (executable)
@@ -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&nbsp</td>\n";
+        print "<td align=center>";
+        print "X" if $isactive;
+        print "&nbsp</td>\n";
+        print "<td> &nbsp ";
+        print (($isbuggroup == 0 ) ? "system" : "user"); 
+        print "&nbsp</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
 #
index 5b4c3c24968e5324d5ef8700a8b1a53f893d35c6..18ad4216db0be33155aa1dc63453aa60c6eff245 100755 (executable)
@@ -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";
     }
index 4586345af65d622b0eda3d6c870af8d4deb311cc..cc6be6665ba3a6f39b16e24d7005806c768796c2 100755 (executable)
@@ -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 ) {
index a779aa85d3e951dd7a47d4220f7d027d11ad820f..a7733ec3eae341b57d658e4a463fc2d9e8d2fb92 100755 (executable)
@@ -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());          
+
index 4570a5658082fcfdd0ca22233b25fe242e96c558..242c8a5f65416ad053112f0bd58f1d8930b8e0e0 100644 (file)
@@ -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;
index 42f493fcb318dcc5ea728828928f34e05f366db5..7e9fb5aab8484104cc159a16096236e70810df9b 100755 (executable)
--- a/index.cgi
+++ b/index.cgi
@@ -45,7 +45,6 @@ use vars qw(
 ConnectToDatabase();
 
 # Check whether or not the user is logged in and, if so, set the $::userid 
-# and $::usergroupset variables.
 quietly_check_login();
 
 ###############################################################################
index 6acee0332aae7cb07ca4530a765765fcdc3c77bd..6df8a8bad998144308880aff9a2bc2dbb22c0a80 100755 (executable)
@@ -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();
index 430ae37a8e874a9618de40dd418b8335fdf55ab7..22aa0e6bd9a19747e90a9ed7731f6adabbac9a4d 100755 (executable)
@@ -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) . ")");
index f62285ffbb210654b64a5097c5758375a00bc7d3..3468a9790498ba3c36ee262be312c426353d976d 100755 (executable)
@@ -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;
index 45aaacc77ef51f25125c22039a3b50cf5372dcd2..a472975976167cb8f9dff4acafdb2b6336d93e9a 100755 (executable)
@@ -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;
index e038d98ae263f7b59df1d5c0670190e7ed29efbf..cd679e8e8602d7909223c502ea015aafa2a2adb3 100755 (executable)
--- a/query.cgi
+++ b/query.cgi
@@ -50,19 +50,19 @@ use vars qw(
 );
 
 ConnectToDatabase();
-
+my $userid = 0;
 if (defined $::FORM{"GoAheadAndLogIn"}) {
     # We got here from a login page, probably from relogin.cgi.  We better
     # make sure the password is legit.
-    confirm_login();
+    $userid = confirm_login();
 } else {
-    quietly_check_login();
+    $userid = quietly_check_login();
 }
 
 # Backwards compatibility hack -- if there are any of the old QUERY_*
 # cookies around, and we are logged in, then move them into the database
 # and nuke the cookie. This is required for Bugzilla 2.8 and earlier.
-if ($::userid) {
+if ($userid) {
     my @oldquerycookies;
     foreach my $i (keys %::COOKIE) {
         if ($i =~ /^QUERY_(.*)$/) {
@@ -79,12 +79,12 @@ if ($::userid) {
             if ($value) {
                 my $qname = SqlQuote($name);
                 SendSQL("SELECT query FROM namedqueries " .
-                        "WHERE userid = $::userid AND name = $qname");
+                        "WHERE userid = $userid AND name = $qname");
                 my $query = FetchOneColumn();
                 if (!$query) {
                     SendSQL("REPLACE INTO namedqueries " .
                             "(userid, name, query) VALUES " .
-                            "($::userid, $qname, " . SqlQuote($value) . ")");
+                            "($userid, $qname, " . SqlQuote($value) . ")");
                 }
             }
             print "Set-Cookie: $cookiename= ; path=" . Param("cookiepath") . 
@@ -94,17 +94,17 @@ if ($::userid) {
 }
 
 if ($::FORM{'nukedefaultquery'}) {
-    if ($::userid) {
+    if ($userid) {
         SendSQL("DELETE FROM namedqueries " .
-                "WHERE userid = $::userid AND name = '$::defaultqueryname'");
+                "WHERE userid = $userid AND name = '$::defaultqueryname'");
     }
     $::buffer = "";
 }
 
 my $userdefaultquery;
-if ($::userid) {
+if ($userid) {
     SendSQL("SELECT query FROM namedqueries " .
-            "WHERE userid = $::userid AND name = '$::defaultqueryname'");
+            "WHERE userid = $userid AND name = '$::defaultqueryname'");
     $userdefaultquery = FetchOneColumn();
 }
 
@@ -285,7 +285,7 @@ $vars->{'rep_platform'} = \@::legal_platform;
 $vars->{'op_sys'} = \@::legal_opsys;
 $vars->{'priority'} = \@::legal_priority;
 $vars->{'bug_severity'} = \@::legal_severity;
-$vars->{'userid'} = $::userid;
+$vars->{'userid'} = $userid;
 
 # Boolean charts
 my @fields;
@@ -332,10 +332,10 @@ for (my $chart = 0; $::FORM{"field$chart-0-0"}; $chart++) {
 $default{'charts'} = \@charts;
 
 # Named queries
-if ($::userid) {
+if ($userid) {
     my @namedqueries;
     SendSQL("SELECT name FROM namedqueries " .
-            "WHERE userid = $::userid AND name != '$::defaultqueryname' " .
+            "WHERE userid = $userid AND name != '$::defaultqueryname' " .
             "ORDER BY name");
     while (MoreSQLData()) {
         push(@namedqueries, FetchOneColumn());
index 451ad23daba7363e7eccc083b0cb6eb4b8d89e38..da71163cc56c7d39e55ba6bbfcc641c2515b7b7f 100755 (executable)
@@ -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
index f6d9b75c9d12336952584f33ecff0a56413c809d..14b4149ba2649542deb530708e7a23a8d6102218 100755 (executable)
@@ -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
index e5885d180f63ce9640876b8bc65292c012ac9714..021150bf01fe9c056329ddc6ff561606ecd250fd 100755 (executable)
@@ -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();
index 7917f00fec603fec413adc7a871b28b915c170af..e2e9d52b0ed76de71d6c012cb5a02c6e1e7cdbbd 100755 (executable)
@@ -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'} = [];
index d3692b16d32d2157a4aea381d31e22df17b3863a..ec021ea1c76433cbe8b29b73c247e04633fb5ec7 100755 (executable)
@@ -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'}) );
index 15bca6deb899e56eb8689088f21ddbf7378f398a..064220d6e30c43508a4657476e50ca82978df7ae 100644 (file)
   #%]
 
 [%# 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>
index 987ca370bc4d7562a6b77c9b83a0d6f0c35f8949..1e5b09df1a649c2907d02fb675a917003f9bfed6 100644 (file)
@@ -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&nbsp;statuses</a>'
index ad7158b13d3ad43bc57816b3a54e73d56aee1c07..21a07c998834e80040ceda6f0acac4361e2df38d 100644 (file)
@@ -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') %]
index 43ee1b04e3705ba4be1511b8367f334493828fc4..86e51939d348309b3cc5f61efe21287a060abde5 100755 (executable)
--- a/token.cgi
+++ b/token.cgi
@@ -265,6 +265,7 @@ sub changeEmail {
     SendSQL("DELETE FROM tokens WHERE userid = $userid 
                                   AND tokentype = 'emailnew'");
     SendSQL("UNLOCK TABLES");
+    DeriveGroup($userid);
 
     # Return HTTP response headers.
     print "Content-Type: text/html\n\n";
@@ -300,6 +301,7 @@ sub cancelChangeEmail {
                  SET      login_name = $quotedoldemail
                  WHERE    userid = $userid");
             SendSQL("UNLOCK TABLES");
+            DeriveGroup($userid);
             $vars->{'message'} .= 
                 "  Your old account settings have been reinstated.";
         } 
index 808aebf405d4cb7ff2d4fba5912d0dc88903ec64..369c681ca8d834c0ae0be894fdf62276e9614d1d 100755 (executable)
@@ -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});
         }
     }
     
index 88e3039712b3770eb22ba1b9ca92696e951b0b37..3bfe11682684bff53bf25d976c93c853868ef658 100755 (executable)
--- a/votes.cgi
+++ b/votes.cgi
@@ -28,7 +28,6 @@ use lib ".";
 
 require "CGI.pl";
 
-use vars qw($usergroupset);
 
 # Use global template variables
 use vars qw($template $vars);
@@ -188,7 +187,7 @@ sub show_user {
             # and they can see there are votes 'missing', but not on what bug
             # they are. This seems a reasonable compromise; the alternative is
             # to lie in the totals.
-            next if !CanSeeBug($id, $who, $usergroupset);            
+            next if !CanSeeBug($id, $who);            
             
             push (@bugs, { id => $id, 
                            summary => $summary,