Files
Siro/IMPLEMENTATION_STEPS.md
2026-06-16 17:47:19 +03:00

16 KiB
Raw Blame History

إجراءات عملية - البدء الفوري بالإصلاحات

تاريخ التحديث: 16 يونيو 2026
الأولوية: 🔴 حرج جداً


المرحلة 1: الإجراءات الفورية (اليوم الأول - 4 ساعات)

الخطوة 1: تعطيل نقاط نهاية المحفظة الخطيرة

السبب: منع الاحتيال المالي الفوري

# نسخ احتياطية أولاً
cp /walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/add.php \
   /walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/add.php.bak

cp /walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/addFromAdmin.php \
   /walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/addFromAdmin.php.bak

التعطيل المؤقت:

<?php
// /walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/add.php
// ⚠️ معطل مؤقتاً - يتم إصلاح الأمان

http_response_code(503);
echo json_encode([
    'status' => 'error',
    'message' => 'Wallet service temporarily disabled for security updates',
    'eta' => '2026-06-17 00:00:00 UTC'
]);
exit;
?>

الخطوة 2: إنشاء endpoint جديد في Backend

الموقع: backend/wallet/add.php (جديد)

<?php
// backend/wallet/add.php - ✅ جديد وآمن

require_once __DIR__ . '/../core/bootstrap.php';

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: https://siromove.com');
header('Access-Control-Allow-Methods: POST');

try {
    // 1⃣ مصادقة JWT إجبارية
    $headers = getallheaders();
    $authHeader = $headers['Authorization'] ?? '';

    if (!preg_match('/Bearer\s+(\S+)/', $authHeader, $matches)) {
        http_response_code(401);
        jsonError('Authentication required (JWT needed)', 401);
    }

    // التحقق من JWT
    $jwtService = new JwtService($redis);
    try {
        $decoded = $jwtService->verifyAccessToken($matches[1]);
    } catch (Exception $e) {
        http_response_code(401);
        jsonError('Invalid or expired token', 401);
    }

    // 2⃣ التحقق من الدور (التفويض)
    if ($decoded->role !== 'driver') {
        http_response_code(403);
        jsonError('Only drivers can add funds', 403);
    }

    $driverID = (int) $decoded->id;
    $amount = (float) filterRequest('amount');
    $source = filterRequest('source');

    // 3⃣ تحديد السرعة
    $limiter = new RateLimiter($redis);
    try {
        $limiter->enforce("wallet_add_" . $driverID, 'add');
    } catch (Exception $e) {
        http_response_code(429);
        jsonError('Too many requests. Please try again later.', 429);
    }

    // 4⃣ التحقق من المبلغ
    if ($amount <= 0) {
        jsonError('Amount must be greater than 0', 400);
    }
    if ($amount > 10000) {
        jsonError('Amount cannot exceed 10,000', 400);
    }

    // 5⃣ تسجيل التدقيق
    securityLog("Driver wallet add request", [
        'driver_id' => $driverID,
        'amount' => $amount,
        'source' => $source,
        'timestamp' => date('Y-m-d H:i:s')
    ]);

    // 6⃣ حفظ في قاعدة البيانات المحلية أولاً
    $con = Database::get('main');
    $stmt = $con->prepare(
        "INSERT INTO driver_wallet_requests (driver_id, amount, source, status)
         VALUES (?, ?, ?, 'pending')"
    );
    $stmt->execute([$driverID, $amount, $source]);
    $requestID = $con->lastInsertId();

    // 7⃣ استدعاء خادم المحفظة عبر S2S API
    $walletConnector = new WalletConnector();
    try {
        $response = $walletConnector->call('ride/driverWallet/add_s2s', [
            'driver_id' => $driverID,
            'amount' => $amount,
            'source' => $source,
            'request_id' => $requestID,
            'backend_id' => getenv('BACKEND_ID'),
        ]);

        // ✅ النجاح
        $stmt = $con->prepare(
            "UPDATE driver_wallet_requests SET status = 'completed' WHERE id = ?"
        );
        $stmt->execute([$requestID]);

        jsonSuccess(['message' => 'Funds added successfully']);

    } catch (Exception $e) {
        // فشل العملية - سجل الخطأ
        $stmt = $con->prepare(
            "UPDATE driver_wallet_requests SET status = 'failed', error = ? WHERE id = ?"
        );
        $stmt->execute([$e->getMessage(), $requestID]);

        securityLog("Wallet S2S call failed", [
            'driver_id' => $driverID,
            'error' => $e->getMessage()
        ]);

        http_response_code(500);
        jsonError('Failed to process payment', 500);
    }

} catch (Exception $e) {
    securityLog("Wallet endpoint error: " . $e->getMessage());
    http_response_code(500);
    jsonError('Internal server error', 500);
}
?>

الخطوة 3: إنشاء فئة WalletConnector آمنة

الموقع: backend/core/WalletConnector.php (جديد)

<?php
// backend/core/WalletConnector.php - ✅ جديد وآمن

class WalletConnector {
    private $walletUrl;
    private $hmacSecret;
    private $backendID;
    private $timeout = 10;

    public function __construct() {
        $this->walletUrl = getenv('WALLET_API_URL') ?? 'https://walletintaleq.intaleq.xyz/v2/main/';
        $this->hmacSecret = getenv('WALLET_HMAC_SECRET');
        $this->backendID = getenv('BACKEND_ID');

        if (!$this->walletUrl || !$this->hmacSecret || !$this->backendID) {
            throw new Exception("Missing wallet configuration");
        }
    }

    /**
     * استدعاء API خادم المحفظة بأمان (S2S)
     */
    public function call($endpoint, $data) {
        // ✅ أضف timestamp ونonce لمنع Replay Attacks
        $data['timestamp'] = time();
        $data['nonce'] = bin2hex(random_bytes(16));
        $data['backend_id'] = $this->backendID;

        // ✅ فرز البيانات
        ksort($data);

        // ✅ إنشاء JSON payload
        $payload = json_encode($data);

        // ✅ إنشاء توقيع HMAC
        $signature = hash_hmac('sha256', $payload, $this->hmacSecret);

        // ✅ إرسال الطلب
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $this->walletUrl . $endpoint,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,

            // ✅ رؤوس HTTP آمنة
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'X-Signature: ' . $signature,
                'X-Timestamp: ' . $data['timestamp'],
                'X-Backend-ID: ' . $this->backendID,
                'User-Agent: SiroBackend/1.0',
            ],

            // ✅ تأمين SSL/TLS
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_CAINFO => '/etc/ssl/certs/ca-bundle.crt',
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);

        // ✅ فحص الأخطاء
        if ($curlError) {
            throw new Exception("CURL Error: $curlError");
        }

        // ✅ فحص رمز HTTP
        if ($httpCode !== 200) {
            throw new Exception("Wallet API returned HTTP $httpCode: $response");
        }

        // ✅ فحص الاستجابة
        $decoded = json_decode($response, true);
        if ($decoded === null) {
            throw new Exception("Invalid JSON response from wallet");
        }

        if ($decoded['status'] !== 'success') {
            throw new Exception("Wallet error: " . ($decoded['message'] ?? 'Unknown'));
        }

        return $decoded;
    }
}
?>

الخطوة 4: تعديل طريقة الترحيل

قاعدة البيانات الجديدة:

-- جديد: جدول لتتبع طلبات إضافة الأموال
CREATE TABLE driver_wallet_requests (
    id INT AUTO_INCREMENT PRIMARY KEY,
    driver_id INT NOT NULL,
    amount DECIMAL(10, 2) NOT NULL,
    source VARCHAR(50),
    status ENUM('pending', 'completed', 'failed') DEFAULT 'pending',
    error TEXT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (driver_id) REFERENCES driver(id),
    INDEX (driver_id, status),
    INDEX (created_at)
);

-- ملف .env جديد (اضف هذه المتغيرات)
# wallet configuration
WALLET_API_URL=https://walletintaleq.intaleq.xyz/v2/main/
WALLET_HMAC_SECRET=<secret-key-here-change-me>
BACKEND_ID=siromove-backend-01

المرحلة 2: إصلاح التشفير (اليوم الثاني - 4 ساعات)

الخطوة 5: تعديل encrypt_decrypt.php

الملف: backend/encrypt_decrypt.php

سأنشئ نسخة محدثة آمنة:

<?php
// backend/encrypt_decrypt.php - ✅ تم تحديثه

class EncryptionHelper {
    private $key;

    public function __construct($key = null) {
        if ($key === null) {
            $key = getenv('ENC_KEY');
            if (!$key) {
                $keyPath = getenv('ENCRYPTION_KEY_PATH');
                if ($keyPath && file_exists($keyPath)) {
                    $key = trim(file_get_contents($keyPath));
                }
            }
        }

        if (strlen($key) !== 32) {
            throw new Exception("Key must be exactly 32 bytes (256 bits) for AES-256");
        }

        $this->key = $key;
    }

    // ✅ ضمّ IV مع النص المشفر قبل base64_encode
    private function addPadding($data, $blockSize = 16) {
        $pad = $blockSize - (strlen($data) % $blockSize);
        return $data . str_repeat(chr($pad), $pad);
    }

    private function removePadding($data) {
        $pad = ord($data[strlen($data) - 1]);
        if ($pad < 1 || $pad > 16) {
            throw new Exception("Invalid padding");
        }
        return substr($data, 0, -$pad);
    }

    /**
     * تشفير البيانات بـ IV عشوائي
     * ✅ التحسين: IV عشوائي لكل تشفير
     */
    public function encryptData($plainText) {
        try {
            $plainText = mb_convert_encoding($plainText, 'UTF-8');
            $paddedText = $this->addPadding($plainText);

            // ✅ توليد IV عشوائي - هذا هو الإصلاح الحرج
            $randomIV = openssl_random_pseudo_bytes(16, $strongRandom);
            if (!$strongRandom) {
                throw new Exception("openssl_random_pseudo_bytes failed to generate strong random");
            }

            // تشفير البيانات
            $encrypted = openssl_encrypt(
                $paddedText,
                'AES-256-CBC',
                $this->key,
                OPENSSL_RAW_DATA,
                $randomIV
            );

            if (!$encrypted) {
                throw new Exception("Encryption failed");
            }

            // ✅ ضمّ IV مع النص المشفر (IV يجب أن يكون أول 16 بايت)
            $encryptedWithIV = $randomIV . $encrypted;

            // تحويل إلى base64
            return base64_encode($encryptedWithIV);

        } catch (Exception $e) {
            error_log("Encryption error: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * فك التشفير - استخرج IV من البيانات المشفرة
     * ✅ التحسين: قراءة IV من البيانات المشفرة
     */
    public function decryptData($encryptedData) {
        try {
            // فك base64
            $encrypted = base64_decode($encryptedData, true);
            if (!$encrypted) {
                throw new Exception("Invalid base64 data");
            }

            if (strlen($encrypted) < 16) {
                throw new Exception("Encrypted data too short");
            }

            // ✅ استخرج IV من أول 16 بايت
            $iv = substr($encrypted, 0, 16);
            $ciphertext = substr($encrypted, 16);

            // فك التشفير
            $decrypted = openssl_decrypt(
                $ciphertext,
                'AES-256-CBC',
                $this->key,
                OPENSSL_RAW_DATA,
                $iv
            );

            if (!$decrypted) {
                throw new Exception("Decryption failed");
            }

            // إزالة الـ padding
            $unpadded = $this->removePadding($decrypted);

            return $unpadded;

        } catch (Exception $e) {
            error_log("Decryption error: " . $e->getMessage());
            throw $e;
        }
    }
}

// استخدام
$encryption = new EncryptionHelper();

// ✅ التشفير
$plaintext = "+20123456789";
$encrypted1 = $encryption->encryptData($plaintext);
$encrypted2 = $encryption->encryptData($plaintext);

echo "Encryption 1: $encrypted1\n";
echo "Encryption 2: $encrypted2\n";
echo "Different? " . ($encrypted1 !== $encrypted2 ? "YES ✅" : "NO ❌") . "\n";

// ✅ فك التشفير
$decrypted = $encryption->decryptData($encrypted1);
echo "Decrypted: $decrypted\n";
echo "Correct? " . ($decrypted === $plaintext ? "YES ✅" : "NO ❌") . "\n";
?>

المرحلة 3: تقليل الأذونات (ساعة واحدة)

الخطوة 6: تحديث AndroidManifest.xml

<!-- قبل -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- بعد: احذف هذه الأسطر الأربعة ↑ -->

السبب:

  • لا يوجد استخدام فعلي لـ External Storage
  • لا يوجد استخدام لـ SYSTEM_ALERT_WINDOW
  • ⚠️ MODIFY_AUDIO_SETTINGS يمكن استبداله بـ requestAudioFocus

المرحلة 4: تحديث الروابط (30 دقيقة)

الخطوة 7: تحديث functions.php

في backend/functions.php:

// ❌ احذف هذه:
function getAllowedSocketUrls(): array {
    return [
        'http://188.68.36.205:2021',
        'http://188.68.36.205:3031',
        'https://location.intaleq.xyz',
    ];
}

// ✅ استبدل بهذا:
function getAllowedSocketUrls(): array {
    $urls = getenv('ALLOWED_SOCKET_URLS');
    if ($urls) {
        return array_map('trim', explode(',', $urls));
    }
    return [
        'https://location.siromove.com',    // HTTPS فقط
        'https://socket.siromove.com',      // HTTPS فقط
    ];
}

في .env:

# Socket URLs - HTTPS only
ALLOWED_SOCKET_URLS=https://location.siromove.com,https://socket.siromove.com

الخلاصة والجدول الزمني

المرحلة الخطوات المدة التاريخ المتوقع
1 تعطيل + Backend endpoint + WalletConnector 4 س اليوم (16/6)
2 تحديث encrypt_decrypt.php + اختبار 4 س غد (17/6)
3 تقليل الأذونات + نشر تطبيق 1 س غد (17/6)
4 تحديث الروابط + اختبار 30 د غد (17/6)
الإجمالي - ~9.5 ساعة بحلول 17/6