" . $row['user_name'] . " is the only active admin - $what would leave nobody to run ITFlow. Make someone else an admin first.", 'error'); redirect(); } } if (isset($_POST['add_user'])) { validateCSRFToken(); if (agentUserLimitReached()) { flashAlert(agentUserLimitMessage(), 'error'); redirect(); } $error = ''; $identity = userCheckIdentity($_POST, 0, $error); $role = $identity ? userCheckRole($_POST['role'] ?? 0, 0, $error) : null; $password = (string) ($_POST['password'] ?? ''); if ($role && ($problem = userPasswordProblem($password))) { $error = $problem; } if (!$identity || !$role || $error !== '') { flashAlert($error, 'error'); redirect(); } [$name, $email] = $identity; $role_id = intval($role['role_id']); mysqli_query($mysqli, "INSERT INTO users SET user_name = '" . escapeSql($name) . "', user_email = '" . escapeSql($email) . "', user_password = '', user_role_id = $role_id"); $user_id = mysqli_insert_id($mysqli); userSetPassword($user_id, $password); $force_mfa = !empty($_POST['force_mfa']) ? 1 : 0; mysqli_query($mysqli, "INSERT INTO user_settings SET user_id = $user_id, user_config_force_mfa = $force_mfa"); if (!intval($role['role_is_admin'])) { userSetClientAccess($user_id, $_POST['client_permission'] ?? []); } $notes = []; $avatar_error = ''; if (!empty($_FILES['avatar']['tmp_name']) && !userSaveAvatar($user_id, $_FILES['avatar'], $avatar_error)) { $notes[] = $avatar_error . ' The user was created without one'; } // Their sign in details by email - the password goes as typed, so it is theirs to change if (!empty($_POST['send_email']) && emailReady()) { if (userMailNewAccount($name, $email, $password, $force_mfa)) { $notes[] = 'their sign in details were emailed to them'; } else { $notes[] = 'the email with their sign in details could not be queued'; logApp('Mail', 'error', 'Failed to queue the new account email for ' . escapeSql($email)); } } logAudit("User", "Create", "$session_name created user " . escapeSql($name), 0, $user_id); flashAlert("User $name created" . ($notes ? ' - ' . implode('; ', $notes) : ''), $avatar_error ? 'warning' : 'success'); redirect(); } if (isset($_POST['edit_user'])) { validateCSRFToken(); $row = userForRequest($_POST['user_id'] ?? 0); $user_id = intval($row['user_id']); $error = ''; $identity = userCheckIdentity($_POST, $user_id, $error); $role = $identity ? userCheckRole($_POST['role'] ?? 0, $row['user_role_id'], $error) : null; $new_password = (string) ($_POST['new_password'] ?? ''); if ($role && $new_password !== '' && ($problem = userPasswordProblem($new_password))) { $error = $problem; } if (!$identity || !$role || $error !== '') { flashAlert($error, 'error'); redirect(); } [$name, $email] = $identity; if (intval($row['role_is_admin']) && !intval($role['role_is_admin'])) { userGuardLastAdmin($row, 'a role without admin'); } mysqli_query($mysqli, "UPDATE users SET user_name = '" . escapeSql($name) . "', user_email = '" . escapeSql($email) . "', user_role_id = " . intval($role['role_id']) . " WHERE user_id = $user_id"); $force_mfa = !empty($_POST['force_mfa']) ? 1 : 0; mysqli_query($mysqli, "INSERT INTO user_settings SET user_id = $user_id, user_config_force_mfa = $force_mfa ON DUPLICATE KEY UPDATE user_config_force_mfa = $force_mfa"); // An admin sees every client - their rules are left as they were, in case the role changes back if (!intval($role['role_is_admin']) && isset($_POST['client_permission_shown'])) { userSetClientAccess($user_id, $_POST['client_permission'] ?? []); } $changes = []; if ($new_password !== '') { userSetPassword($user_id, $new_password); $changes[] = 'password changed'; userNotifyChange($name, $email, 'an administrator set a new password'); } if (!empty($_POST['reset_mfa']) && trim((string) $row['user_token']) !== '') { userResetMfa($user_id); $changes[] = 'MFA reset'; userNotifyChange($name, $email, 'an administrator turned off multi-factor authentication' . ($force_mfa ? ' - you will set it up again at your next sign in' : '')); } if (strcasecmp($email, $row['user_email']) !== 0) { $changes[] = 'email changed'; userNotifyChange($name, $row['user_email'], 'its email address is now ' . $email); } $avatar_error = ''; if (!empty($_POST['remove_avatar'])) { userRemoveAvatar($user_id); $changes[] = 'avatar removed'; } elseif (!empty($_FILES['avatar']['tmp_name'])) { if (userSaveAvatar($user_id, $_FILES['avatar'], $avatar_error)) { $changes[] = 'avatar updated'; } } logAudit("User", "Edit", "$session_name edited user " . escapeSql($name) . ($changes ? ' - ' . escapeSql(implode(', ', $changes)) : ''), 0, $user_id); flashAlert("User $name saved" . ($changes ? ' - ' . implode(', ', $changes) : '') . ($avatar_error ? ". $avatar_error" : ''), $avatar_error ? 'warning' : 'success'); redirect(); } if (isset($_GET['activate_user'])) { validateCSRFToken(); $row = userForRequest($_GET['activate_user']); $user_id = intval($row['user_id']); // Only a user who isn't already using a seat can push the count over the limit if ((intval($row['user_status']) !== 1 || $row['user_archived_at']) && agentUserLimitReached()) { flashAlert(agentUserLimitMessage(), 'error'); redirect(); } mysqli_query($mysqli, "UPDATE users SET user_status = 1 WHERE user_id = $user_id"); logAudit("User", "Activate", "$session_name activated user " . escapeSql($row['user_name']), 0, $user_id); flashAlert("User " . $row['user_name'] . " can sign in again"); redirect(); } if (isset($_GET['disable_user'])) { validateCSRFToken(); $row = userForRequest($_GET['disable_user']); $user_id = intval($row['user_id']); userGuardLastAdmin($row, 'disabling them'); mysqli_query($mysqli, "UPDATE users SET user_status = 0 WHERE user_id = $user_id"); userHandOverWork($user_id, 0, 'when ' . escapeSql($row['user_name']) . ' was disabled'); logAudit("User", "Disable", "$session_name disabled user " . escapeSql($row['user_name']), 0, $user_id); flashAlert("User " . $row['user_name'] . " disabled - they are signed out, and their open tickets are unassigned", 'error'); redirect(); } if (isset($_GET['revoke_remember_me'])) { validateCSRFToken(); $row = userForRequest($_GET['revoke_remember_me']); $user_id = intval($row['user_id']); mysqli_query($mysqli, "DELETE FROM remember_tokens WHERE remember_token_user_id = $user_id"); logAudit("User", "Edit", "$session_name revoked all remember me tokens for user " . escapeSql($row['user_name']), 0, $user_id); flashAlert("Remembered browsers forgotten for " . $row['user_name'] . " - every browser asks for their MFA code again"); redirect(); } if (isset($_GET['reset_user_mfa'])) { validateCSRFToken(); $row = userForRequest($_GET['reset_user_mfa']); $user_id = intval($row['user_id']); userResetMfa($user_id); userNotifyChange($row['user_name'], $row['user_email'], 'an administrator turned off multi-factor authentication' . (intval($row['user_config_force_mfa']) ? ' - you will set it up again at your next sign in' : '')); logAudit("User", "Edit", "$session_name reset MFA for user " . escapeSql($row['user_name']), 0, $user_id); flashAlert("MFA reset for " . $row['user_name'] . "" . (intval($row['user_config_force_mfa']) ? ' - they set it up again at their next sign in' : ' - they sign in with their password alone until they turn it on')); redirect(); } if (isset($_POST['archive_user'])) { validateCSRFToken(); $row = userForRequest($_POST['user_id'] ?? 0); $user_id = intval($row['user_id']); userGuardLastAdmin($row, 'archiving them'); // Their work to an active agent, or to nobody $to_user_id = intval($_POST['ticket_assign'] ?? 0); if ($to_user_id && ($to_user_id === $user_id || !mysqli_num_rows(mysqli_query($mysqli, "SELECT 1 FROM users WHERE user_id = $to_user_id AND user_type = 1 AND user_status = 1 AND user_archived_at IS NULL")))) { $to_user_id = 0; } userHandOverWork($user_id, $to_user_id, 'when ' . escapeSql($row['user_name']) . ' was archived'); // They can no longer sign in, and their copy of the vault key is gone - restoring sets a new password $archived_name = escapeSql(mb_substr($row['user_name'], 0, 189) . ' (archived)'); $scrambled = escapeSql(password_hash(randomString(), PASSWORD_DEFAULT)); mysqli_query($mysqli, "UPDATE users SET user_name = '$archived_name', user_password = '$scrambled', user_status = 0, user_specific_encryption_ciphertext = '', user_archived_at = NOW() WHERE user_id = $user_id"); mysqli_query($mysqli, "DELETE FROM remember_tokens WHERE remember_token_user_id = $user_id"); logAudit("User", "Archive", "$session_name archived user " . escapeSql($row['user_name']), 0, $user_id); flashAlert("User " . $row['user_name'] . " archived" . ($to_user_id ? ' - their open tickets went to ' . getFieldById('users', $to_user_id, 'user_name') : ''), 'error'); redirect(); } if (isset($_POST['restore_user'])) { validateCSRFToken(); $row = userForRequest($_POST['user_id'] ?? 0); $user_id = intval($row['user_id']); if (agentUserLimitReached()) { flashAlert(agentUserLimitMessage(), 'error'); redirect(); } $error = ''; $role = userCheckRole($_POST['role'] ?? 0, $row['user_role_id'], $error); $password = (string) ($_POST['new_password'] ?? ''); if ($role && ($problem = userPasswordProblem($password))) { $error = $problem; } $name = trim(str_replace(' (archived)', '', $row['user_name'])); if ($role && mysqli_num_rows(mysqli_query($mysqli, "SELECT 1 FROM users WHERE user_email = '" . escapeSql($row['user_email']) . "' AND user_archived_at IS NULL AND user_id <> $user_id"))) { $error = '' . $row['user_email'] . ' signs in to another account now - change that one\'s email first.'; } if (!$role || $error !== '') { flashAlert($error, 'error'); redirect(); } mysqli_query($mysqli, "UPDATE users SET user_name = '" . escapeSql($name) . "', user_status = 1, user_role_id = " . intval($role['role_id']) . ", user_archived_at = NULL WHERE user_id = $user_id"); userSetPassword($user_id, $password); logAudit("User", "Restore", "$session_name restored user " . escapeSql($name), 0, $user_id); flashAlert("User $name restored - give them their new password"); redirect(); } if (isset($_POST['bulk_role_users'])) { validateCSRFToken(); $error = ''; $role = userCheckRole($_POST['role'] ?? 0, 0, $error); if (!$role) { flashAlert($error, 'error'); redirect(); } $count = 0; $kept = []; foreach (usersForBulk() as $user_id => $row) { if (intval($row['role_is_admin']) && !intval($role['role_is_admin']) && userIsLastAdmin($row)) { $kept[] = $row['user_name']; continue; } mysqli_query($mysqli, "UPDATE users SET user_role_id = " . intval($role['role_id']) . " WHERE user_id = $user_id"); logAudit("User", "Edit", "$session_name set the role of user " . escapeSql($row['user_name']) . " to " . escapeSql($role['role_name']), 0, $user_id); $count++; } flashAlert("$count user" . ($count === 1 ? '' : 's') . " now " . ($count === 1 ? 'has' : 'have') . " the " . $role['role_name'] . " role" . ($kept ? ' - ' . implode(', ', $kept) . ' kept theirs, as the only admin' : ''), $kept ? 'warning' : 'success'); redirect(); } if (isset($_POST['bulk_require_mfa_users']) || isset($_POST['bulk_unrequire_mfa_users'])) { validateCSRFToken(); $force = isset($_POST['bulk_require_mfa_users']) ? 1 : 0; $count = 0; foreach (usersForBulk() as $user_id => $row) { mysqli_query($mysqli, "INSERT INTO user_settings SET user_id = $user_id, user_config_force_mfa = $force ON DUPLICATE KEY UPDATE user_config_force_mfa = $force"); logAudit("User", "Edit", "$session_name " . ($force ? 'required' : 'stopped requiring') . " MFA for user " . escapeSql($row['user_name']), 0, $user_id); $count++; } flashAlert(($force ? "MFA required for" : "MFA no longer required for") . " $count user" . ($count === 1 ? '' : 's') . ($force ? ' - anyone without it sets it up at their next sign in' : '')); redirect(); } if (isset($_POST['bulk_revoke_remember_users'])) { validateCSRFToken(); $count = 0; foreach (usersForBulk() as $user_id => $row) { mysqli_query($mysqli, "DELETE FROM remember_tokens WHERE remember_token_user_id = $user_id"); logAudit("User", "Edit", "$session_name revoked all remember me tokens for user " . escapeSql($row['user_name']), 0, $user_id); $count++; } flashAlert("Remembered browsers forgotten for $count user" . ($count === 1 ? '' : 's')); redirect(); } if (isset($_POST['bulk_disable_users']) || isset($_POST['bulk_activate_users'])) { validateCSRFToken(); $activate = isset($_POST['bulk_activate_users']); $count = 0; $kept = []; foreach (usersForBulk() as $user_id => $row) { if ($row['user_archived_at'] || intval($row['user_status']) === ($activate ? 1 : 0)) { continue; } if ($activate && agentUserLimitReached()) { $kept[] = $row['user_name']; continue; } if (!$activate && userIsLastAdmin($row)) { $kept[] = $row['user_name']; continue; } mysqli_query($mysqli, "UPDATE users SET user_status = " . ($activate ? 1 : 0) . " WHERE user_id = $user_id"); if (!$activate) { userHandOverWork($user_id, 0, 'when ' . escapeSql($row['user_name']) . ' was disabled'); } logAudit("User", $activate ? "Activate" : "Disable", "$session_name " . ($activate ? 'activated' : 'disabled') . " user " . escapeSql($row['user_name']), 0, $user_id); $count++; } $why = $activate ? 'the agent user limit is reached' : 'the only admin'; flashAlert(($activate ? 'Activated' : 'Disabled') . " $count user" . ($count === 1 ? '' : 's') . ($kept ? ' - not ' . implode(', ', $kept) . " ($why)" : ''), $kept ? 'warning' : ($activate ? 'success' : 'error')); redirect(); } if (isExportRequest('export_users')) { validateCSRFToken(); enforceAdminPermission(); $format = resolveExportFormat($_POST['export_users']); // Filters inherited from the users page - mirrors admin/users.php $filter_summary = []; if (isset($_POST['archived']) && $_POST['archived'] == 1) { $archive_query = "user_archived_at IS NOT NULL"; $filter_summary['Archived'] = 'Archived only'; } else { $archive_query = "user_archived_at IS NULL"; } $q = escapeSql($_POST['q'] ?? ''); if ($q !== '') { $filter_summary['Search'] = $_POST['q']; } $role_query = ''; $role = userRoles()[intval($_POST['role'] ?? 0)] ?? null; if ($role) { $role_query = 'AND user_role_id = ' . intval($role['role_id']); $filter_summary['Role'] = $role['role_name']; } $rows = []; $sql = mysqli_query($mysqli, "SELECT users.user_id, role_name, role_is_admin, user_created_at, user_email, user_name, user_status, user_token, user_config_force_mfa FROM users LEFT JOIN user_roles ON user_role_id = role_id LEFT JOIN user_settings ON user_settings.user_id = users.user_id WHERE (user_name LIKE '%$q%' OR user_email LIKE '%$q%' OR role_name LIKE '%$q%') AND user_type = 1 AND $archive_query $role_query ORDER BY user_name ASC"); while ($row = mysqli_fetch_assoc($sql)) { $rows[intval($row['user_id'])] = $row; } $logins = userLastLogins(array_keys($rows)); $access = userClientAccess(array_keys($rows)); $num_rows = count($rows); if ($num_rows > 0) { guardExportPdfRowCount($format, $num_rows); $export = beginExport('users', $format, "$session_company_name-Users", 'Users', summarizeExportFilters($filter_summary)); foreach ($rows as $user_id => $row) { $row['user_status_display'] = USER_STATUSES[intval($row['user_status'])][0] ?? 'Disabled'; $row['user_mfa_display'] = trim((string) $row['user_token']) !== '' ? 'On' : (intval($row['user_config_force_mfa']) ? 'Required, not set up' : 'Off'); $row['user_last_login'] = $logins[$user_id]['log_created_at'] ?? 'Never'; $row['user_client_access'] = userClientAccessText($access[$user_id], intval($row['role_is_admin'])); addExportRow($export, $row); } finishExport($export); } logAudit("User", "Export", "$session_name exported $num_rows user(s) to a " . strtoupper($format) . " file"); exit; } if (isset($_POST['ir_reset_user_password'])) { // Incident response: every other agent gets a new password, shown once here validateCSRFToken(); // Confirm logged-in user password, for security $user_row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT user_password FROM users WHERE user_id = $session_user_id")); if (!password_verify((string) ($_POST['admin_password'] ?? ''), $user_row['user_password'])) { flashAlert("Incorrect password - nothing was reset.", 'error'); redirect(); } // Agents only - client portal logins have their own reset, and no vault key to rewrite $reset = []; $sql_users = mysqli_query($mysqli, "SELECT user_id, user_name, user_email FROM users WHERE user_type = 1 AND user_archived_at IS NULL AND user_id <> $session_user_id ORDER BY user_name"); while ($row = mysqli_fetch_assoc($sql_users)) { $new_password = generateReadablePassword(4); userSetPassword($row['user_id'], $new_password); // A remembered browser would skip their MFA code - in an incident, it must not mysqli_query($mysqli, "DELETE FROM remember_tokens WHERE remember_token_user_id = " . intval($row['user_id'])); $reset[] = $row + ['password' => $new_password]; } logAudit("User", "Edit", "$session_name reset the passwords of " . count($reset) . " agent(s) and forgot their remembered browsers (incident response)"); // Shown once, not kept - a plain page to read out or copy, and nothing else on it header('Cache-Control: no-store'); ?>
| Agent | New password | |
|---|---|---|
| = escapeHtml($row['user_name']) ?> | = escapeHtml($row['user_email']) ?> | = escapeHtml($row['password']) ?> |
| There are no other agents. | ||