bổ sung đủ 57 trường hợp test
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Models\UserRole;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* SecurityTest — Kiểm thử bảo mật phân quyền và chống tấn công XSS.
|
||||
*
|
||||
* [TC-53] test_regular_user_cannot_access_admin_area
|
||||
* [TC-54] test_xss_injection_on_registration_form
|
||||
*/
|
||||
class SecurityTest extends DuskTestCase
|
||||
{
|
||||
private User $regularUser;
|
||||
private User $adminUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// User thường — KHÔNG có role admin (không có bản ghi UserRole)
|
||||
$phone = '09' . rand(10000000, 99999999);
|
||||
$this->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 = <script>alert('XSS')</script>
|
||||
* - Controller: uppercase → <SCRIPT>ALERT('XSS')</SCRIPT> 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 <script> thực
|
||||
*/
|
||||
public function test_xss_injection_on_registration_form(): void
|
||||
{
|
||||
$xssPhone = '09' . rand(10000000, 99999999);
|
||||
$xssPayload = "<script>alert('XSS')</script>";
|
||||
|
||||
// NOTE: mb_strtoupper converts the name to <SCRIPT>ALERT('XSS')</SCRIPT>.
|
||||
// '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 <script> thô (unescaped)
|
||||
// Blade {{ }} chuyển <SCRIPT>ALERT(...)</SCRIPT> thành:
|
||||
// <SCRIPT>ALERT('XSS')</SCRIPT>
|
||||
// Nên source page KHÔNG chứa literal <script> hay <SCRIPT> tags.
|
||||
// ----------------------------------------------------------------
|
||||
$browser->assertSourceMissing("<script>alert('XSS')</script>")
|
||||
->assertSourceMissing("<SCRIPT>ALERT('XSS')</SCRIPT>");
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user