#!/usr/bin/perl -wT # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is mozilla.org code. # # The Initial Developer of the Original Code is Holger # Schurig. Portions created by Holger Schurig are # Copyright (C) 1999 Holger Schurig. All # Rights Reserved. # # Contributor(s): Holger Schurig # Dave Miller # Joe Robins # Dan Mosedale # Joel Peshkin # Erik Stambaugh # # Direct any questions on this source code to # # Holger Schurig use strict; use lib "."; require "CGI.pl"; require "globals.pl"; use Bugzilla; use Bugzilla::User; use Bugzilla::Constants; use Bugzilla::Auth; # Shut up misguided -w warnings about "used only once". "use vars" just # doesn't work for me. sub sillyness { my $zz; $zz = $::userid; } my $editall; # TestUser: just returns if the specified user does exists # CheckUser: same check, optionally emit an error text sub TestUser ($) { my $user = shift; # does the product exist? SendSQL("SELECT login_name FROM profiles WHERE login_name=" . SqlQuote($user)); return FetchOneColumn(); } sub CheckUser ($) { my $user = shift; # do we have a user? unless ($user) { print "Sorry, you haven't specified a user."; PutTrailer(); exit; } unless (TestUser $user) { print "Sorry, user '$user' does not exist."; PutTrailer(); exit; } } sub EmitElement ($$) { my ($name, $value) = (@_); $value = value_quote($value); if ($editall) { print qq{\n}; } else { print qq{$value\n}; } } # # Displays the form to edit a user parameters # sub EmitFormElements ($$$$) { my ($user_id, $user, $realname, $disabledtext) = @_; print " Login name:\n"; EmitElement("user", $user); print "\n"; print " Real name:\n"; EmitElement("realname", $realname); if ($editall) { print "\n"; print " Password:\n"; print qq|
(enter new password to change) |; print "\n"; print " Disable text:\n"; print " \n"; print " \n"; print "\n"; print " If non-empty, then the account will\n"; print "be disabled, and this text should explain why.\n"; } if($user ne "") { print "Group Access:"; SendSQL("SELECT groups.id, groups.name, groups.description, " . "MAX(CASE WHEN grant_type = " . GRANT_DIRECT . " THEN 1 ELSE 0 END)," . "MAX(CASE WHEN grant_type = " . GRANT_DERIVED . " THEN 1 ELSE 0 END)," . "MAX(CASE WHEN grant_type = " . GRANT_REGEXP . " THEN 1 ELSE 0 END)" . "FROM groups " . "LEFT JOIN user_group_map " . "ON user_group_map.group_id = groups.id " . "AND isbless = 0 " . "AND user_id = $user_id " . "GROUP BY groups.name "); if (MoreSQLData()) { if ($editall) { print "\n"; print "\n\n"; } print "\n"; while (MoreSQLData()) { my ($groupid, $name, $description, $checked, $isderived, $isregexp) = FetchSQLData(); next unless ($editall || UserCanBlessGroup($name)); 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.grant_type = " . GROUP_BLESS . " AND user_group_map.group_id = member_id"); my $derivedbless = FetchOneColumn(); PopGlobalSQLState(); print "\n"; print "\n"; print "\n"; if ($editall) { $blchecked = ($blchecked) ? "CHECKED" : ""; print "\n"; } $checked = ($checked) ? "CHECKED" : ""; print "\n"; } } print "
Can turn this bit on for other users
|User is a member of these groups
"; print "[" if $derivedbless; print ""; print "]" if $derivedbless; print ""; print '[' if ($isderived); print '*' if ($isregexp); print ""; print ']' if ($isderived); print '*' if ($isregexp); print ""; print ucfirst($name) . ": $description
\n"; } } # # Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d." # sub PutTrailer (@) { my (@links) = ("Back to the index"); if($editall) { push(@links, "add a new user"); } push(@links, @_); my $count = $#links; my $num = 0; print "

\n"; foreach (@links) { print $_; if ($num == $count) { print ".\n"; } elsif ($num == $count-1) { print " or "; } else { print ", "; } $num++; } PutFooter(); } # # Preliminary checks: # Bugzilla->login(LOGIN_REQUIRED); print Bugzilla->cgi->header(); $editall = UserInGroup("editusers"); $editall || Bugzilla->user->can_bless || ThrowUserError("auth_failure", {group => "editusers", reason => "cant_bless", action => "edit", object => "users"}); # # often used variables # my $user = trim($::FORM{user} || ''); my $action = trim($::FORM{action} || ''); my $localtrailer = 'edit more users'; my $candelete = Param('allowuserdeletion'); my $dbh = Bugzilla->dbh; # # action='' -> Ask for match string for users. # unless ($action) { PutHeader("Select match string"); print qq{

List users with login name matching:
}; PutTrailer(); exit; } # # action='list' -> Show nice list of matching users # if ($action eq 'list') { PutHeader("Select user"); my $query = ""; my $matchstr = $::FORM{'matchstr'}; if (exists $::FORM{'matchtype'}) { $query = "SELECT login_name,realname,disabledtext " . "FROM profiles WHERE login_name "; if ($::FORM{'matchtype'} eq 'substr') { $query .= "like"; $matchstr = '%' . $matchstr . '%'; } elsif ($::FORM{'matchtype'} eq 'regexp') { $query .= $dbh->sql_regexp(); $matchstr = '.' unless $matchstr; } elsif ($::FORM{'matchtype'} eq 'notregexp') { $query .= $dbh->sql_not_regexp(); $matchstr = '.' unless $matchstr; } else { die "Unknown match type"; } $query .= SqlQuote($matchstr) . " ORDER BY login_name"; } elsif (exists $::FORM{'group'}) { detaint_natural($::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"; } SendSQL($query); my $count = 0; my $header = " "; if ($candelete) { $header .= "\n"; } $header .= "\n"; print $header; while ( MoreSQLData() ) { $count++; if ($count % 100 == 0) { print "
Edit user ... Real nameAction
$header"; } my ($user, $realname, $disabledtext) = FetchSQLData(); my $s = ""; my $e = ""; if ($disabledtext) { $s = ''; $e = ''; } $realname = ($realname ? html_quote($realname) : "missing"); print "\n"; print " $s", html_quote($user), "$e\n"; print " $s$realname$e\n"; if ($candelete) { print " Delete\n"; } print ""; } if ($editall) { print "\n"; my $span = $candelete ? 3 : 2; print qq{ add a new user }; print ""; } print "\n"; print "$count users found.\n"; PutTrailer($localtrailer); exit; } # # action='add' -> present form for parameters for new user # # (next action will be 'new') # if ($action eq 'add') { $editall || ThrowUserError("auth_failure", {group => "editusers", action => "add", object => "users"}); PutHeader("Add user"); print "
\n"; print "\n"; EmitFormElements(0, '', '', ''); print "
\n
\n"; print "\n"; print "\n"; print "
"; my $other = $localtrailer; $other =~ s/more/other/; PutTrailer($other); exit; } # # action='new' -> add user entered in the 'action=add' screen # if ($action eq 'new') { $editall || ThrowUserError("auth_failure", {group => "editusers", action => "add", object => "users"}); # Cleanups and valididy checks my $realname = trim($::FORM{realname} || ''); # We don't trim the password since that could falsely lead the user # to believe a password with a space was accepted even though a space # is an illegal character in a Bugzilla password. my $password = $::FORM{'password'}; my $disabledtext = trim($::FORM{disabledtext} || ''); my $emailregexp = Param("emailregexp"); PutHeader("Adding new user"); unless ($user) { print "You must enter a name for the new user. Please press\n"; print "Back and try again.\n"; PutTrailer($localtrailer); exit; } unless ($user =~ m/$emailregexp/) { print "The user name entered must be a valid e-mail address. Please press\n"; print "Back and try again.\n"; PutTrailer($localtrailer); exit; } if (!is_available_username($user)) { print "The user '$user' does already exist. Please press\n"; print "Back and try again.\n"; PutTrailer($localtrailer); exit; } my $passworderror = ValidatePassword($password); if ( $passworderror ) { print $passworderror; PutTrailer($localtrailer); exit; } # Add the new user SendSQL("INSERT INTO profiles ( " . "login_name, cryptpassword, realname, " . "emailflags, disabledtext" . " ) VALUES ( " . SqlQuote($user) . "," . SqlQuote(bz_crypt($password)) . "," . SqlQuote($realname) . "," . SqlQuote(Bugzilla::Constants::DEFAULT_EMAIL_SETTINGS) . "," . SqlQuote($disabledtext) . ")" ); #+++ send e-mail away print "OK, done.
\n"; my $newuserid = $dbh->bz_last_key('profiles', 'userid'); my $changeduser = new Bugzilla::User($newuserid); $changeduser->derive_groups(); print "To change ${user}'s permissions, go back and " . "edit this user."; print "

\n"; PutTrailer($localtrailer); exit; } # # action='del' -> ask if user really wants to delete # # (next action would be 'delete') # if ($action eq 'del') { $candelete || ThrowUserError("users_deletion_disabled"); $editall || ThrowUserError("auth_failure", {group => "editusers", action => "delete", object => "users"}); CheckUser($user); # display some data about the user SendSQL("SELECT userid, realname FROM profiles WHERE login_name=" . SqlQuote($user)); my ($thisuserid, $realname) = FetchSQLData(); $realname = ($realname ? html_quote($realname) : "missing"); PutHeader("Delete user $user"); print "\n"; print "\n"; print " \n"; print " \n"; print "\n"; print " \n"; print " \n"; print "\n"; print " \n"; print " \n"; print "\n"; print " \n"; print " \n"; # Check if the user is an initialowner my $nodelete = ''; SendSQL("SELECT products.name, components.name " . "FROM products, components " . "WHERE products.id = components.product_id " . " AND initialowner=" . login_to_id($user)); $found = 0; while (MoreSQLData()) { if ($found) { print "
\n"; } else { print "\n"; print " \n"; print " \n" if $found; # Check if the user is an initialqacontact SendSQL("SELECT products.name, components.name " . "FROM products, components " . "WHERE products.id = components.product_id " . " AND initialqacontact=" . login_to_id($user)); $found = 0; while (MoreSQLData()) { if ($found) { print "
\n"; } else { print "\n"; print " \n"; print " \n" if $found; print "
PartValue
Login name:$user
Real name:$realname
Group set:"; SendSQL("SELECT 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(); print "
\n" if $found; print ucfirst $name; $found = 1; } print "none" unless $found; print "
Initial owner:"; } my ($product, $component) = FetchSQLData(); print "$product: $component"; $found = 1; $nodelete = 'initial bug owner'; } print "
Initial QA contact:"; } my ($product, $component) = FetchSQLData(); print "$product: $component"; $found = 1; $nodelete = 'initial QA contact'; } print "
\n"; if ($nodelete) { print "

You can't delete this user because '$user' is an $nodelete ", "for at least one product."; PutTrailer($localtrailer); exit; } print "

Confirmation

\n"; print "

Do you really want to delete this user?

\n"; print "

\n"; print "\n"; print "\n"; print "\n"; print "
"; PutTrailer($localtrailer); exit; } # # action='delete' -> really delete the user # if ($action eq 'delete') { $candelete || ThrowUserError("users_deletion_disabled"); $editall || ThrowUserError("auth_failure", {group => "editusers", action => "delete", object => "users"}); CheckUser($user); SendSQL("SELECT userid FROM profiles WHERE login_name=" . SqlQuote($user)); my $userid = FetchOneColumn(); Bugzilla->logout_user_by_id($userid); SendSQL("DELETE FROM profiles WHERE login_name=" . SqlQuote($user)); SendSQL("DELETE FROM user_group_map WHERE user_id=" . $userid); PutHeader("Deleting user"); print "User deleted.
\n"; PutTrailer($localtrailer); exit; } # # action='edit' -> present the user edit from # # (next action would be 'update') # if ($action eq 'edit') { PutHeader("Edit user $user"); CheckUser($user); # get data of user SendSQL("SELECT userid, realname, disabledtext FROM profiles WHERE login_name=" . SqlQuote($user)); my ($thisuserid, $realname, $disabledtext) = FetchSQLData(); if ($thisuserid > 0) { # Force groups to be up to date my $changeduser = new Bugzilla::User($thisuserid); $changeduser->derive_groups(); } print "
\n"; print "\n"; EmitFormElements($thisuserid, $user, $realname, $disabledtext); print "
\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
User is a member of any groups shown with a check or grey bar. A grey bar indicates indirect membership, either derived from other groups (marked with square brackets) or via regular expression (marked with '*').

Square brackets around the bless checkbox indicate the ability to bless users (grant them membership in the group) as a result of membership in another group.
"; print "

"; if ($candelete) { print "
\n"; print "\n"; print "\n"; print "\n"; print "
"; } my $x = $localtrailer; $x =~ s/more/other/; PutTrailer($x); exit; } # # action='update' -> update the user # if ($action eq 'update') { my $userold = trim($::FORM{userold} || ''); my $realname = trim($::FORM{realname} || ''); my $realnameold = trim($::FORM{realnameold} || ''); my $password = $::FORM{password} || ''; my $disabledtext = trim($::FORM{disabledtext} || ''); my $disabledtextold = trim($::FORM{disabledtextold} || ''); my @localtrailers = ($localtrailer); $localtrailer = qq|edit user again|; PutHeader("Updating user $userold" . ($realnameold && " ($realnameold)")); CheckUser($userold); SendSQL("SELECT userid FROM profiles WHERE login_name=" . SqlQuote($userold)); my ($thisuserid) = FetchSQLData(); my $emailregexp = Param("emailregexp"); unless ($user =~ m/$emailregexp/) { print "The user name entered must be a valid e-mail address. Please press\n"; print "Back and try again.\n"; PutTrailer($localtrailer); exit; } my @grpadd = (); my @grpdel = (); my $chggrp = 0; SendSQL("SELECT id, name FROM groups"); while (my ($groupid, $name) = FetchSQLData()) { next unless ($editall || UserCanBlessGroup($name)); 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 grant_type = " . GRANT_DIRECT); if ($::FORM{"group_$groupid"}) { SendSQL("INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) VALUES ($thisuserid, $groupid, 0," . GRANT_DIRECT . ")"); print "Added user to group $name
\n"; push(@grpadd, $name); } else { print "Dropped user from group $name
\n"; push(@grpdel, $name); } PopGlobalSQLState(); } if ($editall && ($::FORM{"oldbless_$groupid"} != ($::FORM{"bless_$groupid"} ? 1 : 0))) { # group membership changed PushGlobalSQLState(); SendSQL("DELETE FROM user_group_map WHERE user_id = $thisuserid AND group_id = $groupid AND isbless = 1 AND grant_type = " . GRANT_DIRECT); if ($::FORM{"bless_$groupid"}) { SendSQL("INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) VALUES ($thisuserid, $groupid, 1," . GRANT_DIRECT . ")"); print "Granted user permission to bless group $name
\n"; } else { print "Revoked user's permission to bless group $name
\n"; } PopGlobalSQLState(); } } 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)) . ")"); SendSQL("UPDATE profiles SET refreshed_when='1900-01-01 00:00:00' " . "WHERE userid = $thisuserid"); } # Update the database with the user's new password if they changed it. if ( $editall && $password ) { my $passworderror = ValidatePassword($password); if ( !$passworderror ) { my $cryptpassword = SqlQuote(bz_crypt($password)); my $loginname = SqlQuote($userold); SendSQL("UPDATE profiles SET cryptpassword = $cryptpassword WHERE login_name = $loginname"); SendSQL("SELECT userid FROM profiles WHERE login_name=" . SqlQuote($userold)); my $userid = FetchOneColumn(); Bugzilla->logout_user_by_id($userid); print "Updated password.
\n"; } else { print "Did not update password: $passworderror
\n"; } } if ($editall && $realname ne $realnameold) { SendSQL("UPDATE profiles SET realname=" . SqlQuote($realname) . " WHERE login_name=" . SqlQuote($userold)); print 'Updated real name to ' . html_quote($realname) . ".
\n"; } if ($editall && $disabledtext ne $disabledtextold) { SendSQL("UPDATE profiles SET disabledtext=" . SqlQuote($disabledtext) . " WHERE login_name=" . SqlQuote($userold)); SendSQL("SELECT userid FROM profiles WHERE login_name=" . SqlQuote($userold)); my $userid = FetchOneColumn(); Bugzilla->logout_user_by_id($userid); print "Updated disabled text.
\n"; } if ($editall && $user ne $userold) { unless ($user) { print "Sorry, I can't delete the user's name."; $userold = url_quote($userold); $localtrailer =~ s/XXX/$userold/; push @localtrailers, $localtrailer; PutTrailer(@localtrailers); exit; } if (TestUser($user)) { print "Sorry, user name '$user' is already in use."; $userold = url_quote($userold); $localtrailer =~ s/XXX/$userold/; push @localtrailers, $localtrailer; PutTrailer($localtrailer); exit; } SendSQL("UPDATE profiles SET login_name=" . SqlQuote($user) . " WHERE login_name=" . SqlQuote($userold)); print q|Updated user's name to ' . html_quote($user) . ".
\n"; } my $changeduser = new Bugzilla::User($thisuserid); $changeduser->derive_groups(); $user = url_quote($user); $localtrailer =~ s/XXX/$user/; push @localtrailers, $localtrailer; PutTrailer(@localtrailers); exit; } # # No valid action found # PutHeader("Error"); print "I don't have a clue what you want.
\n";