Skip to content

Commit c77a0fd

Browse files
committed
Page Content: Added form elements to filtering
Added and updated tests to cover. Also updated API auth to a narrower focus of existing session instead of also existing user auth. This is mainly for tests, to ensure they're following the session process we'd see for activity in the UI.
1 parent 6a63b38 commit c77a0fd

File tree

11 files changed

+176
-31
lines changed

11 files changed

+176
-31
lines changed

app/Activity/Models/Comment.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function logDescriptor(): string
8282

8383
public function safeHtml(): string
8484
{
85-
return HtmlContentFilter::removeScriptsFromHtmlString($this->html ?? '');
85+
return HtmlContentFilter::removeActiveContentFromHtmlString($this->html ?? '');
8686
}
8787

8888
public function jointPermissions(): HasMany

app/Entities/Tools/EntityHtmlDescription.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function getHtml(bool $raw = false): string
5050
return $html;
5151
}
5252

53-
return HtmlContentFilter::removeScriptsFromHtmlString($html);
53+
return HtmlContentFilter::removeActiveContentFromHtmlString($html);
5454
}
5555

5656
public function getPlain(): string

app/Entities/Tools/PageContent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public function render(bool $blankIncludes = false): string
318318
}
319319

320320
if (!config('app.allow_content_scripts')) {
321-
HtmlContentFilter::removeScriptsFromDocument($doc);
321+
HtmlContentFilter::removeActiveContentFromDocument($doc);
322322
}
323323

324324
return $doc->getBodyInnerHtml();

app/Http/Middleware/ApiAuthenticate.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public function handle(Request $request, Closure $next)
3030
*/
3131
protected function ensureAuthorizedBySessionOrToken(Request $request): void
3232
{
33-
// Return if the user is already found to be signed in via session-based auth.
34-
// This is to make it easy to browse the API via browser when exploring endpoints via the UI.
35-
if (!user()->isGuest() || session()->isStarted()) {
33+
// Use the active user session already exists.
34+
// This is to make it easy to explore API endpoints via the UI.
35+
if (session()->isStarted()) {
3636
// Ensure the user has API access permission
3737
if (!$this->sessionUserHasApiAccess()) {
3838
throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
@@ -49,7 +49,7 @@ protected function ensureAuthorizedBySessionOrToken(Request $request): void
4949
// Set our api guard to be the default for this request lifecycle.
5050
auth()->shouldUse('api');
5151

52-
// Validate the token and it's users API access
52+
// Validate the token and its users API access
5353
auth()->authenticate();
5454
}
5555

app/Theming/CustomHtmlHeadContentProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function forExport(): string
5050
$hash = md5($content);
5151

5252
return $this->cache->remember('custom-head-export:' . $hash, 86400, function () use ($content) {
53-
return HtmlContentFilter::removeScriptsFromHtmlString($content);
53+
return HtmlContentFilter::removeActiveContentFromHtmlString($content);
5454
});
5555
}
5656

app/Util/HtmlContentFilter.php

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
class HtmlContentFilter
1010
{
1111
/**
12-
* Remove all the script elements from the given HTML document.
12+
* Remove all active content from the given HTML document.
13+
* This aims to cover anything which can dynamically deal with, or send, data
14+
* like any JavaScript actions or form content.
1315
*/
14-
public static function removeScriptsFromDocument(HtmlDocument $doc)
16+
public static function removeActiveContentFromDocument(HtmlDocument $doc): void
1517
{
1618
// Remove standard script tags
1719
$scriptElems = $doc->queryXPath('//script');
@@ -21,7 +23,7 @@ public static function removeScriptsFromDocument(HtmlDocument $doc)
2123
$badLinks = $doc->queryXPath('//*[' . static::xpathContains('@href', 'javascript:') . ']');
2224
static::removeNodes($badLinks);
2325

24-
// Remove forms with calls to JavaScript URI
26+
// Remove elements with form-like attributes with calls to JavaScript URI
2527
$badForms = $doc->queryXPath('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']');
2628
static::removeNodes($badForms);
2729

@@ -47,25 +49,71 @@ public static function removeScriptsFromDocument(HtmlDocument $doc)
4749
// Remove 'on*' attributes
4850
$onAttributes = $doc->queryXPath('//@*[starts-with(name(), \'on\')]');
4951
static::removeAttributes($onAttributes);
52+
53+
// Remove form elements
54+
$formElements = ['form', 'fieldset', 'button', 'textarea', 'select'];
55+
foreach ($formElements as $formElement) {
56+
$matchingFormElements = $doc->queryXPath('//' . $formElement);
57+
static::removeNodes($matchingFormElements);
58+
}
59+
60+
// Remove non-checkbox inputs
61+
$inputsToRemove = $doc->queryXPath('//input');
62+
/** @var DOMElement $input */
63+
foreach ($inputsToRemove as $input) {
64+
$type = strtolower($input->getAttribute('type'));
65+
if ($type !== 'checkbox') {
66+
$input->parentNode->removeChild($input);
67+
}
68+
}
69+
70+
// Remove form attributes
71+
$formAttrs = ['form', 'formaction', 'formmethod', 'formtarget'];
72+
foreach ($formAttrs as $formAttr) {
73+
$matchingFormAttrs = $doc->queryXPath('//@' . $formAttr);
74+
static::removeAttributes($matchingFormAttrs);
75+
}
5076
}
5177

5278
/**
53-
* Remove scripts from the given HTML string.
79+
* Remove active content from the given HTML string.
80+
* This aims to cover anything which can dynamically deal with, or send, data
81+
* like any JavaScript actions or form content.
5482
*/
55-
public static function removeScriptsFromHtmlString(string $html): string
83+
public static function removeActiveContentFromHtmlString(string $html): string
5684
{
5785
if (empty($html)) {
5886
return $html;
5987
}
6088

6189
$doc = new HtmlDocument($html);
62-
static::removeScriptsFromDocument($doc);
90+
static::removeActiveContentFromDocument($doc);
6391

6492
return $doc->getBodyInnerHtml();
6593
}
6694

6795
/**
68-
* Create a xpath contains statement with a translation automatically built within
96+
* Alias using the old method name to avoid potential compatibility breaks during patch release.
97+
* To remove in future feature release.
98+
* @deprecated Use removeActiveContentFromDocument instead.
99+
*/
100+
public static function removeScriptsFromDocument(HtmlDocument $doc): void
101+
{
102+
static::removeActiveContentFromDocument($doc);
103+
}
104+
105+
/**
106+
* Alias using the old method name to avoid potential compatibility breaks during patch release.
107+
* To remove in future feature release.
108+
* @deprecated Use removeActiveContentFromHtmlString instead.
109+
*/
110+
public static function removeScriptsFromHtmlString(string $html): string
111+
{
112+
return static::removeActiveContentFromHtmlString($html);
113+
}
114+
115+
/**
116+
* Create an x-path 'contains' statement with a translation automatically built within
69117
* to affectively search in a cases-insensitive manner.
70118
*/
71119
protected static function xpathContains(string $property, string $value): string

tests/Api/ApiAuthTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public function test_requests_succeed_with_default_auth()
2424

2525
$this->actingAs($viewer, 'standard');
2626

27-
$resp = $this->get($this->endpoint);
27+
$this->startSession();
28+
$resp = $this->withCredentials()->get($this->endpoint);
2829
$resp->assertStatus(200);
2930
}
3031

@@ -75,6 +76,7 @@ public function test_api_access_permission_required_to_access_api_with_session_a
7576
{
7677
$editor = $this->users->editor();
7778
$this->actingAs($editor, 'standard');
79+
$this->startSession();
7880

7981
$resp = $this->get($this->endpoint);
8082
$resp->assertStatus(200);
@@ -116,6 +118,7 @@ public function test_only_get_requests_are_supported_with_session_auth()
116118
{
117119
$user = $this->users->admin();
118120
$this->actingAs($user, 'standard');
121+
$this->startSession();
119122

120123
$uriByMethods = [
121124
'POST' => '/books',

tests/Api/ChaptersApiTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public function test_update_with_new_book_id_requires_delete_permission()
252252
{
253253
$editor = $this->users->editor();
254254
$this->permissions->removeUserRolePermissions($editor, ['chapter-delete-all', 'chapter-delete-own']);
255-
$this->actingAs($editor);
255+
$this->actingAsForApi($editor);
256256
$chapter = $this->entities->chapterHasPages();
257257
$newBook = Book::query()->where('id', '!=', $chapter->book_id)->first();
258258

tests/Api/RecycleBinApiTest.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function test_settings_manage_permission_needed_for_all_endpoints()
2323
{
2424
$editor = $this->users->editor();
2525
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
26-
$this->actingAs($editor);
26+
$this->actingAsForApi($editor);
2727

2828
foreach ($this->endpointMap as [$method, $uri]) {
2929
$resp = $this->json($method, $uri);
@@ -36,7 +36,7 @@ public function test_restrictions_manage_all_permission_needed_for_all_endpoints
3636
{
3737
$editor = $this->users->editor();
3838
$this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']);
39-
$this->actingAs($editor);
39+
$this->actingAsForApi($editor);
4040

4141
foreach ($this->endpointMap as [$method, $uri]) {
4242
$resp = $this->json($method, $uri);
@@ -53,6 +53,7 @@ public function test_index_endpoint_returns_expected_page()
5353
$book = $this->entities->book();
5454
$this->actingAs($admin)->delete($page->getUrl());
5555
$this->delete($book->getUrl());
56+
$this->actingAsForApi($admin);
5657

5758
$deletions = Deletion::query()->orderBy('id')->get();
5859

@@ -89,7 +90,7 @@ public function test_index_endpoint_returns_children_count()
8990

9091
$deletion = Deletion::query()->orderBy('id')->first();
9192

92-
$resp = $this->getJson($this->baseEndpoint);
93+
$resp = $this->actingAsForApi($admin)->getJson($this->baseEndpoint);
9394

9495
$expectedData = [
9596
[
@@ -115,6 +116,7 @@ public function test_index_endpoint_returns_parent()
115116
$this->actingAs($admin)->delete($page->getUrl());
116117
$deletion = Deletion::query()->orderBy('id')->first();
117118

119+
$this->actingAsForApi($admin);
118120
$resp = $this->getJson($this->baseEndpoint);
119121

120122
$expectedData = [
@@ -141,6 +143,7 @@ public function test_restore_endpoint()
141143
$page = $this->entities->page();
142144
$this->asAdmin()->delete($page->getUrl());
143145
$page->refresh();
146+
$this->actingAsApiAdmin();
144147

145148
$deletion = Deletion::query()->orderBy('id')->first();
146149

@@ -165,6 +168,7 @@ public function test_destroy_endpoint()
165168
$page = $this->entities->page();
166169
$this->asAdmin()->delete($page->getUrl());
167170
$page->refresh();
171+
$this->actingAsApiAdmin();
168172

169173
$deletion = Deletion::query()->orderBy('id')->first();
170174

tests/Api/UsersApiTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function test_index_endpoint_has_correct_created_and_last_activity_dates(
8080
/** @var ActivityModel $activity */
8181
$activity = ActivityModel::query()->where('user_id', '=', $user->id)->latest()->first();
8282

83-
$resp = $this->asAdmin()->getJson($this->baseEndpoint . '?filter[id]=3');
83+
$resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?filter[id]=3');
8484
$resp->assertJson(['data' => [
8585
[
8686
'id' => $user->id,

0 commit comments

Comments
 (0)