summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xBug.pm41
-rwxr-xr-xBugzilla/Bug.pm41
-rw-r--r--Bugzilla/Search.pm36
-rw-r--r--CGI.pl69
-rwxr-xr-xattachment.cgi1
-rw-r--r--bug_form.pl108
-rwxr-xr-xbuglist.cgi29
-rwxr-xr-xchecksetup.pl794
-rwxr-xr-xcontrib/bug_email.pl2
-rw-r--r--docs/sgml/administration.sgml64
-rw-r--r--docs/xml/administration.xml64
-rwxr-xr-xduplicates.cgi7
-rwxr-xr-xeditgroups.cgi506
-rwxr-xr-xeditproducts.cgi185
-rwxr-xr-xeditusers.cgi286
-rwxr-xr-xenter_bug.cgi92
-rw-r--r--globals.pl369
-rwxr-xr-xindex.cgi1
-rwxr-xr-xlong_list.cgi6
-rwxr-xr-xpost_bug.cgi28
-rwxr-xr-xprocess_bug.cgi103
-rwxr-xr-xprocessmail35
-rwxr-xr-xquery.cgi26
-rwxr-xr-xsanitycheck.cgi90
-rwxr-xr-xshow_activity.cgi5
-rwxr-xr-xshowdependencygraph.cgi16
-rwxr-xr-xshowdependencytree.cgi23
-rwxr-xr-xsidebar.cgi6
-rw-r--r--template/en/default/account/prefs/permissions.html.tmpl34
-rw-r--r--template/en/default/global/useful-links.html.tmpl2
-rw-r--r--template/en/default/sidebar.xul.tmpl2
-rwxr-xr-xtoken.cgi2
-rwxr-xr-xuserprefs.cgi28
-rwxr-xr-xvotes.cgi3
34 files changed, 1695 insertions, 1409 deletions
diff --git a/Bug.pm b/Bug.pm
index 7e703d14f..3dadd3cd5 100755
--- a/Bug.pm
+++ b/Bug.pm
@@ -37,8 +37,8 @@ use Bugzilla::Util;
for my $key (qw (bug_id alias product version rep_platform op_sys bug_status
resolution priority bug_severity component assigned_to
reporter bug_file_loc short_desc target_milestone
- qa_contact status_whiteboard creation_ts groupset
- delta_ts votes whoid usergroupset comment query error) ){
+ qa_contact status_whiteboard creation_ts
+ delta_ts votes whoid comment query error) ){
$ok_field{$key}++;
}
@@ -105,10 +105,6 @@ sub initBug {
$self->{'whoid'} = $user_id;
- &::SendSQL("SELECT groupset FROM profiles WHERE userid=$self->{'whoid'}");
- my $usergroupset = &::FetchOneColumn();
- if (!$usergroupset) { $usergroupset = '0' }
- $self->{'usergroupset'} = $usergroupset;
my $query = "
select
@@ -116,7 +112,7 @@ sub initBug {
resolution, priority, bug_severity, components.name, assigned_to, reporter,
bug_file_loc, short_desc, target_milestone, qa_contact,
status_whiteboard, date_format(creation_ts,'%Y-%m-%d %H:%i'),
- groupset, delta_ts, sum(votes.count)
+ delta_ts, sum(votes.count)
from bugs left join votes using(bug_id),
products, components
where bugs.bug_id = $bug_id
@@ -124,10 +120,10 @@ sub initBug {
AND components.id = bugs.component_id
group by bugs.bug_id";
- &::SendSQL(&::SelectVisible($query, $user_id, $usergroupset));
- my @row;
+ &::SendSQL($query);
+ my @row = ();
- if (@row = &::FetchSQLData()) {
+ if ((@row = &::FetchSQLData()) && &::CanSeeBug($bug_id, $self->{'whoid'})) {
my $count = 0;
my %fields;
foreach my $field ("bug_id", "alias", "product", "version", "rep_platform",
@@ -135,24 +131,21 @@ sub initBug {
"bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone",
"qa_contact", "status_whiteboard", "creation_ts",
- "groupset", "delta_ts", "votes") {
+ "delta_ts", "votes") {
$fields{$field} = shift @row;
if ($fields{$field}) {
$self->{$field} = $fields{$field};
}
$count++;
}
- } else {
- &::SendSQL("select groupset from bugs where bug_id = $bug_id");
- if (@row = &::FetchSQLData()) {
+ } elsif (@row) {
$self->{'bug_id'} = $bug_id;
$self->{'error'} = "NotPermitted";
return $self;
- } else {
+ } else {
$self->{'bug_id'} = $bug_id;
$self->{'error'} = "NotFound";
return $self;
- }
}
$self->{'assigned_to'} = &::DBID_to_name($self->{'assigned_to'});
@@ -356,22 +349,6 @@ sub XML_Footer {
return ("</bugzilla>\n");
}
-sub UserInGroup {
- my $self = shift();
- my ($groupname) = (@_);
- if ($self->{'usergroupset'} eq "0") {
- return 0;
- }
- &::ConnectToDatabase();
- &::SendSQL("select (bit & $self->{'usergroupset'}) != 0 from groups where name = "
- . &::SqlQuote($groupname));
- my $bit = &::FetchOneColumn();
- if ($bit) {
- return 1;
- }
- return 0;
-}
-
sub CanChangeField {
my $self = shift();
my ($f, $oldvalue, $newvalue) = (@_);
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 7e703d14f..3dadd3cd5 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -37,8 +37,8 @@ use Bugzilla::Util;
for my $key (qw (bug_id alias product version rep_platform op_sys bug_status
resolution priority bug_severity component assigned_to
reporter bug_file_loc short_desc target_milestone
- qa_contact status_whiteboard creation_ts groupset
- delta_ts votes whoid usergroupset comment query error) ){
+ qa_contact status_whiteboard creation_ts
+ delta_ts votes whoid comment query error) ){
$ok_field{$key}++;
}
@@ -105,10 +105,6 @@ sub initBug {
$self->{'whoid'} = $user_id;
- &::SendSQL("SELECT groupset FROM profiles WHERE userid=$self->{'whoid'}");
- my $usergroupset = &::FetchOneColumn();
- if (!$usergroupset) { $usergroupset = '0' }
- $self->{'usergroupset'} = $usergroupset;
my $query = "
select
@@ -116,7 +112,7 @@ sub initBug {
resolution, priority, bug_severity, components.name, assigned_to, reporter,
bug_file_loc, short_desc, target_milestone, qa_contact,
status_whiteboard, date_format(creation_ts,'%Y-%m-%d %H:%i'),
- groupset, delta_ts, sum(votes.count)
+ delta_ts, sum(votes.count)
from bugs left join votes using(bug_id),
products, components
where bugs.bug_id = $bug_id
@@ -124,10 +120,10 @@ sub initBug {
AND components.id = bugs.component_id
group by bugs.bug_id";
- &::SendSQL(&::SelectVisible($query, $user_id, $usergroupset));
- my @row;
+ &::SendSQL($query);
+ my @row = ();
- if (@row = &::FetchSQLData()) {
+ if ((@row = &::FetchSQLData()) && &::CanSeeBug($bug_id, $self->{'whoid'})) {
my $count = 0;
my %fields;
foreach my $field ("bug_id", "alias", "product", "version", "rep_platform",
@@ -135,24 +131,21 @@ sub initBug {
"bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone",
"qa_contact", "status_whiteboard", "creation_ts",
- "groupset", "delta_ts", "votes") {
+ "delta_ts", "votes") {
$fields{$field} = shift @row;
if ($fields{$field}) {
$self->{$field} = $fields{$field};
}
$count++;
}
- } else {
- &::SendSQL("select groupset from bugs where bug_id = $bug_id");
- if (@row = &::FetchSQLData()) {
+ } elsif (@row) {
$self->{'bug_id'} = $bug_id;
$self->{'error'} = "NotPermitted";
return $self;
- } else {
+ } else {
$self->{'bug_id'} = $bug_id;
$self->{'error'} = "NotFound";
return $self;
- }
}
$self->{'assigned_to'} = &::DBID_to_name($self->{'assigned_to'});
@@ -356,22 +349,6 @@ sub XML_Footer {
return ("</bugzilla>\n");
}
-sub UserInGroup {
- my $self = shift();
- my ($groupname) = (@_);
- if ($self->{'usergroupset'} eq "0") {
- return 0;
- }
- &::ConnectToDatabase();
- &::SendSQL("select (bit & $self->{'usergroupset'}) != 0 from groups where name = "
- . &::SqlQuote($groupname));
- my $bit = &::FetchOneColumn();
- if ($bit) {
- return 1;
- }
- return 0;
-}
-
sub CanChangeField {
my $self = shift();
my ($f, $oldvalue, $newvalue) = (@_);
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 482daca5c..d6e7a9b7f 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -29,7 +29,7 @@ use strict;
# The caller MUST require CGI.pl and globals.pl before using this
-use vars qw($userid $usergroupset);
+use vars qw($userid);
package Bugzilla::Search;
@@ -117,7 +117,7 @@ sub init {
my @legal_fields = ("product", "version", "rep_platform", "op_sys",
"bug_status", "resolution", "priority", "bug_severity",
"assigned_to", "reporter", "component",
- "target_milestone", "groupset");
+ "target_milestone", "bug_group");
foreach my $field (keys %F) {
if (lsearch(\@legal_fields, $field) != -1) {
@@ -322,6 +322,12 @@ sub init {
push(@wherepart, "$table.bug_id = bugs.bug_id");
$f = "$table.thetext";
},
+ "^bug_group,(?!changed)" => sub {
+ push(@supptables, "LEFT JOIN bug_group_map bug_group_map_$chartid ON bugs.bug_id = bug_group_map_$chartid.bug_id");
+
+ push(@supptables, "LEFT JOIN groups groups_$chartid ON groups_$chartid.id = bug_group_map_$chartid.group_id");
+ $f = "groups_$chartid.name";
+ },
"^attachments\..*," => sub {
my $table = "attachments_$chartid";
push(@supptables, "attachments $table");
@@ -747,7 +753,7 @@ sub init {
# chart -1 is generated by other code above, not from the user-
# submitted form, so we'll blindly accept any values in chart -1
if ((!$chartfields{$f}) && ($chart != -1)) {
- my $errstr = "Can't use " . html_quote($f) . " as a field name. " .
+ my $errstr = "Can't use $f as a field name. " .
"If you think you're getting this in error, please copy the " .
"entire URL out of the address bar at the top of your browser " .
"window and email it to <109679\@bugzilla.org>";
@@ -807,11 +813,27 @@ sub init {
$suppseen{$str} = 1;
}
}
- my $query = ("SELECT DISTINCT " . join(', ', @fields) .
+ my $query = ("SELECT DISTINCT " .
+ join(', ', @fields) .
+ ", COUNT(DISTINCT ugmap.group_id) AS cntuseringroups, " .
+ " COUNT(DISTINCT bgmap.group_id) AS cntbugingroups, " .
+ " ((COUNT(DISTINCT ccmap.who) AND cclist_accessible) " .
+ " OR ((bugs.reporter = $::userid) AND bugs.reporter_accessible) " .
+ " OR bugs.assigned_to = $::userid ) AS canseeanyway " .
" FROM $suppstring" .
- " WHERE " . join(' AND ', (@wherepart, @andlist)));
-
- $query = &::SelectVisible($query, $::userid, $::usergroupset);
+ " LEFT JOIN bug_group_map AS bgmap " .
+ " ON bgmap.bug_id = bugs.bug_id " .
+ " LEFT JOIN user_group_map AS ugmap " .
+ " ON bgmap.group_id = ugmap.group_id " .
+ " AND ugmap.user_id = $::userid " .
+ " AND ugmap.isbless = 0" .
+ " LEFT JOIN cc AS ccmap " .
+ " ON ccmap.who = $::userid AND ccmap.bug_id = bugs.bug_id " .
+ " WHERE " . join(' AND ', (@wherepart, @andlist)) .
+ " GROUP BY bugs.bug_id " .
+ " HAVING cntuseringroups = cntbugingroups" .
+ " OR canseeanyway"
+ );
if ($debug) {
print "<p><code>" . value_quote($query) . "</code></p>\n";
diff --git a/CGI.pl b/CGI.pl
index 0c85128d1..70c15c932 100644
--- a/CGI.pl
+++ b/CGI.pl
@@ -289,11 +289,6 @@ sub ValidateBugID {
# converted-from-alias ID.
$_[0] = $id;
- # Get the values of the usergroupset and userid global variables
- # and write them to local variables for use within this function,
- # setting those local variables to the default value of zero if
- # the global variables are undefined.
-
# First check that the bug exists
SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id");
@@ -303,7 +298,7 @@ sub ValidateBugID {
return if $skip_authorization;
- return if CanSeeBug($id, $::userid, $::usergroupset);
+ return if CanSeeBug($id, $::userid);
# The user did not pass any of the authorization tests, which means they
# are not authorized to see the bug. Display an error and stop execution.
@@ -438,30 +433,25 @@ sub PasswordForLogin {
}
sub quietly_check_login() {
- $::usergroupset = '0';
- my $loginok = 0;
$::disabledreason = '';
- $::userid = 0;
+ my $userid = 0;
if (defined $::COOKIE{"Bugzilla_login"} &&
defined $::COOKIE{"Bugzilla_logincookie"}) {
- SendSQL("SELECT profiles.userid, profiles.groupset, " .
- "profiles.login_name, " .
- "profiles.login_name = " .
- SqlQuote($::COOKIE{"Bugzilla_login"}) .
- " AND logincookies.ipaddr = " .
- SqlQuote($ENV{"REMOTE_ADDR"}) .
- ", profiles.disabledtext " .
+ SendSQL("SELECT profiles.userid," .
+ " profiles.login_name, " .
+ " profiles.disabledtext " .
" FROM profiles, logincookies WHERE logincookies.cookie = " .
SqlQuote($::COOKIE{"Bugzilla_logincookie"}) .
- " AND profiles.userid = logincookies.userid");
+ " AND profiles.userid = logincookies.userid AND" .
+ " profiles.login_name = " .
+ SqlQuote($::COOKIE{"Bugzilla_login"}) .
+ " AND logincookies.ipaddr = " .
+ SqlQuote($ENV{"REMOTE_ADDR"}));
my @row;
- if (@row = FetchSQLData()) {
- my ($userid, $groupset, $loginname, $ok, $disabledtext) = (@row);
- if ($ok) {
+ if (MoreSQLData()) {
+ ($userid, my $loginname, my $disabledtext) = FetchSQLData();
+ if ($userid > 0) {
if ($disabledtext eq '') {
- $loginok = 1;
- $::userid = $userid;
- $::usergroupset = $groupset;
$::COOKIE{"Bugzilla_login"} = $loginname; # Makes sure case
# is in
# canonical form.
@@ -469,6 +459,7 @@ sub quietly_check_login() {
detaint_natural($::COOKIE{"Bugzilla_logincookie"});
} else {
$::disabledreason = $disabledtext;
+ $userid = 0;
}
}
}
@@ -478,13 +469,14 @@ sub quietly_check_login() {
my $whoid = DBname_to_id($::FORM{'who'});
delete $::FORM{'who'} unless $whoid;
}
- if (!$loginok) {
+ if (!$userid) {
delete $::COOKIE{"Bugzilla_login"};
}
+ $::userid = $userid;
+ ConfirmGroup($userid);
$vars->{'user'} = GetUserInfo($::userid);
-
- return $loginok;
+ return $userid;
}
# Populate a hash with information about this user.
@@ -500,10 +492,9 @@ sub GetUserInfo {
$user{'login'} = $::COOKIE{"Bugzilla_login"};
$user{'userid'} = $userid;
- SendSQL("SELECT mybugslink, realname, groupset, blessgroupset " .
+ SendSQL("SELECT mybugslink, realname " .
"FROM profiles WHERE userid = $userid");
- ($user{'showmybugslink'}, $user{'realname'}, $user{'groupset'},
- $user{'blessgroupset'}) = FetchSQLData();
+ ($user{'showmybugslink'}, $user{'realname'}) = FetchSQLData();
SendSQL("SELECT name, query, linkinfooter FROM namedqueries " .
"WHERE userid = $userid");
@@ -516,10 +507,15 @@ sub GetUserInfo {
$user{'queries'} = \@queries;
- SendSQL("select name, (bit & $user{'groupset'}) != 0 from groups");
+ $user{'canblessany'} = UserCanBlessAnything();
+
+ SendSQL("SELECT name FROM groups, user_group_map " .
+ "WHERE groups.id = user_group_map.group_id " .
+ "AND user_id = $userid " .
+ "AND NOT isbless");
while (MoreSQLData()) {
- my ($name, $bit) = FetchSQLData();
- $groups{$name} = $bit;
+ my ($name) = FetchSQLData();
+ $groups{$name} = 1;
}
$user{'groups'} = \%groups;
@@ -561,6 +557,7 @@ sub confirm_login {
# to a later section. -Joe Robins, 8/3/00
my $enteredlogin = "";
my $realcryptpwd = "";
+ my $userid;
# If the form contains Bugzilla login and password fields, use Bugzilla's
# built-in authentication to authenticate the user (otherwise use LDAP below).
@@ -570,7 +567,6 @@ sub confirm_login {
CheckEmailSyntax($enteredlogin);
# Retrieve the user's ID and crypted password from the database.
- my $userid;
SendSQL("SELECT userid, cryptpassword FROM profiles
WHERE login_name = " . SqlQuote($enteredlogin));
($userid, $realcryptpwd) = FetchSQLData();
@@ -765,9 +761,9 @@ sub confirm_login {
print "Set-Cookie: Bugzilla_logincookie=$logincookie ; path=$cookiepath; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
}
- my $loginok = quietly_check_login();
+ $userid = quietly_check_login();
- if ($loginok != 1) {
+ if (!$userid) {
if ($::disabledreason) {
my $cookiepath = Param("cookiepath");
print "Set-Cookie: Bugzilla_login= ; path=$cookiepath; expires=Sun, 30-Jun-80 00:00:00 GMT
@@ -810,7 +806,8 @@ Content-type: text/html
SendSQL("UPDATE logincookies SET lastused = null " .
"WHERE cookie = $::COOKIE{'Bugzilla_logincookie'}");
}
- return $::userid;
+ ConfirmGroup($userid);
+ return $userid;
}
sub PutHeader {
diff --git a/attachment.cgi b/attachment.cgi
index 3b29a321e..cfdbd4ea6 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -50,7 +50,6 @@ require "CGI.pl";
ConnectToDatabase();
# Check whether or not the user is logged in and, if so, set the $::userid
-# and $::usergroupset variables.
quietly_check_login();
################################################################################
diff --git a/bug_form.pl b/bug_form.pl
index 8d7a2b02e..dfffca9b8 100644
--- a/bug_form.pl
+++ b/bug_form.pl
@@ -82,7 +82,7 @@ sub show_bug {
bug_file_loc, short_desc, target_milestone,
qa_contact, status_whiteboard,
date_format(creation_ts,'%Y-%m-%d %H:%i'),
- groupset, delta_ts, sum(votes.count), delta_ts calc_disp_date
+ delta_ts, sum(votes.count), delta_ts calc_disp_date
FROM bugs LEFT JOIN votes USING(bug_id), products, components
WHERE bugs.bug_id = $id
AND bugs.product_id = products.id
@@ -106,7 +106,7 @@ sub show_bug {
"bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone",
"qa_contact", "status_whiteboard", "creation_ts",
- "groupset", "delta_ts", "votes","calc_disp_date")
+ "delta_ts", "votes", "calc_disp_date")
{
$value = shift(@row);
if ($field eq "calc_disp_date") {
@@ -221,58 +221,68 @@ sub show_bug {
# Groups
my @groups;
- if ($::usergroupset ne '0' || $bug{'groupset'} ne '0') {
- my $bug_groupset = $bug{'groupset'};
- SendSQL("SELECT bit, name, description, (bit & $bug_groupset != 0),
- (bit & $::usergroupset != 0) FROM groups
- WHERE isbuggroup != 0 " .
- # Include active groups as well as inactive groups to which
- # the bug already belongs. This way the bug can be removed
- # from an inactive group but can only be added to active ones.
- "AND ((isactive = 1 AND (bit & $::usergroupset != 0)) OR
- (bit & $bug_groupset != 0))");
+ # For every group, we need to know if there is ANY bug_group_map
+ # record putting the current bug in that group and if there is ANY
+ # user_group_map record putting the user in that group.
+ # The LEFT JOINs are checking for record existence.
+ #
+ SendSQL("SELECT DISTINCT groups.id, name, description," .
+ " bug_group_map.group_id IS NOT NULL," .
+ " user_group_map.group_id IS NOT NULL," .
+ " isactive" .
+ " FROM groups" .
+ " LEFT JOIN bug_group_map" .
+ " ON bug_group_map.group_id = groups.id" .
+ " AND bug_id = $bug{'bug_id'}" .
+ " LEFT JOIN user_group_map" .
+ " ON user_group_map.group_id = groups.id" .
+ " AND user_id = $::userid" .
+ " AND NOT isbless" .
+ " WHERE isbuggroup");
+
+ $user{'inallgroups'} = 1;
- $user{'inallgroups'} = 1;
+ while (MoreSQLData()) {
+ my ($groupid, $name, $description, $ison, $ingroup, $isactive)
+ = FetchSQLData();
+
+ $bug{'inagroup'} = 1 if ($ison);
+
+ # For product groups, we only want to display the checkbox if either
+ # (1) The bit is already set, or
+ # (2) The user is in the group, but either:
+ # (a) The group is a product group for the current product, or
+ # (b) The group name isn't a product name
+ # This means that all product groups will be skipped, but
+ # non-product bug groups will still be displayed.
+ if($ison ||
+ ($isactive && ($ingroup && (!Param("usebuggroups") || ($name eq $bug{'product'}) ||
+ (!defined $::proddesc{$name})))))
+ {
+ $user{'inallgroups'} &= $ingroup;
- while (MoreSQLData()) {
- my ($bit, $name, $description, $ison, $ingroup) = FetchSQLData();
- # For product groups, we only want to display the checkbox if either
- # (1) The bit is already set, or
- # (2) The user is in the group, but either:
- # (a) The group is a product group for the current product, or
- # (b) The group name isn't a product name
- # This means that all product groups will be skipped, but
- # non-product bug groups will still be displayed.
- if($ison ||
- ($ingroup && (($name eq $bug{'product'}) ||
- (!defined $::proddesc{$name}))))
- {
- $user{'inallgroups'} &= $ingroup;
-
- push (@groups, { "bit" => $bit,
- "ison" => $ison,
- "ingroup" => $ingroup,
- "description" => $description });
- }
+ push (@groups, { "bit" => $groupid,
+ "ison" => $ison,
+ "ingroup" => $ingroup,
+ "description" => $description });
}
+ }
- # If the bug is restricted to a group, display checkboxes that allow
- # the user to set whether or not the reporter
- # and cc list can see the bug even if they are not members of all
- # groups to which the bug is restricted.
- if ($bug{'groupset'} != 0) {
- $bug{'inagroup'} = 1;
-
- # Determine whether or not the bug is always accessible by the
- # reporter, QA contact, and/or users on the cc: list.
- SendSQL("SELECT reporter_accessible, cclist_accessible
- FROM bugs
- WHERE bug_id = $id
- ");
- ($bug{'reporter_accessible'},
- $bug{'cclist_accessible'}) = FetchSQLData();
- }
+ # If the bug is restricted to a group, get flags that allow
+ # the user to set whether or not the reporter
+ # and cc list can see the bug even if they are not members of all
+ # groups to which the bug is restricted.
+ if ($bug{'inagroup'}) {
+
+ # Determine whether or not the bug is always accessible by the
+ # reporter, QA contact, and/or users on the cc: list.
+ SendSQL("SELECT reporter_accessible, cclist_accessible
+ FROM bugs
+ WHERE bug_id = $id
+ ");
+ ($bug{'reporter_accessible'},
+ $bug{'cclist_accessible'}) = FetchSQLData();
}
$vars->{'groups'} = \@groups;
diff --git a/buglist.cgi b/buglist.cgi
index 6597fbe3e..728ead4d1 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -207,23 +207,24 @@ sub GetQuip {
return $quip;
}
-sub GetGroupsByGroupSet {
- my ($groupset) = @_;
+sub GetGroupsByUserId {
+ my ($userid) = @_;
- return if !$groupset;
+ return if !$userid;
SendSQL("
- SELECT bit, name, description, isactive
- FROM groups
- WHERE (bit & $groupset) != 0
- AND isbuggroup != 0
+ SELECT groups.id, name, description, isactive
+ FROM groups, user_group_map
+ WHERE user_id = $userid AND NOT isbless
+ AND user_group_map.group_id = groups.id
+ AND isbuggroup
ORDER BY description ");
my @groups;
while (MoreSQLData()) {
my $group = {};
- ($group->{'bit'}, $group->{'name'},
+ ($group->{'id'}, $group->{'name'},
$group->{'description'}, $group->{'isactive'}) = FetchSQLData();
push(@groups, $group);
}
@@ -379,7 +380,6 @@ sub DefineColumn {
# Column: ID Name Title
DefineColumn("id" , "bugs.bug_id" , "ID" );
-DefineColumn("groupset" , "bugs.groupset" , "Groupset" );
DefineColumn("opendate" , "bugs.creation_ts" , "Opened" );
DefineColumn("changeddate" , "bugs.delta_ts" , "Changed" );
DefineColumn("severity" , "bugs.bug_severity" , "Severity" );
@@ -437,9 +437,6 @@ else {
# and are hard-coded into the display templates.
@displaycolumns = grep($_ ne 'id', @displaycolumns);
-# IMPORTANT! Never allow the groupset column to be displayed!
-@displaycolumns = grep($_ ne 'groupset', @displaycolumns);
-
# Add the votes column to the list of columns to be displayed
# in the bug list if the user is searching for bugs with a certain
# number of votes and the votes column is not already on the list.
@@ -458,10 +455,8 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) {
# Generate the list of columns that will be selected in the SQL query.
-# The bug ID and groupset are always selected because bug IDs are always
-# displayed and we need the groupset to determine whether or not the bug
-# is visible to the user.
-my @selectcolumns = ("id", "groupset");
+# The bug ID is always selected because bug IDs are always displayed
+my @selectcolumns = ("id");
# Display columns are selected because otherwise we could not display them.
push (@selectcolumns, @displaycolumns);
@@ -721,7 +716,7 @@ if ($dotweak) {
$vars->{'bugstatuses'} = [ keys %$bugstatuses ];
# The groups to which the user belongs.
- $vars->{'groups'} = GetGroupsByGroupSet($::usergroupset) if $::usergroupset ne '0';
+ $vars->{'groups'} = GetGroupsByUserId($::userid);
# If all bugs being changed are in the same product, the user can change
# their version and component, so generate a list of products, a list of
diff --git a/checksetup.pl b/checksetup.pl
index c00301742..b752f9b65 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -1360,7 +1360,6 @@ $table{attachstatusdefs} =
#
$table{bugs} =
'bug_id mediumint not null auto_increment primary key,
- groupset bigint not null,
assigned_to mediumint not null, # This is a comment.
bug_file_loc text,
bug_severity enum($my_severities) not null,
@@ -1454,16 +1453,7 @@ $table{dependencies} =
index(dependson)';
-# Group bits must be a power of two. Groups are identified by a bit; sets of
-# groups are indicated by or-ing these values together.
-#
-# isbuggroup is nonzero if this is a group that controls access to a set
-# of bugs. In otherword, the groupset field in the bugs table should only
-# have this group's bit set if isbuggroup is nonzero.
-#
-# User regexp is which email addresses are initially put into this group.
-# This is only used when an email account is created; otherwise, profiles
-# may be individually tweaked to add them in and out of groups.
+# User regexp is which email addresses are put into this group.
#
# 2001-04-10 myk@mozilla.org:
# isactive determines whether or not a group is active. An inactive group
@@ -1473,14 +1463,14 @@ $table{dependencies} =
# http://bugzilla.mozilla.org/show_bug.cgi?id=75482
$table{groups} =
- 'bit bigint not null,
+ 'id mediumint not null auto_increment primary key,
name varchar(255) not null,
description text not null,
isbuggroup tinyint not null,
+ last_changed datetime not null,
userregexp tinytext not null,
isactive tinyint not null default 1,
- unique(bit),
unique(name)';
$table{logincookies} =
@@ -1511,13 +1501,10 @@ $table{profiles} =
login_name varchar(255) not null,
cryptpassword varchar(34),
realname varchar(255),
- groupset bigint not null,
disabledtext mediumtext not null,
mybugslink tinyint not null default 1,
- blessgroupset bigint not null default 0,
emailflags mediumtext,
-
-
+ refreshed_when datetime not null,
unique(login_name)';
@@ -1610,6 +1597,38 @@ $table{tokens} =
index(userid)';
+# group membership tables for tracking group and privilege
+#
+# This table determines the groups that a user belongs to
+# directly or due to regexp and which groups can be blessed
+# by a user.
+#
+# isderived:
+# if 0 - record was explicitly granted
+# if 1 - record was created by evaluating a regexp or group hierarchy
+$table{user_group_map} =
+ 'user_id mediumint not null,
+ group_id mediumint not null,
+ isbless tinyint not null default 0,
+ isderived tinyint not null default 0,
+
+ unique(user_id, group_id, isderived, isbless)';
+
+$table{group_group_map} =
+ 'member_id mediumint not null,
+ grantor_id mediumint not null,
+ isbless tinyint not null default 0,
+
+ unique(member_id, grantor_id, isbless)';
+
+# This table determines which groups a user must be a member of
+# in order to see a bug.
+$table{bug_group_map} =
+ 'bug_id mediumint not null,
+ group_id mediumint not null,
+ unique(bug_id, group_id),
+ index(group_id)';
+
# 2002-07-19, davef@tetsubo.com, bug 67950:
# Store quips in the db.
$table{quips} =
@@ -1617,7 +1636,6 @@ $table{quips} =
userid mediumint not null default 0,
quip text not null';
-
###########################################################################
# Create tables
###########################################################################
@@ -1692,7 +1710,7 @@ sub GroupDoesExist ($)
#
# This subroutine checks if a group exist. If not, it will be automatically
-# created with the next available bit set
+# created with the next available groupid
#
sub AddGroup {
@@ -1701,57 +1719,19 @@ sub AddGroup {
return if GroupDoesExist($name);
- # get highest bit number
- my $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC");
- $sth->execute;
- my @row = $sth->fetchrow_array;
-
- # normalize bits
- my $bit;
- if (defined $row[0]) {
- $bit = $row[0] << 1;
- } else {
- $bit = 1;
- }
-
-
print "Adding group $name ...\n";
- $sth = $dbh->prepare('INSERT INTO groups
- (bit, name, description, userregexp, isbuggroup)
- VALUES (?, ?, ?, ?, ?)');
- $sth->execute($bit, $name, $desc, $userregexp, 0);
- return $bit;
-}
-
-
-#
-# BugZilla uses --GROUPS-- to assign various rights to its users.
-#
-
-AddGroup 'tweakparams', 'Can tweak operating parameters';
-AddGroup 'editusers', 'Can edit or disable users';
-AddGroup 'creategroups', 'Can create and destroy groups.';
-AddGroup 'editcomponents', 'Can create, destroy, and edit components.';
-AddGroup 'editkeywords', 'Can create, destroy, and edit keywords.';
-
-# Add the groupset field here because this code is run before the
-# code that updates the database structure.
-&AddField('profiles', 'groupset', 'bigint not null');
-
-if (!GroupDoesExist("editbugs")) {
- my $id = AddGroup('editbugs', 'Can edit all aspects of any bug.', ".*");
- $dbh->do("UPDATE profiles SET groupset = groupset | $id");
-}
+ my $sth = $dbh->prepare('INSERT INTO groups
+ (name, description, userregexp, isbuggroup)
+ VALUES (?, ?, ?, ?)');
+ $sth->execute($name, $desc, $userregexp, 0);
-if (!GroupDoesExist("canconfirm")) {
- my $id = AddGroup('canconfirm', 'Can confirm a bug.', ".*");
- $dbh->do("UPDATE profiles SET groupset = groupset | $id");
+ $sth = $dbh->prepare("select last_insert_id()");
+ $sth->execute();
+ my ($last) = $sth->fetchrow_array();
+ return $last;
}
-
-
-
###########################################################################
# Populate the list of fields.
###########################################################################
@@ -1818,9 +1798,9 @@ AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed",
AddFDef("longdesc", "Comment", 0);
AddFDef("alias", "Alias", 0);
AddFDef("everconfirmed", "Ever Confirmed", 0);
-AddFDef("groupset", "Groupset", 0);
AddFDef("reporter_accessible", "Reporter Accessible", 0);
AddFDef("cclist_accessible", "CC Accessible", 0);
+AddFDef("bug_group", "Group", 0);
# Oops. Bug 163299
$dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
@@ -1938,180 +1918,8 @@ CheckEnumField('bugs', 'rep_platform', @my_platforms);
###########################################################################
-# Create Administrator --ADMIN--
-###########################################################################
-
-# Prompt the user for the email address and name of an administrator. Create
-# that login, if it doesn't exist already, and make it a member of all groups.
-
-sub bailout { # this is just in case we get interrupted while getting passwd
- system("stty","echo"); # re-enable input echoing
- exit 1;
-}
-
-$sth = $dbh->prepare(<<_End_Of_SQL_);
- SELECT login_name
- FROM profiles
- WHERE groupset=9223372036854775807
-_End_Of_SQL_
-$sth->execute;
-# when we have no admin users, prompt for admin email address and password ...
-if ($sth->rows == 0) {
- my $login = "";
- my $realname = "";
- my $pass1 = "";
- my $pass2 = "*";
- my $admin_ok = 0;
- my $admin_create = 1;
- my $mailcheckexp = Param('emailregexp');
- my $mailcheck = Param('emailregexpdesc');
-
- print "\nLooks like we don't have an administrator set up yet. Either this is your\n";
- print "first time using Bugzilla, or your administrator's privs might have accidently\n";
- print "gotten deleted at some point.\n";
- while(! $admin_ok ) {
- while( $login eq "" ) {
- print "Enter the e-mail address of the administrator: ";
- $login = $answer{'ADMIN_EMAIL'}
- || ($silent && die("cant preload ADMIN_EMAIL"))
- || <STDIN>;
- chomp $login;
- if(! $login ) {
- print "\nYou DO want an administrator, don't you?\n";
- }
- unless ($login =~ /$mailcheckexp/) {
- print "\nThe login address is invalid:\n";
- print "$mailcheck\n";
- print "You can change this test on the params page once checksetup has successfully\n";
- print "completed.\n\n";
- # Go round, and ask them again
- $login = "";
- }
- }
- $login = $dbh->quote($login);
- $sth = $dbh->prepare(<<_End_Of_SQL_);
- SELECT login_name
- FROM profiles
- WHERE login_name=$login
-_End_Of_SQL_
- $sth->execute;
- if ($sth->rows > 0) {
- print "$login already has an account.\n";
- print "Make this user the administrator? [Y/n] ";
- my $ok = $answer{'ADMIN_OK'}
- || ($silent && die("cant preload ADMIN_OK"))
- || <STDIN>;
- chomp $ok;
- if ($ok !~ /^n/i) {
- $admin_ok = 1;
- $admin_create = 0;
- } else {
- print "OK, well, someone has to be the administrator. Try someone else.\n";
- $login = "";
- }
- } else {
- print "You entered $login. Is this correct? [Y/n] ";
- my $ok = $answer{'ADMIN_OK'}
- || ($silent && die("cant preload ADMIN_OK"))
- || <STDIN>;
- chomp $ok;
- if ($ok !~ /^n/i) {
- $admin_ok = 1;
- } else {
- print "That's okay, typos happen. Give it another shot.\n";
- $login = "";
- }
- }
- }
-
- if ($admin_create) {
-
- while( $realname eq "" ) {
- print "Enter the real name of the administrator: ";
- $realname = $answer{'ADMIN_REALNAME'}
- || ($silent && die("cant preload ADMIN_REALNAME"))
- || <STDIN>;
- chomp $realname;
- if(! $realname ) {
- print "\nReally. We need a full name.\n";
- }
- }
-
- # trap a few interrupts so we can fix the echo if we get aborted.
- $SIG{HUP} = \&bailout;
- $SIG{INT} = \&bailout;
- $SIG{QUIT} = \&bailout;
- $SIG{TERM} = \&bailout;
-
- system("stty","-echo"); # disable input echoing
-
- while( $pass1 ne $pass2 ) {
- while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
- print "Enter a password for the administrator account: ";
- $pass1 = $answer{'ADMIN_PASSWORD'}
- || ($silent && die("cant preload ADMIN_PASSWORD"))
- || <STDIN>;
- chomp $pass1;
- if(! $pass1 ) {
- print "\n\nIt's just plain stupid to not have a password. Try again!\n";
- } elsif ( $pass1 !~ /^.{3,16}$/ ) {
- print "The password must be 3-16 characters in length.";
- }
- }
- print "\nPlease retype the password to verify: ";
- $pass2 = $answer{'ADMIN_PASSWORD'}
- || ($silent && die("cant preload ADMIN_PASSWORD"))
- || <STDIN>;
- chomp $pass2;
- if ($pass1 ne $pass2) {
- print "\n\nPasswords don't match. Try again!\n";
- $pass1 = "";
- $pass2 = "*";
- }
- }
-
- # Crypt the administrator's password
- my $cryptedpassword = Crypt($pass1);
-
- system("stty","echo"); # re-enable input echoing
- $SIG{HUP} = 'DEFAULT'; # and remove our interrupt hooks
- $SIG{INT} = 'DEFAULT';
- $SIG{QUIT} = 'DEFAULT';
- $SIG{TERM} = 'DEFAULT';
-
- $realname = $dbh->quote($realname);
- $cryptedpassword = $dbh->quote($cryptedpassword);
-
- $dbh->do(<<_End_Of_SQL_);
- INSERT INTO profiles
- (login_name, realname, cryptpassword, groupset)
- VALUES ($login, $realname, $cryptedpassword, 0x7fffffffffffffff)
-_End_Of_SQL_
- } else {
- $dbh->do(<<_End_Of_SQL_);
- UPDATE profiles
- SET groupset=0x7fffffffffffffff
- WHERE login_name=$login
-_End_Of_SQL_
- }
- print "\n$login is now set up as the administrator account.\n";
-}
-
-
-
-
-###########################################################################
# Create initial test product if there are no products present.
###########################################################################
-
-$sth = $dbh->prepare(<<_End_Of_SQL_);
- SELECT userid
- FROM profiles
- WHERE groupset=9223372036854775807
-_End_Of_SQL_
-$sth->execute;
-my ($adminuid) = $sth->fetchrow_array;
-if (!$adminuid) { die "No administator!" } # should never get here
$sth = $dbh->prepare("SELECT description FROM products");
$sth->execute;
unless ($sth->rows) {
@@ -2126,19 +1934,20 @@ unless ($sth->rows) {
$sth->execute;
my ($product_id) = $sth->fetchrow_array;
$dbh->do(qq{INSERT INTO versions (value, product_id) VALUES ("other", $product_id)});
+ # note: since admin user is not yet known, components gets a 0 for
+ # initialowner and this is fixed during final checks.
$dbh->do("INSERT INTO components (name, product_id, description, initialowner, initialqacontact)
VALUES (" .
"'TestComponent', $product_id, " .
"'This is a test component in the test product database. " .
"This ought to be blown away and replaced with real stuff in " .
- "a finished installation of bugzilla.', $adminuid, 0)");
+ "a finished installation of Bugzilla.', 0, 0)");
$dbh->do(qq{INSERT INTO milestones (product_id, value) VALUES ($product_id,"---")});
}
-
###########################################################################
# Update the tables to the current definition
###########################################################################
@@ -2238,8 +2047,10 @@ sub TableExists ($)
# really old fields that were added before checksetup.pl existed
# but aren't in very old bugzilla's (like 2.1)
# Steve Stock (sstock@iconnect-inc.com)
+
+# bug 157756 - groupsets replaced by maps
+# AddField('bugs', 'groupset', 'bigint not null');
AddField('bugs', 'target_milestone', 'varchar(20) not null default "---"');
-AddField('bugs', 'groupset', 'bigint not null');
AddField('bugs', 'qa_contact', 'mediumint not null');
AddField('bugs', 'status_whiteboard', 'mediumtext not null');
AddField('products', 'disallownew', 'tinyint not null');
@@ -2673,7 +2484,8 @@ if (!GetFieldDef('bugs', 'everconfirmed')) {
}
AddField('products', 'maxvotesperbug', 'smallint not null default 10000');
AddField('products', 'votestoconfirm', 'smallint not null');
-AddField('profiles', 'blessgroupset', 'bigint not null');
+# bug 157756 - groupsets replaced by maps
+# AddField('profiles', 'blessgroupset', 'bigint not null');
# 2000-03-21 Adding a table for target milestones to
# database - matthew@zeroknowledge.com
@@ -3216,6 +3028,222 @@ if (($fielddef = GetFieldDef("attachments", "creation_ts")) &&
ChangeFieldType("attachments", "creation_ts", "datetime NOT NULL");
}
+# 2002-08-XX - bugreport@peshkin.net - bug 157756
+#
+# If the whole groups system is new, but the installation isn't,
+# convert all the old groupset groups, etc...
+#
+# This requires:
+# 1) define groups ids in group table
+# 2) populate user_group_map with grants from old groupsets and blessgroupsets
+# 3) populate bug_group_map with data converted from old bug groupsets
+# 4) convert activity logs to use group names instead of numbers
+# 5) identify the admin from the old all-ones groupset
+#
+# ListBits(arg) returns a list of UNKNOWN<n> if the group
+# has been deleted for all bits set in arg. When the activity
+# records are converted from groupset numbers to lists of
+# group names, ListBits is used to fill in a list of references
+# to groupset bits for groups that no longer exist.
+#
+sub ListBits {
+ my ($num) = @_;
+ my @res = ();
+ my $curr = 1;
+ while (1) {
+ # Convert a big integer to a list of bits
+ my $sth = $dbh->prepare("SELECT ($num & ~$curr) > 0,
+ ($num & $curr),
+ ($num & ~$curr),
+ $curr << 1");
+ $sth->execute;
+ my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array;
+ push @res,"UNKNOWN<$curr>" if ($thisbit);
+ $curr = $nval;
+ $num = $remain;
+ last if (!$more);
+ }
+ return @res;
+}
+
+my @admins = ();
+# The groups system needs to be converted if groupset exists
+if (GetFieldDef("profiles", "groupset")) {
+ AddField('groups', 'last_changed', 'datetime not null');
+ # Some mysql versions will promote any unique key to primary key
+ # so all unique keys are removed first and then added back in
+ $dbh->do("ALTER TABLE groups DROP INDEX bit") if GetIndexDef("groups","bit");
+ $dbh->do("ALTER TABLE groups DROP INDEX name") if GetIndexDef("groups","name");
+ $dbh->do("ALTER TABLE groups DROP PRIMARY KEY");
+ AddField('groups', 'id', 'mediumint not null auto_increment primary key');
+ $dbh->do("ALTER TABLE groups ADD UNIQUE (name)");
+ AddField('profiles', 'refreshed_when', 'datetime not null');
+
+ # Convert all existing groupset records to map entries before removing
+ # groupset fields or removing "bit" from groups.
+ $sth = $dbh->prepare("SELECT bit, id FROM groups
+ WHERE bit > 0");
+ $sth->execute();
+ while (my ($bit, $gid) = $sth->fetchrow_array) {
+ # Create user_group_map membership grants for old groupsets.
+ # Get each user with the old groupset bit set
+ my $sth2 = $dbh->prepare("SELECT userid FROM profiles
+ WHERE (groupset & $bit) != 0");
+ $sth2->execute();
+ while (my ($uid) = $sth2->fetchrow_array) {
+ # Check to see if the user is already a member of the group
+ # and, if not, insert a new record.
+ my $query = "SELECT user_id FROM user_group_map
+ WHERE group_id = $gid AND user_id = $uid
+ AND isbless = 0";
+ my $sth3 = $dbh->prepare($query);
+ $sth3->execute();
+ if ( !$sth3->fetchrow_array() ) {
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES($uid, $gid, 0, 0)");
+ }
+ }
+ # Create user can bless group grants for old groupsets.
+ # Get each user with the old blessgroupset bit set
+ $sth2 = $dbh->prepare("SELECT userid FROM profiles
+ WHERE (blessgroupset & $bit) != 0");
+ $sth2->execute();
+ while (my ($uid) = $sth2->fetchrow_array) {
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES($uid, $gid, 1, 0)");
+ }
+ # Create bug_group_map records for old groupsets.
+ # Get each bug with the old group bit set.
+ $sth2 = $dbh->prepare("SELECT bug_id FROM bugs
+ WHERE (groupset & $bit) != 0");
+ $sth2->execute();
+ while (my ($bug_id) = $sth2->fetchrow_array) {
+ # Insert the bug, group pair into the bug_group_map.
+ $dbh->do("INSERT INTO bug_group_map
+ (bug_id, group_id)
+ VALUES($bug_id, $gid)");
+ }
+ }
+ # Replace old activity log groupset records with lists of names of groups.
+ # Start by defining the bug_group field and getting its id.
+ AddFDef("bug_group", "Group", 0);
+ $sth = $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = " . $dbh->quote('bug_group'));
+ $sth->execute();
+ my ($bgfid) = $sth->fetchrow_array;
+ # Get the field id for the old groupset field
+ $sth = $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = " . $dbh->quote('groupset'));
+ $sth->execute();
+ my ($gsid) = $sth->fetchrow_array;
+ # Get all bugs_activity records from groupset changes
+ $sth = $dbh->prepare("SELECT bug_id, bug_when, who, added, removed
+ FROM bugs_activity WHERE fieldid = $gsid");
+ $sth->execute();
+ while (my ($bug_id, $bug_when, $who, $added, $removed) = $sth->fetchrow_array) {
+ $added ||= 0;
+ $removed ||= 0;
+ # Get names of groups added.
+ my $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $added) != 0 AND (bit & $removed) = 0");
+ $sth2->execute();
+ my @logadd = ();
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logadd, $n;
+ }
+ # Get names of groups removed.
+ $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $removed) != 0 AND (bit & $added) = 0");
+ $sth2->execute();
+ my @logrem = ();
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logrem, $n;
+ }
+ # Get list of group bits added that correspond to missing groups.
+ $sth2 = $dbh->prepare("SELECT ($added & ~BIT_OR(bit)) FROM groups");
+ $sth2->execute();
+ my ($miss) = $sth2->fetchrow_array;
+ if ($miss) {
+ push @logadd, ListBits($miss);
+ print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id CONTAINS DELETED GROUPS\n";
+ }
+ # Get list of group bits deleted that correspond to missing groups.
+ $sth2 = $dbh->prepare("SELECT ($removed & ~BIT_OR(bit)) FROM groups");
+ $sth2->execute();
+ ($miss) = $sth2->fetchrow_array;
+ if ($miss) {
+ push @logrem, ListBits($miss);
+ print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id CONTAINS DELETED GROUPS\n";
+ }
+ my $logr = "";
+ my $loga = "";
+ $logr = join(", ", @logrem) . '?' if @logrem;
+ $loga = join(", ", @logadd) . '?' if @logadd;
+ # Replace to old activity record with the converted data.
+ $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " .
+ $dbh->quote($loga) . ", removed = " .
+ $dbh->quote($logr) .
+ " WHERE bug_id = $bug_id AND bug_when = " . $dbh->quote($bug_when) .
+ " AND who = $who AND fieldid = $gsid");
+
+ }
+ # Replace groupset changes with group name changes in profiles_activity.
+ # Get profiles_activity records for groupset.
+ $sth = $dbh->prepare("SELECT userid, profiles_when, who, newvalue, oldvalue
+ FROM profiles_activity WHERE fieldid = $gsid");
+ $sth->execute();
+ while (my ($uid, $uwhen, $uwho, $added, $removed) = $sth->fetchrow_array) {
+ $added ||= 0;
+ $removed ||= 0;
+ # Get names of groups added.
+ my $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $added) != 0 AND (bit & $removed) = 0");
+ $sth2->execute();
+ my @logadd = ();
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logadd, $n;
+ }
+ # Get names of groups removed.
+ $sth2 = $dbh->prepare("SELECT name FROM groups WHERE (bit & $removed) != 0 AND (bit & $added) = 0");
+ $sth2->execute();
+ my @logrem = ();
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logrem, $n;
+ }
+ my $ladd = "";
+ my $lrem = "";
+ $ladd = join(", ", @logadd) . '?' if @logadd;
+ $lrem = join(", ", @logrem) . '?' if @logrem;
+ # Replace profiles_activity record for groupset change with group list.
+ $dbh->do("UPDATE profiles_activity SET fieldid = $bgfid, newvalue = " .
+ $dbh->quote($ladd) . ", oldvalue = " .
+ $dbh->quote($lrem) .
+ " WHERE userid = $uid AND profiles_when = " .
+ $dbh->quote($uwhen) .
+ " AND who = $uwho AND fieldid = $gsid");
+
+ }
+
+ # Identify admin group.
+ my $sth = $dbh->prepare("SELECT id FROM groups
+ WHERE name = 'admin'");
+ $sth->execute();
+ my ($adminid) = $sth->fetchrow_array();
+ # find existing admins
+ # Don't lose admins from DBs where Bug 157704 applies
+ $sth = $dbh->prepare("SELECT userid, (groupset & 65536), login_name FROM profiles
+ WHERE (groupset | 65536) = 9223372036854775807");
+ $sth->execute();
+ while ( my ($userid, $iscomplete, $login_name) = $sth->fetchrow_array() ) {
+ # existing administrators are made members of group "admin"
+ print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG 157704\n\n"
+ if (!$iscomplete);
+ push @admins, $userid;
+ }
+ DropField('profiles','groupset');
+ DropField('profiles','blessgroupset');
+ DropField('bugs','groupset');
+ DropField('groups','bit');
+ $dbh->do("DELETE FROM fielddefs WHERE name = " . $dbh->quote('groupset'));
+}
+
# If you had to change the --TABLE-- definition in any way, then add your
# differential change code *** A B O V E *** this comment.
#
@@ -3225,9 +3253,291 @@ if (($fielddef = GetFieldDef("attachments", "creation_ts")) &&
# AddField/DropField/ChangeFieldType/RenameField code above. This would then
# be honored by everyone who updates his Bugzilla installation.
#
+
+#
+# BugZilla uses --GROUPS-- to assign various rights to its users.
+#
+
+AddGroup('tweakparams', 'Can tweak operating parameters');
+AddGroup('editusers', 'Can edit or disable users');
+AddGroup('creategroups', 'Can create and destroy groups.');
+AddGroup('editcomponents', 'Can create, destroy, and edit components.');
+AddGroup('editkeywords', 'Can create, destroy, and edit keywords.');
+AddGroup('admin', 'Administrators');
+
+
+if (!GroupDoesExist("editbugs")) {
+ my $id = AddGroup('editbugs', 'Can edit all aspects of any bug.', ".*");
+ my $sth = $dbh->prepare("SELECT userid FROM profiles");
+ $sth->execute();
+ while (my ($userid) = $sth->fetchrow_array()) {
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($userid, $id, 0, 0)");
+ }
+}
+
+if (!GroupDoesExist("canconfirm")) {
+ my $id = AddGroup('canconfirm', 'Can confirm a bug.', ".*");
+ my $sth = $dbh->prepare("SELECT userid FROM profiles");
+ $sth->execute();
+ while (my ($userid) = $sth->fetchrow_array()) {
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($userid, $id, 0, 0)");
+ }
+
+}
+
+
+###########################################################################
+# Create Administrator --ADMIN--
+###########################################################################
+
+
+sub bailout { # this is just in case we get interrupted while getting passwd
+ system("stty","echo"); # re-enable input echoing
+ exit 1;
+}
+
+if (@admins) {
+ # Identify admin group.
+ my $sth = $dbh->prepare("SELECT id FROM groups
+ WHERE name = 'admin'");
+ $sth->execute();
+ my ($adminid) = $sth->fetchrow_array();
+ foreach my $userid (@admins) {
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($userid, $adminid, 0, 0)");
+ # Existing administrators are made blessers of group "admin"
+ # but only explitly defined blessers can bless group admin.
+ # Other groups can be blessed by any admin (by default) or additional
+ # defined blessers.
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($userid, $adminid, 1, 0)");
+ }
+ $sth = $dbh->prepare("SELECT id FROM groups");
+ $sth->execute();
+ while ( my ($id) = $sth->fetchrow_array() ) {
+ # Admins can bless every group.
+ $dbh->do("INSERT INTO group_group_map
+ (member_id, grantor_id, isbless)
+ VALUES ($adminid, $id, 1)");
+ # Admins are initially members of every group.
+ next if ($id == $adminid);
+ $dbh->do("INSERT INTO group_group_map
+ (member_id, grantor_id, isbless)
+ VALUES ($adminid, $id, 0)");
+ }
+}
+
+
+my @groups = ();
+$sth = $dbh->prepare("select id from groups");
+$sth->execute();
+while ( my @row = $sth->fetchrow_array() ) {
+ push (@groups, $row[0]);
+}
+
+# Prompt the user for the email address and name of an administrator. Create
+# that login, if it doesn't exist already, and make it a member of all groups.
+
+$sth = $dbh->prepare("SELECT user_id FROM groups, user_group_map" .
+ " WHERE name = 'admin' AND id = group_id");
+$sth->execute;
+# when we have no admin users, prompt for admin email address and password ...
+if ($sth->rows == 0) {
+ my $login = "";
+ my $realname = "";
+ my $pass1 = "";
+ my $pass2 = "*";
+ my $admin_ok = 0;
+ my $admin_create = 1;
+ my $mailcheckexp = "";
+ my $mailcheck = "";
+
+ # Here we look to see what the emailregexp is set to so we can
+ # check the email addy they enter. Bug 96675. If they have no
+ # params (likely but not always the case), we use the default.
+ if (-e "data/params") {
+ require "data/params"; # if they have a params file, use that
+ }
+ if (Param('emailregexp')) {
+ $mailcheckexp = Param('emailregexp');
+ $mailcheck = Param('emailregexpdesc');
+ } else {
+ $mailcheckexp = '^[^@]+@[^@]+\\.[^@]+$';
+ $mailcheck = 'A legal address must contain exactly one \'@\',
+ and at least one \'.\' after the @.';
+ }
+
+ print "\nLooks like we don't have an administrator set up yet. Either this is your\n";
+ print "first time using Bugzilla, or your administrator's privileges might have accidently\n";
+ print "been deleted.\n";
+ while(! $admin_ok ) {
+ while( $login eq "" ) {
+ print "Enter the e-mail address of the administrator: ";
+ $login = $answer{'ADMIN_EMAIL'}
+ || ($silent && die("cant preload ADMIN_EMAIL"))
+ || <STDIN>;
+ chomp $login;
+ if(! $login ) {
+ print "\nYou DO want an administrator, don't you?\n";
+ }
+ unless ($login =~ /$mailcheckexp/) {
+ print "\nThe login address is invalid:\n";
+ print "$mailcheck\n";
+ print "You can change this test on the params page once checksetup has successfully\n";
+ print "completed.\n\n";
+ # Go round, and ask them again
+ $login = "";
+ }
+ }
+ $login = $dbh->quote($login);
+ $sth = $dbh->prepare("SELECT login_name FROM profiles" .
+ " WHERE login_name=$login");
+ $sth->execute;
+ if ($sth->rows > 0) {
+ print "$login already has an account.\n";
+ print "Make this user the administrator? [Y/n] ";
+ my $ok = $answer{'ADMIN_OK'}
+ || ($silent && die("cant preload ADMIN_OK"))
+ || <STDIN>;
+ chomp $ok;
+ if ($ok !~ /^n/i) {
+ $admin_ok = 1;
+ $admin_create = 0;
+ } else {
+ print "OK, well, someone has to be the administrator. Try someone else.\n";
+ $login = "";
+ }
+ } else {
+ print "You entered $login. Is this correct? [Y/n] ";
+ my $ok = $answer{'ADMIN_OK'}
+ || ($silent && die("cant preload ADMIN_OK"))
+ || <STDIN>;
+ chomp $ok;
+ if ($ok !~ /^n/i) {
+ $admin_ok = 1;
+ } else {
+ print "That's okay, typos happen. Give it another shot.\n";
+ $login = "";
+ }
+ }
+ }
+
+ if ($admin_create) {
+
+ while( $realname eq "" ) {
+ print "Enter the real name of the administrator: ";
+ $realname = $answer{'ADMIN_REALNAME'}
+ || ($silent && die("cant preload ADMIN_REALNAME"))
+ || <STDIN>;
+ chomp $realname;
+ if(! $realname ) {
+ print "\nReally. We need a full name.\n";
+ }
+ }
+
+ # trap a few interrupts so we can fix the echo if we get aborted.
+ $SIG{HUP} = \&bailout;
+ $SIG{INT} = \&bailout;
+ $SIG{QUIT} = \&bailout;
+ $SIG{TERM} = \&bailout;
+
+ system("stty","-echo"); # disable input echoing
+
+ while( $pass1 ne $pass2 ) {
+ while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
+ print "Enter a password for the administrator account: ";
+ $pass1 = $answer{'ADMIN_PASSWORD'}
+ || ($silent && die("cant preload ADMIN_PASSWORD"))
+ || <STDIN>;
+ chomp $pass1;
+ if(! $pass1 ) {
+ print "\n\nIt's just plain stupid to not have a password. Try again!\n";
+ } elsif ( $pass1 !~ /^.{3,16}$/ ) {
+ print "The password must be 3-16 characters in length.";
+ }
+ }
+ print "\nPlease retype the password to verify: ";
+ $pass2 = $answer{'ADMIN_PASSWORD'}
+ || ($silent && die("cant preload ADMIN_PASSWORD"))
+ || <STDIN>;
+ chomp $pass2;
+ if ($pass1 ne $pass2) {
+ print "\n\nPasswords don't match. Try again!\n";
+ $pass1 = "";
+ $pass2 = "*";
+ }
+ }
+
+ # Crypt the administrator's password
+ my $cryptedpassword = Crypt($pass1);
+
+ system("stty","echo"); # re-enable input echoing
+ $SIG{HUP} = 'DEFAULT'; # and remove our interrupt hooks
+ $SIG{INT} = 'DEFAULT';
+ $SIG{QUIT} = 'DEFAULT';
+ $SIG{TERM} = 'DEFAULT';
+
+ $realname = $dbh->quote($realname);
+ $cryptedpassword = $dbh->quote($cryptedpassword);
+
+ $dbh->do("INSERT INTO profiles (login_name, realname, cryptpassword)" .
+ " VALUES ($login, $realname, $cryptedpassword)");
+ }
+ # Put the admin in each group if not already
+ my $query = "select userid from profiles where login_name = $login";
+ $sth = $dbh->prepare($query);
+ $sth->execute();
+ my ($userid) = $sth->fetchrow_array();
+
+ foreach my $group (@groups) {
+ my $query = "SELECT user_id FROM user_group_map
+ WHERE group_id = $group AND user_id = $userid
+ AND isbless = 0";
+ $sth = $dbh->prepare($query);
+ $sth->execute();
+ if ( !$sth->fetchrow_array() ) {
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($userid, $group, 0, 0)");
+ }
+ }
+ # the admin also gets an explicit bless capability for the admin group
+ my $sth = $dbh->prepare("SELECT id FROM groups
+ WHERE name = 'admin'");
+ $sth->execute();
+ my ($id) = $sth->fetchrow_array();
+ $dbh->do("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($userid, $id, 1, 0)");
+ foreach my $group ( @groups ) {
+ $dbh->do("INSERT INTO group_group_map
+ (member_id, grantor_id, isbless)
+ VALUES ($id, $group, 1)");
+ }
+
+ print "\n$login is now set up as an administrator account.\n";
+}
+
+
+
#
# Final checks...
+$sth = $dbh->prepare("SELECT user_id FROM groups, user_group_map" .
+ " WHERE groups.name = 'admin'" .
+ " AND groups.id = user_group_map.group_id");
+$sth->execute;
+my ($adminuid) = $sth->fetchrow_array;
+if (!$adminuid) { die "No administrator!" } # should never get here
+# when test product was created, admin was unknown
+$dbh->do("UPDATE components SET initialowner = $adminuid WHERE initialowner = 0");
+
unlink "data/versioncache";
print "Reminder: Bugzilla now requires version 8.7 or later of sendmail.\n" unless $silent;
diff --git a/contrib/bug_email.pl b/contrib/bug_email.pl
index fb2bbec4d..bf442502c 100755
--- a/contrib/bug_email.pl
+++ b/contrib/bug_email.pl
@@ -37,7 +37,7 @@
#
# You need to work with bug_email.pl the MIME::Parser installed.
#
-# $Id: bug_email.pl,v 1.13 2002/08/26 06:17:21 bbaetz%student.usyd.edu.au Exp $
+# $Id: bug_email.pl,v 1.14 2002/09/22 17:15:03 bugreport%peshkin.net Exp $
###############################################################
# 02/12/2000 (SML)
diff --git a/docs/sgml/administration.sgml b/docs/sgml/administration.sgml
index a82a659bf..a0ff3e174 100644
--- a/docs/sgml/administration.sgml
+++ b/docs/sgml/administration.sgml
@@ -212,33 +212,11 @@
you for this username and password.</para>
<tip>
- <para>If you wish to add more administrative users, you must use the
- MySQL interface. Run "mysql" from the command line, and use these
- commands:
- <simplelist>
- <member>
- <prompt>mysql&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>
@@ -698,10 +676,22 @@
</listitem>
<listitem>
- <para>Fill out the "New Name", "New Description", and
- "New User RegExp" fields. "New User RegExp" allows you to automatically
+ <para>Fill out the "Group", "Description", and
+ "User RegExp" fields. "New User RegExp" allows you to automatically
place all users who fulfill the Regular Expression into the new group.
When you have finished, click "Add".</para>
+ <warning>
+ <para>The User Regexp is a perl regexp and, if not anchored, will match
+ any part of an address. So, if you do not want to grant access
+ into 'mycompany.com' to 'badperson@mycompany.com.hacker.net', use
+ '@mycompany\.com$' as the regexp.</para>
+ </warning>
+ </listitem>
+ <listitem>
+ <para>After you add your new group, edit the new group. On the
+ edit page, you can specify other groups that should be included
+ in this group and which groups should be permitted to add and delete
+ users from this group.</para>
</listitem>
</orderedlist>
@@ -712,17 +702,6 @@
<para>Turn on "usebuggroups" and "usebuggroupsentry" in the "Edit
Parameters" screen.</para>
- <warning>
- <para>XXX is this still true?
- "usebuggroupsentry" has the capacity to prevent the
- administrative user from directly altering bugs because of
- conflicting group permissions. If you plan on using
- "usebuggroupsentry", you should plan on restricting
- administrative account usage to administrative duties only. In
- other words, manage bugs with an unpriveleged user account, and
- manage users, groups, Products, etc. with the administrative
- account.</para>
- </warning>
</listitem>
<listitem>
@@ -734,13 +713,6 @@
</listitem>
</orderedlist>
- <warning>
- <para>Bugzilla currently has a limit of 64 groups per installation. If
- you have more than about 50 products, you should consider
- running multiple Bugzillas. Ask in the newsgroup for other
- suggestions for working around this restriction.</para>
- </warning>
-
<para>
Note that group permissions are such that you need to be a member
of <emphasis>all</emphasis> the groups a bug is in, for whatever
diff --git a/docs/xml/administration.xml b/docs/xml/administration.xml
index a82a659bf..a0ff3e174 100644
--- a/docs/xml/administration.xml
+++ b/docs/xml/administration.xml
@@ -212,33 +212,11 @@
you for this username and password.</para>
<tip>
- <para>If you wish to add more administrative users, you must use the
- MySQL interface. Run "mysql" from the command line, and use these
- commands:
- <simplelist>
- <member>
- <prompt>mysql&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>
@@ -698,10 +676,22 @@
</listitem>
<listitem>
- <para>Fill out the "New Name", "New Description", and
- "New User RegExp" fields. "New User RegExp" allows you to automatically
+ <para>Fill out the "Group", "Description", and
+ "User RegExp" fields. "New User RegExp" allows you to automatically
place all users who fulfill the Regular Expression into the new group.
When you have finished, click "Add".</para>
+ <warning>
+ <para>The User Regexp is a perl regexp and, if not anchored, will match
+ any part of an address. So, if you do not want to grant access
+ into 'mycompany.com' to 'badperson@mycompany.com.hacker.net', use
+ '@mycompany\.com$' as the regexp.</para>
+ </warning>
+ </listitem>
+ <listitem>
+ <para>After you add your new group, edit the new group. On the
+ edit page, you can specify other groups that should be included
+ in this group and which groups should be permitted to add and delete
+ users from this group.</para>
</listitem>
</orderedlist>
@@ -712,17 +702,6 @@
<para>Turn on "usebuggroups" and "usebuggroupsentry" in the "Edit
Parameters" screen.</para>
- <warning>
- <para>XXX is this still true?
- "usebuggroupsentry" has the capacity to prevent the
- administrative user from directly altering bugs because of
- conflicting group permissions. If you plan on using
- "usebuggroupsentry", you should plan on restricting
- administrative account usage to administrative duties only. In
- other words, manage bugs with an unpriveleged user account, and
- manage users, groups, Products, etc. with the administrative
- account.</para>
- </warning>
</listitem>
<listitem>
@@ -734,13 +713,6 @@
</listitem>
</orderedlist>
- <warning>
- <para>Bugzilla currently has a limit of 64 groups per installation. If
- you have more than about 50 products, you should consider
- running multiple Bugzillas. Ask in the newsgroup for other
- suggestions for working around this restriction.</para>
- </warning>
-
<para>
Note that group permissions are such that you need to be a member
of <emphasis>all</emphasis> the groups a bug is in, for whatever
diff --git a/duplicates.cgi b/duplicates.cgi
index 3eeab3fb5..0e27f077e 100755
--- a/duplicates.cgi
+++ b/duplicates.cgi
@@ -40,7 +40,7 @@ GetVersionTable();
quietly_check_login();
-use vars qw (%FORM $userid $usergroupset @legal_product);
+use vars qw (%FORM $userid @legal_product);
my %dbmcount;
my %count;
@@ -160,9 +160,7 @@ if (scalar(%count)) {
# Limit to a single product if requested
$query .= (" AND bugs.product_id = " . $product_id) if $product_id;
- SendSQL(SelectVisible($query,
- $userid,
- $usergroupset));
+ SendSQL($query);
while (MoreSQLData()) {
# Note: maximum row count is dealt with in the template.
@@ -170,6 +168,7 @@ if (scalar(%count)) {
my ($id, $component, $bug_severity, $op_sys, $target_milestone,
$short_desc, $bug_status, $resolution) = FetchSQLData();
+ next if (!CanSeeBug($id, $::userid));
# Limit to open bugs only if requested
next if $openonly && ($resolution ne "");
diff --git a/editgroups.cgi b/editgroups.cgi
index 5bcd4f61c..9ecda4138 100755
--- a/editgroups.cgi
+++ b/editgroups.cgi
@@ -19,6 +19,7 @@
# Rights Reserved.
#
# Contributor(s): Dave Miller <justdave@syndicomm.com>
+# Joel Peshkin <bugreport@peshkin.net>
# Jacob Steenhagen <jake@bugzilla.org>
# Code derived from editowners.cgi and editusers.cgi
@@ -99,36 +100,36 @@ sub PutTrailer (@)
unless ($action) {
PutHeader("Edit Groups","Edit Groups","This lets you edit the groups available to put users in.");
- print "<form method=post action=editgroups.cgi>\n";
print "<table border=1>\n";
print "<tr>";
- print "<th>Bit</th>";
print "<th>Name</th>";
print "<th>Description</th>";
print "<th>User RegExp</th>";
- print "<th>Active</th>";
+ print "<th>Use For Bugs</th>";
+ print "<th>Type</th>";
print "<th>Action</th>";
print "</tr>\n";
- SendSQL("SELECT bit,name,description,userregexp,isactive " .
+ SendSQL("SELECT id,name,description,userregexp,isactive,isbuggroup " .
"FROM groups " .
- "WHERE isbuggroup != 0 " .
- "ORDER BY bit");
+ "ORDER BY isbuggroup, name");
while (MoreSQLData()) {
- my ($bit, $name, $desc, $regexp, $isactive) = FetchSQLData();
+ my ($groupid, $name, $desc, $regexp, $isactive, $isbuggroup) = FetchSQLData();
print "<tr>\n";
- print "<td valign=middle>$bit</td>\n";
- print "<td><input size=20 name=\"name-$bit\" value=\"$name\">\n";
- print "<input type=hidden name=\"oldname-$bit\" value=\"$name\"></td>\n";
- print "<td><input size=40 name=\"desc-$bit\" value=\"$desc\">\n";
- print "<input type=hidden name=\"olddesc-$bit\" value=\"$desc\"></td>\n";
- print "<td><input size=30 name=\"regexp-$bit\" value=\"$regexp\">\n";
- print "<input type=hidden name=\"oldregexp-$bit\" value=\"$regexp\"></td>\n";
- print "<td><input type=\"checkbox\" name=\"isactive-$bit\" value=\"1\"" . ($isactive ? " checked" : "") . ">\n";
- print "<input type=hidden name=\"oldisactive-$bit\" value=\"$isactive\"></td>\n";
- print "<td align=center valign=middle><a href=\"editgroups.cgi?action=del&group=$bit\">Delete</a></td>\n";
- print "</tr>\n";
+ print "<td>$name</td>\n";
+ print "<td>$desc</td>\n";
+ print "<td>$regexp&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
#
diff --git a/editproducts.cgi b/editproducts.cgi
index 5b4c3c249..18ad4216d 100755
--- a/editproducts.cgi
+++ b/editproducts.cgi
@@ -79,9 +79,9 @@ sub CheckProduct ($)
# Displays the form to edit a products parameters
#
-sub EmitFormElements ($$$$$$$$$)
+sub EmitFormElements ($$$$$$$$)
{
- my ($product, $description, $milestoneurl, $userregexp, $disallownew,
+ my ($product, $description, $milestoneurl, $disallownew,
$votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone)
= @_;
@@ -110,13 +110,6 @@ sub EmitFormElements ($$$$$$$$$)
print qq{<INPUT TYPE=HIDDEN NAME="defaultmilestone" VALUE="$defaultmilestone">\n};
}
- # Added -JMR, 2/16/00
- if (Param("usebuggroups")) {
- $userregexp = value_quote($userregexp);
- print "</TR><TR>\n";
- print " <TH ALIGN=\"right\">User Regexp for Bug Group:</TH>\n";
- print " <TD><INPUT TYPE=TEXT SIZE=64 MAXLENGTH=255 NAME=\"userregexp\" VALUE=\"$userregexp\"></TD>\n";
- }
print "</TR><TR>\n";
print " <TH ALIGN=\"right\">Closed for bug entry:</TH>\n";
@@ -263,7 +256,7 @@ if ($action eq 'add') {
print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
- EmitFormElements('', '', '', '', 0, 0, 10000, 0, "---");
+ EmitFormElements('', '', '', 0, 0, 10000, 0, "---");
print "</TR><TR>\n";
print " <TH ALIGN=\"right\">Version:</TH>\n";
@@ -315,7 +308,6 @@ if ($action eq 'new') {
my $description = trim($::FORM{description} || '');
my $milestoneurl = trim($::FORM{milestoneurl} || '');
- my $userregexp = trim($::FORM{userregexp} || '');
my $disallownew = 0;
$disallownew = 1 if $::FORM{disallownew};
my $votesperuser = $::FORM{votesperuser};
@@ -351,48 +343,20 @@ if ($action eq 'new') {
# If we're using bug groups, then we need to create a group for this
# product as well. -JMR, 2/16/00
if(Param("usebuggroups")) {
- # First we need to figure out the bit for this group. We'll simply
- # use the next highest bit available. We'll use a minimum bit of 256,
- # to leave room for a few more Bugzilla operation groups at the bottom.
- SendSQL("SELECT MAX(bit) FROM groups");
- my $bit = FetchOneColumn();
- if($bit < 256) {
- $bit = 256;
- } else {
- $bit = $bit * 2;
- }
-
# Next we insert into the groups table
SendSQL("INSERT INTO groups " .
- "(bit, name, description, isbuggroup, userregexp) " .
+ "(name, description, isbuggroup, last_changed) " .
"VALUES (" .
- $bit . ", " .
SqlQuote($product) . ", " .
- SqlQuote($product . " Bugs Access") . ", " .
- "1, " .
- SqlQuote($userregexp) . ")");
+ SqlQuote("Access to bugs in the $product product") . ", 1, NOW())");
+ SendSQL("SELECT last_insert_id()");
+ my $gid = FetchOneColumn();
+ my $admin = GroupNameToId('admin');
+ SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless)
+ VALUES ($admin, $gid, 0)");
+ SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless)
+ VALUES ($admin, $gid, 1)");
- # And last, we need to add any existing users that match the regexp
- # to the group.
- # There may be a better way to do this in MySql, but I need to compare
- # the login_names to this regexp, and the only way I can think of to
- # do that is to get the list of login_names, and then update them
- # one by one if they match. Furthermore, I need to do it with two
- # separate loops, since opening a new SQL statement to do the update
- # seems to clobber the previous one.
-
- # Modified, 7/17/00, Joe Robins
- # If the userregexp is left empty, then no users should be added to
- # the bug group. As is, it was adding all users, since they all
- # matched the empty pattern.
- # In addition, I've replaced the rigamarole I was going through to
- # find matching users with a much simpler statement that lets the
- # mySQL database do the work.
- unless($userregexp eq "") {
- SendSQL("UPDATE profiles ".
- "SET groupset = groupset | " . $bit . " " .
- "WHERE LOWER(login_name) REGEXP LOWER(" . SqlQuote($userregexp) . ")");
- }
}
# Make versioncache flush
@@ -444,22 +408,6 @@ if ($action eq 'del') {
print " <TD VALIGN=\"top\">$milestonelink</TD>\n";
}
- # Added -JMR, 2/16/00
- if(Param('usebuggroups')) {
- # Get the regexp for this product.
- SendSQL("SELECT userregexp
- FROM groups
- WHERE name=" . SqlQuote($product));
- my $userregexp = FetchOneColumn();
- if(!defined $userregexp) {
- $userregexp = "<FONT COLOR=\"red\">undefined</FONT>";
- } elsif ($userregexp eq "") {
- $userregexp = "<FONT COLOR=\"blue\">blank</FONT>";
- }
- print "</TR><TR>\n";
- print " <TD VALIGN=\"top\">User Regexp for Bug Group:</TD>\n";
- print " <TD VALIGN=\"top\">$userregexp</TD>\n";
- }
print "</TR><TR>\n";
print " <TD VALIGN=\"top\">Closed for bugs:</TD>\n";
@@ -637,30 +585,6 @@ if ($action eq 'delete') {
WHERE id=$product_id");
print "Product '$product' deleted.<BR>\n";
- # Added -JMR, 2/16/00
- if (Param("usebuggroups")) {
- # We need to get the bit of the group from the table, then update the
- # groupsets of members of that group and remove the group.
- SendSQL("SELECT bit, description FROM groups " .
- "WHERE name = " . SqlQuote($product));
- my ($bit, $group_desc) = FetchSQLData();
-
- # Make sure there is a group before we try to do any deleting...
- if($bit) {
- # I'm kludging a bit so that I don't break superuser access;
- # I'm merely checking to make sure that the groupset is not
- # the superuser groupset in doing this update...
- SendSQL("UPDATE profiles " .
- "SET groupset = groupset - $bit " .
- "WHERE (groupset & $bit) " .
- "AND (groupset != 9223372036854710271)");
- print "Users dropped from group '$group_desc'.<BR>\n";
-
- SendSQL("DELETE FROM groups " .
- "WHERE bit = $bit");
- print "Group '$group_desc' deleted.<BR>\n";
- }
- }
SendSQL("UNLOCK TABLES");
@@ -690,18 +614,10 @@ if ($action eq 'edit') {
$votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone) =
FetchSQLData();
- my $userregexp = '';
- if(Param("usebuggroups")) {
- SendSQL("SELECT userregexp
- FROM groups
- WHERE name=" . SqlQuote($product));
- $userregexp = FetchOneColumn() || "";
- }
-
print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
- EmitFormElements($product, $description, $milestoneurl, $userregexp,
+ EmitFormElements($product, $description, $milestoneurl,
$disallownew, $votesperuser, $maxvotesperbug,
$votestoconfirm, $defaultmilestone);
@@ -787,10 +703,6 @@ if ($action eq 'edit') {
value_quote($description) . "\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"milestoneurlold\" VALUE=\"" .
value_quote($milestoneurl) . "\">\n";
- if(Param("usebuggroups")) {
- print "<INPUT TYPE=HIDDEN NAME=\"userregexpold\" VALUE=\"" .
- value_quote($userregexp) . "\">\n";
- }
print "<INPUT TYPE=HIDDEN NAME=\"disallownewold\" VALUE=\"$disallownew\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"votesperuserold\" VALUE=\"$votesperuser\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"maxvotesperbugold\" VALUE=\"$maxvotesperbug\">\n";
@@ -826,8 +738,6 @@ if ($action eq 'update') {
my $milestoneurlold = trim($::FORM{milestoneurlold} || '');
my $votesperuser = trim($::FORM{votesperuser} || 0);
my $votesperuserold = trim($::FORM{votesperuserold} || 0);
- my $userregexp = trim($::FORM{userregexp} || '');
- my $userregexpold = trim($::FORM{userregexpold} || '');
my $maxvotesperbug = trim($::FORM{maxvotesperbug} || 0);
my $maxvotesperbugold = trim($::FORM{maxvotesperbugold} || 0);
my $votestoconfirm = trim($::FORM{votestoconfirm} || 0);
@@ -883,68 +793,6 @@ if ($action eq 'update') {
print "Updated mile stone URL.<BR>\n";
}
- # Added -JMR, 2/16/00
- if (Param("usebuggroups") && $userregexp ne $userregexpold) {
- # This will take a little bit of work here, since there may not be
- # an existing bug group for this product, and we will also have to
- # update users groupsets.
- # First we find out if there's an existing group for this product, and
- # get its bit if there is.
- SendSQL("SELECT bit " .
- "FROM groups " .
- "WHERE name = " . SqlQuote($productold));
- my $bit = FetchOneColumn();
- if($bit) {
- # Group exists, so we do an update statement.
- SendSQL("UPDATE groups " .
- "SET userregexp = " . SqlQuote($userregexp) . " " .
- "WHERE name = " . SqlQuote($productold));
- print "Updated user regexp for bug group.<BR>\n";
- } else {
- # Group doesn't exist. Let's make it, the same way as we make a
- # group for a new product above.
- SendSQL("SELECT MAX(bit) FROM groups");
- my $tmp_bit = FetchOneColumn();
- if($tmp_bit < 256) {
- $bit = 256;
- } else {
- $bit = $tmp_bit * 2;
- }
- SendSQL("INSERT INTO groups " .
- "(bit, name, description, isbuggroup, userregexp) " .
- "values (" . $bit . ", " .
- SqlQuote($productold) . ", " .
- SqlQuote($productold . " Bugs Access") . ", " .
- "1, " .
- SqlQuote($userregexp) . ")");
- print "Created bug group.<BR>\n";
- }
-
- # And now we have to update the profiles again to add any users who
- # match the new regexp to the group. I'll do this the same way as
- # when I create a new group above. Note that I'm not taking out
- # users who matched the old regexp and not the new one; that would
- # be insanely messy. Use the group administration page for that
- # instead.
- SendSQL("SELECT login_name FROM profiles");
- my @login_list = ();
- my $this_login;
- while($this_login = FetchOneColumn()) {
- push @login_list, $this_login;
- }
- my $updated_profiles = 0;
- foreach $this_login (@login_list) {
- if($this_login =~ /$userregexp/) {
- SendSQL("UPDATE profiles " .
- "SET groupset = groupset | " . $bit . " " .
- "WHERE login_name = " . SqlQuote($this_login));
- $updated_profiles = 1;
- }
- }
- if($updated_profiles) {
- print "Added users matching regexp to group.<BR>\n";
- }
- }
if ($votesperuser ne $votesperuserold) {
SendSQL("UPDATE products
@@ -1012,9 +860,10 @@ if ($action eq 'update') {
# update it so it will match in the future. If there is no group, this
# update statement will do nothing, so no harm done. -JMR, 3/8/00
SendSQL("UPDATE groups " .
- "SET name=$qp, " .
- "description=".SqlQuote($product." Bugs Access")." ".
- "WHERE name=$qpold");
+ "SET name = $qp, " .
+ "description = " .
+ SqlQuote("Access to bugs in the $product product") .
+ " WHERE name = $qpold");
print "Updated product name.<BR>\n";
}
diff --git a/editusers.cgi b/editusers.cgi
index 4586345af..cc6be6665 100755
--- a/editusers.cgi
+++ b/editusers.cgi
@@ -22,6 +22,7 @@
# Dave Miller <justdave@syndicomm.com>
# Joe Robins <jmrobins@tgix.com>
# Dan Mosedale <dmose@mozilla.org>
+# Joel Peshkin <bugreport@peshkin.net>
#
# Direct any questions on this source code to
#
@@ -39,11 +40,9 @@ require "globals.pl";
sub sillyness {
my $zz;
$zz = $::userid;
- $zz = $::superusergroupset;
}
my $editall;
-my $opblessgroupset = '9223372036854775807'; # This is all 64 bits.
@@ -97,9 +96,9 @@ sub EmitElement ($$)
# Displays the form to edit a user parameters
#
-sub EmitFormElements ($$$$$)
+sub EmitFormElements ($$$$)
{
- my ($user, $realname, $groupset, $blessgroupset, $disabledtext) = @_;
+ my ($user_id, $user, $realname, $disabledtext) = @_;
print " <TH ALIGN=\"right\">Login name:</TH>\n";
EmitElement("user", $user);
@@ -134,11 +133,15 @@ sub EmitFormElements ($$$$$)
if($user ne "") {
print "</TR><TR><TH VALIGN=TOP ALIGN=RIGHT>Group Access:</TH><TD><TABLE><TR>";
- SendSQL("SELECT bit,name,description,bit & $groupset != 0, " .
- " bit & $blessgroupset " .
+ SendSQL("SELECT groups.id, groups.name, groups.description, " .
+ "COUNT(user_id), " .
+ "MAX(isderived) " .
"FROM groups " .
- "WHERE bit & $opblessgroupset != 0 AND isbuggroup " .
- "ORDER BY name");
+ "LEFT JOIN user_group_map " .
+ "ON user_group_map.group_id = groups.id " .
+ "AND isbless = 0 " .
+ "AND user_id = $user_id " .
+ "GROUP BY groups.name ");
if (MoreSQLData()) {
if ($editall) {
print "<TD COLSPAN=3 ALIGN=LEFT><B>Can turn this bit on for other users</B></TD>\n";
@@ -146,50 +149,50 @@ sub EmitFormElements ($$$$$)
}
print "<TD COLSPAN=2 ALIGN=LEFT><B>User is a member of these groups</B></TD>\n";
while (MoreSQLData()) {
- my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData();
- print "</TR><TR>\n";
+ my ($groupid, $name, $description, $member, $isderived) = FetchSQLData();
+ next if (!$editall && !UserCanBlessGroup($name));
+ $isderived = $isderived || 0;
+ my $checked = $member - $isderived;
+ PushGlobalSQLState();
+ SendSQL("SELECT user_id " .
+ "FROM user_group_map " .
+ "WHERE isbless = 1 " .
+ "AND user_id = $user_id " .
+ "AND group_id = $groupid");
+ my ($blchecked) = FetchSQLData() ? 1 : 0;
+ SendSQL("SELECT grantor_id FROM user_group_map,
+ group_group_map
+ WHERE $groupid = grantor_id
+ AND user_group_map.user_id = $user_id
+ AND user_group_map.isbless = 0
+ AND group_group_map.isbless = 1
+ AND user_group_map.group_id = member_id");
+ my $derivedbless = FetchOneColumn();
+ PopGlobalSQLState();
+ print "</TR><TR";
+ print ' bgcolor=#cccccc' if ($isderived);
+ print ">\n";
+ print "<INPUT TYPE=HIDDEN NAME=\"oldgroup_$groupid\" VALUE=\"$checked\">\n";
+ print "<INPUT TYPE=HIDDEN NAME=\"oldbless_$groupid\" VALUE=\"$blchecked\">\n";
if ($editall) {
$blchecked = ($blchecked) ? "CHECKED" : "";
- print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"blbit_$name\" $blchecked VALUE=\"$bit\"></TD>";
+ print "<TD ALIGN=CENTER>";
+ print "[" if $derivedbless;
+ print "<INPUT TYPE=CHECKBOX NAME=\"bless_$groupid\" $blchecked VALUE=\"$groupid\">";
+ print "]" if $derivedbless;
+ print "</TD>\n";
}
$checked = ($checked) ? "CHECKED" : "";
- print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"bit_$name\" $checked VALUE=\"$bit\"></TD>";
- print "<TD><B>" . ucfirst($name) . "</B>: $description</TD>\n";
- }
- }
- print "</TR></TABLE></TD>\n";
-
- print "</TR><TR><TH VALIGN=TOP ALIGN=RIGHT>Privileges:</TH><TD><TABLE><TR>";
- SendSQL("SELECT bit,name,description,bit & $groupset != 0, " .
- " bit & $blessgroupset " .
- "FROM groups " .
- "WHERE bit & $opblessgroupset != 0 AND !isbuggroup " .
- "ORDER BY name");
- if (MoreSQLData()) {
- if ($editall) {
- print "<TD COLSPAN=3 ALIGN=LEFT><B>Can turn this bit on for other users</B></TD>\n";
- print "</TR><TR>\n<TD ALIGN=CENTER><B>|</B></TD>\n";
- }
- print "<TD COLSPAN=2 ALIGN=LEFT><B>User has these privileges</B></TD>\n";
- while (MoreSQLData()) {
- my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData();
- print "</TR><TR>\n";
- if ($editall) {
- $blchecked = ($blchecked) ? "CHECKED" : "";
- print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"blbit_$name\" $blchecked VALUE=\"$bit\"></TD>";
+ print "<TD ALIGN=CENTER>";
+ print '[' if ($isderived);
+ print "<INPUT TYPE=CHECKBOX NAME=\"group_$groupid\" $checked VALUE=\"$groupid\">";
+ print ']' if ($isderived);
+ print "</TD><TD><B>";
+ print ucfirst($name) . "</B>: $description</TD>\n";
}
- $checked = ($checked) ? "CHECKED" : "";
- print "<TD ALIGN=CENTER><INPUT TYPE=CHECKBOX NAME=\"bit_$name\" $checked VALUE=\"$bit\"></TD>";
- print "<TD><B>" . ucfirst($name) . "</B>: $description</TD>\n";
}
}
- } else {
- print "</TR><TR><TH ALIGN=RIGHT>Groups and<br>Privileges:</TH><TD><TABLE><TR>";
- print "<TD COLSPAN=3>The new user will be inserted into groups " .
- "based on their userregexps.<BR>To change the group " .
- "permissions for this user, you must edit the account after ".
- "creating it.</TD>\n";
- }
+ print "</TR></TABLE></TD>\n";
print "</TR></TABLE></TD>\n";
}
@@ -238,9 +241,7 @@ print "Content-type: text/html\n\n";
$editall = UserInGroup("editusers");
if (!$editall) {
- SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $::userid");
- $opblessgroupset = FetchOneColumn();
- if (!$opblessgroupset) {
+ if (!UserCanBlessAnything()) {
PutHeader("Not allowed");
print "Sorry, you aren't a member of the 'editusers' group, and you\n";
print "don't have permissions to put people in or out of any group.\n";
@@ -316,6 +317,10 @@ if ($action eq 'list') {
} elsif (exists $::FORM{'query'}) {
$query = "SELECT login_name,realname,disabledtext " .
"FROM profiles WHERE " . $::FORM{'query'} . " ORDER BY login_name";
+ } elsif (exists $::FORM{'group'}) {
+ $query = "SELECT DISTINCT login_name,realname,disabledtext " .
+ "FROM profiles, user_group_map WHERE profiles.userid = user_group_map.user_id
+ AND group_id=" . $::FORM{'group'} . " ORDER BY login_name";
} else {
die "Missing parameters";
}
@@ -396,7 +401,7 @@ if ($action eq 'add') {
print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
- EmitFormElements('', '', 0, 0, '');
+ EmitFormElements(0, '', '', '');
print "</TR></TABLE>\n<HR>\n";
print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
@@ -465,48 +470,22 @@ if ($action eq 'new') {
exit;
}
- # For new users, we use the regexps from the groups table to determine
- # their initial group membership.
- # We also keep a list of groups the user was added to for display on the
- # confirmation page.
- my $bits = "0";
- my @grouplist = ();
- SendSQL("select bit, name, userregexp from groups where userregexp != ''");
- while (MoreSQLData()) {
- my @row = FetchSQLData();
- if ($user =~ m/$row[2]/i) {
- $bits .= "+ $row[0]"; # Silly hack to let MySQL do the math,
- # not Perl, since we're dealing with 64
- # bit ints here, and I don't *think* Perl
- # does that.
- push(@grouplist, $row[1]);
- }
- }
-
# Add the new user
SendSQL("INSERT INTO profiles ( " .
- "login_name, cryptpassword, realname, groupset, " .
+ "login_name, cryptpassword, realname, " .
"disabledtext" .
" ) VALUES ( " .
SqlQuote($user) . "," .
SqlQuote(Crypt($password)) . "," .
SqlQuote($realname) . "," .
- $bits . "," .
SqlQuote($disabledtext) . ")" );
#+++ send e-mail away
print "OK, done.<br>\n";
- if($#grouplist > -1) {
- print "New user added to these groups based on group regexps:\n";
- print "<ul>\n";
- foreach (@grouplist) {
- print "<li>$_</li>\n";
- }
- print "</ul>\n";
- } else {
- print "New user not added to any groups.<br><br>\n";
- }
+ SendSQL("SELECT last_insert_id()");
+ my ($newuserid) = FetchSQLData();
+ DeriveGroup($newuserid);
print "To change ${user}'s permissions, go back and <a href=\"editusers.cgi?action=edit&user=" . url_quote($user)."\">edit this user</A>";
print "<p>\n";
PutTrailer($localtrailer,
@@ -538,9 +517,9 @@ if ($action eq 'del') {
CheckUser($user);
# display some data about the user
- SendSQL("SELECT realname, groupset FROM profiles
+ SendSQL("SELECT userid, realname FROM profiles
WHERE login_name=" . SqlQuote($user));
- my ($realname, $groupset) =
+ my ($thisuserid, $realname) =
FetchSQLData();
$realname = ($realname ? html_quote($realname) : "<FONT COLOR=\"red\">missing</FONT>");
@@ -561,9 +540,11 @@ if ($action eq 'del') {
print " <TD VALIGN=\"top\">Group set:</TD>\n";
print " <TD VALIGN=\"top\">";
SendSQL("SELECT name
- FROM groups
- WHERE bit & $groupset = bit
- ORDER BY isbuggroup, name");
+ FROM groups, user_group_map
+ WHERE groups.id = user_group_map.group_id
+ AND user_group_map.user_id = $thisuserid
+ AND isbless = 0
+ ORDER BY name");
my $found = 0;
while ( MoreSQLData() ) {
my ($name) = FetchSQLData();
@@ -697,27 +678,34 @@ if ($action eq 'edit') {
CheckUser($user);
# get data of user
- SendSQL("SELECT realname, groupset, blessgroupset, disabledtext
+ SendSQL("SELECT userid, realname, disabledtext
FROM profiles
WHERE login_name=" . SqlQuote($user));
- my ($realname, $groupset, $blessgroupset,
- $disabledtext) = FetchSQLData();
+ my ($thisuserid, $realname, $disabledtext) = FetchSQLData();
+ if ($thisuserid > 0) {
+ DeriveGroup($thisuserid);
+ }
print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
- EmitFormElements($user, $realname, $groupset, $blessgroupset, $disabledtext);
+ EmitFormElements($thisuserid, $user, $realname, $disabledtext);
print "</TR></TABLE>\n";
-
print "<INPUT TYPE=HIDDEN NAME=\"userold\" VALUE=\"$user\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"realnameold\" VALUE=\"$realname\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"groupsetold\" VALUE=\"$groupset\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"blessgroupsetold\" VALUE=\"$blessgroupset\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"disabledtextold\" VALUE=\"" .
value_quote($disabledtext) . "\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n";
print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n";
+ print "<BR>User is a member of any groups shown with grey bars and
+ marked with brackets surrounding the membership checkbox as a
+ result of a regular expression match
+ or membership in another group.
+ User can bless any group
+ marked with brackets surrounding the bless checkbox as a
+ result of membership in another group.
+ <BR>";
print "</FORM>";
@@ -740,69 +728,67 @@ if ($action eq 'update') {
my $password = $::FORM{password} || '';
my $disabledtext = trim($::FORM{disabledtext} || '');
my $disabledtextold = trim($::FORM{disabledtextold} || '');
- my $groupsetold = trim($::FORM{groupsetold} || '0');
- my $blessgroupsetold = trim($::FORM{blessgroupsetold} || '0');
- my $groupset = "0";
- foreach (keys %::FORM) {
- next unless /^bit_/;
- #print "$_=$::FORM{$_}<br>\n";
- detaint_natural($::FORM{$_}) || die "Groupset field tampered with";
- $groupset .= " + $::FORM{$_}";
+ CheckUser($userold);
+ SendSQL("SELECT userid FROM profiles
+ WHERE login_name=" . SqlQuote($userold));
+ my ($thisuserid) = FetchSQLData();
+
+ my @grpadd = ();
+ my @grpdel = ();
+ my $chggrp = 0;
+ SendSQL("SELECT id, name FROM groups");
+ while (my ($groupid, $name) = FetchSQLData()) {
+ if ($::FORM{"oldgroup_$groupid"} != ($::FORM{"group_$groupid"} ? 1 : 0)) {
+ # group membership changed
+ PushGlobalSQLState();
+ $chggrp = 1;
+ SendSQL("DELETE FROM user_group_map
+ WHERE user_id = $thisuserid
+ AND group_id = $groupid
+ AND isbless = 0
+ AND isderived = 0");
+ if ($::FORM{"group_$groupid"}) {
+ SendSQL("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($thisuserid, $groupid, 0, 0)");
+ print "Added user to group $name<BR>\n";
+ push(@grpadd, $name);
+ } else {
+ print "Dropped user from group $name<BR>\n";
+ push(@grpdel, $name);
+ }
+ PopGlobalSQLState();
+ }
+ if ($editall && ($::FORM{"oldbless_$groupid"} != ($::FORM{"bless_$groupid"} ? 1 : 0))) {
+ # group membership changed
+ PushGlobalSQLState();
+ SendSQL("DELETE FROM user_group_map
+ WHERE user_id = $thisuserid
+ AND group_id = $groupid
+ AND isbless = 1
+ AND isderived = 0");
+ if ($::FORM{"bless_$groupid"}) {
+ SendSQL("INSERT INTO user_group_map
+ (user_id, group_id, isbless, isderived)
+ VALUES ($thisuserid, $groupid, 1, 0)");
+ print "Granted user permission to bless group $name<BR>\n";
+ } else {
+ print "Revoked user's permission to bless group $name<BR>\n";
+ }
+ PopGlobalSQLState();
+
+ }
}
- my $blessgroupset = "0";
- foreach (keys %::FORM) {
- next unless /^blbit_/;
- #print "$_=$::FORM{$_}<br>\n";
- detaint_natural($::FORM{$_}) || die "Blessgroupset field tampered with";
- $blessgroupset .= " + $::FORM{$_}";
+ my $fieldid = GetFieldID("bug_group");
+ if ($chggrp) {
+ SendSQL("INSERT INTO profiles_activity " .
+ "(userid, who, profiles_when, fieldid, oldvalue, newvalue) " .
+ "VALUES " . "($thisuserid, $::userid, now(), $fieldid, " .
+ SqlQuote(join(", ",@grpdel)) . ", " .
+ SqlQuote(join(", ",@grpadd)) . ")");
}
- CheckUser($userold);
-
- # Note that the order of this tests is important. If you change
- # them, be sure to test for WHERE='$product' or WHERE='$productold'
-
- if ($groupset ne $groupsetold) {
- SendSQL("SELECT groupset FROM profiles WHERE login_name=" .
- SqlQuote($userold));
- $groupsetold = FetchOneColumn();
- # Updated, 5/7/00, Joe Robins
- # We don't want to change the groupset of a superuser.
- if($groupsetold eq $::superusergroupset) {
- print "Cannot change permissions of superuser.\n";
- } else {
- SendSQL("UPDATE profiles
- SET groupset =
- groupset - (groupset & $opblessgroupset) +
- (($groupset) & $opblessgroupset)
- WHERE login_name=" . SqlQuote($userold));
-
- # I'm paranoid that someone who I give the ability to bless people
- # will start misusing it. Let's log who blesses who (even though
- # nothing actually uses this log right now).
- my $fieldid = GetFieldID("groupset");
- SendSQL("SELECT userid, groupset FROM profiles WHERE login_name=" .
- SqlQuote($userold));
- my $u;
- ($u, $groupset) = (FetchSQLData());
- if ($groupset ne $groupsetold) {
- SendSQL("INSERT INTO profiles_activity " .
- "(userid,who,profiles_when,fieldid,oldvalue,newvalue) " .
- "VALUES " .
- "($u, $::userid, now(), $fieldid, " .
- " $groupsetold, $groupset)");
- }
- print "Updated permissions.\n";
- }
- }
-
- if ($editall && $blessgroupset ne $blessgroupsetold) {
- SendSQL("UPDATE profiles
- SET blessgroupset=" . $blessgroupset . "
- WHERE login_name=" . SqlQuote($userold));
- print "Updated ability to tweak permissions of other users.\n";
- }
# Update the database with the user's new password if they changed it.
if ( !Param('useLDAP') && $editall && $password ) {
diff --git a/enter_bug.cgi b/enter_bug.cgi
index a779aa85d..a7733ec3e 100755
--- a/enter_bug.cgi
+++ b/enter_bug.cgi
@@ -48,6 +48,7 @@ use vars qw(
@legal_platform
@legal_priority
@legal_severity
+ $userid
%MFORM
%versions
);
@@ -326,60 +327,58 @@ $default{'bug_status'} = $status[0];
# Select whether to restrict this bug to the product's bug group or not,
# if the usebuggroups parameter is set, and if this product has a bug group.
-if ($::usergroupset ne '0') {
- # First we get the bit and description for the group.
- my $group_bit = '0';
-
- if(Param("usebuggroups") && GroupExists($product)) {
- SendSQL("SELECT bit FROM groups ".
- "WHERE name = " . SqlQuote($product) . " " .
- "AND isbuggroup != 0");
- ($group_bit) = FetchSQLData();
- }
-
- SendSQL("SELECT bit, name, description FROM groups " .
- "WHERE bit & $::usergroupset != 0 " .
- "AND isbuggroup != 0 AND isactive = 1 ORDER BY description");
-
- my @groups;
-
- while (MoreSQLData()) {
- my ($bit, $prodname, $description) = FetchSQLData();
- # Don't want to include product groups other than this product.
- next unless($prodname eq $product ||
- !defined($::proddesc{$prodname}));
+# First we get the bit and description for the group.
+my $group_id = '0';
- my $check;
+if(Param("usebuggroups")) {
+ ($group_id) = GroupExists($product);
+}
- # If this is the group for this product, make it checked.
- if(formvalue("maketemplate") eq
- "Remember values as bookmarkable template")
- {
- # If this is a bookmarked template, then we only want to set the
- # bit for those bits set in the template.
- $check = formvalue("bit-$bit", 0);
- }
- else {
- # $group_bit will only have a non-zero value if we're using
- # bug groups and have one for this product.
- # If $group_bit is 0, it won't match the current group, so compare
- # it to the current bit instead of checking for non-zero.
- $check = ($group_bit == $bit);
- }
+SendSQL("SELECT DISTINCT groups.id, groups.name, groups.description " .
+ "FROM groups, user_group_map " .
+ "WHERE user_group_map.group_id = groups.id " .
+ "AND user_group_map.user_id = $::userid " .
+ "AND isbless = 0 " .
+ "AND isbuggroup = 1 AND isactive = 1 ORDER BY description");
- my $group =
- {
- 'bit' => $bit ,
- 'checked' => $check ,
- 'description' => $description
- };
+my @groups;
- push @groups, $group;
+while (MoreSQLData()) {
+ my ($id, $prodname, $description) = FetchSQLData();
+ # Don't want to include product groups other than this product.
+ next unless(!Param("usebuggroups") || $prodname eq $product ||
+ !defined($::proddesc{$prodname}));
+
+ my $check;
+
+ # If this is the group for this product, make it checked.
+ if(formvalue("maketemplate") eq
+ "Remember values as bookmarkable template")
+ {
+ # If this is a bookmarked template, then we only want to set the
+ # bit for those bits set in the template.
+ $check = formvalue("bit-$id", 0);
+ }
+ else {
+ # $group_bit will only have a non-zero value if we're using
+ # bug groups and have one for this product.
+ # If $group_bit is 0, it won't match the current group, so compare
+ # it to the current bit instead of checking for non-zero.
+ $check = ($group_id == $id);
}
- $vars->{'group'} = \@groups;
+ my $group =
+ {
+ 'bit' => $id ,
+ 'checked' => $check ,
+ 'description' => $description
+ };
+
+ push @groups, $group;
}
+$vars->{'group'} = \@groups;
+
$vars->{'default'} = \%default;
my $format =
@@ -388,3 +387,4 @@ my $format =
print "Content-type: $format->{'ctype'}\n\n";
$template->process($format->{'template'}, $vars)
|| ThrowTemplateError($template->error());
+
diff --git a/globals.pl b/globals.pl
index 4570a5658..242c8a5f6 100644
--- a/globals.pl
+++ b/globals.pl
@@ -22,6 +22,7 @@
# Jacob Steenhagen <jake@bugzilla.org>
# Bradley Baetz <bbaetz@cs.mcgill.ca>
# Christopher Aillon <christopher@aillon.com>
+# Joel Peshkin <bugreport@peshkin.net>
# Contains some global variables and routines used throughout bugzilla.
@@ -57,7 +58,6 @@ sub globals_pl_sillyness {
$zz = @main::milestoneurl;
$zz = %main::proddesc;
$zz = @main::prodmaxvotes;
- $zz = $main::superusergroupset;
$zz = $main::template;
$zz = $main::userid;
$zz = $main::vars;
@@ -102,10 +102,6 @@ $::defaultqueryname = "(Default query)";
$::unconfirmedstate = "UNCONFIRMED";
$::dbwritesallowed = 1;
-# Adding a global variable for the value of the superuser groupset.
-# Joe Robins, 7/5/00
-$::superusergroupset = "9223372036854775807";
-
#sub die_with_dignity {
# my ($err_msg) = @_;
# print $err_msg;
@@ -583,30 +579,14 @@ sub InsertNewUser {
my $password = GenerateRandomPassword();
my $cryptpassword = Crypt($password);
- # Determine what groups the user should be in by default
- # and add them to those groups.
- PushGlobalSQLState();
- SendSQL("select bit, userregexp from groups where userregexp != ''");
- my $groupset = "0";
- while (MoreSQLData()) {
- my @row = FetchSQLData();
- # Modified -Joe Robins, 2/17/00
- # Making this case insensitive, since usernames are email addresses,
- # and could be any case.
- if ($username =~ m/$row[1]/i) {
- $groupset .= "+ $row[0]"; # Silly hack to let MySQL do the math,
- # not Perl, since we're dealing with 64
- # bit ints here, and I don't *think* Perl
- # does that.
- }
- }
# Insert the new user record into the database.
$username = SqlQuote($username);
$realname = SqlQuote($realname);
$cryptpassword = SqlQuote($cryptpassword);
- SendSQL("INSERT INTO profiles (login_name, realname, cryptpassword, groupset)
- VALUES ($username, $realname, $cryptpassword, $groupset)");
+ PushGlobalSQLState();
+ SendSQL("INSERT INTO profiles (login_name, realname, cryptpassword)
+ VALUES ($username, $realname, $cryptpassword)");
PopGlobalSQLState();
# Return the password to the calling code so it can be included
@@ -650,98 +630,47 @@ sub GenerateRandomPassword {
return $password;
}
-sub SelectVisible {
- my ($query, $userid, $usergroupset) = @_;
-
- # Run the SQL $query with the additional restriction that
- # the bugs can be seen by $userid. $usergroupset is provided
- # as an optimisation when this is already known, eg from CGI.pl
- # If not present, it will be obtained from the db.
- # Assumes that 'bugs' is mentioned as a table name. You should
- # also make sure that bug_id is qualified bugs.bug_id!
- # Your query must have a WHERE clause. This is unlikely to be a problem.
-
- # Also, note that mySQL requires aliases for tables to be locked, as well
- # This means that if you change the name from selectVisible_cc (or add
- # additional tables), you will need to update anywhere which does a
- # LOCK TABLE, and then calls routines which call this
-
- $usergroupset = 0 unless $userid;
-
- unless (defined($usergroupset)) {
- PushGlobalSQLState();
- SendSQL("SELECT groupset FROM profiles WHERE userid = $userid");
- $usergroupset = FetchOneColumn();
- PopGlobalSQLState();
- }
-
- # Users are authorized to access bugs if they are a member of all
- # groups to which the bug is restricted. User group membership and
- # bug restrictions are stored as bits within bitsets, so authorization
- # can be determined by comparing the intersection of the user's
- # bitset with the bug's bitset. If the result matches the bug's bitset
- # the user is a member of all groups to which the bug is restricted
- # and is authorized to access the bug.
-
- # A user is also authorized to access a bug if she is the reporter,
- # or member of the cc: list of the bug and the bug allows users in those
- # roles to see the bug. The boolean fields reporter_accessible and
- # cclist_accessible identify whether or not those roles can see the bug.
-
- # Bit arithmetic is performed by MySQL instead of Perl because bitset
- # fields in the database are 64 bits wide (BIGINT), and Perl installations
- # may or may not support integers larger than 32 bits. Using bitsets
- # and doing bitset arithmetic is probably not cross-database compatible,
- # however, so these mechanisms are likely to change in the future.
-
- my $replace = " ";
-
- if ($userid) {
- $replace .= "LEFT JOIN cc selectVisible_cc ON
- bugs.bug_id = selectVisible_cc.bug_id AND
- selectVisible_cc.who = $userid "
- }
-
- $replace .= "WHERE ((bugs.groupset & $usergroupset) = bugs.groupset ";
-
- if ($userid) {
- # There is a mysql bug affecting v3.22 and 3.23 (at least), where this will
- # cause all rows to be returned! We work arround this by adding an not isnull
- # test to the JOINed cc table. See http://lists.mysql.com/cgi-ez/ezmlm-cgi?9:mss:11417
- # Its needed, even though it shouldn't be
- $replace .= "OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid)" .
- " OR (bugs.cclist_accessible = 1 AND selectVisible_cc.who = $userid AND not isnull(selectVisible_cc.who))" .
- " OR (bugs.assigned_to = $userid)";
- if (Param("useqacontact")) {
- $replace .= " OR (bugs.qa_contact = $userid)";
- }
- }
-
- $replace .= ") AND ";
-
- $query =~ s/\sWHERE\s/$replace/i;
-
- return $query;
-}
-
sub CanSeeBug {
- # Note that we pass in the usergroupset, since this is known
- # in most cases (ie viewing bugs). Maybe make this an optional
- # parameter?
- my ($id, $userid, $usergroupset) = @_;
+ my ($id, $userid) = @_;
# Query the database for the bug, retrieving a boolean value that
# represents whether or not the user is authorized to access the bug.
+ # if no groups are found --> user is permitted to access
+ # if no user is found for any group --> user is not permitted to access
+ my $query = "SELECT bugs.bug_id, reporter, assigned_to, qa_contact," .
+ " reporter_accessible, cclist_accessible," .
+ " cc.who IS NOT NULL," .
+ " COUNT(bug_group_map.group_id) as cntbugingroups," .
+ " COUNT(DISTINCT(user_group_map.group_id)) as cntuseringroups" .
+ " FROM bugs" .
+ " LEFT JOIN cc ON bugs.bug_id = cc.bug_id" .
+ " AND cc.who = $userid" .
+ " LEFT JOIN bug_group_map ON bugs.bug_id = bug_group_map.bug_id" .
+ " LEFT JOIN user_group_map ON" .
+ " user_group_map.group_id = bug_group_map.group_id" .
+ " AND user_group_map.isbless = 0" .
+ " AND user_group_map.user_id = $userid" .
+ " WHERE bugs.bug_id = $id GROUP BY bugs.bug_id";
PushGlobalSQLState();
- SendSQL(SelectVisible("SELECT bugs.bug_id FROM bugs WHERE bugs.bug_id = $id",
- $userid, $usergroupset));
-
- my $ret = defined(FetchSQLData());
+ SendSQL($query);
+ my ($found_id, $reporter, $assigned_to, $qa_contact,
+ $rep_access, $cc_access,
+ $found_cc, $found_groups, $found_members)
+ = FetchSQLData();
PopGlobalSQLState();
-
- return $ret;
+ return (
+ ($found_groups == 0)
+ || (($userid > 0) &&
+ (
+ ($assigned_to == $userid)
+ || ($qa_contact == $userid)
+ || (($reporter == $userid) && $rep_access)
+ || ($found_cc && $cc_access)
+ || ($found_groups == $found_members)
+ ))
+ );
}
sub ValidatePassword {
@@ -799,6 +728,94 @@ sub Crypt {
return $cryptedpassword;
}
+# ConfirmGroup(userid) is called prior to any activity that relies
+# on user_group_map to ensure that derived group permissions are up-to-date.
+# Permissions must be rederived if ANY groups have a last_changed newer
+# than the profiles.refreshed_when value.
+sub ConfirmGroup {
+ my ($user) = (@_);
+ PushGlobalSQLState();
+ SendSQL("SELECT userid FROM profiles, groups WHERE userid = $user " .
+ "AND profiles.refreshed_when <= groups.last_changed ");
+ my $ret = FetchSQLData();
+ PopGlobalSQLState();
+ if ($ret) {
+ DeriveGroup($user);
+ }
+}
+
+# DeriveGroup removes and rederives all derived group permissions for
+# the specified user.
+sub DeriveGroup {
+ my ($user) = (@_);
+ PushGlobalSQLState();
+
+ SendSQL("LOCK TABLES profiles WRITE, user_group_map WRITE, group_group_map READ, groups READ");
+
+ # avoid races, we are only as up to date as the BEGINNING of this process
+ SendSQL("SELECT login_name, NOW() FROM profiles WHERE userid = $user");
+ my ($login, $starttime) = FetchSQLData();
+
+ # first remove any old derived stuff for this user
+ SendSQL("DELETE FROM user_group_map WHERE user_id = $user " .
+ "AND isderived = 1");
+
+ my %groupidsadded = ();
+ # add derived records for any matching regexps
+ SendSQL("SELECT id, userregexp FROM groups WHERE userregexp != ''");
+ while (MoreSQLData()) {
+ my ($groupid, $rexp) = FetchSQLData();
+ if ($login =~ m/$rexp/i) {
+ PushGlobalSQLState();
+ $groupidsadded{$groupid} = 1;
+ SendSQL("INSERT INTO user_group_map " .
+ "(user_id, group_id, isbless, isderived) " .
+ "VALUES ($user, $groupid, 0, 1)");
+ PopGlobalSQLState();
+
+ }
+ }
+
+ # Get a list of the groups of which the user is a member.
+ my %groupidschecked = ();
+ my @groupidstocheck = ();
+ SendSQL("SELECT group_id FROM user_group_map WHERE user_id = $user
+ AND NOT isbless");
+ while (MoreSQLData()) {
+ my ($groupid) = FetchSQLData();
+ push(@groupidstocheck,$groupid);
+ }
+
+ # Each group needs to be checked for inherited memberships once.
+ while (@groupidstocheck) {
+ my $group = shift @groupidstocheck;
+ if (!defined($groupidschecked{"$group"})) {
+ $groupidschecked{"$group"} = 1;
+ SendSQL("SELECT grantor_id FROM group_group_map WHERE"
+ . " member_id = $group AND NOT isbless");
+ while (MoreSQLData()) {
+ my ($groupid) = FetchSQLData();
+ if (!defined($groupidschecked{"$groupid"})) {
+ push(@groupidstocheck,$groupid);
+ }
+ if (!$groupidsadded{$groupid}) {
+ $groupidsadded{$groupid} = 1;
+ PushGlobalSQLState();
+ SendSQL("INSERT INTO user_group_map"
+ . " (user_id, group_id, isbless, isderived)"
+ . " VALUES ($user, $groupid, 0, 1)");
+ PopGlobalSQLState();
+ }
+ }
+ }
+ }
+
+ SendSQL("UPDATE profiles SET refreshed_when = " .
+ SqlQuote($starttime) . "WHERE userid = $user");
+ SendSQL("UNLOCK TABLES");
+ PopGlobalSQLState();
+};
+
sub DBID_to_real_or_loginname {
my ($id) = (@_);
@@ -860,6 +877,9 @@ sub DBNameToIdAndCheck {
ThrowUserError("invalid_username");
}
+
+
+
sub get_product_id {
my ($prod) = @_;
PushGlobalSQLState();
@@ -1015,12 +1035,12 @@ sub GetBugLink {
# is saved off rather than overwritten
PushGlobalSQLState();
- SendSQL("SELECT bugs.bug_status, resolution, short_desc, groupset " .
+ SendSQL("SELECT bugs.bug_status, resolution, short_desc " .
"FROM bugs WHERE bugs.bug_id = $bug_num");
# If the bug exists, save its data off for use later in the sub
if (MoreSQLData()) {
- my ($bug_state, $bug_res, $bug_desc, $bug_grp) = FetchSQLData();
+ my ($bug_state, $bug_res, $bug_desc) = FetchSQLData();
# Initialize these variables to be "" so that we don't get warnings
# if we don't change them below (which is highly likely).
my ($pre, $title, $post) = ("", "", "");
@@ -1035,7 +1055,7 @@ sub GetBugLink {
$title .= " $bug_res";
$post = "</strike>";
}
- if ($bug_grp == 0 || CanSeeBug($bug_num, $::userid, $::usergroupset)) {
+ if (CanSeeBug($bug_num, $::userid)) {
$title .= " - $bug_desc";
}
$::buglink{$bug_num} = [$pre, value_quote($title), $post];
@@ -1186,15 +1206,100 @@ sub SqlQuote {
return "'$str'";
}
+
+# UserInGroup returns information aboout the current user if no second
+# parameter is specified
sub UserInGroup {
- return $::vars->{'user'}{'groups'}{$_[0]};
+ my ($groupname, $userid) = (@_);
+ if (!$userid) {
+ return $::vars->{'user'}{'groups'}{$_[0]};
+ }
+ PushGlobalSQLState();
+ $userid ||= $::userid;
+ SendSQL("SELECT groups.id FROM groups, user_group_map
+ WHERE groups.id = user_group_map.group_id
+ AND user_group_map.user_id = $userid
+ AND isbless = 0
+ AND groups.name = " . SqlQuote($groupname));
+ my $result = FetchOneColumn();
+ PopGlobalSQLState();
+ return defined($result);
+}
+
+sub UserCanBlessGroup {
+ my ($groupname) = (@_);
+ PushGlobalSQLState();
+ # check if user explicitly can bless group
+ SendSQL("SELECT groups.id FROM groups, user_group_map
+ WHERE groups.id = user_group_map.group_id
+ AND user_group_map.user_id = $::userid
+ AND isbless = 1
+ AND groups.name = " . SqlQuote($groupname));
+ my $result = FetchOneColumn();
+ PopGlobalSQLState();
+ if ($result) {
+ return 1;
+ }
+ PushGlobalSQLState();
+ # check if user is a member of a group that can bless this group
+ # this group does not count
+ SendSQL("SELECT groups.id FROM groups, user_group_map,
+ group_group_map
+ WHERE groups.id = grantor_id
+ AND user_group_map.user_id = $::userid
+ AND user_group_map.isbless = 0
+ AND group_group_map.isbless = 1
+ AND user_group_map.group_id = member_id
+ AND groups.name = " . SqlQuote($groupname));
+ $result = FetchOneColumn();
+ PopGlobalSQLState();
+ return $result;
+}
+
+sub UserCanBlessAnything {
+ PushGlobalSQLState();
+ # check if user explicitly can bless a group
+ SendSQL("SELECT group_id FROM user_group_map
+ WHERE user_id = $::userid AND isbless = 1");
+ my $result = FetchOneColumn();
+ PopGlobalSQLState();
+ if ($result) {
+ return 1;
+ }
+ PushGlobalSQLState();
+ # check if user is a member of a group that can bless this group
+ SendSQL("SELECT groups.id FROM groups, user_group_map,
+ group_group_map
+ WHERE groups.id = grantor_id
+ AND user_group_map.user_id = $::userid
+ AND group_group_map.isbless = 1
+ AND user_group_map.group_id = member_id");
+ $result = FetchOneColumn();
+ PopGlobalSQLState();
+ if ($result) {
+ return 1;
+ }
+ return 0;
}
sub BugInGroup {
my ($bugid, $groupname) = (@_);
- my $groupbit = GroupNameToBit($groupname);
PushGlobalSQLState();
- SendSQL("SELECT (bugs.groupset & $groupbit) != 0 FROM bugs WHERE bugs.bug_id = $bugid");
+ SendSQL("SELECT bug_group_map.bug_id != 0 FROM bug_group_map, groups
+ WHERE bug_group_map.bug_id = $bugid
+ AND bug_group_map.group_id = groups.id
+ AND groups.name = " . SqlQuote($groupname));
+ my $bugingroup = FetchOneColumn();
+ PopGlobalSQLState();
+ return $bugingroup;
+}
+
+sub BugInGroupId {
+ my ($bugid, $groupid) = (@_);
+ PushGlobalSQLState();
+ SendSQL("SELECT bug_id != 0 FROM bug_group_map
+ WHERE bug_id = $bugid
+ AND group_id = $groupid");
my $bugingroup = FetchOneColumn();
PopGlobalSQLState();
return $bugingroup;
@@ -1203,32 +1308,39 @@ sub BugInGroup {
sub GroupExists {
my ($groupname) = (@_);
PushGlobalSQLState();
- SendSQL("select count(*) from groups where name=" . SqlQuote($groupname));
- my $count = FetchOneColumn();
+ SendSQL("SELECT id FROM groups WHERE name=" . SqlQuote($groupname));
+ my $id = FetchOneColumn();
PopGlobalSQLState();
- return $count;
+ return $id;
}
-# Given the name of an existing group, returns the bit associated with it.
-# If the group does not exist, returns 0.
-# !!! Remove this function when the new group system is implemented!
-sub GroupNameToBit {
+sub GroupNameToId {
my ($groupname) = (@_);
PushGlobalSQLState();
- SendSQL("SELECT bit FROM groups WHERE name = " . SqlQuote($groupname));
- my $bit = FetchOneColumn() || 0;
+ SendSQL("SELECT id FROM groups WHERE name=" . SqlQuote($groupname));
+ my $id = FetchOneColumn();
PopGlobalSQLState();
- return $bit;
+ return $id;
}
+sub GroupIdToName {
+ my ($groupid) = (@_);
+ PushGlobalSQLState();
+ SendSQL("SELECT name FROM groups WHERE id = $groupid");
+ my $name = FetchOneColumn();
+ PopGlobalSQLState();
+ return $name;
+}
+
+
# Determines whether or not a group is active by checking
# the "isactive" column for the group in the "groups" table.
-# Note: This function selects groups by bit rather than by name.
+# Note: This function selects groups by id rather than by name.
sub GroupIsActive {
- my ($groupbit) = (@_);
- $groupbit ||= 0;
+ my ($groupid) = (@_);
+ $groupid ||= 0;
PushGlobalSQLState();
- SendSQL("select isactive from groups where bit=$groupbit");
+ SendSQL("SELECT isactive FROM groups WHERE id=$groupid");
my $isactive = FetchOneColumn();
PopGlobalSQLState();
return $isactive;
@@ -1561,4 +1673,5 @@ $::vars =
'VERSION' => $Bugzilla::Config::VERSION,
};
+
1;
diff --git a/index.cgi b/index.cgi
index 42f493fcb..7e9fb5aab 100755
--- a/index.cgi
+++ b/index.cgi
@@ -45,7 +45,6 @@ use vars qw(
ConnectToDatabase();
# Check whether or not the user is logged in and, if so, set the $::userid
-# and $::usergroupset variables.
quietly_check_login();
###############################################################################
diff --git a/long_list.cgi b/long_list.cgi
index 6acee0332..6df8a8bad 100755
--- a/long_list.cgi
+++ b/long_list.cgi
@@ -26,7 +26,7 @@ use lib qw(.);
require "CGI.pl";
-use vars qw($userid $usergroupset @legal_keywords %FORM);
+use vars qw($userid @legal_keywords %FORM);
# Use global template variables.
use vars qw($template $vars);
@@ -69,8 +69,8 @@ my @bugs;
foreach my $bug_id (split(/[:,]/, $buglist)) {
detaint_natural($bug_id) || next;
- SendSQL(SelectVisible("$generic_query AND bugs.bug_id = $bug_id",
- $::userid, $::usergroupset));
+ CanSeeBug($bug_id, $::userid) || next;
+ SendSQL("$generic_query AND bugs.bug_id = $bug_id");
my %bug;
my @row = FetchSQLData();
diff --git a/post_bug.cgi b/post_bug.cgi
index 430ae37a8..22aa0e6bd 100755
--- a/post_bug.cgi
+++ b/post_bug.cgi
@@ -34,7 +34,6 @@ require "bug_form.pl";
sub sillyness {
my $zz;
$zz = $::buffer;
- $zz = $::usergroupset;
$zz = %::COOKIE;
$zz = %::components;
$zz = %::versions;
@@ -242,7 +241,7 @@ if ($::FORM{'keywords'} && UserInGroup("editbugs")) {
# Build up SQL string to add bug.
my $sql = "INSERT INTO bugs " .
- "(" . join(",", @used_fields) . ", reporter, creation_ts, groupset) " .
+ "(" . join(",", @used_fields) . ", reporter, creation_ts) " .
"VALUES (";
foreach my $field (@used_fields) {
@@ -255,14 +254,15 @@ $comment = trim($comment);
# OK except for the fact that it causes e-mail to be suppressed.
$comment = $comment ? $comment : " ";
-$sql .= "$::userid, now(), (0";
+$sql .= "$::userid, now() )";
# Groups
+my @groupstoadd = ();
foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) {
if ($::FORM{$b}) {
my $v = substr($b, 4);
$v =~ /^(\d+)$/
- || ThrowCodeError("group_bit_invalid", "abort");
+ || ThrowCodeError("group_id_invalid", "abort");
if (!GroupIsActive($v)) {
# Prevent the user from adding the bug to an inactive group.
# Should only happen if there is a bug in Bugzilla or the user
@@ -271,18 +271,22 @@ foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) {
$vars->{'bit'} = $v;
ThrowCodeError("inactive_group", "abort");
}
- $sql .= " + $v"; # Carefully written so that the math is
- # done by MySQL, which can handle 64-bit math,
- # and not by Perl, which I *think* can not.
+ SendSQL("SELECT user_id FROM user_group_map
+ WHERE user_id = $::userid
+ AND group_id = $v
+ AND isbless = 0");
+ my ($member) = FetchSQLData();
+ if ($member) {
+ push(@groupstoadd, $v)
+ }
}
}
-$sql .= ") & $::usergroupset)\n";
# Lock tables before inserting records for the new bug into the database
# if we are using a shadow database to prevent shadow database corruption
# when two bugs get created at the same time.
-SendSQL("LOCK TABLES bugs WRITE, longdescs WRITE, cc WRITE, profiles READ") if Param("shadowdb");
+SendSQL("LOCK TABLES bugs WRITE, bug_group_map WRITE, longdescs WRITE, cc WRITE, profiles READ") if Param("shadowdb");
# Add the bug report to the DB.
SendSQL($sql);
@@ -291,6 +295,12 @@ SendSQL($sql);
SendSQL("select LAST_INSERT_ID()");
my $id = FetchOneColumn();
+# Add the group restrictions
+foreach my $grouptoadd (@groupstoadd) {
+ SendSQL("INSERT INTO bug_group_map (bug_id, group_id)
+ VALUES ($id, $grouptoadd)");
+}
+
# Add the comment
SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext)
VALUES ($id, $::userid, now(), " . SqlQuote($comment) . ")");
diff --git a/process_bug.cgi b/process_bug.cgi
index f62285ffb..3468a9790 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -47,7 +47,6 @@ use vars qw(%versions
%settable_resolution
%target_milestone
%legal_severity
- %superusergroupset
$next_bug);
ConnectToDatabase();
@@ -143,7 +142,7 @@ if ( Param("usetargetmilestone") ) {
#
# This function checks if there is a comment required for a specific
# function and tests, if the comment was given.
-# If comments are required for functions is defined by params.
+# If comments are required for functions is defined by params.
#
sub CheckonComment( $ ) {
my ($function) = (@_);
@@ -410,10 +409,8 @@ sub DuplicateUserConfirm {
SendSQL("SELECT reporter FROM bugs WHERE bug_id = " . SqlQuote($dupe));
my $reporter = FetchOneColumn();
- SendSQL("SELECT profiles.groupset FROM profiles WHERE profiles.userid =".SqlQuote($reporter));
- my $reportergroupset = FetchOneColumn();
- if (CanSeeBug($original, $reporter, $reportergroupset)) {
+ if (CanSeeBug($original, $reporter)) {
$::FORM{'confirm_add_duplicate'} = "1";
return;
}
@@ -460,9 +457,9 @@ if (defined $::FORM{'id'}) {
CheckFormFieldDefined(\%::FORM, 'longdesclength');
}
-my $action = '';
+my $action = '';
if (defined $::FORM{action}) {
- $action = trim($::FORM{action});
+ $action = trim($::FORM{action});
}
if (Param("move-enabled") && $action eq Param("move-button-text")) {
$::FORM{'buglist'} = join (":", @idlist);
@@ -564,33 +561,27 @@ sub ChangeResolution {
# operations
# If the form element isn't present, or the user isn't in the group, leave
# it as-is
-if($::usergroupset ne '0') {
- my $groupAdd = "0";
- my $groupDel = "0";
-
- SendSQL("SELECT bit, isactive FROM groups WHERE " .
- "isbuggroup != 0 AND bit & $::usergroupset != 0 ORDER BY bit");
- while (my ($b, $isactive) = FetchSQLData()) {
- # The multiple change page may not show all groups a bug is in
- # (eg product groups when listing more than one product)
- # Only consider groups which were present on the form. We can't do this
- # for single bug changes because non-checked checkboxes aren't present.
- # All the checkboxes should be shown in that case, though, so its not
- # an issue there
- if ($::FORM{'id'} || exists $::FORM{"bit-$b"}) {
- if (!$::FORM{"bit-$b"}) {
- $groupDel .= "+$b";
- } elsif ($::FORM{"bit-$b"} == 1 && $isactive) {
- $groupAdd .= "+$b";
- }
+my @groupAdd = ();
+my @groupDel = ();
+
+SendSQL("SELECT groups.id, isactive FROM groups, user_group_map WHERE " .
+ "groups.id = user_group_map.group_id AND " .
+ "user_group_map.user_id = $::userid AND " .
+ "isbless = 0 AND isbuggroup = 1");
+while (my ($b, $isactive) = FetchSQLData()) {
+ # The multiple change page may not show all groups a bug is in
+ # (eg product groups when listing more than one product)
+ # Only consider groups which were present on the form. We can't do this
+ # for single bug changes because non-checked checkboxes aren't present.
+ # All the checkboxes should be shown in that case, though, so its not
+ # an issue there
+ if ($::FORM{'id'} || exists $::FORM{"bit-$b"}) {
+ if (!$::FORM{"bit-$b"}) {
+ push(@groupDel, $b);
+ } elsif ($::FORM{"bit-$b"} == 1 && $isactive) {
+ push(@groupAdd, $b);
}
}
- if ($groupAdd ne "0" || $groupDel ne "0") {
- DoComma();
- # mysql < 3.23.5 doesn't support the ~ operator, even though
- # the docs say that it does
- $::query .= "groupset = ((groupset & ($::superusergroupset - ($groupDel))) | ($groupAdd))";
- }
}
foreach my $field ("rep_platform", "priority", "bug_severity",
@@ -708,9 +699,9 @@ if (defined $::FORM{'qa_contact'}) {
# and cc list can see the bug even if they are not members of all groups
# to which the bug is restricted.
if ( $::FORM{'id'} ) {
- SendSQL("SELECT groupset FROM bugs WHERE bug_id = $::FORM{'id'}");
- my ($groupset) = FetchSQLData();
- if ( $groupset ) {
+ SendSQL("SELECT group_id FROM bug_group_map WHERE bug_id = $::FORM{'id'}");
+ my ($havegroup) = FetchSQLData();
+ if ( $havegroup ) {
DoComma();
$::FORM{'reporter_accessible'} = $::FORM{'reporter_accessible'} ? '1' : '0';
$::query .= "reporter_accessible = $::FORM{'reporter_accessible'}";
@@ -1047,6 +1038,8 @@ foreach my $id (@idlist) {
"profiles $write, dependencies $write, votes $write, " .
"products READ, components READ, " .
"keywords $write, longdescs $write, fielddefs $write, " .
+ "bug_group_map $write, " .
+ "user_group_map READ, " .
"keyworddefs READ, groups READ, attachments READ");
my @oldvalues = SnapShotBug($id);
my %oldhash;
@@ -1206,9 +1199,29 @@ foreach my $id (@idlist) {
if ($::comma ne "") {
SendSQL($query);
}
+ my @groupAddNames = ();
+ foreach my $grouptoadd (@groupAdd) {
+ if (!BugInGroupId($id, $grouptoadd)) {
+ push(@groupAddNames, GroupIdToName($grouptoadd));
+ SendSQL("INSERT INTO bug_group_map (bug_id, group_id)
+ VALUES ($id, $grouptoadd)");
+ }
+ }
+ my @groupDelNames = ();
+ foreach my $grouptodel (@groupDel) {
+ if (BugInGroupId($id, $grouptodel)) {
+ push(@groupDelNames, GroupIdToName($grouptodel));
+ }
+ SendSQL("DELETE FROM bug_group_map
+ WHERE bug_id = $id AND group_id = $grouptodel");
+ }
SendSQL("select now()");
$timestamp = FetchOneColumn();
-
+
+ my $groupDelNames = join(',', @groupDelNames);
+ my $groupAddNames = join(',', @groupAddNames);
+
+ LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames);
if (defined $::FORM{'comment'}) {
AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'},
$::FORM{'commentprivacy'});
@@ -1322,7 +1335,7 @@ foreach my $id (@idlist) {
# the user wants to add the bug to the new product's group;
($::FORM{'addtonewgroup'} eq 'yes'
|| ($::FORM{'addtonewgroup'} eq 'yesifinold'
- && GroupNameToBit($oldhash{'product'}) & $oldhash{'groupset'}))
+ && BugInGroup($id, $oldhash{'product'})))
# the new product is associated with a group;
&& GroupExists($::FORM{'product'})
@@ -1344,14 +1357,16 @@ foreach my $id (@idlist) {
&& (UserInGroup($::FORM{'product'}) || !Param('usebuggroupsentry'))
# the associated group is active, indicating it can accept new bugs;
- && GroupIsActive(GroupNameToBit($::FORM{'product'}))
+ && GroupIsActive(GroupNameToId($::FORM{'product'}))
) {
# Add the bug to the group associated with its new product.
- my $groupbit = GroupNameToBit($::FORM{'product'});
- SendSQL("UPDATE bugs SET groupset = groupset + $groupbit WHERE bug_id = $id");
+ my $groupid = GroupNameToId($::FORM{'product'});
+ if (!BugInGroupId($id, $groupid)) {
+ SendSQL("INSERT INTO bug_group_map (bug_id, group_id) VALUES ($id, $groupid)");
+ }
}
- if (
+ if (
# the old product is associated with a group;
GroupExists($oldhash{'product'})
@@ -1359,8 +1374,8 @@ foreach my $id (@idlist) {
&& BugInGroup($id, $oldhash{'product'})
) {
# Remove the bug from the group associated with its old product.
- my $groupbit = GroupNameToBit($oldhash{'product'});
- SendSQL("UPDATE bugs SET groupset = groupset - $groupbit WHERE bug_id = $id");
+ my $groupid = GroupNameToId($oldhash{'product'});
+ SendSQL("DELETE FROM bug_group_map WHERE bug_id = $id AND group_id = $groupid");
}
}
@@ -1523,7 +1538,7 @@ if ($::COOKIE{"BUGLIST"} && $::FORM{'id'}) {
my $cur = lsearch(\@bugs, $::FORM{"id"});
if ($cur >= 0 && $cur < $#bugs) {
my $next_bug = $bugs[$cur + 1];
- if (detaint_natural($next_bug) && CanSeeBug($next_bug)) {
+ if (detaint_natural($next_bug) && CanSeeBug($next_bug, $::userid)) {
$::FORM{'id'} = $next_bug;
$vars->{'next_id'} = $next_bug;
diff --git a/processmail b/processmail
index 45aaacc77..a47297597 100755
--- a/processmail
+++ b/processmail
@@ -632,14 +632,17 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) {
}
- SendSQL("SELECT userid, groupset " .
+ SendSQL("SELECT userid, (refreshed_when > " . SqlQuote($::last_changed) . ") " .
"FROM profiles WHERE login_name = " . SqlQuote($person));
- my ($userid, $groupset) = (FetchSQLData());
+ my ($userid, $current) = (FetchSQLData());
$seen{$person} = 1;
detaint_natural($userid);
- detaint_natural($groupset);
+
+ if (!$current) {
+ DeriveGroup($userid);
+ }
# if this person doesn't have permission to see info on this bug,
# return.
@@ -649,19 +652,13 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) {
# see the action of restricting the bug itself; the bug will just
# quietly disappear from their radar.
#
- return unless CanSeeBug($id, $userid, $groupset);
+ return unless CanSeeBug($id, $userid);
+
# Drop any non-insiders if the comment is private
- if (Param("insidergroup") && ($anyprivate != 0)) {
- ConnectToDatabase();
- PushGlobalSQLState();
- SendSQL("select (bit & $groupset ) != 0 from groups where name = " . SqlQuote(Param("insidergroup")));
- my $bit = FetchOneColumn();
- PopGlobalSQLState();
- if (!$bit) {
- return;
- }
- }
+ return if (Param("insidergroup") &&
+ ($anyprivate != 0) &&
+ (!UserInGroup(Param("insidergroup"), $userid)));
# We shouldn't send changedmail if this is a dependency mail, and any of
# the depending bugs is not visible to the user.
@@ -669,7 +666,7 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) {
my $save_id = $dep_id;
detaint_natural($dep_id) || warn("Unexpected Error: \@depbugs contains a non-numeric value: '$save_id'")
&& return;
- return unless CanSeeBug($dep_id, $userid, $groupset);
+ return unless CanSeeBug($dep_id, $userid);
}
my %mailhead = %defmailhead;
@@ -781,6 +778,14 @@ if (open(FID, "<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;
diff --git a/query.cgi b/query.cgi
index e038d98ae..cd679e8e8 100755
--- a/query.cgi
+++ b/query.cgi
@@ -50,19 +50,19 @@ use vars qw(
);
ConnectToDatabase();
-
+my $userid = 0;
if (defined $::FORM{"GoAheadAndLogIn"}) {
# We got here from a login page, probably from relogin.cgi. We better
# make sure the password is legit.
- confirm_login();
+ $userid = confirm_login();
} else {
- quietly_check_login();
+ $userid = quietly_check_login();
}
# Backwards compatibility hack -- if there are any of the old QUERY_*
# cookies around, and we are logged in, then move them into the database
# and nuke the cookie. This is required for Bugzilla 2.8 and earlier.
-if ($::userid) {
+if ($userid) {
my @oldquerycookies;
foreach my $i (keys %::COOKIE) {
if ($i =~ /^QUERY_(.*)$/) {
@@ -79,12 +79,12 @@ if ($::userid) {
if ($value) {
my $qname = SqlQuote($name);
SendSQL("SELECT query FROM namedqueries " .
- "WHERE userid = $::userid AND name = $qname");
+ "WHERE userid = $userid AND name = $qname");
my $query = FetchOneColumn();
if (!$query) {
SendSQL("REPLACE INTO namedqueries " .
"(userid, name, query) VALUES " .
- "($::userid, $qname, " . SqlQuote($value) . ")");
+ "($userid, $qname, " . SqlQuote($value) . ")");
}
}
print "Set-Cookie: $cookiename= ; path=" . Param("cookiepath") .
@@ -94,17 +94,17 @@ if ($::userid) {
}
if ($::FORM{'nukedefaultquery'}) {
- if ($::userid) {
+ if ($userid) {
SendSQL("DELETE FROM namedqueries " .
- "WHERE userid = $::userid AND name = '$::defaultqueryname'");
+ "WHERE userid = $userid AND name = '$::defaultqueryname'");
}
$::buffer = "";
}
my $userdefaultquery;
-if ($::userid) {
+if ($userid) {
SendSQL("SELECT query FROM namedqueries " .
- "WHERE userid = $::userid AND name = '$::defaultqueryname'");
+ "WHERE userid = $userid AND name = '$::defaultqueryname'");
$userdefaultquery = FetchOneColumn();
}
@@ -285,7 +285,7 @@ $vars->{'rep_platform'} = \@::legal_platform;
$vars->{'op_sys'} = \@::legal_opsys;
$vars->{'priority'} = \@::legal_priority;
$vars->{'bug_severity'} = \@::legal_severity;
-$vars->{'userid'} = $::userid;
+$vars->{'userid'} = $userid;
# Boolean charts
my @fields;
@@ -332,10 +332,10 @@ for (my $chart = 0; $::FORM{"field$chart-0-0"}; $chart++) {
$default{'charts'} = \@charts;
# Named queries
-if ($::userid) {
+if ($userid) {
my @namedqueries;
SendSQL("SELECT name FROM namedqueries " .
- "WHERE userid = $::userid AND name != '$::defaultqueryname' " .
+ "WHERE userid = $userid AND name != '$::defaultqueryname' " .
"ORDER BY name");
while (MoreSQLData()) {
push(@namedqueries, FetchOneColumn());
diff --git a/sanitycheck.cgi b/sanitycheck.cgi
index 451ad23da..da71163cc 100755
--- a/sanitycheck.cgi
+++ b/sanitycheck.cgi
@@ -109,6 +109,17 @@ sub CrossCheck {
}
}
+sub DateCheck {
+ my $table = shift @_;
+ my $field = shift @_;
+ Status("Checking dates in $table.$field");
+ SendSQL("SELECT COUNT( $field ) FROM $table WHERE $field > NOW()");
+ my $c = FetchOneColumn();
+ if ($c) {
+ Alert("Found $c dates in future");
+ }
+}
+
my @badbugs;
@@ -139,6 +150,57 @@ if (exists $::FORM{'rebuildvotecache'}) {
Status("Vote cache has been rebuilt.");
}
+if (exists $::FORM{'rederivegroups'}) {
+ Status("OK, All users' inherited permissions will be rechecked when " .
+ "they next access Bugzilla.");
+ SendSQL("UPDATE groups SET last_changed = NOW() LIMIT 1");
+}
+
+# rederivegroupsnow is REALLY only for testing.
+if (exists $::FORM{'rederivegroupsnow'}) {
+ Status("OK, now rederiving groups.");
+ SendSQL("SELECT userid FROM profiles");
+ while ((my $id) = FetchSQLData()) {
+ DeriveGroup($id);
+ Status("Group $id");
+ }
+}
+
+if (exists $::FORM{'cleangroupsnow'}) {
+ Status("OK, now cleaning stale groups.");
+ # Only users that were out of date already long ago should be cleaned
+ # and the cleaning is done with tables locked. This is require in order
+ # to keep another session from proceeding with permission checks
+ # after the groups have been cleaned unless it first had an opportunity
+ # to get the groups up to date.
+ # If any page starts taking longer than one hour to load, this interval
+ # should be revised.
+ SendSQL("SELECT MAX(last_changed) FROM groups WHERE last_changed < NOW() - INTERVAL 1 HOUR");
+ (my $cutoff) = FetchSQLData();
+ Status("Cutoff is $cutoff");
+ SendSQL("SELECT COUNT(*) FROM user_group_map");
+ (my $before) = FetchSQLData();
+ SendSQL("LOCK TABLES user_group_map WRITE, profiles WRITE");
+ SendSQL("SELECT userid FROM profiles " .
+ "WHERE refreshed_when > 0 " .
+ "AND refreshed_when < " . SqlQuote($cutoff) .
+ " LIMIT 1000");
+ my $count = 0;
+ while ((my $id) = FetchSQLData()) {
+ $count++;
+ PushGlobalSQLState();
+ SendSQL("DELETE FROM user_group_map WHERE " .
+ "user_id = $id AND isderived = 1 AND isbless = 0");
+ SendSQL("UPDATE profiles SET refreshed_when = 0 WHERE userid = $id");
+ PopGlobalSQLState();
+ }
+ SendSQL("UNLOCK TABLES");
+ SendSQL("SELECT COUNT(*) FROM user_group_map");
+ (my $after) = FetchSQLData();
+ Status("Cleaned table for $count users " .
+ "- reduced from $before records to $after records");
+}
+
print "OK, now running sanity checks.<p>\n";
# This one goes first, because if this is wrong, then the below tests
@@ -178,6 +240,7 @@ CrossCheck("attachstatusdefs", "id",
CrossCheck("bugs", "bug_id",
["bugs_activity", "bug_id"],
+ ["bug_group_map", "bug_id"],
["attachments", "bug_id"],
["cc", "bug_id"],
["longdescs", "bug_id"],
@@ -188,6 +251,12 @@ CrossCheck("bugs", "bug_id",
["duplicates", "dupe_of", "dupe"],
["duplicates", "dupe", "dupe_of"]);
+CrossCheck("groups", "id",
+ ["bug_group_map", "group_id"],
+ ["group_group_map", "grantor_id"],
+ ["group_group_map", "member_id"],
+ ["user_group_map", "group_id"]);
+
CrossCheck("profiles", "userid",
["bugs", "reporter", "bug_id"],
["bugs", "assigned_to", "bug_id"],
@@ -203,6 +272,7 @@ CrossCheck("profiles", "userid",
["watch", "watched"],
["tokens", "userid"],
["components", "initialowner", "name"],
+ ["user_group_map", "user_id"],
["components", "initialqacontact", "name", ["0"]]);
CrossCheck("products", "id",
@@ -212,25 +282,9 @@ CrossCheck("products", "id",
["versions", "product_id", "value"],
["attachstatusdefs", "product_id", "name"]);
-###########################################################################
-# Perform group checks
-###########################################################################
+DateCheck("groups", "last_changed");
+DateCheck("profiles", "refreshed_when");
-Status("Checking groups");
-SendSQL("select bit from groups where bit != pow(2, round(log(bit) / log(2)))");
-while (my $bit = FetchOneColumn()) {
- Alert("Illegal bit number found in group table: $bit");
-}
-
-SendSQL("select sum(bit) from groups where isbuggroup != 0");
-my $buggroupset = FetchOneColumn();
-if (!defined $buggroupset || $buggroupset eq "") {
- $buggroupset = 0;
-}
-SendSQL("select bug_id, groupset from bugs where groupset & $buggroupset != groupset");
-while (@row = FetchSQLData()) {
- Alert("Bad groupset $row[1] found in bug " . BugLink($row[0]));
-}
###########################################################################
# Perform product specific field checks
diff --git a/show_activity.cgi b/show_activity.cgi
index f6d9b75c9..14b4149ba 100755
--- a/show_activity.cgi
+++ b/show_activity.cgi
@@ -35,10 +35,7 @@ ConnectToDatabase();
# Begin Data/Security Validation
###############################################################################
-# Check whether or not the user is currently logged in. This function
-# sets the value of $::usergroupset, the binary number that records
-# the set of groups to which the user belongs and which we can use
-# to determine whether or not the user is authorized to access this bug.
+# Check whether or not the user is currently logged in.
quietly_check_login();
# Make sure the bug ID is a positive integer representing an existing
diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi
index e5885d180..021150bf0 100755
--- a/showdependencygraph.cgi
+++ b/showdependencygraph.cgi
@@ -31,7 +31,7 @@ ConnectToDatabase();
quietly_check_login();
-use vars qw($template $vars $userid $usergroupset);
+use vars qw($template $vars $userid);
my %seen;
my %edgesdone;
@@ -128,13 +128,13 @@ foreach my $k (keys(%seen)) {
my $summary = "";
my $stat;
if ($::FORM{'showsummary'}) {
- SendSQL(SelectVisible("SELECT bug_status, short_desc FROM bugs " .
- "WHERE bugs.bug_id = $k",
- $::userid,
- $::usergroupset));
- ($stat, $summary) = FetchSQLData();
- $stat = "NEW" if !defined $stat;
- $summary = "" if !defined $summary;
+ if (CanSeeBug($k, $::userid)) {
+ SendSQL("SELECT bug_status, short_desc FROM bugs " .
+ "WHERE bugs.bug_id = $k");
+ ($stat, $summary) = FetchSQLData();
+ $stat = "NEW" if !defined $stat;
+ $summary = "" if !defined $summary;
+ }
} else {
SendSQL("SELECT bug_status FROM bugs WHERE bug_id = $k");
$stat = FetchOneColumn();
diff --git a/showdependencytree.cgi b/showdependencytree.cgi
index 7917f00fe..e2e9d52b0 100755
--- a/showdependencytree.cgi
+++ b/showdependencytree.cgi
@@ -39,7 +39,6 @@ quietly_check_login();
# More warning suppression silliness.
$::userid = $::userid;
-$::usergroupset = $::usergroupset;
################################################################################
# Data/Security Validation #
@@ -144,7 +143,9 @@ sub GetBug {
# and returns it to the calling code.
my ($id) = @_;
- SendSQL(SelectVisible("SELECT 1,
+ my $bug = {};
+ if (CanSeeBug($id, $::userid)) {
+ SendSQL("SELECT 1,
bug_status,
short_desc,
$milestone_column,
@@ -152,18 +153,16 @@ sub GetBug {
assignee.login_name
FROM bugs, profiles AS assignee
WHERE bugs.bug_id = $id
- AND bugs.assigned_to = assignee.userid",
- $::userid,
- $::usergroupset));
+ AND bugs.assigned_to = assignee.userid");
- my $bug = {};
- ($bug->{'exists'},
- $bug->{'status'},
- $bug->{'summary'},
- $bug->{'milestone'},
- $bug->{'assignee_id'},
- $bug->{'assignee_email'}) = FetchSQLData();
+ ($bug->{'exists'},
+ $bug->{'status'},
+ $bug->{'summary'},
+ $bug->{'milestone'},
+ $bug->{'assignee_id'},
+ $bug->{'assignee_email'}) = FetchSQLData();
+ }
$bug->{'open'} = IsOpenedState($bug->{'status'});
$bug->{'dependencies'} = [];
diff --git a/sidebar.cgi b/sidebar.cgi
index d3692b16d..ec021ea1c 100755
--- a/sidebar.cgi
+++ b/sidebar.cgi
@@ -36,11 +36,11 @@ quietly_check_login();
$vars->{'username'} = $::COOKIE{'Bugzilla_login'} || '';
if (defined $::COOKIE{'Bugzilla_login'}) {
- SendSQL("SELECT mybugslink, userid, blessgroupset FROM profiles " .
+ SendSQL("SELECT mybugslink, userid FROM profiles " .
"WHERE login_name = " . SqlQuote($::COOKIE{'Bugzilla_login'}));
- my ($mybugslink, $userid, $blessgroupset) = (FetchSQLData());
+ my ($mybugslink, $userid) = (FetchSQLData());
$vars->{'userid'} = $userid;
- $vars->{'blessgroupset'} = $blessgroupset;
+ $vars->{'canblessanything'} = UserCanBlessAnything();
if ($mybugslink) {
my $mybugstemplate = Param("mybugstemplate");
my %substs = ( 'userid' => url_quote($::COOKIE{'Bugzilla_login'}) );
diff --git a/template/en/default/account/prefs/permissions.html.tmpl b/template/en/default/account/prefs/permissions.html.tmpl
index 15bca6deb..064220d6e 100644
--- a/template/en/default/account/prefs/permissions.html.tmpl
+++ b/template/en/default/account/prefs/permissions.html.tmpl
@@ -20,36 +20,48 @@
#%]
[%# INTERFACE:
- # has_bits: array of strings. May be empty.
- # Descriptions of the permission bits the user has.
- # set_bits: array of strings. May be empty.
- # Descriptions of the permission bits the user can set for
+ # has_bits: array of hashes. May be empty.
+ # name => Names of the permissions the user has.
+ # desc => Descriptions of the permissions the user has.
+ # set_bits: array of hashes. May be empty.
+ # name => Names of the permissions the user can set for
+ # other people.
+ # desc => Descriptions of the permissions the user can set for
# other people.
#%]
-<table>
+<table align="center">
<tr>
<td>
[% IF has_bits.size %]
You have the following permission bits set on your account:
- <ul>
+ <p>
+ <br>
+ <table align="center">
[% FOREACH bit_description = has_bits %]
- <li>[% bit_description %]</li>
+ <tr>
+ <td>[% bit_description.name %]</td>
+ <td>[% bit_description.desc %]</td>
+ </tr>
[% END %]
- </ul>
+ </table>
[% ELSE %]
There are no permission bits set on your account.
[% END %]
[% IF set_bits.size %]
+ <br>
And you can turn on or off the following bits for
<a href="editusers.cgi">other users</a>:
<p>
- <ul>
+ <table align="center">
[% FOREACH bit_description = set_bits %]
- <li>[% bit_description %]</li>
+ <tr>
+ <td>[% bit_description.name %]</td>
+ <td>[% bit_description.desc %]</td>
+ </tr>
[% END %]
- </ul>
+ </table>
</p>
[% END %]
</td>
diff --git a/template/en/default/global/useful-links.html.tmpl b/template/en/default/global/useful-links.html.tmpl
index 987ca370b..1e5b09df1 100644
--- a/template/en/default/global/useful-links.html.tmpl
+++ b/template/en/default/global/useful-links.html.tmpl
@@ -65,7 +65,7 @@
[% ', <a href="editparams.cgi">parameters</a>'
IF user.groups.tweakparams %]
[% ', <a href="editusers.cgi">users</a>' IF user.groups.editusers
- || (user.blessgroupset > 0) %]
+ || user.canblessany %]
[% ', <a href="editproducts.cgi">products</a>'
IF user.groups.editcomponents %]
[% ', <a href="editattachstatuses.cgi"> attachment&nbsp;statuses</a>'
diff --git a/template/en/default/sidebar.xul.tmpl b/template/en/default/sidebar.xul.tmpl
index ad7158b13..21a07c998 100644
--- a/template/en/default/sidebar.xul.tmpl
+++ b/template/en/default/sidebar.xul.tmpl
@@ -73,7 +73,7 @@ function normal_keypress_handler( aEvent ) {
[%- IF UserInGroup('tweakparams') %]
<text class="text-link" onclick="load_relative_url('editparams.cgi')" value="edit params"/>
[%- END %]
- [%- IF UserInGroup('editusers') || blessgroupset %]
+ [%- IF UserInGroup('editusers') || canblessany %]
<text class="text-link" onclick="load_relative_url('editusers.cgi')" value="edit users"/>
[%- END %]
[%- IF UserInGroup('editcomponents') %]
diff --git a/token.cgi b/token.cgi
index 43ee1b04e..86e51939d 100755
--- a/token.cgi
+++ b/token.cgi
@@ -265,6 +265,7 @@ sub changeEmail {
SendSQL("DELETE FROM tokens WHERE userid = $userid
AND tokentype = 'emailnew'");
SendSQL("UNLOCK TABLES");
+ DeriveGroup($userid);
# Return HTTP response headers.
print "Content-Type: text/html\n\n";
@@ -300,6 +301,7 @@ sub cancelChangeEmail {
SET login_name = $quotedoldemail
WHERE userid = $userid");
SendSQL("UNLOCK TABLES");
+ DeriveGroup($userid);
$vars->{'message'} .=
" Your old account settings have been reinstated.";
}
diff --git a/userprefs.cgi b/userprefs.cgi
index 808aebf40..369c681ca 100755
--- a/userprefs.cgi
+++ b/userprefs.cgi
@@ -33,7 +33,6 @@ use RelationSet;
sub sillyness {
my $zz;
$zz = $::defaultqueryname;
- $zz = $::usergroupset;
}
# Use global template variables.
@@ -331,21 +330,22 @@ sub SaveFooter {
sub DoPermissions {
my (@has_bits, @set_bits);
- SendSQL("SELECT description FROM groups " .
- "WHERE bit & $::usergroupset != 0 " .
- "ORDER BY bit");
+ SendSQL("SELECT DISTINCT name, description FROM groups, user_group_map " .
+ "WHERE user_group_map.group_id = groups.id " .
+ "AND user_id = $::userid " .
+ "AND isbless = 0 " .
+ "ORDER BY name");
while (MoreSQLData()) {
- push(@has_bits, FetchSQLData());
+ my ($nam, $desc) = FetchSQLData();
+ push(@has_bits, {"desc" => $desc, "name" => $nam});
}
-
- SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $userid");
- my $blessgroupset = FetchOneColumn();
- if ($blessgroupset) {
- SendSQL("SELECT description FROM groups " .
- "WHERE bit & $blessgroupset != 0 " .
- "ORDER BY bit");
- while (MoreSQLData()) {
- push(@set_bits, FetchSQLData());
+ my @set_ids = ();
+ SendSQL("SELECT DISTINCT name, description FROM groups " .
+ "ORDER BY name");
+ while (MoreSQLData()) {
+ my ($nam, $desc) = FetchSQLData();
+ if (UserCanBlessGroup($nam)) {
+ push(@set_bits, {"desc" => $desc, "name" => $nam});
}
}
diff --git a/votes.cgi b/votes.cgi
index 88e303971..3bfe11682 100755
--- a/votes.cgi
+++ b/votes.cgi
@@ -28,7 +28,6 @@ use lib ".";
require "CGI.pl";
-use vars qw($usergroupset);
# Use global template variables
use vars qw($template $vars);
@@ -188,7 +187,7 @@ sub show_user {
# and they can see there are votes 'missing', but not on what bug
# they are. This seems a reasonable compromise; the alternative is
# to lie in the totals.
- next if !CanSeeBug($id, $who, $usergroupset);
+ next if !CanSeeBug($id, $who);
push (@bugs, { id => $id,
summary => $summary,