generated from jric11/baseProject
ChatGPT's update to harden software and run db updates.
This commit is contained in:
@@ -120,19 +120,20 @@ class MaintenanceService extends Base {
|
||||
}
|
||||
|
||||
private function lockAdministrators(PDO $db): array {
|
||||
return $db->query("SELECT user_serial FROM users WHERE user_status='active' AND user_role IN ('Administrator','System Administrator') ORDER BY user_serial FOR UPDATE")->fetchAll(PDO::FETCH_COLUMN);
|
||||
return $db->query("SELECT user_serial FROM users WHERE user_status='active' AND user_role='Super System Administrator' ORDER BY user_serial FOR UPDATE")->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
private function guardAdministrator(array $admins, int $id, string $role, string $status, bool $deleting = false): void {
|
||||
if ($deleting && $id === (int) ($_SESSION[self::USER_SERIAL] ?? 0))
|
||||
throw new AppException('You cannot delete your own account.', 409);
|
||||
if (in_array($id, array_map('intval', $admins), true) && count($admins) <= 1 && ($deleting || $status !== 'active' || !in_array($role, ['Administrator', 'System Administrator'], true))) {
|
||||
throw new AppException('The last active administrator cannot be removed, disabled or demoted.', 409);
|
||||
if (in_array($id, array_map('intval', $admins), true) && count($admins) <= 1 && ($deleting || $status !== 'active' || $role!==TenantAccess::SUPER)) {
|
||||
throw new AppException('The last active Super System Administrator cannot be removed, disabled or demoted.', 409);
|
||||
}
|
||||
}
|
||||
|
||||
public function userUsed(int $id, ?PDO $db = null): bool {
|
||||
$db ??= $this->connect();
|
||||
(new TenantAccess())->requireSuper($db);
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM notes WHERE note_type='User' AND note_reference=?");
|
||||
$stmt->execute([$id]);
|
||||
if ((int) $stmt->fetchColumn() > 0)
|
||||
@@ -149,20 +150,25 @@ class MaintenanceService extends Base {
|
||||
if ((int) $stmt->fetchColumn() > 0)
|
||||
return true;
|
||||
}
|
||||
foreach(['club_invitations'=>'invited_by','club_tickets'=>'user_serial','club_ticket_messages'=>'author_serial'] as $table=>$column){
|
||||
$stmt=$db->prepare("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?");$stmt->execute([$table]);
|
||||
if($stmt->fetchColumn()){$stmt=$db->prepare('SELECT 1 FROM '.$table.' WHERE '.$column.'=? LIMIT 1');$stmt->execute([$id]);if($stmt->fetchColumn())return true;}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteUser(int $id): void {
|
||||
(new TenantAccess())->requireSuper();
|
||||
$db = $this->connect();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
DatabaseTransaction::run($db,function(PDO $db)use($id){
|
||||
$admins = $this->lockAdministrators($db);
|
||||
(new TenantAccess())->requireSuper($db);
|
||||
$stmt = $db->prepare('SELECT user_role,user_status FROM users WHERE user_serial=? FOR UPDATE');
|
||||
$stmt->execute([$id]);
|
||||
$user = $stmt->fetch();
|
||||
if (!$user)
|
||||
throw new AppException('User not found.', 404);
|
||||
if ($user['user_role'] === 'System Administrator' && ($_SESSION[self::USER_ROLE] ?? '') !== 'System Administrator')
|
||||
if ($user['user_role'] === TenantAccess::SUPER && !(new TenantAccess())->isSuper($db))
|
||||
throw new AppException('Only a system administrator can modify that account.', 403);
|
||||
$this->guardAdministrator($admins, $id, $user['user_role'], $user['user_status'], true);
|
||||
if ($this->userUsed($id, $db))
|
||||
@@ -172,35 +178,32 @@ class MaintenanceService extends Base {
|
||||
$stmt = $db->prepare('DELETE FROM users WHERE user_serial=?');
|
||||
$stmt->execute([$id]);
|
||||
Audit::required($db, (int) ($_SESSION[self::USER_SERIAL] ?? 0), 'deleted unused user ' . $id);
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction())
|
||||
$db->rollBack();throw $e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function updateUser(int $id, array $input): void {
|
||||
(new TenantAccess())->requireSuper();
|
||||
$name = trim($input['user_name'] ?? '');
|
||||
$email = trim($input['user_email'] ?? '');
|
||||
$first = trim($input['user_first_name'] ?? '');
|
||||
$last = trim($input['user_last_name'] ?? '');
|
||||
$role = $input['user_role'] ?? '';
|
||||
$status = self::boolean($input['user_active'] ?? false) ? 'active' : 'inactive';
|
||||
if (!preg_match('/\A[A-Za-z0-9_.-]{3,64}\z/', $name) || !filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 100 || !$first || !$last || strlen($first) > 128 || strlen($last) > 128 || !in_array($role, ['User', 'Guest', 'Administrator', 'System Administrator'], true))
|
||||
if (!preg_match('/\A[A-Za-z0-9_.-]{3,64}\z/', $name) || !filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 100 || !$first || !$last || strlen($first) > 128 || strlen($last) > 128 || !in_array($role, ['User', 'Guest', 'Administrator', TenantAccess::SUPER], true))
|
||||
throw new AppException('Enter a valid username, first/last name, email and role.');
|
||||
// Only an existing system administrator may assign that role.
|
||||
if ($role === 'System Administrator' && ($_SESSION[self::USER_ROLE] ?? '') !== 'System Administrator')
|
||||
if ($role === TenantAccess::SUPER && !(new TenantAccess())->isSuper())
|
||||
throw new AppException('Only a system administrator can assign that role.', 403);
|
||||
$db = $this->connect();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
DatabaseTransaction::run($db,function(PDO $db)use($id,$name,$email,$first,$last,$role,$status){
|
||||
$admins = $this->lockAdministrators($db);
|
||||
(new TenantAccess())->requireSuper($db);
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE user_serial=? FOR UPDATE');
|
||||
$stmt->execute([$id]);
|
||||
$old = $stmt->fetch();
|
||||
if (!$old)
|
||||
throw new AppException('User not found.', 404);
|
||||
if ($old['user_role'] === 'System Administrator' && ($_SESSION[self::USER_ROLE] ?? '') !== 'System Administrator')
|
||||
if ($old['user_role'] === TenantAccess::SUPER && !(new TenantAccess())->isSuper($db))
|
||||
throw new AppException('Only a system administrator can modify that account.', 403);
|
||||
$this->guardAdministrator($admins, $id, $role, $status);
|
||||
$revoke = $old['user_role'] !== $role || $old['user_status'] !== $status || $old['user_email'] !== $email || $old['user_name'] !== $name;
|
||||
@@ -218,24 +221,21 @@ class MaintenanceService extends Base {
|
||||
}
|
||||
$fields = ['user_name', 'user_email', 'user_first_name', 'user_last_name', 'user_role', 'user_status'];
|
||||
Audit::required($db, (int) ($_SESSION[self::USER_SERIAL] ?? 0), 'updated user ' . json_encode(['user' => $id, 'before' => array_intersect_key($old, array_flip($fields)), 'after' => ['user_name' => $name, 'user_email' => $email, 'user_first_name' => $first, 'user_last_name' => $last, 'user_role' => $role, 'user_status' => $status]]));
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction())
|
||||
$db->rollBack();throw $e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function setUserActive(int $id, bool $active): void {
|
||||
(new TenantAccess())->requireSuper();
|
||||
$db = $this->connect();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
DatabaseTransaction::run($db,function(PDO $db)use($id,$active){
|
||||
$admins = $this->lockAdministrators($db);
|
||||
(new TenantAccess())->requireSuper($db);
|
||||
$stmt = $db->prepare('SELECT user_role FROM users WHERE user_serial=? FOR UPDATE');
|
||||
$stmt->execute([$id]);
|
||||
$role = $stmt->fetchColumn();
|
||||
if (!$role)
|
||||
throw new AppException('User not found.', 404);
|
||||
if ($role === 'System Administrator' && ($_SESSION[self::USER_ROLE] ?? '') !== 'System Administrator')
|
||||
if ($role === TenantAccess::SUPER && !(new TenantAccess())->isSuper($db))
|
||||
throw new AppException('Only a system administrator can modify that account.', 403);
|
||||
$this->guardAdministrator($admins, $id, $role, $active ? 'active' : 'inactive');
|
||||
$stmt = $db->prepare('UPDATE users SET user_status=?,user_auth_version=user_auth_version+1 WHERE user_serial=?');
|
||||
@@ -245,11 +245,7 @@ class MaintenanceService extends Base {
|
||||
$stmt = $db->prepare('DELETE FROM email_verifications WHERE user_serial=?');
|
||||
$stmt->execute([$id]);
|
||||
Audit::required($db, (int) ($_SESSION[self::USER_SERIAL] ?? 0), 'changed user status ' . json_encode(['user' => $id, 'active' => $active]));
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction())
|
||||
$db->rollBack();throw $e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteStatus(int $id): void {
|
||||
|
||||
Reference in New Issue
Block a user