]> BookStack Code Mirror - bookstack/commitdiff
Attachments: Hid edit/delete controls where lacking permission
authorDan Brown <redacted>
Wed, 11 Dec 2024 20:38:30 +0000 (20:38 +0000)
committerDan Brown <redacted>
Wed, 11 Dec 2024 20:38:30 +0000 (20:38 +0000)
Added test to cover.
Also migrated related ajax-delete-row component to ts.

For #5323

resources/js/components/ajax-delete-row.ts [moved from resources/js/components/ajax-delete-row.js with 65% similarity]
resources/js/components/component.js
resources/views/attachments/manager-list.blade.php
tests/Uploads/AttachmentTest.php

similarity index 65%
rename from resources/js/components/ajax-delete-row.js
rename to resources/js/components/ajax-delete-row.ts
index 6ed3deedf4dd6ed9e16d7f77b9a5edaa54bded8a..4c7942a9e361a34bab2332a58be3e5721a185c16 100644 (file)
@@ -1,12 +1,16 @@
-import {onSelect} from '../services/dom.ts';
+import {onSelect} from '../services/dom';
 import {Component} from './component';
 
 export class AjaxDeleteRow extends Component {
 
+    protected row!: HTMLElement;
+    protected url!: string;
+    protected deleteButtons: HTMLElement[] = [];
+
     setup() {
         this.row = this.$el;
         this.url = this.$opts.url;
-        this.deleteButtons = this.$manyRefs.delete;
+        this.deleteButtons = this.$manyRefs.delete || [];
 
         onSelect(this.deleteButtons, this.runDelete.bind(this));
     }
@@ -21,8 +25,8 @@ export class AjaxDeleteRow extends Component {
             }
             this.row.remove();
         }).catch(() => {
-            this.row.style.opacity = null;
-            this.row.style.pointerEvents = null;
+            this.row.style.removeProperty('opacity');
+            this.row.style.removeProperty('pointer-events');
         });
     }
 
index 654f41a96643ef750d0c63bf6dac3d32a3203a08..c23898bbcbbd6f1e098485de427a70a872e5e69e 100644 (file)
@@ -8,20 +8,20 @@ export class Component {
 
     /**
      * The element that the component is registered upon.
-     * @type {Element}
+     * @type {HTMLElement}
      */
     $el = null;
 
     /**
      * Mapping of referenced elements within the component.
-     * @type {Object<string, Element>}
+     * @type {Object<string, HTMLElement>}
      */
     $refs = {};
 
     /**
      * Mapping of arrays of referenced elements within the component so multiple
      * references, sharing the same name, can be fetched.
-     * @type {Object<string, Element[]>}
+     * @type {Object<string, HTMLElement[]>}
      */
     $manyRefs = {};
 
index 0e841a042f7ef6cd0ed0b08b2edeba79c96e0f50..6314aa7b5d714c3293a7b6430bfacc793fa5e8e1 100644 (file)
                         option:event-emit-select:name="insert"
                         type="button"
                         title="{{ trans('entities.attachments_insert_link') }}"
-                        class="drag-card-action text-center text-link">@icon('link')                 </button>
-                <button component="event-emit-select"
-                        option:event-emit-select:name="edit"
-                        option:event-emit-select:id="{{ $attachment->id }}"
-                        type="button"
-                        title="{{ trans('common.edit') }}"
-                        class="drag-card-action text-center text-link">@icon('edit')</button>
-                <div component="dropdown" class="flex-fill relative">
-                    <button refs="dropdown@toggle"
+                        class="drag-card-action text-center text-link">@icon('link')</button>
+                @if(userCan('attachment-update', $attachment))
+                    <button component="event-emit-select"
+                            option:event-emit-select:name="edit"
+                            option:event-emit-select:id="{{ $attachment->id }}"
                             type="button"
-                            title="{{ trans('common.delete') }}"
-                            class="drag-card-action text-center text-neg">@icon('close')</button>
-                    <div refs="dropdown@menu" class="dropdown-menu">
-                        <p class="text-neg small px-m mb-xs">{{ trans('entities.attachments_delete') }}</p>
-                        <button refs="ajax-delete-row@delete" type="button" class="text-link small delete text-item">{{ trans('common.confirm') }}</button>
+                            title="{{ trans('common.edit') }}"
+                            class="drag-card-action text-center text-link">@icon('edit')</button>
+                @endif
+                @if(userCan('attachment-delete', $attachment))
+                    <div component="dropdown" class="flex-fill relative">
+                        <button refs="dropdown@toggle"
+                                type="button"
+                                title="{{ trans('common.delete') }}"
+                                class="drag-card-action text-center text-neg">@icon('close')</button>
+                        <div refs="dropdown@menu" class="dropdown-menu">
+                            <p class="text-neg small px-m mb-xs">{{ trans('entities.attachments_delete') }}</p>
+                            <button refs="ajax-delete-row@delete" type="button" class="text-link small delete text-item">{{ trans('common.confirm') }}</button>
+                        </div>
                     </div>
-                </div>
+                @endif
             </div>
         </div>
     @endforeach
index de448d93a4c26139d96b2f76d1ff873482e2c488..2eaf21d9c6f5f6722ff758dd0d86e4162396c018 100644 (file)
@@ -267,6 +267,50 @@ class AttachmentTest extends TestCase
         }
     }
 
+    public function test_attachment_delete_only_shows_with_permission()
+    {
+        $this->asAdmin();
+        $page = $this->entities->page();
+        $this->files->uploadAttachmentFile($this, 'upload_test.txt', $page->id);
+        $attachment = $page->attachments()->first();
+        $viewer = $this->users->viewer();
+
+        $this->permissions->grantUserRolePermissions($viewer, ['page-update-all', 'attachment-create-all']);
+
+        $resp = $this->actingAs($viewer)->get($page->getUrl('/edit'));
+        $html = $this->withHtml($resp);
+        $html->assertElementExists(".card[data-id=\"{$attachment->id}\"]");
+        $html->assertElementNotExists(".card[data-id=\"{$attachment->id}\"] button[title=\"Delete\"]");
+
+        $this->permissions->grantUserRolePermissions($viewer, ['attachment-delete-all']);
+
+        $resp = $this->actingAs($viewer)->get($page->getUrl('/edit'));
+        $html = $this->withHtml($resp);
+        $html->assertElementExists(".card[data-id=\"{$attachment->id}\"] button[title=\"Delete\"]");
+    }
+
+    public function test_attachment_edit_only_shows_with_permission()
+    {
+        $this->asAdmin();
+        $page = $this->entities->page();
+        $this->files->uploadAttachmentFile($this, 'upload_test.txt', $page->id);
+        $attachment = $page->attachments()->first();
+        $viewer = $this->users->viewer();
+
+        $this->permissions->grantUserRolePermissions($viewer, ['page-update-all', 'attachment-create-all']);
+
+        $resp = $this->actingAs($viewer)->get($page->getUrl('/edit'));
+        $html = $this->withHtml($resp);
+        $html->assertElementExists(".card[data-id=\"{$attachment->id}\"]");
+        $html->assertElementNotExists(".card[data-id=\"{$attachment->id}\"] button[title=\"Edit\"]");
+
+        $this->permissions->grantUserRolePermissions($viewer, ['attachment-update-all']);
+
+        $resp = $this->actingAs($viewer)->get($page->getUrl('/edit'));
+        $html = $this->withHtml($resp);
+        $html->assertElementExists(".card[data-id=\"{$attachment->id}\"] button[title=\"Edit\"]");
+    }
+
     public function test_file_access_with_open_query_param_provides_inline_response_with_correct_content_type()
     {
         $page = $this->entities->page();