editusers.cgi 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. #!/usr/bin/env perl -wT
  2. # -*- Mode: perl; indent-tabs-mode: nil -*-
  3. #
  4. # The contents of this file are subject to the Mozilla Public
  5. # License Version 1.1 (the "License"); you may not use this file
  6. # except in compliance with the License. You may obtain a copy of
  7. # the License at http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS
  10. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. # implied. See the License for the specific language governing
  12. # rights and limitations under the License.
  13. #
  14. # The Original Code is the Bugzilla Bug Tracking System.
  15. #
  16. # Contributor(s): Marc Schumann <wurblzap@gmail.com>
  17. # Lance Larsh <lance.larsh@oracle.com>
  18. # Frédéric Buclin <LpSolit@gmail.com>
  19. # David Lawrence <dkl@redhat.com>
  20. # Vlad Dascalu <jocuri@softhome.net>
  21. # Gavin Shelley <bugzilla@chimpychompy.org>
  22. use strict;
  23. use lib qw(. lib);
  24. use Bugzilla;
  25. use Bugzilla::Constants;
  26. use Bugzilla::Util;
  27. use Bugzilla::Error;
  28. use Bugzilla::User;
  29. use Bugzilla::Bug;
  30. use Bugzilla::BugMail;
  31. use Bugzilla::Flag;
  32. use Bugzilla::Field;
  33. use Bugzilla::Group;
  34. use Bugzilla::Token;
  35. my $user = Bugzilla->login(LOGIN_REQUIRED);
  36. my $cgi = Bugzilla->cgi;
  37. my $template = Bugzilla->template;
  38. my $dbh = Bugzilla->dbh;
  39. my $userid = $user->id;
  40. my $editusers = $user->in_group('editusers');
  41. local our $vars = {};
  42. # Reject access if there is no sense in continuing.
  43. $editusers
  44. || $user->can_bless()
  45. || ThrowUserError("auth_failure", {group => "editusers",
  46. reason => "cant_bless",
  47. action => "edit",
  48. object => "users"});
  49. print $cgi->header();
  50. # Common CGI params
  51. my $action = $cgi->param('action') || 'search';
  52. my $otherUserID = $cgi->param('userid');
  53. my $otherUserLogin = $cgi->param('user');
  54. my $token = $cgi->param('token');
  55. # Prefill template vars with data used in all or nearly all templates
  56. $vars->{'editusers'} = $editusers;
  57. mirrorListSelectionValues();
  58. ###########################################################################
  59. if ($action eq 'search') {
  60. # Allow to restrict the search to any group the user is allowed to bless.
  61. $vars->{'restrictablegroups'} = $user->bless_groups();
  62. $template->process('admin/users/search.html.tmpl', $vars)
  63. || ThrowTemplateError($template->error());
  64. ###########################################################################
  65. } elsif ($action eq 'list') {
  66. my $matchvalue = $cgi->param('matchvalue') || '';
  67. my $matchstr = $cgi->param('matchstr');
  68. my $matchtype = $cgi->param('matchtype');
  69. my $grouprestrict = $cgi->param('grouprestrict') || '0';
  70. my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
  71. 'FROM profiles';
  72. my @bindValues;
  73. my $nextCondition;
  74. my $visibleGroups;
  75. # If a group ID is given, make sure it is a valid one.
  76. my $group;
  77. if ($grouprestrict) {
  78. $group = new Bugzilla::Group(scalar $cgi->param('groupid'));
  79. $group || ThrowUserError('invalid_group_ID');
  80. }
  81. if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
  82. # Show only users in visible groups.
  83. $visibleGroups = $user->visible_groups_as_string();
  84. if ($visibleGroups) {
  85. $query .= qq{, user_group_map AS ugm
  86. WHERE ugm.user_id = profiles.userid
  87. AND ugm.isbless = 0
  88. AND ugm.group_id IN ($visibleGroups)
  89. };
  90. $nextCondition = 'AND';
  91. }
  92. } else {
  93. $visibleGroups = 1;
  94. if ($grouprestrict eq '1') {
  95. $query .= qq{, user_group_map AS ugm
  96. WHERE ugm.user_id = profiles.userid
  97. AND ugm.isbless = 0
  98. };
  99. $nextCondition = 'AND';
  100. }
  101. else {
  102. $nextCondition = 'WHERE';
  103. }
  104. }
  105. if (!$visibleGroups) {
  106. $vars->{'users'} = {};
  107. }
  108. else {
  109. # Handle selection by login name, real name, or userid.
  110. if (defined($matchtype)) {
  111. $query .= " $nextCondition ";
  112. my $expr = "";
  113. if ($matchvalue eq 'userid') {
  114. if ($matchstr) {
  115. my $stored_matchstr = $matchstr;
  116. detaint_natural($matchstr)
  117. || ThrowUserError('illegal_user_id', {userid => $stored_matchstr});
  118. }
  119. $expr = "profiles.userid";
  120. } elsif ($matchvalue eq 'realname') {
  121. $expr = "profiles.realname";
  122. } else {
  123. $expr = "profiles.login_name";
  124. }
  125. if ($matchtype eq 'regexp') {
  126. $query .= $dbh->sql_regexp($expr, '?');
  127. $matchstr = '.' unless $matchstr;
  128. } elsif ($matchtype eq 'notregexp') {
  129. $query .= $dbh->sql_not_regexp($expr, '?');
  130. $matchstr = '.' unless $matchstr;
  131. } elsif ($matchtype eq 'exact') {
  132. $query .= $expr . ' = ?';
  133. $matchstr = '.' unless $matchstr;
  134. } else { # substr or unknown
  135. $query .= $dbh->sql_istrcmp($expr, '?', 'LIKE');
  136. $matchstr = "%$matchstr%";
  137. }
  138. $nextCondition = 'AND';
  139. # We can trick_taint because we use the value in a SELECT only,
  140. # using a placeholder.
  141. trick_taint($matchstr);
  142. push(@bindValues, $matchstr);
  143. }
  144. # Handle selection by group.
  145. if ($grouprestrict eq '1') {
  146. my $grouplist = join(',',
  147. @{Bugzilla::User->flatten_group_membership($group->id)});
  148. $query .= " $nextCondition ugm.group_id IN($grouplist) ";
  149. }
  150. $query .= ' ORDER BY profiles.login_name';
  151. $vars->{'users'} = $dbh->selectall_arrayref($query,
  152. {'Slice' => {}},
  153. @bindValues);
  154. }
  155. if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
  156. my $match_user_id = $vars->{'users'}[0]->{'userid'};
  157. my $match_user = check_user($match_user_id);
  158. edit_processing($match_user);
  159. } else {
  160. $template->process('admin/users/list.html.tmpl', $vars)
  161. || ThrowTemplateError($template->error());
  162. }
  163. ###########################################################################
  164. } elsif ($action eq 'add') {
  165. $editusers || ThrowUserError("auth_failure", {group => "editusers",
  166. action => "add",
  167. object => "users"});
  168. $vars->{'token'} = issue_session_token('add_user');
  169. $template->process('admin/users/create.html.tmpl', $vars)
  170. || ThrowTemplateError($template->error());
  171. ###########################################################################
  172. } elsif ($action eq 'new') {
  173. $editusers || ThrowUserError("auth_failure", {group => "editusers",
  174. action => "add",
  175. object => "users"});
  176. check_token_data($token, 'add_user');
  177. my $new_user = Bugzilla::User->create({
  178. login_name => scalar $cgi->param('login'),
  179. cryptpassword => scalar $cgi->param('password'),
  180. realname => scalar $cgi->param('name'),
  181. disabledtext => scalar $cgi->param('disabledtext'),
  182. disable_mail => scalar $cgi->param('disable_mail')});
  183. userDataToVars($new_user->id);
  184. delete_token($token);
  185. # We already display the updated page. We have to recreate a token now.
  186. $vars->{'token'} = issue_session_token('edit_user');
  187. $vars->{'message'} = 'account_created';
  188. $template->process('admin/users/edit.html.tmpl', $vars)
  189. || ThrowTemplateError($template->error());
  190. ###########################################################################
  191. } elsif ($action eq 'edit') {
  192. my $otherUser = check_user($otherUserID, $otherUserLogin);
  193. edit_processing($otherUser);
  194. ###########################################################################
  195. } elsif ($action eq 'update') {
  196. check_token_data($token, 'edit_user');
  197. my $otherUser = check_user($otherUserID, $otherUserLogin);
  198. $otherUserID = $otherUser->id;
  199. # Lock tables during the check+update session.
  200. $dbh->bz_start_transaction();
  201. $editusers || $user->can_see_user($otherUser)
  202. || ThrowUserError('auth_failure', {reason => "not_visible",
  203. action => "modify",
  204. object => "user"});
  205. $vars->{'loginold'} = $otherUser->login;
  206. # Update profiles table entry; silently skip doing this if the user
  207. # is not authorized.
  208. my %changes;
  209. if ($editusers) {
  210. $otherUser->set_login($cgi->param('login'));
  211. $otherUser->set_name($cgi->param('name'));
  212. $otherUser->set_password($cgi->param('password'))
  213. if $cgi->param('password');
  214. $otherUser->set_disabledtext($cgi->param('disabledtext'));
  215. $otherUser->set_disable_mail($cgi->param('disable_mail'));
  216. %changes = %{$otherUser->update()};
  217. }
  218. # Update group settings.
  219. my $sth_add_mapping = $dbh->prepare(
  220. qq{INSERT INTO user_group_map (
  221. user_id, group_id, isbless, grant_type
  222. ) VALUES (
  223. ?, ?, ?, ?
  224. )
  225. });
  226. my $sth_remove_mapping = $dbh->prepare(
  227. qq{DELETE FROM user_group_map
  228. WHERE user_id = ?
  229. AND group_id = ?
  230. AND isbless = ?
  231. AND grant_type = ?
  232. });
  233. my @groupsAddedTo;
  234. my @groupsRemovedFrom;
  235. my @groupsGrantedRightsToBless;
  236. my @groupsDeniedRightsToBless;
  237. # Regard only groups the user is allowed to bless and skip all others
  238. # silently.
  239. # XXX: checking for existence of each user_group_map entry
  240. # would allow to display a friendlier error message on page reloads.
  241. userDataToVars($otherUserID);
  242. my $permissions = $vars->{'permissions'};
  243. foreach (@{$user->bless_groups()}) {
  244. my $id = $$_{'id'};
  245. my $name = $$_{'name'};
  246. # Change memberships.
  247. my $groupid = $cgi->param("group_$id") || 0;
  248. if ($groupid != $permissions->{$id}->{'directmember'}) {
  249. if (!$groupid) {
  250. $sth_remove_mapping->execute(
  251. $otherUserID, $id, 0, GRANT_DIRECT);
  252. push(@groupsRemovedFrom, $name);
  253. } else {
  254. $sth_add_mapping->execute(
  255. $otherUserID, $id, 0, GRANT_DIRECT);
  256. push(@groupsAddedTo, $name);
  257. }
  258. }
  259. # Only members of the editusers group may change bless grants.
  260. # Skip silently if this is not the case.
  261. if ($editusers) {
  262. my $groupid = $cgi->param("bless_$id") || 0;
  263. if ($groupid != $permissions->{$id}->{'directbless'}) {
  264. if (!$groupid) {
  265. $sth_remove_mapping->execute(
  266. $otherUserID, $id, 1, GRANT_DIRECT);
  267. push(@groupsDeniedRightsToBless, $name);
  268. } else {
  269. $sth_add_mapping->execute(
  270. $otherUserID, $id, 1, GRANT_DIRECT);
  271. push(@groupsGrantedRightsToBless, $name);
  272. }
  273. }
  274. }
  275. }
  276. if (@groupsAddedTo || @groupsRemovedFrom) {
  277. $dbh->do(qq{INSERT INTO profiles_activity (
  278. userid, who,
  279. profiles_when, fieldid,
  280. oldvalue, newvalue
  281. ) VALUES (
  282. ?, ?, now(), ?, ?, ?
  283. )
  284. },
  285. undef,
  286. ($otherUserID, $userid,
  287. get_field_id('bug_group'),
  288. join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
  289. }
  290. # XXX: should create profiles_activity entries for blesser changes.
  291. $dbh->bz_commit_transaction();
  292. # XXX: userDataToVars may be off when editing ourselves.
  293. userDataToVars($otherUserID);
  294. delete_token($token);
  295. $vars->{'message'} = 'account_updated';
  296. $vars->{'changed_fields'} = [keys %changes];
  297. $vars->{'groups_added_to'} = \@groupsAddedTo;
  298. $vars->{'groups_removed_from'} = \@groupsRemovedFrom;
  299. $vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
  300. $vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless;
  301. # We already display the updated page. We have to recreate a token now.
  302. $vars->{'token'} = issue_session_token('edit_user');
  303. $template->process('admin/users/edit.html.tmpl', $vars)
  304. || ThrowTemplateError($template->error());
  305. ###########################################################################
  306. } elsif ($action eq 'del') {
  307. my $otherUser = check_user($otherUserID, $otherUserLogin);
  308. $otherUserID = $otherUser->id;
  309. Bugzilla->params->{'allowuserdeletion'}
  310. || ThrowUserError('users_deletion_disabled');
  311. $editusers || ThrowUserError('auth_failure', {group => "editusers",
  312. action => "delete",
  313. object => "users"});
  314. $vars->{'otheruser'} = $otherUser;
  315. # Find other cross references.
  316. $vars->{'attachments'} = $dbh->selectrow_array(
  317. 'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?',
  318. undef, $otherUserID);
  319. $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
  320. qq{SELECT COUNT(*)
  321. FROM bugs
  322. WHERE assigned_to = ? OR qa_contact = ?},
  323. undef, ($otherUserID, $otherUserID));
  324. $vars->{'reporter'} = $dbh->selectrow_array(
  325. 'SELECT COUNT(*) FROM bugs WHERE reporter = ?',
  326. undef, $otherUserID);
  327. $vars->{'cc'} = $dbh->selectrow_array(
  328. 'SELECT COUNT(*) FROM cc WHERE who = ?',
  329. undef, $otherUserID);
  330. $vars->{'bugs_activity'} = $dbh->selectrow_array(
  331. 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
  332. undef, $otherUserID);
  333. $vars->{'component_cc'} = $dbh->selectrow_array(
  334. 'SELECT COUNT(*) FROM component_cc WHERE user_id = ?',
  335. undef, $otherUserID);
  336. $vars->{'email_setting'} = $dbh->selectrow_array(
  337. 'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
  338. undef, $otherUserID);
  339. $vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
  340. 'SELECT COUNT(*) FROM flags WHERE requestee_id = ?',
  341. undef, $otherUserID);
  342. $vars->{'flags'}{'setter'} = $dbh->selectrow_array(
  343. 'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
  344. undef, $otherUserID);
  345. $vars->{'longdescs'} = $dbh->selectrow_array(
  346. 'SELECT COUNT(*) FROM longdescs WHERE who = ?',
  347. undef, $otherUserID);
  348. my $namedquery_ids = $dbh->selectcol_arrayref(
  349. 'SELECT id FROM namedqueries WHERE userid = ?',
  350. undef, $otherUserID);
  351. $vars->{'namedqueries'} = scalar(@$namedquery_ids);
  352. if (scalar(@$namedquery_ids)) {
  353. $vars->{'namedquery_group_map'} = $dbh->selectrow_array(
  354. 'SELECT COUNT(*) FROM namedquery_group_map WHERE namedquery_id IN' .
  355. ' (' . join(', ', @$namedquery_ids) . ')');
  356. }
  357. else {
  358. $vars->{'namedquery_group_map'} = 0;
  359. }
  360. $vars->{'profile_setting'} = $dbh->selectrow_array(
  361. 'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
  362. undef, $otherUserID);
  363. $vars->{'profiles_activity'} = $dbh->selectrow_array(
  364. 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
  365. undef, ($otherUserID, $otherUserID));
  366. $vars->{'quips'} = $dbh->selectrow_array(
  367. 'SELECT COUNT(*) FROM quips WHERE userid = ?',
  368. undef, $otherUserID);
  369. $vars->{'series'} = $dbh->selectrow_array(
  370. 'SELECT COUNT(*) FROM series WHERE creator = ?',
  371. undef, $otherUserID);
  372. $vars->{'votes'} = $dbh->selectrow_array(
  373. 'SELECT COUNT(*) FROM votes WHERE who = ?',
  374. undef, $otherUserID);
  375. $vars->{'watch'}{'watched'} = $dbh->selectrow_array(
  376. 'SELECT COUNT(*) FROM watch WHERE watched = ?',
  377. undef, $otherUserID);
  378. $vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
  379. 'SELECT COUNT(*) FROM watch WHERE watcher = ?',
  380. undef, $otherUserID);
  381. $vars->{'whine_events'} = $dbh->selectrow_array(
  382. 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
  383. undef, $otherUserID);
  384. $vars->{'whine_schedules'} = $dbh->selectrow_array(
  385. qq{SELECT COUNT(distinct eventid)
  386. FROM whine_schedules
  387. WHERE mailto = ?
  388. AND mailto_type = ?
  389. },
  390. undef, ($otherUserID, MAILTO_USER));
  391. $vars->{'token'} = issue_session_token('delete_user');
  392. $template->process('admin/users/confirm-delete.html.tmpl', $vars)
  393. || ThrowTemplateError($template->error());
  394. ###########################################################################
  395. } elsif ($action eq 'delete') {
  396. check_token_data($token, 'delete_user');
  397. my $otherUser = check_user($otherUserID, $otherUserLogin);
  398. $otherUserID = $otherUser->id;
  399. # Cache for user accounts.
  400. my %usercache = (0 => new Bugzilla::User());
  401. my %updatedbugs;
  402. # Lock tables during the check+removal session.
  403. # XXX: if there was some change on these tables after the deletion
  404. # confirmation checks, we may do something here we haven't warned
  405. # about.
  406. $dbh->bz_start_transaction();
  407. Bugzilla->params->{'allowuserdeletion'}
  408. || ThrowUserError('users_deletion_disabled');
  409. $editusers || ThrowUserError('auth_failure',
  410. {group => "editusers",
  411. action => "delete",
  412. object => "users"});
  413. @{$otherUser->product_responsibilities()}
  414. && ThrowUserError('user_has_responsibility');
  415. Bugzilla->logout_user($otherUser);
  416. # Get the named query list so we can delete namedquery_group_map entries.
  417. my $namedqueries_as_string = join(', ', @{$dbh->selectcol_arrayref(
  418. 'SELECT id FROM namedqueries WHERE userid = ?', undef, $otherUserID)});
  419. # Get the timestamp for LogActivityEntry.
  420. my $timestamp = $dbh->selectrow_array('SELECT NOW()');
  421. # When we update a bug_activity entry, we update the bug timestamp, too.
  422. my $sth_set_bug_timestamp =
  423. $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
  424. # Reference removals which need LogActivityEntry.
  425. my $statement_flagupdate = 'UPDATE flags set requestee_id = NULL
  426. WHERE bug_id = ?
  427. AND attach_id %s
  428. AND requestee_id = ?';
  429. my $sth_flagupdate_attachment =
  430. $dbh->prepare(sprintf($statement_flagupdate, '= ?'));
  431. my $sth_flagupdate_bug =
  432. $dbh->prepare(sprintf($statement_flagupdate, 'IS NULL'));
  433. my $buglist = $dbh->selectall_arrayref('SELECT DISTINCT bug_id, attach_id
  434. FROM flags
  435. WHERE requestee_id = ?',
  436. undef, $otherUserID);
  437. foreach (@$buglist) {
  438. my ($bug_id, $attach_id) = @$_;
  439. my @old_summaries = Bugzilla::Flag->snapshot($bug_id, $attach_id);
  440. if ($attach_id) {
  441. $sth_flagupdate_attachment->execute($bug_id, $attach_id, $otherUserID);
  442. }
  443. else {
  444. $sth_flagupdate_bug->execute($bug_id, $otherUserID);
  445. }
  446. my @new_summaries = Bugzilla::Flag->snapshot($bug_id, $attach_id);
  447. # Let update_activity do all the dirty work, including setting
  448. # the bug timestamp.
  449. Bugzilla::Flag::update_activity($bug_id, $attach_id, $timestamp,
  450. \@old_summaries, \@new_summaries);
  451. $updatedbugs{$bug_id} = 1;
  452. }
  453. # Simple deletions in referred tables.
  454. $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef,
  455. $otherUserID);
  456. $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
  457. $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
  458. $dbh->do('DELETE FROM namedqueries_link_in_footer WHERE user_id = ?', undef,
  459. $otherUserID);
  460. if ($namedqueries_as_string) {
  461. $dbh->do('DELETE FROM namedquery_group_map WHERE namedquery_id IN ' .
  462. "($namedqueries_as_string)");
  463. }
  464. $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef,
  465. $otherUserID);
  466. $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
  467. ($otherUserID, $otherUserID));
  468. $dbh->do('UPDATE quips SET userid = NULL where userid = ?', undef, $otherUserID);
  469. $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
  470. $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
  471. $otherUserID);
  472. $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
  473. $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
  474. ($otherUserID, $otherUserID));
  475. # Deletions in referred tables which need LogActivityEntry.
  476. $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc
  477. WHERE who = ?',
  478. undef, $otherUserID);
  479. $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
  480. foreach my $bug_id (@$buglist) {
  481. LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
  482. $timestamp);
  483. $sth_set_bug_timestamp->execute($timestamp, $bug_id);
  484. $updatedbugs{$bug_id} = 1;
  485. }
  486. # Even more complex deletions in referred tables.
  487. my $id;
  488. # 1) Series
  489. my $sth_seriesid = $dbh->prepare(
  490. 'SELECT series_id FROM series WHERE creator = ?');
  491. my $sth_deleteSeries = $dbh->prepare(
  492. 'DELETE FROM series WHERE series_id = ?');
  493. my $sth_deleteSeriesData = $dbh->prepare(
  494. 'DELETE FROM series_data WHERE series_id = ?');
  495. $sth_seriesid->execute($otherUserID);
  496. while ($id = $sth_seriesid->fetchrow_array()) {
  497. $sth_deleteSeriesData->execute($id);
  498. $sth_deleteSeries->execute($id);
  499. }
  500. # 2) Whines
  501. my $sth_whineidFromEvents = $dbh->prepare(
  502. 'SELECT id FROM whine_events WHERE owner_userid = ?');
  503. my $sth_deleteWhineEvent = $dbh->prepare(
  504. 'DELETE FROM whine_events WHERE id = ?');
  505. my $sth_deleteWhineQuery = $dbh->prepare(
  506. 'DELETE FROM whine_queries WHERE eventid = ?');
  507. my $sth_deleteWhineSchedule = $dbh->prepare(
  508. 'DELETE FROM whine_schedules WHERE eventid = ?');
  509. $dbh->do('DELETE FROM whine_schedules WHERE mailto = ? AND mailto_type = ?',
  510. undef, ($otherUserID, MAILTO_USER));
  511. $sth_whineidFromEvents->execute($otherUserID);
  512. while ($id = $sth_whineidFromEvents->fetchrow_array()) {
  513. $sth_deleteWhineQuery->execute($id);
  514. $sth_deleteWhineSchedule->execute($id);
  515. $sth_deleteWhineEvent->execute($id);
  516. }
  517. # 3) Bugs
  518. # 3.1) fall back to the default assignee
  519. $buglist = $dbh->selectall_arrayref(
  520. 'SELECT bug_id, initialowner
  521. FROM bugs
  522. INNER JOIN components ON components.id = bugs.component_id
  523. WHERE assigned_to = ?', undef, $otherUserID);
  524. my $sth_updateAssignee = $dbh->prepare(
  525. 'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
  526. foreach my $bug (@$buglist) {
  527. my ($bug_id, $default_assignee_id) = @$bug;
  528. $sth_updateAssignee->execute($default_assignee_id,
  529. $timestamp, $bug_id);
  530. $updatedbugs{$bug_id} = 1;
  531. $default_assignee_id ||= 0;
  532. $usercache{$default_assignee_id} ||=
  533. new Bugzilla::User($default_assignee_id);
  534. LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
  535. $usercache{$default_assignee_id}->login,
  536. $userid, $timestamp);
  537. }
  538. # 3.2) fall back to the default QA contact
  539. $buglist = $dbh->selectall_arrayref(
  540. 'SELECT bug_id, initialqacontact
  541. FROM bugs
  542. INNER JOIN components ON components.id = bugs.component_id
  543. WHERE qa_contact = ?', undef, $otherUserID);
  544. my $sth_updateQAcontact = $dbh->prepare(
  545. 'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
  546. foreach my $bug (@$buglist) {
  547. my ($bug_id, $default_qa_contact_id) = @$bug;
  548. $sth_updateQAcontact->execute($default_qa_contact_id,
  549. $timestamp, $bug_id);
  550. $updatedbugs{$bug_id} = 1;
  551. $default_qa_contact_id ||= 0;
  552. $usercache{$default_qa_contact_id} ||=
  553. new Bugzilla::User($default_qa_contact_id);
  554. LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
  555. $usercache{$default_qa_contact_id}->login,
  556. $userid, $timestamp);
  557. }
  558. # Finally, remove the user account itself.
  559. $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
  560. $dbh->bz_commit_transaction();
  561. delete_token($token);
  562. $vars->{'message'} = 'account_deleted';
  563. $vars->{'otheruser'}{'login'} = $otherUser->login;
  564. $vars->{'restrictablegroups'} = $user->bless_groups();
  565. $template->process('admin/users/search.html.tmpl', $vars)
  566. || ThrowTemplateError($template->error());
  567. # Send mail about what we've done to bugs.
  568. # The deleted user is not notified of the changes.
  569. foreach (keys(%updatedbugs)) {
  570. Bugzilla::BugMail::Send($_, {'changer' => $user->login} );
  571. }
  572. ###########################################################################
  573. } elsif ($action eq 'activity') {
  574. my $otherUser = check_user($otherUserID, $otherUserLogin);
  575. $vars->{'profile_changes'} = $dbh->selectall_arrayref(
  576. "SELECT profiles.login_name AS who, " .
  577. $dbh->sql_date_format('profiles_activity.profiles_when') . " AS activity_when,
  578. fielddefs.description AS what,
  579. profiles_activity.oldvalue AS removed,
  580. profiles_activity.newvalue AS added
  581. FROM profiles_activity
  582. INNER JOIN profiles ON profiles_activity.who = profiles.userid
  583. INNER JOIN fielddefs ON fielddefs.id = profiles_activity.fieldid
  584. WHERE profiles_activity.userid = ?
  585. ORDER BY profiles_activity.profiles_when",
  586. {'Slice' => {}},
  587. $otherUser->id);
  588. $vars->{'otheruser'} = $otherUser;
  589. $template->process("account/profile-activity.html.tmpl", $vars)
  590. || ThrowTemplateError($template->error());
  591. ###########################################################################
  592. } else {
  593. $vars->{'action'} = $action;
  594. ThrowCodeError('action_unrecognized', $vars);
  595. }
  596. exit;
  597. ###########################################################################
  598. # Helpers
  599. ###########################################################################
  600. # Try to build a user object using its ID, else its login name, and throw
  601. # an error if the user does not exist.
  602. sub check_user {
  603. my ($otherUserID, $otherUserLogin) = @_;
  604. my $otherUser;
  605. my $vars = {};
  606. if ($otherUserID) {
  607. $otherUser = Bugzilla::User->new($otherUserID);
  608. $vars->{'user_id'} = $otherUserID;
  609. }
  610. elsif ($otherUserLogin) {
  611. $otherUser = new Bugzilla::User({ name => $otherUserLogin });
  612. $vars->{'user_login'} = $otherUserLogin;
  613. }
  614. ($otherUser && $otherUser->id) || ThrowCodeError('invalid_user', $vars);
  615. return $otherUser;
  616. }
  617. # Copy incoming list selection values from CGI params to template variables.
  618. sub mirrorListSelectionValues {
  619. my $cgi = Bugzilla->cgi;
  620. if (defined($cgi->param('matchtype'))) {
  621. foreach ('matchvalue', 'matchstr', 'matchtype', 'grouprestrict', 'groupid') {
  622. $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
  623. }
  624. }
  625. }
  626. # Retrieve user data for the user editing form. User creation and user
  627. # editing code rely on this to call derive_groups().
  628. sub userDataToVars {
  629. my $otheruserid = shift;
  630. my $otheruser = new Bugzilla::User($otheruserid);
  631. my $query;
  632. my $user = Bugzilla->user;
  633. my $dbh = Bugzilla->dbh;
  634. my $grouplist = $otheruser->groups_as_string;
  635. $vars->{'otheruser'} = $otheruser;
  636. $vars->{'groups'} = $user->bless_groups();
  637. $vars->{'permissions'} = $dbh->selectall_hashref(
  638. qq{SELECT id,
  639. COUNT(directmember.group_id) AS directmember,
  640. COUNT(regexpmember.group_id) AS regexpmember,
  641. (CASE WHEN (groups.id IN ($grouplist)
  642. AND COUNT(directmember.group_id) = 0
  643. AND COUNT(regexpmember.group_id) = 0
  644. ) THEN 1 ELSE 0 END)
  645. AS derivedmember,
  646. COUNT(directbless.group_id) AS directbless
  647. FROM groups
  648. LEFT JOIN user_group_map AS directmember
  649. ON directmember.group_id = id
  650. AND directmember.user_id = ?
  651. AND directmember.isbless = 0
  652. AND directmember.grant_type = ?
  653. LEFT JOIN user_group_map AS regexpmember
  654. ON regexpmember.group_id = id
  655. AND regexpmember.user_id = ?
  656. AND regexpmember.isbless = 0
  657. AND regexpmember.grant_type = ?
  658. LEFT JOIN user_group_map AS directbless
  659. ON directbless.group_id = id
  660. AND directbless.user_id = ?
  661. AND directbless.isbless = 1
  662. AND directbless.grant_type = ?
  663. } . $dbh->sql_group_by('id'),
  664. 'id', undef,
  665. ($otheruserid, GRANT_DIRECT,
  666. $otheruserid, GRANT_REGEXP,
  667. $otheruserid, GRANT_DIRECT));
  668. # Find indirect bless permission.
  669. $query = qq{SELECT groups.id
  670. FROM groups, group_group_map AS ggm
  671. WHERE groups.id = ggm.grantor_id
  672. AND ggm.member_id IN ($grouplist)
  673. AND ggm.grant_type = ?
  674. } . $dbh->sql_group_by('id');
  675. foreach (@{$dbh->selectall_arrayref($query, undef,
  676. (GROUP_BLESS))}) {
  677. # Merge indirect bless permissions into permission variable.
  678. $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
  679. }
  680. }
  681. sub edit_processing {
  682. my $otherUser = shift;
  683. my $user = Bugzilla->user;
  684. my $template = Bugzilla->template;
  685. $user->in_group('editusers') || $user->can_see_user($otherUser)
  686. || ThrowUserError('auth_failure', {reason => "not_visible",
  687. action => "modify",
  688. object => "user"});
  689. userDataToVars($otherUser->id);
  690. $vars->{'token'} = issue_session_token('edit_user');
  691. $template->process('admin/users/edit.html.tmpl', $vars)
  692. || ThrowTemplateError($template->error());
  693. }