- Implemented BlogBodySanitizer to clean HTML content before saving articles, ensuring security against XSS attacks. - Added tests for BlogBodySanitizer to verify that unsafe tags and attributes are stripped from the content. - Introduced ApiLeastPrivilegeTest to ensure that unauthorized users cannot access sensitive API routes, maintaining strict access control.
112 lines
4.6 KiB
PHP
112 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Blog;
|
|
|
|
use App\Blog\Repository\BlogRepository;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* آدیت ۲۰۲۶-۰۸-۰۷: بدنهٔ مقاله بدون پاکسازی ذخیره میشد و پنل با
|
|
* `dangerouslySetInnerHTML` رندرش میکرد. پاکسازی حالا در لحظهٔ ذخیره است، پس
|
|
* این تستها بهجای خروجی، **آنچه در DB نشسته** را میسنجند.
|
|
*/
|
|
class BlogBodySanitizerTest extends ApiTestCase
|
|
{
|
|
private function storedBody(string $uuid): string
|
|
{
|
|
$this->em->clear();
|
|
|
|
return self::getContainer()->get(BlogRepository::class)->findByUuid($uuid)->getBody();
|
|
}
|
|
|
|
public function testScriptTagIsStrippedOnCreate(): void
|
|
{
|
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
|
|
|
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
|
|
'title' => 'مقالهٔ تست',
|
|
'body' => '<p>سلام</p><script>alert(1)</script>',
|
|
]);
|
|
$this->assertSame(201, $this->responseCode());
|
|
|
|
$body = $this->storedBody($res['data']['data']['uuid'] ?? $res['data']['uuid']);
|
|
$this->assertStringNotContainsString('<script', $body);
|
|
$this->assertStringNotContainsString('alert(1)', $body);
|
|
$this->assertStringContainsString('سلام', $body, 'متن سالم نباید حذف شود');
|
|
}
|
|
|
|
public function testInlineHandlerAndJavascriptUrlAreStripped(): void
|
|
{
|
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
|
|
|
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
|
|
'title' => 'مقالهٔ تست',
|
|
'body' => '<p onclick="steal()">متن</p><a href="javascript:alert(1)">لینک</a>'
|
|
. '<img src="x" onerror="alert(2)">',
|
|
]);
|
|
$this->assertSame(201, $this->responseCode());
|
|
|
|
$body = $this->storedBody($res['data']['data']['uuid'] ?? $res['data']['uuid']);
|
|
$this->assertStringNotContainsString('onclick', $body);
|
|
$this->assertStringNotContainsString('onerror', $body);
|
|
$this->assertStringNotContainsString('javascript:', $body);
|
|
$this->assertStringContainsString('متن', $body);
|
|
}
|
|
|
|
public function testSafeRichTextSurvives(): void
|
|
{
|
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
|
|
|
$html = '<h2>عنوان</h2><p><strong>پررنگ</strong> و <em>کج</em></p>'
|
|
. '<ul><li>یک</li><li>دو</li></ul>'
|
|
. '<a href="https://example.com">پیوند</a>'
|
|
. '<table><tbody><tr><td>خانه</td></tr></tbody></table>';
|
|
|
|
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
|
|
'title' => 'مقالهٔ تست',
|
|
'body' => $html,
|
|
]);
|
|
$this->assertSame(201, $this->responseCode());
|
|
|
|
$body = $this->storedBody($res['data']['data']['uuid'] ?? $res['data']['uuid']);
|
|
foreach (['<h2', '<strong', '<em', '<ul', '<li', '<a', '<table', '<td'] as $tag) {
|
|
$this->assertStringContainsString($tag, $body, "عنصر مجاز {$tag} نباید حذف شود");
|
|
}
|
|
$this->assertStringContainsString('https://example.com', $body);
|
|
// لینک باید rel امن بگیرد، وگرنه tabnabbing باز میماند.
|
|
$this->assertStringContainsString('noopener', $body);
|
|
}
|
|
|
|
public function testUpdatePathIsSanitizedToo(): void
|
|
{
|
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
|
|
|
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
|
|
'title' => 'مقالهٔ تست',
|
|
'body' => '<p>اولیه</p>',
|
|
]);
|
|
$uuid = $res['data']['data']['uuid'] ?? $res['data']['uuid'];
|
|
|
|
$this->authJson('PATCH', "/api/v1/blog/{$uuid}", $admin, [
|
|
'body' => '<p>ویرایش</p><script>alert(3)</script>',
|
|
]);
|
|
$this->assertSame(200, $this->responseCode());
|
|
|
|
$body = $this->storedBody($uuid);
|
|
$this->assertStringNotContainsString('<script', $body, 'مسیر ویرایش هم باید پاکسازی شود');
|
|
$this->assertStringContainsString('ویرایش', $body);
|
|
}
|
|
|
|
/** بدنهای که چیزی جز markup ناامن ندارد، بعد از پاکسازی خالی است → ۴۲۲، نه ذخیره. */
|
|
public function testBodyThatIsOnlyUnsafeMarkupIsRejected(): void
|
|
{
|
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
|
|
|
$this->authJson('POST', '/api/v1/blog', $admin, [
|
|
'title' => 'مقالهٔ تست',
|
|
'body' => '<script>alert(1)</script>',
|
|
]);
|
|
$this->assertSame(422, $this->responseCode());
|
|
}
|
|
}
|