#!/usr/bin/perl -T # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. use 5.14.0; use strict; use warnings; use lib qw(. lib local/lib/perl5); use Bugzilla; use Bugzilla::Constants; use Bugzilla::Util; use Bugzilla::Error; use Bugzilla::User; use Bugzilla::Bug; use Bugzilla::BugMail; use Bugzilla::Flag; use Bugzilla::Field; use Bugzilla::Group; use Bugzilla::Token; use Bugzilla::Mailer; my $user = Bugzilla->login(LOGIN_REQUIRED); my $cgi = Bugzilla->cgi; my $template = Bugzilla->template; my $dbh = Bugzilla->dbh; my $userid = $user->id; my $editusers = $user->in_group('editusers'); local our $vars = {}; # Reject access if there is no sense in continuing. $editusers || $user->can_bless() || ThrowUserError("auth_failure", {group => "editusers", reason => "cant_bless", action => "edit", object => "users"}); print $cgi->header(); # Common CGI params my $action = $cgi->param('action') || 'search'; my $otherUserID = $cgi->param('userid'); my $otherUserLogin = $cgi->param('user'); my $token = $cgi->param('token'); # Prefill template vars with data used in all or nearly all templates $vars->{'editusers'} = $editusers; mirrorListSelectionValues(); Bugzilla::Hook::process('admin_editusers_action', { vars => $vars, user => $user, action => $action }); ########################################################################### if ($action eq 'search') { # Allow to restrict the search to any group the user is allowed to bless. $vars->{'restrictablegroups'} = $user->bless_groups(); $template->process('admin/users/search.html.tmpl', $vars) || ThrowTemplateError($template->error()); ########################################################################### } elsif ($action eq 'list') { my $matchvalue = $cgi->param('matchvalue') || ''; my $matchstr = trim(scalar $cgi->param('matchstr')); my $matchtype = $cgi->param('matchtype'); my $grouprestrict = $cgi->param('grouprestrict') || '0'; # 0 = disabled only, 1 = enabled only, 2 = everyone my $is_enabled = $cgi->param('is_enabled') // 2; my $query = 'SELECT DISTINCT userid, login_name, email, realname, is_enabled, ' . $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date ' . 'FROM profiles'; my @bindValues; my $nextCondition; my $visibleGroups; # If a group ID is given, make sure it is a valid one. my $group; if ($grouprestrict) { $group = new Bugzilla::Group(scalar $cgi->param('groupid')); $group || ThrowUserError('invalid_group_ID'); } if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) { # Show only users in visible groups. $visibleGroups = $user->visible_groups_as_string(); if ($visibleGroups) { $query .= qq{, user_group_map AS ugm WHERE ugm.user_id = profiles.userid AND ugm.isbless = 0 AND ugm.group_id IN ($visibleGroups) }; $nextCondition = 'AND'; } } else { $visibleGroups = 1; if ($grouprestrict eq '1') { $query .= qq{, user_group_map AS ugm WHERE ugm.user_id = profiles.userid AND ugm.isbless = 0 }; $nextCondition = 'AND'; } else { $nextCondition = 'WHERE'; } } if (!$visibleGroups) { $vars->{'users'} = {}; } else { # Handle selection by login name, email, real name, or userid. if (defined($matchtype)) { $query .= " $nextCondition "; my $expr = ''; if ($matchvalue eq 'userid') { if ($matchstr) { my $stored_matchstr = $matchstr; detaint_natural($matchstr) || ThrowUserError('illegal_user_id', {userid => $stored_matchstr}); } $expr = "profiles.userid"; } elsif ($matchvalue eq 'email') { $expr = 'profiles.email'; } elsif ($matchvalue eq 'realname') { $expr = "profiles.realname"; } elsif ($matchvalue eq 'extern_id') { $expr = "profiles.extern_id"; } else { $expr = "profiles.login_name"; } if ($matchtype =~ /^(regexp|notregexp|exact)$/) { $matchstr ||= '.'; } else { $matchstr = '' unless defined $matchstr; } # We can trick_taint because we use the value in a SELECT only, # using a placeholder. trick_taint($matchstr); if ($matchtype eq 'regexp') { $query .= $dbh->sql_regexp($expr, '?', 0, $dbh->quote($matchstr)); } elsif ($matchtype eq 'notregexp') { $query .= $dbh->sql_not_regexp($expr, '?', 0, $dbh->quote($matchstr)); } elsif ($matchtype eq 'exact') { $query .= $expr . ' = ?'; } else { # substr or unknown $query .= $dbh->sql_iposition('?', $expr) . ' > 0'; } $nextCondition = 'AND'; push(@bindValues, $matchstr); } # Handle selection by group. if ($grouprestrict eq '1') { my $grouplist = join(',', @{Bugzilla::Group->flatten_group_membership($group->id)}); $query .= " $nextCondition ugm.group_id IN($grouplist) "; } detaint_natural($is_enabled); if ($is_enabled && ($is_enabled == 0 || $is_enabled == 1)) { $query .= " $nextCondition profiles.is_enabled = ?"; $nextCondition = 'AND'; push(@bindValues, $is_enabled); } $query .= ' ORDER BY profiles.login_name'; $vars->{'users'} = $dbh->selectall_arrayref($query, {'Slice' => {}}, @bindValues); } if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) { my $match_user_id = $vars->{'users'}[0]->{'userid'}; my $match_user = check_user($match_user_id); edit_processing($match_user); } else { $template->process('admin/users/list.html.tmpl', $vars) || ThrowTemplateError($template->error()); } ########################################################################### } elsif ($action eq 'add') { $editusers || ThrowUserError("auth_failure", {group => "editusers", action => "add", object => "users"}); $vars->{'token'} = issue_session_token('add_user'); $template->process('admin/users/create.html.tmpl', $vars) || ThrowTemplateError($template->error()); ########################################################################### } elsif ($action eq 'new') { $editusers || ThrowUserError("auth_failure", {group => "editusers", action => "add", object => "users"}); check_token_data($token, 'add_user'); # When e.g. the 'Env' auth method is used, the password field # is not displayed. In that case, set the password to *. my $password = $cgi->param('password'); $password = '*' if !defined $password; my $login_name = $cgi->param('login'); my $email = $cgi->param('email'); $login_name = $email if Bugzilla->params->{use_email_as_login}; my $new_user = Bugzilla::User->create({ login_name => $login_name, email => $email, cryptpassword => $password, realname => scalar $cgi->param('name'), disabledtext => scalar $cgi->param('disabledtext'), disable_mail => scalar $cgi->param('disable_mail'), extern_id => scalar $cgi->param('extern_id'), }); userDataToVars($new_user->id); delete_token($token); if ($cgi->param('notify_user')) { $vars->{'new_user'} = $new_user; my $message; $template->process('email/new-user-details.txt.tmpl', $vars, \$message) || ThrowTemplateError($template->error()); MessageToMTA($message); } # We already display the updated page. We have to recreate a token now. $vars->{'token'} = issue_session_token('edit_user'); $vars->{'message'} = 'account_created'; $template->process('admin/users/edit.html.tmpl', $vars) || ThrowTemplateError($template->error()); ########################################################################### } elsif ($action eq 'edit') { my $otherUser = check_user($otherUserID, $otherUserLogin); edit_processing($otherUser); ########################################################################### } elsif ($action eq 'update') { check_token_data($token, 'edit_user'); my $otherUser = check_user($otherUserID, $otherUserLogin); $otherUserID = $otherUser->id; # Lock tables during the check+update session. $dbh->bz_start_transaction(); $editusers || $user->can_see_user($otherUser) || ThrowUserError('auth_failure', {reason => "not_visible", action => "modify", object => "user"}); $vars->{'loginold'} = $otherUser->login; # Update groups my @group_ids = grep { s/group_// } keys %{ Bugzilla->cgi->Vars }; $otherUser->set_groups({ set => \@group_ids }); # Update profiles table entry; silently skip doing this if the user # is not authorized. my $changes = {}; if ($editusers) { $otherUser->set_login(scalar $cgi->param('login')) unless Bugzilla->params->{use_email_as_login}; $otherUser->set_email(scalar $cgi->param('email')); $otherUser->set_name(scalar $cgi->param('name')); $otherUser->set_password(scalar $cgi->param('password')) if $cgi->param('password'); $otherUser->set_disabledtext(scalar $cgi->param('disabledtext')); $otherUser->set_disable_mail(scalar $cgi->param('disable_mail')); $otherUser->set_extern_id(scalar $cgi->param('extern_id')) if defined $cgi->param('extern_id'); # Update bless groups my @bless_ids = grep { s/bless_// } keys %{ Bugzilla->cgi->Vars }; $otherUser->set_bless_groups({ set => \@bless_ids }); } $changes = $otherUser->update(); $dbh->bz_commit_transaction(); # XXX: userDataToVars may be off when editing ourselves. userDataToVars($otherUserID); delete_token($token); $vars->{'message'} = 'account_updated'; $vars->{'changes'} = \%$changes; # We already display the updated page. We have to recreate a token now. $vars->{'token'} = issue_session_token('edit_user'); $template->process('admin/users/edit.html.tmpl', $vars) || ThrowTemplateError($template->error()); ########################################################################### } elsif ($action eq 'del') { my $otherUser = check_user($otherUserID, $otherUserLogin); $otherUserID = $otherUser->id; Bugzilla->params->{'allowuserdeletion'} || ThrowUserError('users_deletion_disabled'); $editusers || ThrowUserError('auth_failure', {group => "editusers", action => "delete", object => "users"}); $vars->{'otheruser'} = $otherUser; # Find other cross references. $vars->{'attachments'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?', undef, $otherUserID); $vars->{'assignee_or_qa'} = $dbh->selectrow_array( qq{SELECT COUNT(*) FROM bugs WHERE assigned_to = ? OR qa_contact = ?}, undef, ($otherUserID, $otherUserID)); $vars->{'reporter'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM bugs WHERE reporter = ?', undef, $otherUserID); $vars->{'cc'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM cc WHERE who = ?', undef, $otherUserID); $vars->{'bugs_activity'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?', undef, $otherUserID); $vars->{'component_cc'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM component_cc WHERE user_id = ?', undef, $otherUserID); $vars->{'email_setting'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM email_setting WHERE user_id = ?', undef, $otherUserID); $vars->{'flags'}{'requestee'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM flags WHERE requestee_id = ?', undef, $otherUserID); $vars->{'flags'}{'setter'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM flags WHERE setter_id = ?', undef, $otherUserID); $vars->{'longdescs'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM longdescs WHERE who = ?', undef, $otherUserID); my $namedquery_ids = $dbh->selectcol_arrayref( 'SELECT id FROM namedqueries WHERE userid = ?', undef, $otherUserID); $vars->{'namedqueries'} = scalar(@$namedquery_ids); if (scalar(@$namedquery_ids)) { $vars->{'namedquery_group_map'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM namedquery_group_map WHERE namedquery_id IN' . ' (' . join(', ', @$namedquery_ids) . ')'); } else { $vars->{'namedquery_group_map'} = 0; } $vars->{'profile_setting'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?', undef, $otherUserID); $vars->{'profiles_activity'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?', undef, ($otherUserID, $otherUserID)); $vars->{'quips'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM quips WHERE userid = ?', undef, $otherUserID); $vars->{'series'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM series WHERE creator = ?', undef, $otherUserID); $vars->{'watch'}{'watched'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM watch WHERE watched = ?', undef, $otherUserID); $vars->{'watch'}{'watcher'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM watch WHERE watcher = ?', undef, $otherUserID); $vars->{'whine_events'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?', undef, $otherUserID); $vars->{'whine_schedules'} = $dbh->selectrow_array( qq{SELECT COUNT(distinct eventid) FROM whine_schedules WHERE mailto = ? AND mailto_type = ? }, undef, ($otherUserID, MAILTO_USER)); $vars->{'token'} = issue_session_token('delete_user'); $template->process('admin/users/confirm-delete.html.tmpl', $vars) || ThrowTemplateError($template->error()); ########################################################################### } elsif ($action eq 'delete') { check_token_data($token, 'delete_user'); my $otherUser = check_user($otherUserID, $otherUserLogin); $otherUserID = $otherUser->id; # Cache for user accounts. my %usercache = (0 => new Bugzilla::User()); my %updatedbugs; # Lock tables during the check+removal session. # XXX: if there was some change on these tables after the deletion # confirmation checks, we may do something here we haven't warned # about. $dbh->bz_start_transaction(); Bugzilla->params->{'allowuserdeletion'} || ThrowUserError('users_deletion_disabled'); $editusers || ThrowUserError('auth_failure', {group => "editusers", action => "delete", object => "users"}); @{$otherUser->product_responsibilities()} && ThrowUserError('user_has_responsibility'); Bugzilla->logout_user($otherUser); # Get the named query list so we can delete namedquery_group_map entries. my $namedqueries_as_string = join(', ', @{$dbh->selectcol_arrayref( 'SELECT id FROM namedqueries WHERE userid = ?', undef, $otherUserID)}); # Get the timestamp for LogActivityEntry. my $timestamp = $dbh->selectrow_array('SELECT NOW()'); # When we update a bug_activity entry, we update the bug timestamp, too. my $sth_set_bug_timestamp = $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?'); # Flags my $flag_ids = $dbh->selectcol_arrayref('SELECT id FROM flags WHERE requestee_id = ?', undef, $otherUserID); my $flags = Bugzilla::Flag->new_from_list($flag_ids); $dbh->do('UPDATE flags SET requestee_id = NULL, modification_date = ? WHERE requestee_id = ?', undef, ($timestamp, $otherUserID)); # We want to remove the requestee but leave the requester alone, # so we have to log these changes manually. my %bugs; push(@{$bugs{$_->bug_id}->{$_->attach_id || 0}}, $_) foreach @$flags; foreach my $bug_id (keys %bugs) { foreach my $attach_id (keys %{$bugs{$bug_id}}) { my @old_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id}); $_->_set_requestee() foreach @{$bugs{$bug_id}->{$attach_id}}; my @new_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id}); my ($removed, $added) = Bugzilla::Flag->update_activity(\@old_summaries, \@new_summaries); LogActivityEntry($bug_id, 'flagtypes.name', $removed, $added, $userid, $timestamp, undef, $attach_id); } $sth_set_bug_timestamp->execute($timestamp, $bug_id); $updatedbugs{$bug_id} = 1; } # Simple deletions in referred tables. $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef, $otherUserID); $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID); $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID); $dbh->do('DELETE FROM namedqueries_link_in_footer WHERE user_id = ?', undef, $otherUserID); if ($namedqueries_as_string) { $dbh->do('DELETE FROM namedquery_group_map WHERE namedquery_id IN ' . "($namedqueries_as_string)"); } $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef, $otherUserID); $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef, ($otherUserID, $otherUserID)); $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID); $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef, $otherUserID); $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef, ($otherUserID, $otherUserID)); # Deletions in referred tables which need LogActivityEntry. my $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc WHERE who = ?', undef, $otherUserID); $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID); foreach my $bug_id (@$buglist) { LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid, $timestamp); $sth_set_bug_timestamp->execute($timestamp, $bug_id); $updatedbugs{$bug_id} = 1; } # Even more complex deletions in referred tables. my $id; # 1) Series my $sth_seriesid = $dbh->prepare( 'SELECT series_id FROM series WHERE creator = ?'); my $sth_deleteSeries = $dbh->prepare( 'DELETE FROM series WHERE series_id = ?'); my $sth_deleteSeriesData = $dbh->prepare( 'DELETE FROM series_data WHERE series_id = ?'); $sth_seriesid->execute($otherUserID); while ($id = $sth_seriesid->fetchrow_array()) { $sth_deleteSeriesData->execute($id); $sth_deleteSeries->execute($id); } # 2) Whines my $sth_whineidFromEvents = $dbh->prepare( 'SELECT id FROM whine_events WHERE owner_userid = ?'); my $sth_deleteWhineEvent = $dbh->prepare( 'DELETE FROM whine_events WHERE id = ?'); my $sth_deleteWhineQuery = $dbh->prepare( 'DELETE FROM whine_queries WHERE eventid = ?'); my $sth_deleteWhineSchedule = $dbh->prepare( 'DELETE FROM whine_schedules WHERE eventid = ?'); $dbh->do('DELETE FROM whine_schedules WHERE mailto = ? AND mailto_type = ?', undef, ($otherUserID, MAILTO_USER)); $sth_whineidFromEvents->execute($otherUserID); while ($id = $sth_whineidFromEvents->fetchrow_array()) { $sth_deleteWhineQuery->execute($id); $sth_deleteWhineSchedule->execute($id); $sth_deleteWhineEvent->execute($id); } # 3) Bugs # 3.1) fall back to the default assignee $buglist = $dbh->selectall_arrayref( 'SELECT bug_id, initialowner FROM bugs INNER JOIN components ON components.id = bugs.component_id WHERE assigned_to = ?', undef, $otherUserID); my $sth_updateAssignee = $dbh->prepare( 'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?'); foreach my $bug (@$buglist) { my ($bug_id, $default_assignee_id) = @$bug; $sth_updateAssignee->execute($default_assignee_id, $timestamp, $bug_id); $updatedbugs{$bug_id} = 1; $default_assignee_id ||= 0; $usercache{$default_assignee_id} ||= new Bugzilla::User($default_assignee_id); LogActivityEntry($bug_id, 'assigned_to', $otherUser->login, $usercache{$default_assignee_id}->login, $userid, $timestamp); } # 3.2) fall back to the default QA contact $buglist = $dbh->selectall_arrayref( 'SELECT bug_id, initialqacontact FROM bugs INNER JOIN components ON components.id = bugs.component_id WHERE qa_contact = ?', undef, $otherUserID); my $sth_updateQAcontact = $dbh->prepare( 'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?'); foreach my $bug (@$buglist) { my ($bug_id, $default_qa_contact_id) = @$bug; $sth_updateQAcontact->execute($default_qa_contact_id, $timestamp, $bug_id); $updatedbugs{$bug_id} = 1; $default_qa_contact_id ||= 0; $usercache{$default_qa_contact_id} ||= new Bugzilla::User($default_qa_contact_id); LogActivityEntry($bug_id, 'qa_contact', $otherUser->login, $usercache{$default_qa_contact_id}->login, $userid, $timestamp); } # Finally, remove the user account itself. $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID); $dbh->bz_commit_transaction(); delete_token($token); # It's complex to determine which items now need to be flushed from # memcached. As user deletion is expected to be a rare event, we just # flush the entire cache when a user is deleted. Bugzilla->memcached->clear_all(); $vars->{'message'} = 'account_deleted'; $vars->{'otheruser'}{'login'} = $otherUser->login; $vars->{'restrictablegroups'} = $user->bless_groups(); $template->process('admin/users/search.html.tmpl', $vars) || ThrowTemplateError($template->error()); # Send mail about what we've done to bugs. # The deleted user is not notified of the changes. foreach (keys(%updatedbugs)) { Bugzilla::BugMail::Send($_, {'changer' => $user} ); } ########################################################################### } elsif ($action eq 'activity') { my $otherUser = check_user($otherUserID, $otherUserLogin); $vars->{'profile_changes'} = $dbh->selectall_arrayref( "SELECT profiles.login_name AS who, " . $dbh->sql_date_format('profiles_activity.profiles_when') . " AS activity_when, fielddefs.name AS what, profiles_activity.oldvalue AS removed, profiles_activity.newvalue AS added FROM profiles_activity INNER JOIN profiles ON profiles_activity.who = profiles.userid INNER JOIN fielddefs ON fielddefs.id = profiles_activity.fieldid WHERE profiles_activity.userid = ? ORDER BY profiles_activity.profiles_when", {'Slice' => {}}, $otherUser->id); $vars->{'otheruser'} = $otherUser; $template->process("account/profile-activity.html.tmpl", $vars) || ThrowTemplateError($template->error()); ########################################################################### } else { ThrowUserError('unknown_action', {action => $action}); } exit; ########################################################################### # Helpers ########################################################################### # Try to build a user object using its ID, else its login name, and throw # an error if the user does not exist. sub check_user { my ($otherUserID, $otherUserLogin) = @_; my $otherUser; my $vars = {}; if ($otherUserID) { $otherUser = Bugzilla::User->new($otherUserID); $vars->{'user_id'} = $otherUserID; } elsif ($otherUserLogin) { $otherUser = new Bugzilla::User({ name => $otherUserLogin }); $vars->{'user_login'} = $otherUserLogin; } ($otherUser && $otherUser->id) || ThrowUserError('invalid_user', $vars); return $otherUser; } # Copy incoming list selection values from CGI params to template variables. sub mirrorListSelectionValues { my $cgi = Bugzilla->cgi; if (defined($cgi->param('matchtype'))) { foreach ('matchvalue', 'matchstr', 'matchtype', 'grouprestrict', 'groupid', 'is_enabled') { $vars->{'listselectionvalues'}{$_} = $cgi->param($_); } } } # Retrieve user data for the user editing form. User creation and user # editing code rely on this to call derive_groups(). sub userDataToVars { my $otheruserid = shift; my $otheruser = new Bugzilla::User($otheruserid); my $query; my $user = Bugzilla->user; my $dbh = Bugzilla->dbh; my $grouplist = $otheruser->groups_as_string; $vars->{'otheruser'} = $otheruser; $vars->{'groups'} = $user->bless_groups(); $vars->{'permissions'} = $dbh->selectall_hashref( qq{SELECT id, COUNT(directmember.group_id) AS directmember, COUNT(regexpmember.group_id) AS regexpmember, (CASE WHEN (groups.id IN ($grouplist) AND COUNT(directmember.group_id) = 0 AND COUNT(regexpmember.group_id) = 0 ) THEN 1 ELSE 0 END) AS derivedmember, COUNT(directbless.group_id) AS directbless FROM groups LEFT JOIN user_group_map AS directmember ON directmember.group_id = id AND directmember.user_id = ? AND directmember.isbless = 0 AND directmember.grant_type = ? LEFT JOIN user_group_map AS regexpmember ON regexpmember.group_id = id AND regexpmember.user_id = ? AND regexpmember.isbless = 0 AND regexpmember.grant_type = ? LEFT JOIN user_group_map AS directbless ON directbless.group_id = id AND directbless.user_id = ? AND directbless.isbless = 1 AND directbless.grant_type = ? } . $dbh->sql_group_by('id'), 'id', undef, ($otheruserid, GRANT_DIRECT, $otheruserid, GRANT_REGEXP, $otheruserid, GRANT_DIRECT)); # Find indirect bless permission. $query = qq{SELECT groups.id FROM groups, group_group_map AS ggm WHERE groups.id = ggm.grantor_id AND ggm.member_id IN ($grouplist) AND ggm.grant_type = ? } . $dbh->sql_group_by('id'); foreach (@{$dbh->selectall_arrayref($query, undef, (GROUP_BLESS))}) { # Merge indirect bless permissions into permission variable. $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1; } } sub edit_processing { my $otherUser = shift; my $user = Bugzilla->user; my $template = Bugzilla->template; $user->in_group('editusers') || $user->can_see_user($otherUser) || ThrowUserError('auth_failure', {reason => "not_visible", action => "modify", object => "user"}); userDataToVars($otherUser->id); $vars->{'token'} = issue_session_token('edit_user'); $template->process('admin/users/edit.html.tmpl', $vars) || ThrowTemplateError($template->error()); }