regularUser = User::create([ 'name' => 'DUSK REGULAR USER', 'phone' => $phone, 'email' => $phone . '@phone.local', 'password' => bcrypt('test123'), 'address' => 'TP.HCM', 'known_sis' => 0, 'job' => 'Người dùng thường', 'status' => 1, 'spined' => 0, ]); // Admin user — có đầy đủ roles $this->adminUser = User::create([ 'name' => 'DUSK SECURE ADMIN', 'email' => 'dusk_secure_admin@test.local', 'phone' => '09' . rand(10000000, 99999999), 'password' => bcrypt('dusk_admin_pwd_2026'), 'status' => 1, 'address' => 'Test', 'known_sis' => 0, 'job' => 'Admin', 'spined' => 0, ]); $roles = Role::where('role_status', 1)->get(); foreach ($roles as $role) { UserRole::create(['user_id' => $this->adminUser->id, 'role_id' => $role->id]); } } protected function tearDown(): void { UserRole::where('user_id', $this->adminUser->id)->delete(); $this->adminUser->delete(); $this->regularUser->delete(); parent::tearDown(); } // ========================================================================= // [TC-53] Người dùng thường không thể truy cập khu vực admin // ========================================================================= /** * Người dùng đã đăng nhập nhưng KHÔNG có role admin bị redirect về /admin * khi cố truy cập bất kỳ route nào trong middleware UserRole. * * Middleware UserRole (app/Http/Middleware/UserRole.php): * - Kiểm tra constructMenuLeft() → lấy danh sách route được phép từ roles * - Nếu route hiện tại không trong danh sách → redirect('admin') * - User thường không có UserRole record → menu rỗng → TẤT CẢ admin route bị chặn */ public function test_regular_user_cannot_access_admin_area(): void { $this->browse(function (Browser $browser) { // Đăng nhập là user thường (không có role nào) $browser->loginAs($this->regularUser); // Thử truy cập /admin/account → phải bị redirect về /admin (trang login admin) $browser->visit('/admin/account') ->assertPathIs('/admin'); // Thử truy cập /admin/users → tương tự bị redirect $browser->visit('/admin/users') ->assertPathIs('/admin'); // Thử truy cập /admin/prizes → bị redirect $browser->visit('/admin/prizes') ->assertPathIs('/admin'); }); } // ========================================================================= // [TC-54] XSS Injection vào form đăng ký — output phải được escape đúng cách // ========================================================================= /** * Nhập XSS payload vào trường `name` khi đăng ký. Ứng dụng phải: * 1. Lưu dữ liệu an toàn (không thực thi script) * 2. Render escaped output trong admin — Blade {{ }} tự escape HTML entities * * Luồng kiểm tra: * - Frontend: đăng ký với name = * - Controller: uppercase → rồi lưu DB * - Admin view (table.blade.php): {{ $item->name ?? '' }} → render escaped * - Kết quả: source HTML chứa <SCRIPT> thay vì tag "; // NOTE: mb_strtoupper converts the name to . // 'ALERT' is NOT a valid JS function (case-sensitive) → no JS dialog fires from the payload. // The only alerts that CAN appear are from other sources (e.g. DataTables warnings). // This test checks XSS protection via HTML source inspection, not via alert detection. try { $this->browse(function (Browser $browser) use ($xssPhone, $xssPayload) { // ---------------------------------------------------------------- // BƯỚC 1: Đăng ký tài khoản với XSS payload trong trường name // ---------------------------------------------------------------- $browser->visit('/logout') ->visit('/login') ->type('name', $xssPayload) ->type('phone', $xssPhone) ->type('address', 'Test City') ->radio('known_sis', '0') ->type('job', 'Tester') ->press('Gửi thông tin') ->waitForText('thành công', 10) ->assertPathIs('/game'); $browser->visit('/logout'); // ---------------------------------------------------------------- // BƯỚC 2: Đăng nhập admin và mở trang danh sách user // ---------------------------------------------------------------- $browser->visit('/admin') ->type('email', 'dusk_secure_admin@test.local') ->type('password', 'dusk_admin_pwd_2026') ->press('Login') ->waitForLocation('/admin/account', 10); // Dismiss any alert that appears on the account page (e.g. DataTables warnings). // CRITICAL: Always dismiss before proceeding — leaving an alert open crashes ChromeDriver. $this->dismissAnyAlert($browser); $browser->visit('/admin/users') ->waitFor('#table', 5); // Dismiss any alert on the users page (DataTables may fire errMode alerts // if HTML content in cells triggers a parsing/column-count error). $this->dismissAnyAlert($browser); // ---------------------------------------------------------------- // ASSERTION: HTML source không chứa tag thành: // <SCRIPT>ALERT('XSS')</SCRIPT> // Nên source page KHÔNG chứa literal ") ->assertSourceMissing(""); // Kiểm tra dương: source phải chứa phiên bản đã được HTML-escape $browser->assertSourceHas('<SCRIPT>'); }); } finally { User::where('phone', $xssPhone)->delete(); } } /** * Helper: Dismiss any open alert dialog silently. * Prevents Chrome lockup when an unexpected alert (e.g. DataTables warning) appears. * * @param Browser $browser * @param string|null &$alertText Will be populated with the alert text if one was found. */ private function dismissAnyAlert(Browser $browser, ?string &$alertText = null): void { try { $alert = $browser->driver->switchTo()->alert(); $alertText = $alert->getText(); $alert->accept(); } catch (\Facebook\WebDriver\Exception\NoSuchAlertException $e) { $alertText = null; // No alert present — expected } } }