Chat's updates for security, db and workflow
Security and workflow regressions / regression (push) Has been cancelled

This commit is contained in:
2026-10-20 06:45:30 -04:00
parent 02223083f6
commit 1e63076123
116 changed files with 3252 additions and 2932 deletions
+30 -193
View File
@@ -28,7 +28,7 @@ class Users extends Base {
$this->current_user_serial = (int) ($_SESSION[self::USER_SERIAL] ?? 0);
$this->current_user_name = $_SESSION[self::USER_NAME] ?? "";
$this->current_user_email = $_SESSION[self::USER_EMAIL] ?? "";
$this->pagination_page = $_SESSION[self::PAGINATION_PAGE] ?? 1;
$this->pagination_page = 1;
}
// ===========
@@ -62,11 +62,11 @@ class Users extends Base {
$this->logError("Invalid User ({$user_serial}). " . __METHOD__);
}
$notes = (new Notes())->getAllNotes($user_serial);
$subscriptions = (new Subscriptions())->getAllUsersSubscriptions($user_serial);
$notes = (new Notes())->getNotes('User',(int)$user_serial);
$activity=$this->getLoginActivity($user_serial);
$XML = $this->mergeXML($user, "<XML/>");
$XML = $this->mergeXML($subscriptions, $XML);
$XML = $this->mergeXML($activity, $XML);
$XML = $this->mergeXML($notes, $XML);
$content = $this->applyXSL($XML, $this->getXSL("userSummary"));
@@ -99,71 +99,7 @@ class Users extends Base {
break;
case "add":
$user_name = filter_input(INPUT_POST, "user_name") ?: "";
$user_first_name = filter_input(INPUT_POST, "user_first_name") ?: "";
$user_email = filter_input(INPUT_POST, "user_email") ?: "";
$user_role = filter_input(INPUT_POST, "user_role") ?: "";
if (!in_array($user_role, ['User', 'Guest', 'Administrator'], true) || !filter_var($user_email, FILTER_VALIDATE_EMAIL) || strlen($user_email) > 100 || !preg_match('/\A[A-Za-z0-9_.-]{3,64}\z/', $user_name)) {
http_response_code(422); exit('Enter a valid username, email address and role.');
}
$user_name = strtoupper(preg_replace("/\s+/", " ", $user_name));
$user_random_password = $this->generateRandomPassword();
$user_temp_password = password_hash($user_random_password, PASSWORD_DEFAULT);
$user_change_password = (int) true;
$user_active = 'active';
// Add the User
$connection = $this->connect();
$SQL = "insert into users
( user_name,
user_email,
user_first_name,
user_password_hash,
user_role,
user_change_password,
user_status,
user_creator,
user_created )
values ( :user_name,
:user_email,
:user_first_name,
:user_password_hash,
:user_role,
:user_change_password,
:user_status,
:user_creator,
:user_created )";
$statement = $connection->prepare($SQL);
$statement->bindValue(":user_name", $user_name);
$statement->bindValue(":user_email", $user_email);
$statement->bindValue(":user_first_name", $user_first_name);
$statement->bindValue(":user_password_hash", $user_temp_password);
$statement->bindValue(":user_role", $user_role);
$statement->bindValue(":user_change_password", $user_change_password, PDO::PARAM_BOOL);
$statement->bindValue(":user_status", $user_active);
$statement->bindValue(":user_creator", $this->current_user_name);
$statement->bindValue(":user_created", $this->getTimestamp());
if (!$statement->execute()) {
trigger_error($statement->errorInfo()[2]);
}
$user = $this->getUserByUserName($user_name);
// Send User Credentials
(new AuthService())->requestReset($user_email);
echo json_encode((new AdminAccounts())->create($_POST));
break;
@@ -178,19 +114,7 @@ class Users extends Base {
// ===========
public function deleteUser() {
$user_serial = filter_input(INPUT_POST, "user_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
$users = $this->getUser($user_serial);
if ($users->count() == 0) {
$this->logError("Invalid User ({$user_serial}): " . __METHOD__);
}
$SQL = "delete from users
where user_serial = {$user_serial}";
$this->executeSQL($SQL);
(new MaintenanceService())->deleteUser(MaintenanceService::id($_POST['user_serial']??null));
}
// =========
@@ -227,50 +151,7 @@ class Users extends Base {
break;
case "update":
$user_serial = filter_input(INPUT_POST, "user_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
$user_name = filter_input(INPUT_POST, "user_name") ?: "";
$user_email = filter_input(INPUT_POST, "user_email") ?: "";
$user_first_name = filter_input(INPUT_POST, "user_first_name") ?: "";
$user_role = filter_input(INPUT_POST, "user_role") ?: "";
if (!in_array($user_role, ['User', 'Guest', 'Administrator'], true) || !filter_var($user_email, FILTER_VALIDATE_EMAIL) || strlen($user_email) > 100 || !preg_match('/\A[A-Za-z0-9_.-]{3,64}\z/', $user_name)) {
http_response_code(422); exit('Enter a valid username, email address and role.');
}
$user_active = filter_input(INPUT_POST, 'user_active', FILTER_VALIDATE_BOOLEAN) ? 'active' : 'inactive';
$users = $this->getUser($user_serial);
if ($users->count() == 0) {
$this->logError("Invalid User ({$user_serial}): " . __METHOD__);
}
// Update the User
$SQL = "update users
set user_name = :user_name,
user_email = :user_email,
user_first_name = :user_first_name,
user_role = :user_role,
user_auth_version = user_auth_version + 1,
user_status = :user_status,
user_changer = :user_changer,
user_changed = :user_changed
where user_serial = :user_serial";
$statement = $this->connect()->prepare($SQL);
$statement->bindValue(":user_serial", $user_serial);
$statement->bindValue(":user_name", $user_name);
$statement->bindValue(":user_email", $user_email);
$statement->bindValue(":user_first_name", $user_first_name);
$statement->bindValue(":user_role", $user_role);
$statement->bindValue(":user_status", $user_active);
$statement->bindValue(":user_changer", $this->current_user_name);
$statement->bindValue(":user_changed", $this->getTimestamp());
$statement->execute();
(new MaintenanceService())->updateUser(MaintenanceService::id($_POST['user_serial']??null),$_POST);
break;
default:
@@ -309,7 +190,7 @@ class Users extends Base {
public function getUserSerial($user_name = "") {
$users = $this->getUserByUserId($user_name);
$users = $this->getUserByUserName($user_name);
return ($users->count() === 0) ? 0 : (int) $users->record->user_serial;
}
@@ -330,13 +211,14 @@ class Users extends Base {
// ==========================
public function getUsersPage() {
$this->pagination_size=(int)($_POST['users_per_page']??20);
// Find Criteria
$find_user = filter_input(INPUT_POST, "find_user") ?: "";
$find_user = $this->sanitizeString($find_user);
$find_user = trim($find_user);
$find_words = implode("|", explode(" ", $find_user));
$find_words = implode("|", array_map(static fn($word) => preg_quote($word), explode(" ", $find_user)));
// Record Count
@@ -346,6 +228,7 @@ class Users extends Base {
from view_users
where user_name regexp :user_name
or user_first_name regexp :user_first_name
or user_last_name regexp :user_last_name
or user_email regexp :user_email
or user_role regexp :user_role";
@@ -353,6 +236,7 @@ class Users extends Base {
$statement->bindValue(":user_name", $find_words);
$statement->bindValue(":user_first_name", $find_words);
$statement->bindValue(":user_last_name", $find_words);
$statement->bindValue(":user_email", $find_words);
$statement->bindValue(":user_role", $find_words);
@@ -366,21 +250,9 @@ class Users extends Base {
$pagination = filter_input(INPUT_POST, "pagination") ?: "";
switch ($pagination) {
case "first" : $this->pagination_page = 1;
break;
case "previous" : $this->pagination_page = max(($this->pagination_page - 1), 1);
break;
case "next" : $this->pagination_page = ($this->pagination_page + 1);
break;
case "last" : $this->pagination_page = ($count / $this->pagination_size);
break;
}
$offset = (($this->pagination_page * $this->pagination_size) - $this->pagination_size);
$start = ($offset + 1);
$end = min($count, (($start + $this->pagination_size) - 1));
$paging=Pagination::session(__METHOD__,$count,(int)$this->pagination_size,$pagination);
$this->pagination_size=$paging['size'];$this->pagination_page=$paging['page'];
$offset=$paging['offset'];$start=$paging['start'];$end=$paging['end'];
// Data
@@ -388,8 +260,10 @@ class Users extends Base {
from view_users
where user_name regexp :user_name
or user_first_name regexp :user_first_name
or user_last_name regexp :user_last_name
or user_email regexp :user_email
or user_role regexp :user_role
order by user_serial
limit {$this->pagination_size}
offset {$offset}";
@@ -397,6 +271,7 @@ class Users extends Base {
$statement->bindValue(":user_name", $find_words);
$statement->bindValue(":user_first_name", $find_words);
$statement->bindValue(":user_last_name", $find_words);
$statement->bindValue(":user_email", $find_words);
$statement->bindValue(":user_role", $find_words);
@@ -411,7 +286,6 @@ class Users extends Base {
$users->addAttribute("end", $end);
$users->addAttribute("page", $this->pagination_page);
$_SESSION[self::PAGINATION_PAGE] = $this->pagination_page;
return $users;
}
@@ -474,18 +348,9 @@ class Users extends Base {
// =============================
public function userActive() {
$user_serial = (int) filter_input(INPUT_POST, "user_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
$SQL = "select request_serial
from requests
where request_user = {$user_serial}";
$requests = $this->getTable("requests", $SQL);
$active = ($requests->count() > 0) ? true : $active;
echo json_encode($active);
$id=MaintenanceService::id($_POST['user_serial']??null);
$used=(new MaintenanceService())->userUsed($id);
echo json_encode($used || $id===(int)($_SESSION[self::USER_SERIAL]??0));
}
// ===============
@@ -514,12 +379,10 @@ class Users extends Base {
public function resetPassword() {
$this->requireCsrf();
$stmt = $this->connect()->prepare('SELECT user_email FROM users WHERE user_serial = :id');
$stmt->execute([':id' => (int) ($_POST['user_serial'] ?? 0)]);
$email = $stmt->fetchColumn();
if ($email) (new AuthService())->requestReset($email);
header('Content-Type: application/json');
echo json_encode(true);
$stmt=$this->connect()->prepare('SELECT user_email FROM users WHERE user_serial=?');$stmt->execute([MaintenanceService::id($_POST['user_serial']??null)]);$email=$stmt->fetchColumn();
if(!$email)throw new AppException('Account not found.',404);
if(!(new AuthService())->requestReset($email))throw new AppException('The invitation is rate limited or the account is inactive. Try again later.',429);
echo json_encode(['ok'=>true,'message'=>'A password setup link was sent.']);
}
// ===============================================
@@ -545,27 +408,7 @@ class Users extends Base {
// ====================
public function updateActiveStatus() {
$user_serial = filter_input(INPUT_POST, "user_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
$user_active = filter_input(INPUT_POST, 'user_active', FILTER_VALIDATE_BOOLEAN) ? 'active' : 'inactive';
$SQL = "update users
set user_status = :user_status,
user_auth_version = user_auth_version + 1,
user_changed = :user_changed,
user_changer = :user_changer
where user_serial = :user_serial";
$statement = $this->connect()->prepare($SQL);
$statement->bindValue(":user_serial", $user_serial);
$statement->bindValue(":user_status", $user_active);
$statement->bindValue(":user_changed", $this->getTimestamp());
$statement->bindValue(":user_changer", $this->current_user_name);
if (!$statement->execute()) {
error_log($statement->errorInfo()[2]);
}
(new MaintenanceService())->setUserActive(MaintenanceService::id($_POST['user_serial']??null),MaintenanceService::boolean($_POST['user_active']??null));
}
// ====================================
@@ -573,14 +416,8 @@ class Users extends Base {
// ====================================
public function getLoginActivity($userserial) {
$SQL = "select view_journal.journal_timestamp_verbose, view_journal.journal_ip
from view_journal
where view_journal.journal_origin = '{$userserial}'
and view_journal.journal_entry like '{$userserial} %logged in%'";
return $this->getTable("loginactivity", $SQL);
$stmt=$this->connect()->prepare("SELECT journal_timestamp_verbose,journal_ip FROM view_journal WHERE journal_origin=? AND journal_entry LIKE ? ORDER BY journal_timestamp DESC,journal_serial DESC");
$id=MaintenanceService::id($userserial);$stmt->execute([(string)$id,$id.' logged in%']);
return $this->getTableXML('loginactivity',$stmt);
}
}
?>