mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-19 18:22:16 +03:00
Comments: Added HTML filter on load, tinymce elem filtering
- Added filter on load to help prevent potentially dangerous comment HTML in DB at load time (if it gets passed input filtering, or is existing). - Added TinyMCE valid_elements for input wysiwygs, to gracefully degrade content at point of user-view, rather than surprising the user by stripping content, which TinyMCE would show, post-save.
This commit is contained in:
parent
e9a19d5878
commit
06901b878f
@ -4,6 +4,7 @@ namespace BookStack\Activity\Models;
|
|||||||
|
|
||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||||
|
use BookStack\Util\HtmlContentFilter;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
@ -73,4 +74,9 @@ class Comment extends Model implements Loggable
|
|||||||
{
|
{
|
||||||
return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})";
|
return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function safeHtml(): string
|
||||||
|
{
|
||||||
|
return HtmlContentFilter::removeScriptsFromHtmlString($this->html ?? '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,6 +339,7 @@ export function buildForInput(options) {
|
|||||||
toolbar: 'bold italic link bullist numlist',
|
toolbar: 'bold italic link bullist numlist',
|
||||||
content_style: getContentStyle(options),
|
content_style: getContentStyle(options),
|
||||||
file_picker_types: 'file',
|
file_picker_types: 'file',
|
||||||
|
valid_elements: 'p,a[href|title],ol,ul,li,strong,em,br',
|
||||||
file_picker_callback: filePickerCallback,
|
file_picker_callback: filePickerCallback,
|
||||||
init_instance_callback(editor) {
|
init_instance_callback(editor) {
|
||||||
addCustomHeadContent(editor.getDoc());
|
addCustomHeadContent(editor.getDoc());
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
@php
|
||||||
|
$commentHtml = $comment->safeHtml();
|
||||||
|
@endphp
|
||||||
<div component="{{ $readOnly ? '' : 'page-comment' }}"
|
<div component="{{ $readOnly ? '' : 'page-comment' }}"
|
||||||
option:page-comment:comment-id="{{ $comment->id }}"
|
option:page-comment:comment-id="{{ $comment->id }}"
|
||||||
option:page-comment:comment-local-id="{{ $comment->local_id }}"
|
option:page-comment:comment-local-id="{{ $comment->local_id }}"
|
||||||
@ -71,13 +74,13 @@
|
|||||||
<a class="text-muted text-small" href="#comment{{ $comment->parent_id }}">@icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }}</a>
|
<a class="text-muted text-small" href="#comment{{ $comment->parent_id }}">@icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }}</a>
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
{!! $comment->html !!}
|
{!! $commentHtml !!}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(!$readOnly && userCan('comment-update', $comment))
|
@if(!$readOnly && userCan('comment-update', $comment))
|
||||||
<form novalidate refs="page-comment@form" hidden class="content pt-s px-s block">
|
<form novalidate refs="page-comment@form" hidden class="content pt-s px-s block">
|
||||||
<div class="form-group description-input">
|
<div class="form-group description-input">
|
||||||
<textarea refs="page-comment@input" name="html" rows="3" placeholder="{{ trans('entities.comment_placeholder') }}">{{ $comment->html }}</textarea>
|
<textarea refs="page-comment@input" name="html" rows="3" placeholder="{{ trans('entities.comment_placeholder') }}">{{ $commentHtml }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
<button type="button" class="button outline" refs="page-comment@form-cancel">{{ trans('common.cancel') }}</button>
|
<button type="button" class="button outline" refs="page-comment@form-cancel">{{ trans('common.cancel') }}</button>
|
||||||
|
@ -82,11 +82,10 @@ class CommentTest extends TestCase
|
|||||||
|
|
||||||
public function test_scripts_cannot_be_injected_via_comment_html()
|
public function test_scripts_cannot_be_injected_via_comment_html()
|
||||||
{
|
{
|
||||||
$this->asAdmin();
|
|
||||||
$page = $this->entities->page();
|
$page = $this->entities->page();
|
||||||
|
|
||||||
$script = '<script>const a = "script";</script><p onclick="1">My lovely comment</p>';
|
$script = '<script>const a = "script";</script><p onclick="1">My lovely comment</p>';
|
||||||
$this->postJson("/comment/$page->id", [
|
$this->asAdmin()->postJson("/comment/$page->id", [
|
||||||
'html' => $script,
|
'html' => $script,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -104,6 +103,20 @@ class CommentTest extends TestCase
|
|||||||
$pageView->assertSee('<p>My lovely comment</p><p>updated</p>');
|
$pageView->assertSee('<p>My lovely comment</p><p>updated</p>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_scripts_are_removed_even_if_already_in_db()
|
||||||
|
{
|
||||||
|
$page = $this->entities->page();
|
||||||
|
Comment::factory()->create([
|
||||||
|
'html' => '<script>superbadscript</script><p onclick="superbadonclick">scriptincommentest</p>',
|
||||||
|
'entity_type' => 'page', 'entity_id' => $page
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resp = $this->asAdmin()->get($page->getUrl());
|
||||||
|
$resp->assertSee('scriptincommentest', false);
|
||||||
|
$resp->assertDontSee('superbadscript', false);
|
||||||
|
$resp->assertDontSee('superbadonclick', false);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_reply_comments_are_nested()
|
public function test_reply_comments_are_nested()
|
||||||
{
|
{
|
||||||
$this->asAdmin();
|
$this->asAdmin();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user