]> BookStack Code Mirror - bookstack/commitdiff
Tweaked content permission endpoints, covered with tests
authorDan Brown <redacted>
Mon, 13 Mar 2023 20:06:52 +0000 (20:06 +0000)
committerDan Brown <redacted>
Mon, 13 Mar 2023 20:06:52 +0000 (20:06 +0000)
app/Entities/Tools/PermissionsUpdater.php
app/Http/Controllers/Api/ContentPermissionsController.php
tests/Api/ContentPermissionsApiTest.php [new file with mode: 0644]

index 0d1d307afe9187d03afc44a6a7c3f8a7849c65a6..36ed7ccde85dd986175bcffa7c8a7fec363e6156 100644 (file)
@@ -44,18 +44,18 @@ class PermissionsUpdater
      */
     public function updateFromApiRequestData(Entity $entity, array $data): void
     {
-        if (isset($data['override_role_permissions'])) {
+        if (isset($data['role_permissions'])) {
             $entity->permissions()->where('role_id', '!=', 0)->delete();
-            $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['override_role_permissions'] ?? [], false);
+            $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['role_permissions'] ?? [], false);
             $entity->permissions()->createMany($rolePermissionData);
         }
 
-        if (array_key_exists('override_fallback_permissions', $data)) {
+        if (array_key_exists('fallback_permissions', $data)) {
             $entity->permissions()->where('role_id', '=', 0)->delete();
         }
 
-        if (isset($data['override_fallback_permissions'])) {
-            $data = $data['override_fallback_permissions'];
+        if (isset($data['fallback_permissions']['inheriting']) && $data['fallback_permissions']['inheriting'] !== true) {
+            $data = $data['fallback_permissions'];
             $data['role_id'] = 0;
             $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions([$data], true);
             $entity->permissions()->createMany($rolePermissionData);
index 16fb68a0f23080485aec7c176b6d8a623f8b462e..1db90f97ad50234ece08b8adc786972cb3594343 100644 (file)
@@ -19,18 +19,19 @@ class ContentPermissionsController extends ApiController
         'update' => [
             'owner_id'  => ['int'],
 
-            'override_role_permissions' => ['array'],
-            'override_role_permissions.*.role_id' => ['required', 'int'],
-            'override_role_permissions.*.view' => ['required', 'boolean'],
-            'override_role_permissions.*.create' => ['required', 'boolean'],
-            'override_role_permissions.*.update' => ['required', 'boolean'],
-            'override_role_permissions.*.delete' => ['required', 'boolean'],
-
-            'override_fallback_permissions' => ['nullable'],
-            'override_fallback_permissions.view' => ['required', 'boolean'],
-            'override_fallback_permissions.create' => ['required', 'boolean'],
-            'override_fallback_permissions.update' => ['required', 'boolean'],
-            'override_fallback_permissions.delete' => ['required', 'boolean'],
+            'role_permissions' => ['array'],
+            'role_permissions.*.role_id' => ['required', 'int', 'exists:roles,id'],
+            'role_permissions.*.view' => ['required', 'boolean'],
+            'role_permissions.*.create' => ['required', 'boolean'],
+            'role_permissions.*.update' => ['required', 'boolean'],
+            'role_permissions.*.delete' => ['required', 'boolean'],
+
+            'fallback_permissions' => ['nullable'],
+            'fallback_permissions.inheriting' => ['required_with:fallback_permissions', 'boolean'],
+            'fallback_permissions.view' => ['required_if:fallback_permissions.inheriting,false', 'boolean'],
+            'fallback_permissions.create' => ['required_if:fallback_permissions.inheriting,false', 'boolean'],
+            'fallback_permissions.update' => ['required_if:fallback_permissions.inheriting,false', 'boolean'],
+            'fallback_permissions.delete' => ['required_if:fallback_permissions.inheriting,false', 'boolean'],
         ]
     ];
 
@@ -38,6 +39,9 @@ class ContentPermissionsController extends ApiController
      * Read the configured content-level permissions for the item of the given type and ID.
      * 'contentType' should be one of: page, book, chapter, bookshelf.
      * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for.
+     * The permissions shown are those that override the default for just the specified item, they do not show the
+     * full evaluated permission for a role, nor do they reflect permissions inherited from other items in the hierarchy.
+     * Fallback permission values may be `null` when inheriting is active.
      */
     public function read(string $contentType, string $contentId)
     {
@@ -53,6 +57,10 @@ class ContentPermissionsController extends ApiController
      * Update the configured content-level permissions for the item of the given type and ID.
      * 'contentType' should be one of: page, book, chapter, bookshelf.
      * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for.
+     * Providing an empty `role_permissions` array will remove any existing configured role permissions,
+     * so you may want to fetch existing permissions beforehand if just adding/removing a single item.
+     * You should completely omit the `owner_id`, `role_permissions` and/or the `fallback_permissions` properties
+     * if you don't wish to update details within those categories.
      */
     public function update(Request $request, string $contentType, string $contentId)
     {
@@ -75,13 +83,18 @@ class ContentPermissionsController extends ApiController
             ->get();
 
         $fallback = $entity->permissions()->where('role_id', '=', 0)->first();
-        $fallback?->makeHidden('role_id');
+        $fallbackData = [
+            'inheriting' => is_null($fallback),
+            'view' => $fallback->view ?? null,
+            'create' => $fallback->create ?? null,
+            'update' => $fallback->update ?? null,
+            'delete' => $fallback->delete ?? null,
+        ];
 
         return [
             'owner' => $entity->ownedBy()->first(),
-            'override_role_permissions' => $rolePermissions,
-            'override_fallback_permissions' => $fallback,
-            'inheriting' => is_null($fallback),
+            'role_permissions' => $rolePermissions,
+            'fallback_permissions' => $fallbackData,
         ];
     }
 }
diff --git a/tests/Api/ContentPermissionsApiTest.php b/tests/Api/ContentPermissionsApiTest.php
new file mode 100644 (file)
index 0000000..50b82e5
--- /dev/null
@@ -0,0 +1,262 @@
+<?php
+
+namespace Tests\Api;
+
+use Tests\TestCase;
+
+class ContentPermissionsApiTest extends TestCase
+{
+    use TestsApi;
+
+    protected string $baseEndpoint = '/api/content-permissions';
+
+    public function test_user_roles_manage_permission_needed_for_all_endpoints()
+    {
+        $page = $this->entities->page();
+        $endpointMap = [
+            ['get', "/api/content-permissions/page/{$page->id}"],
+            ['put', "/api/content-permissions/page/{$page->id}"],
+        ];
+        $editor = $this->users->editor();
+
+        $this->actingAs($editor, 'api');
+        foreach ($endpointMap as [$method, $uri]) {
+            $resp = $this->json($method, $uri);
+            $resp->assertStatus(403);
+            $resp->assertJson($this->permissionErrorResponse());
+        }
+
+        $this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']);
+
+        foreach ($endpointMap as [$method, $uri]) {
+            $resp = $this->json($method, $uri);
+            $this->assertNotEquals(403, $resp->getStatusCode());
+        }
+    }
+
+    public function test_read_endpoint_shows_expected_detail()
+    {
+        $page = $this->entities->page();
+        $owner = $this->users->newUser();
+        $role = $this->users->createRole();
+        $this->permissions->addEntityPermission($page, ['view', 'delete'], $role);
+        $this->permissions->changeEntityOwner($page, $owner);
+        $this->permissions->setFallbackPermissions($page, ['update', 'create']);
+
+        $this->actingAsApiAdmin();
+        $resp = $this->getJson($this->baseEndpoint . "/page/{$page->id}");
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => [
+                'id' => $owner->id, 'name' => $owner->name, 'slug' => $owner->slug,
+            ],
+            'role_permissions' => [
+                [
+                    'role_id' => $role->id,
+                    'view' => true,
+                    'create' => false,
+                    'update' => false,
+                    'delete' => true,
+                    'role' => [
+                        'id' => $role->id,
+                        'display_name' => $role->display_name,
+                    ]
+                ]
+            ],
+            'fallback_permissions' => [
+                'inheriting' => false,
+                'view' => false,
+                'create' => true,
+                'update' => true,
+                'delete' => false,
+            ],
+        ]);
+    }
+
+    public function test_read_endpoint_shows_expected_detail_when_items_are_empty()
+    {
+        $page = $this->entities->page();
+        $page->permissions()->delete();
+        $page->owned_by = null;
+        $page->save();
+
+        $this->actingAsApiAdmin();
+        $resp = $this->getJson($this->baseEndpoint . "/page/{$page->id}");
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => null,
+            'role_permissions' => [],
+            'fallback_permissions' => [
+                'inheriting' => true,
+                'view' => null,
+                'create' => null,
+                'update' => null,
+                'delete' => null,
+            ],
+        ]);
+    }
+
+    public function test_update_endpoint_can_change_owner()
+    {
+        $page = $this->entities->page();
+        $newOwner = $this->users->newUser();
+
+        $this->actingAsApiAdmin();
+        $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [
+            'owner_id' => $newOwner->id,
+        ]);
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => ['id' => $newOwner->id, 'name' => $newOwner->name, 'slug' => $newOwner->slug],
+            'role_permissions' => [],
+            'fallback_permissions' => [
+                'inheriting' => true,
+                'view' => null,
+                'create' => null,
+                'update' => null,
+                'delete' => null,
+            ],
+        ]);
+    }
+
+    public function test_update_can_set_role_permissions()
+    {
+        $page = $this->entities->page();
+        $page->owned_by = null;
+        $page->save();
+        $newRoleA = $this->users->createRole();
+        $newRoleB = $this->users->createRole();
+
+        $this->actingAsApiAdmin();
+        $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [
+            'role_permissions' => [
+                ['role_id' => $newRoleA->id, 'view' => true, 'create' => false, 'update' => false, 'delete' => false],
+                ['role_id' => $newRoleB->id, 'view' => true, 'create' => false, 'update' => true, 'delete' => true],
+            ],
+        ]);
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => null,
+            'role_permissions' => [
+                [
+                    'role_id' => $newRoleA->id,
+                    'view' => true,
+                    'create' => false,
+                    'update' => false,
+                    'delete' => false,
+                    'role' => [
+                        'id' => $newRoleA->id,
+                        'display_name' => $newRoleA->display_name,
+                    ]
+                ],
+                [
+                    'role_id' => $newRoleB->id,
+                    'view' => true,
+                    'create' => false,
+                    'update' => true,
+                    'delete' => true,
+                    'role' => [
+                        'id' => $newRoleB->id,
+                        'display_name' => $newRoleB->display_name,
+                    ]
+                ]
+            ],
+            'fallback_permissions' => [
+                'inheriting' => true,
+                'view' => null,
+                'create' => null,
+                'update' => null,
+                'delete' => null,
+            ],
+        ]);
+    }
+
+    public function test_update_can_set_fallback_permissions()
+    {
+        $page = $this->entities->page();
+        $page->owned_by = null;
+        $page->save();
+
+        $this->actingAsApiAdmin();
+        $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [
+            'fallback_permissions' => [
+                'inheriting' => false,
+                'view' => true,
+                'create' => true,
+                'update' => true,
+                'delete' => false,
+            ],
+        ]);
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => null,
+            'role_permissions' => [],
+            'fallback_permissions' => [
+                'inheriting' => false,
+                'view' => true,
+                'create' => true,
+                'update' => true,
+                'delete' => false,
+            ],
+        ]);
+    }
+
+    public function test_update_can_clear_roles_permissions()
+    {
+        $page = $this->entities->page();
+        $this->permissions->addEntityPermission($page, ['view'], $this->users->createRole());
+        $page->owned_by = null;
+        $page->save();
+
+        $this->actingAsApiAdmin();
+        $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [
+            'role_permissions' => [],
+        ]);
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => null,
+            'role_permissions' => [],
+            'fallback_permissions' => [
+                'inheriting' => true,
+                'view' => null,
+                'create' => null,
+                'update' => null,
+                'delete' => null,
+            ],
+        ]);
+    }
+
+    public function test_update_can_clear_fallback_permissions()
+    {
+        $page = $this->entities->page();
+        $this->permissions->setFallbackPermissions($page, ['view', 'update']);
+        $page->owned_by = null;
+        $page->save();
+
+        $this->actingAsApiAdmin();
+        $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [
+            'fallback_permissions' => [
+                'inheriting' => true,
+            ],
+        ]);
+
+        $resp->assertOk();
+        $resp->assertExactJson([
+            'owner' => null,
+            'role_permissions' => [],
+            'fallback_permissions' => [
+                'inheriting' => true,
+                'view' => null,
+                'create' => null,
+                'update' => null,
+                'delete' => null,
+            ],
+        ]);
+    }
+}