Files
timebank-cc-public/tests/Feature/SearchXssProtectionTest.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

251 lines
8.4 KiB
PHP

<?php
namespace Tests\Feature;
use App\Http\Livewire\MainSearchBar;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;
class SearchXssProtectionTest extends TestCase
{
use RefreshDatabase;
/**
* Test that the sanitizeHighlights method properly escapes XSS attempts.
*
* @return void
*/
public function test_search_highlights_escape_script_tags()
{
// Create a test user
$user = User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
// Authenticate as the user
$this->actingAs($user);
// Create MainSearchBar component instance
$component = new MainSearchBar();
// Use reflection to access the private sanitizeHighlights method
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
// Test 1: Simple script tag injection
$maliciousHighlights = [
'about_short_en' => [
'I am a developer <script>alert("XSS")</script> looking for work'
]
];
$sanitized = $method->invoke($component, $maliciousHighlights);
$this->assertStringNotContainsString('<script>', $sanitized['about_short_en'][0]);
$this->assertStringContainsString('&lt;script&gt;', $sanitized['about_short_en'][0]);
}
/**
* Test that img tag with onerror handler is escaped.
*
* @return void
*/
public function test_search_highlights_escape_img_onerror()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
$maliciousHighlights = [
'about_en' => [
'Contact me <img src=x onerror=alert(document.cookie)> for projects'
]
];
$sanitized = $method->invoke($component, $maliciousHighlights);
// The img tag should be escaped
$this->assertStringNotContainsString('<img', $sanitized['about_en'][0]);
$this->assertStringContainsString('&lt;img', $sanitized['about_en'][0]);
// The equals sign in the attribute should be escaped, making it safe
$this->assertStringContainsString('src=x', $sanitized['about_en'][0]); // Text 'onerror' may remain but is harmless when HTML is escaped
}
/**
* Test that iframe injection is escaped.
*
* @return void
*/
public function test_search_highlights_escape_iframe()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
$maliciousHighlights = [
'motivation_en' => [
'My motivation <iframe src="https://evil.com"></iframe> is strong'
]
];
$sanitized = $method->invoke($component, $maliciousHighlights);
$this->assertStringNotContainsString('<iframe', $sanitized['motivation_en'][0]);
$this->assertStringContainsString('&lt;iframe', $sanitized['motivation_en'][0]);
}
/**
* Test that Elasticsearch highlight tags are preserved.
*
* @return void
*/
public function test_search_highlights_preserve_elasticsearch_tags()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
// Simulate Elasticsearch highlight with configured tags
$validHighlights = [
'name' => [
'John <span class="font-semibold text-white leading-tight">Smith</span>'
]
];
$sanitized = $method->invoke($component, $validHighlights);
// Elasticsearch highlight tags should be preserved
$this->assertStringContainsString('<span class="font-semibold text-white leading-tight">Smith</span>', $sanitized['name'][0]);
$this->assertStringContainsString('John', $sanitized['name'][0]);
}
/**
* Test that user content with special characters is escaped but highlight tags preserved.
*
* @return void
*/
public function test_search_highlights_escape_user_content_preserve_highlight_tags()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
// User content with XSS attempt + Elasticsearch highlight tags
$mixedHighlights = [
'about_short_en' => [
'I love <span class="font-semibold text-white leading-tight">coding</span> <script>alert("XSS")</script>'
]
];
$sanitized = $method->invoke($component, $mixedHighlights);
// Elasticsearch tags preserved
$this->assertStringContainsString('<span class="font-semibold text-white leading-tight">coding</span>', $sanitized['about_short_en'][0]);
// User XSS attempt escaped
$this->assertStringNotContainsString('<script>alert("XSS")</script>', $sanitized['about_short_en'][0]);
$this->assertStringContainsString('&lt;script&gt;', $sanitized['about_short_en'][0]);
}
/**
* Test that empty highlights array is handled correctly.
*
* @return void
*/
public function test_search_highlights_handle_empty_array()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
$emptyHighlights = [];
$sanitized = $method->invoke($component, $emptyHighlights);
$this->assertEmpty($sanitized);
}
/**
* Test that event handler attributes are escaped.
*
* @return void
*/
public function test_search_highlights_escape_event_handlers()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
$maliciousHighlights = [
'about_en' => [
'Click <a href="#" onclick="alert(1)">here</a> for more info'
]
];
$sanitized = $method->invoke($component, $maliciousHighlights);
// The anchor tag and attributes should be escaped
$this->assertStringNotContainsString('<a href', $sanitized['about_en'][0]);
$this->assertStringContainsString('&lt;a href=&quot;#&quot;', $sanitized['about_en'][0]);
// Verify the dangerous onclick is escaped (quotes are converted to &quot;)
$this->assertStringContainsString('onclick=&quot;', $sanitized['about_en'][0]);
}
/**
* Test that data URIs with JavaScript are escaped.
*
* @return void
*/
public function test_search_highlights_escape_data_uris()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = new MainSearchBar();
$reflection = new \ReflectionClass($component);
$method = $reflection->getMethod('sanitizeHighlights');
$method->setAccessible(true);
$maliciousHighlights = [
'about_en' => [
'Visit <a href="data:text/html,<script>alert(1)</script>">link</a>'
]
];
$sanitized = $method->invoke($component, $maliciousHighlights);
// The anchor tag should be escaped, making the data URI harmless
$this->assertStringNotContainsString('<a href="data:text/html', $sanitized['about_en'][0]);
$this->assertStringContainsString('&lt;a', $sanitized['about_en'][0]);
// Verify the script tag within the data URI is also escaped
$this->assertStringContainsString('&lt;script&gt;', $sanitized['about_en'][0]);
}
}