Update: 2026-08-03 00:20:40

This commit is contained in:
Hamza-Ayed
2026-08-03 00:20:40 +03:00
parent c23c3c0721
commit 2b9f696372
39 changed files with 38513 additions and 38217 deletions
+28
View File
@@ -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
+12
View File
@@ -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"));
+5 -11
View File
@@ -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);
}
?>
+46
View File
@@ -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 {
+22
View File
@@ -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;
}
+10 -5
View File
@@ -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';
+9 -6
View File
@@ -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;
+18 -1
View File
@@ -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");