Update: 2026-08-03 00:20:40
This commit is contained in:
@@ -1,6 +1,19 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
// هذه النقطة تُرجع بيانات ركّاب مفكوكة التشفير (هاتف، بريد، تاريخ ميلاد،
|
||||
// هاتف طوارئ، وتوكن إشعارات). لم يكن فيها أي فحص للدور، و connect.php لا
|
||||
// يفرض بوّابة عامة على مسار /Admin — أي أن أي مستخدم يملك JWT صالحاً، بما
|
||||
// فيه راكب عادي، كان يستطيع سحب هذه البيانات كاملة.
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'status' => 'failure',
|
||||
'message' => 'Forbidden. Admin access required.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "SELECT
|
||||
`passengers`.`id`,
|
||||
`passengers`.`phone`,
|
||||
@@ -91,6 +104,21 @@ foreach ($result as &$row) {
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
// نموذج الصلاحيات: `super_admin` وحده يرى الرقم كاملاً، و`admin` يراقب برقم
|
||||
// مُخفى جزئياً. توكن الإشعارات لا تعرضه الواجهة إطلاقاً ولا حاجة لإرساله —
|
||||
// تسريبه يتيح انتحال إشعارات موجّهة لراكب بعينه.
|
||||
$isSuperAdmin = ($role === 'super_admin');
|
||||
foreach ($result as &$row) {
|
||||
unset($row['passengerToken']);
|
||||
|
||||
if (!$isSuperAdmin) {
|
||||
if (isset($row['phone'])) $row['phone'] = maskPhone($row['phone']);
|
||||
if (isset($row['sosPhone'])) $row['sosPhone'] = maskPhone($row['sosPhone']);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess($data = $result);
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
// نقطة تُرجع بيانات راكب مفكوكة التشفير. connect.php لا يفرض بوّابة عامة
|
||||
// على مسار /Admin، فبدون هذا الفحص كان أي مستخدم يملك JWT صالحاً — بما فيه
|
||||
// راكب عادي — يستطيع استعلام بيانات أي راكب آخر.
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'status' => 'failure',
|
||||
'message' => 'Forbidden. Admin access required.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$passengerID = filterRequest("passengerID");
|
||||
|
||||
$sql = "SELECT
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
// نقطة تُرجع بيانات راكب مفكوكة التشفير. connect.php لا يفرض بوّابة عامة
|
||||
// على مسار /Admin، فبدون هذا الفحص كان أي مستخدم يملك JWT صالحاً — بما فيه
|
||||
// راكب عادي — يستطيع استعلام بيانات أي راكب آخر.
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'status' => 'failure',
|
||||
'message' => 'Forbidden. Admin access required.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$passengerEmail = $encryptionHelper->encryptData(filterRequest("passengerEmail"));
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$passengerphone = $encryptionHelper->encryptData(filterRequest("passengerphone"));
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
<?php
|
||||
// Admin/v2/security/audit_logs.php
|
||||
|
||||
// ── سجل تتبع ────────────────────────────────────────────
|
||||
$debugFile = __DIR__ . '/../../../logs/audit_debug.txt';
|
||||
$logDir = dirname($debugFile);
|
||||
if (!is_dir($logDir)) @mkdir($logDir, 0750, true);
|
||||
|
||||
@file_put_contents($debugFile, "[" . date('Y-m-d H:i:s') . "] === REQUEST START ===\n", FILE_APPEND);
|
||||
// كان هنا تتبّع تشخيصي يكتب ملفاً نصياً على القرص مع كل طلب، ويسجّل فيه
|
||||
// user_id والدور. أُزيل: يراكم ملفاً بلا حد في الإنتاج ويسرّب سياق الجلسة.
|
||||
// ما يستحق التسجيل يذهب إلى error_log مع الأخطاء وحدها.
|
||||
|
||||
try {
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
@file_put_contents($debugFile, " → connect.php & encryption OK. user_id=$user_id | role=$role\n", FILE_APPEND);
|
||||
} catch (Exception $e) {
|
||||
@file_put_contents($debugFile, " → Loading FAILED: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||
error_log('[audit_logs] bootstrap failed: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
printFailure('loading failed', 500);
|
||||
exit;
|
||||
@@ -21,7 +17,6 @@ try {
|
||||
|
||||
// ── فحص الصلاحيات ────────────────────────────────────────
|
||||
if ($role !== 'super_admin' && $role !== 'admin') {
|
||||
@file_put_contents($debugFile, " → BLOCKED: role=$role\n", FILE_APPEND);
|
||||
printFailure("Unauthorized. role=$role", 403);
|
||||
}
|
||||
|
||||
@@ -55,12 +50,11 @@ try {
|
||||
}
|
||||
|
||||
$count = count($logs);
|
||||
@file_put_contents($debugFile, " → SUCCESS: fetched $count logs\n", FILE_APPEND);
|
||||
|
||||
jsonSuccess($logs);
|
||||
|
||||
} catch (Exception $e) {
|
||||
@file_put_contents($debugFile, " → QUERY ERROR: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||
error_log('[audit_logs] query failed: ' . $e->getMessage());
|
||||
jsonError('Query failed', 500);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -33,6 +33,52 @@ $decoded = $jwtService->authenticate();
|
||||
$user_id = $decoded->user_id ?? null;
|
||||
$role = $decoded->role ?? 'passenger';
|
||||
|
||||
// ============================================================
|
||||
// 2. بوّابة صلاحيات افتراضية لمسار /Admin
|
||||
// ============================================================
|
||||
// النموذج السابق كان «آمن إن تذكّرت»: كل نقطة تحت Admin/ تفحص الدور بنفسها،
|
||||
// ومن يُغفل الفحص يصبح مكشوفاً. وقد وُجد فعلاً أن getPassengerDetails.php
|
||||
// و getPassengerDetailsByPassengerID.php و getPassengerbyEmail.php كانت بلا
|
||||
// أي فحص، فكان أي حامل JWT صالح — بما فيه راكب عادي — يقرأ بيانات ركّاب
|
||||
// مفكوكة التشفير.
|
||||
//
|
||||
// هنا نقلب النموذج إلى «آمن افتراضاً»: كل ملف تحت Admin/ يمرّ عبر connect.php
|
||||
// يتطلب دور admin أو super_admin، ما لم يكن في قائمة استثناء صريحة.
|
||||
//
|
||||
// نقاط تسجيل الدخول (Admin/auth/*, jwtService.php) لا تستدعي connect.php
|
||||
// أصلاً، فلا تتأثر بهذه البوّابة ولا يمكن أن تُقفل على نفسها.
|
||||
$adminDir = realpath(__DIR__ . '/Admin');
|
||||
$script = realpath($_SERVER['SCRIPT_FILENAME'] ?? '');
|
||||
|
||||
if ($adminDir && $script && str_starts_with($script, $adminDir . DIRECTORY_SEPARATOR)) {
|
||||
// نقاط تحت Admin/ تستدعيها تطبيقات الجوال بأدوار غير إدارية.
|
||||
// تم التحقق من مواضع الاستدعاء الفعلية في siro_rider و siro_driver:
|
||||
// errorApp.php → التطبيقان يبلّغان عن الأخطاء
|
||||
// sendEmailToDrivertransaction.php → تطبيق السائق
|
||||
// بقية روابط Admin/ المعرّفة في التطبيقين غير مستدعاة إطلاقاً.
|
||||
$adminGateExempt = [
|
||||
'errorApp.php',
|
||||
'sendEmailToDrivertransaction.php',
|
||||
];
|
||||
|
||||
if (!in_array(basename($script), $adminGateExempt, true)
|
||||
&& $role !== 'admin'
|
||||
&& $role !== 'super_admin'
|
||||
) {
|
||||
securityLog('Admin gate blocked non-admin request', [
|
||||
'script' => basename($script),
|
||||
'role' => $role,
|
||||
'user_id' => $user_id ?? 'unknown',
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||
]);
|
||||
http_response_code(403);
|
||||
exit(json_encode([
|
||||
'status' => 'failure',
|
||||
'message' => 'Forbidden. Admin access required.',
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3. Database Connection
|
||||
try {
|
||||
|
||||
@@ -288,3 +288,25 @@ function normalizePhone(string $phone): string
|
||||
return $d; // رقم خارج النطاق — يُعاد كما هو
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* إخفاء جزء من رقم الهاتف قبل عرضه لدور لا يملك صلاحية رؤيته كاملاً.
|
||||
*
|
||||
* نموذج الصلاحيات في اللوحة: `admin` يراقب بأرقام مُخفاة جزئياً،
|
||||
* و`super_admin` وحده يرى الرقم كاملاً.
|
||||
*
|
||||
* يُبقي مقدّمة الدولة وآخر رقمين حتى يبقى الرقم مميّزاً للمتابعة التشغيلية
|
||||
* دون كشف هوية صاحبه.
|
||||
*/
|
||||
function maskPhone(?string $phone): ?string
|
||||
{
|
||||
if ($phone === null || $phone === '') return $phone;
|
||||
|
||||
$d = preg_replace('/\D+/', '', $phone);
|
||||
$len = strlen($d);
|
||||
if ($len < 5) return str_repeat('*', max(0, $len));
|
||||
|
||||
$head = substr($d, 0, 3);
|
||||
$tail = substr($d, -2);
|
||||
return $head . str_repeat('*', $len - 5) . $tail;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,18 @@ require_once __DIR__ . '/../core/Services/FcmService.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Simple bot authentication
|
||||
// مصادقة البوت.
|
||||
// كان هذا الفحص معطّلاً بالتعليق، فكانت النقطة مفتوحة على الإنترنت لأي أحد:
|
||||
// سحب مهام التسويق، وتعليمها منجزة، والكتابة في قاعدة البيانات.
|
||||
// القيمة الافتراضية هي نفسها المبنية حالياً في APK (BuildConfig.BOT_TOKEN)
|
||||
// حتى لا ينكسر البوت الميداني؛ غيّرها في الطرفين معاً عبر SOCIAL_BOT_TOKEN.
|
||||
$headers = getallheaders();
|
||||
$botToken = $headers['X-Bot-Token'] ?? '';
|
||||
// if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
|
||||
// http_response_code(401);
|
||||
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||
// }
|
||||
$expectedToken = getenv('SOCIAL_BOT_TOKEN') ?: 'YOUR_SECRET_BOT_TOKEN';
|
||||
if (!hash_equals($expectedToken, $botToken)) {
|
||||
http_response_code(401);
|
||||
exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
$platform = $_GET['platform'] ?? 'facebook';
|
||||
|
||||
@@ -7,14 +7,17 @@
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../functions.php';
|
||||
|
||||
// Authentication and validation could be added here similar to driver_socket.php
|
||||
// For now, let's keep it simple or use a static token for the bot
|
||||
// مصادقة البوت.
|
||||
// كان جسم الشرط معطّلاً بالتعليق، فكان الفحص بلا أثر والنقطة مفتوحة على
|
||||
// الإنترنت: أي أحد يستطيع سحب المهام والكتابة في social_logs وقراءة الحسابات.
|
||||
// القيمة الافتراضية هي نفسها المبنية حالياً في APK (BuildConfig.BOT_TOKEN)
|
||||
// حتى لا ينكسر البوت الميداني؛ غيّرها في الطرفين معاً عبر SOCIAL_BOT_TOKEN.
|
||||
$headers = getallheaders();
|
||||
$botToken = $headers['X-Bot-Token'] ?? '';
|
||||
if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
|
||||
// In production, use a secure token or JWT
|
||||
// http_response_code(401);
|
||||
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||
$expectedToken = getenv('SOCIAL_BOT_TOKEN') ?: 'YOUR_SECRET_BOT_TOKEN';
|
||||
if (!hash_equals($expectedToken, $botToken)) {
|
||||
http_response_code(401);
|
||||
exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
-- ============================================================
|
||||
-- admin_audit_log — جدول سجل تدقيق عمليات الأدمن
|
||||
-- ============================================================
|
||||
-- الجدول كان مستخدماً في الكود ولا وجود له في schema_primary.sql، أي أنه
|
||||
-- غير موجود على أي نشر نظيف. والنتيجة:
|
||||
--
|
||||
-- • logAudit() في backend/functions.php يبتلع الاستثناء ويكتفي بـ
|
||||
-- error_log، فكل عملية تدقيق كانت تفشل بصمت (تحديث كازان، تعديل سائق،
|
||||
-- إرسال إشعار جماعي، تشغيل حملة، تحديث إصدار التطبيق).
|
||||
-- • شاشة "سجلات الأمان" تظهر فارغة دائماً — لا لأن العرض معطّل بل لأن
|
||||
-- الاستعلام لا يجد جدولاً.
|
||||
--
|
||||
-- الأعمدة هنا هي اتحاد ما يكتبه المصدران المختلفان:
|
||||
-- - functions.php::logAudit → admin_id, action, table_name, record_id, details
|
||||
-- - Admin/v2/quality/blacklist_manager.php → admin_id, admin_phone, action, table_name, entity_type, details
|
||||
-- ولذلك فالأعمدة غير المشتركة تقبل NULL.
|
||||
--
|
||||
-- ملاحظة الترميز: audit_logs.php يربط admin_id مع employee.id و adminUser.id
|
||||
-- عبر COLLATE utf8mb4_general_ci صراحةً، فنُثبّت الترميز نفسه هنا لتفادي
|
||||
-- خطأ "Illegal mix of collations" عند الربط.
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `admin_audit_log` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`admin_id` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`admin_phone` VARCHAR(32) DEFAULT NULL,
|
||||
`action` VARCHAR(255) NOT NULL,
|
||||
`table_name` VARCHAR(100) DEFAULT NULL,
|
||||
`entity_type` VARCHAR(100) DEFAULT NULL,
|
||||
`record_id` VARCHAR(100) DEFAULT NULL,
|
||||
`details` TEXT DEFAULT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY `idx_admin_id` (`admin_id`),
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_action` (`action`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -1,6 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
// شاشة إدارة الشكاوى في لوحة التحكم. لم يكن فيها أي فحص للدور، و connect.php
|
||||
// لا يفرض بوّابة عامة — فأي مستخدم يملك JWT صالحاً كان يقرأ كل الشكاوى
|
||||
// بأسماء الطرفين وتقييماتهم وتوكنات الإشعارات.
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'status' => 'failure',
|
||||
'message' => 'Forbidden. Admin access required.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
cm.id, cm.ride_id, cm.passenger_id, cm.driver_id,
|
||||
@@ -103,15 +115,20 @@ try {
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$isSuperAdmin = ($role === 'super_admin');
|
||||
foreach ($row as &$item) {
|
||||
foreach (['passengerName', 'driverName', 'driverToken', 'passengerToken'] as $field) {
|
||||
foreach (['passengerName', 'driverName'] as $field) {
|
||||
if (!empty($item[$field])) {
|
||||
$dec = $encryptionHelper->decryptData($item[$field]);
|
||||
if ($dec)
|
||||
$item[$field] = $dec;
|
||||
}
|
||||
}
|
||||
// توكنات الإشعارات لا تستعملها الواجهة إطلاقاً، وتسريبها يتيح
|
||||
// إرسال إشعارات منتحلة إلى سائق أو راكب بعينه.
|
||||
unset($item['driverToken'], $item['passengerToken']);
|
||||
}
|
||||
unset($item);
|
||||
jsonSuccess($row);
|
||||
} else {
|
||||
jsonSuccess([], "No complaints found");
|
||||
|
||||
Reference in New Issue
Block a user