]> BookStack Code Mirror - bookstack/commitdiff
Started removal of non-view permission queries
authorDan Brown <redacted>
Wed, 13 Jul 2022 14:23:03 +0000 (15:23 +0100)
committerDan Brown <redacted>
Wed, 13 Jul 2022 14:23:03 +0000 (15:23 +0100)
Updated ajax search and entity selector usage to display and handle
items that the user does not have permission to interact with.
Started logic changes to not allow permission type to be passed around,
with views instead being the fixed sole permission.

app/Auth/Permissions/PermissionApplicator.php
app/Auth/User.php
app/Entities/Queries/Popular.php
app/Entities/Tools/SearchRunner.php
app/Http/Controllers/SearchController.php
resources/sass/_lists.scss
resources/sass/_variables.scss
resources/views/entities/list-item.blade.php
resources/views/search/parts/entity-ajax-list.blade.php
routes/web.php

index 3dc529e3200fbb7fc48f37d9dcaf587e8bc156f4..40a7f61162336937192871ae23bb336cfbaca9e1 100644 (file)
@@ -72,6 +72,7 @@ class PermissionApplicator
             $action = $permission;
         }
 
+        // TODO - Use a non-query based check
         $hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0;
         $this->clean();
 
@@ -163,14 +164,14 @@ class PermissionApplicator
     /**
      * Add restrictions for a generic entity.
      */
-    public function enforceEntityRestrictions(Entity $entity, Builder $query, string $action = 'view'): Builder
+    public function enforceEntityRestrictions(Entity $entity, Builder $query): Builder
     {
         if ($entity instanceof Page) {
             // Prevent drafts being visible to others.
             $this->enforceDraftVisibilityOnQuery($query);
         }
 
-        return $this->entityRestrictionQuery($query, $action);
+        return $this->entityRestrictionQuery($query, 'view');
     }
 
     /**
index 4e21832449d5a6ab1068e82e3b136c151bf72f6f..c060d5ec8e65ad724e2eff8e8dee8e901e931d9f 100644 (file)
@@ -163,7 +163,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
     }
 
     /**
-     * Get all permissions belonging to the current user.
+     * Get all permissions belonging to the current user.
      */
     protected function permissions(): Collection
     {
index e6b22a1c95fb12880e163674b093c9adbd0ae3a3..66006df1be2fbda84fb556eb3c62c4a647888dfd 100644 (file)
@@ -7,10 +7,10 @@ use Illuminate\Support\Facades\DB;
 
 class Popular extends EntityQuery
 {
-    public function run(int $count, int $page, array $filterModels = null, string $action = 'view')
+    public function run(int $count, int $page, array $filterModels = null)
     {
         $query = $this->permissionService()
-            ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', $action)
+            ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', 'view')
             ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
             ->groupBy('viewable_id', 'viewable_type')
             ->orderBy('view_count', 'desc');
index 5d94379c9871fab3f0491afc9bc78b9266c49596..1dcc2eb44167b604c1735c4c4456cf8d6c178a5f 100644 (file)
@@ -54,7 +54,7 @@ class SearchRunner
      *
      * @return array{total: int, count: int, has_more: bool, results: Entity[]}
      */
-    public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20, string $action = 'view'): array
+    public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20): array
     {
         $entityTypes = array_keys($this->entityProvider->all());
         $entityTypesToSearch = $entityTypes;
@@ -75,7 +75,7 @@ class SearchRunner
             }
 
             $entityModelInstance = $this->entityProvider->get($entityType);
-            $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance, $action);
+            $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance);
             $entityTotal = $searchQuery->count();
             $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count);
 
@@ -159,7 +159,7 @@ class SearchRunner
     /**
      * Create a search query for an entity.
      */
-    protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance, string $action = 'view'): EloquentBuilder
+    protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance): EloquentBuilder
     {
         $entityQuery = $entityModelInstance->newQuery();
 
@@ -193,7 +193,7 @@ class SearchRunner
             }
         }
 
-        return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery, $action);
+        return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery);
     }
 
     /**
index 6b2be5a2d77515433a2fbb4d78deedebb8bf1bda..4a002298cebb617de9eb0c34dd23cde56f920d44 100644 (file)
@@ -12,7 +12,6 @@ use Illuminate\Http\Request;
 class SearchController extends Controller
 {
     protected $searchRunner;
-    protected $entityContextManager;
 
     public function __construct(SearchRunner $searchRunner)
     {
@@ -79,12 +78,12 @@ class SearchController extends Controller
         // Search for entities otherwise show most popular
         if ($searchTerm !== false) {
             $searchTerm .= ' {type:' . implode('|', $entityTypes) . '}';
-            $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20, $permission)['results'];
+            $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results'];
         } else {
-            $entities = (new Popular())->run(20, 0, $entityTypes, $permission);
+            $entities = (new Popular())->run(20, 0, $entityTypes);
         }
 
-        return view('search.parts.entity-ajax-list', ['entities' => $entities]);
+        return view('search.parts.entity-ajax-list', ['entities' => $entities, 'permission' => $permission]);
     }
 
     /**
index 19060fbbf5d3b02e0129656b40f6d5ccd94d46fe..5e251f9c7c1faa7d61c4a4054b394c0a3170b042 100644 (file)
@@ -443,6 +443,14 @@ ul.pagination {
   }
 }
 
+.entity-list-item.disabled {
+  pointer-events: none;
+  cursor: not-allowed;
+  opacity: 0.8;
+  user-select: none;
+  background: var(--bg-disabled);
+}
+
 .entity-list-item-path-sep {
   display: inline-block;
   vertical-align: top;
index 6b57147eff5ff16d387453b65ffcd4abb94ee2d5..3cb2dd4eddc5eda6ce1437fbd86ef96dc7c2ef5c 100644 (file)
@@ -45,6 +45,12 @@ $fs-s: 12px;
   --color-chapter: #af4d0d;
   --color-book: #077b70;
   --color-bookshelf: #a94747;
+
+  --bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(0, 0, 0,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E");
+}
+
+:root.dark-mode {
+  --bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(255, 255, 255,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E");
 }
 
 $positive: #0f7d15;
index 44e06753d507a3df4ff17699b9fb0fda68558af4..5314c8446ce3b30822863cd5e4ec7865702c5dd0 100644 (file)
@@ -1,7 +1,13 @@
-@component('entities.list-item-basic', ['entity' => $entity])
+@component('entities.list-item-basic', ['entity' => $entity, 'classes' => (($locked ?? false) ? 'disabled ' : '') . ($classes ?? '') ])
 
 <div class="entity-item-snippet">
 
+    @if($locked ?? false)
+        <div class="text-warn my-xxs bold">
+            @icon('lock')You don't have the required permissions to select this item.
+        </div>
+    @endif
+
     @if($showPath ?? false)
         @if($entity->relationLoaded('book') && $entity->book)
             <span class="text-book">{{ $entity->book->getShortName(42) }}</span>
index a4eedf75e8c8e3d4a6804b324d84e6fe85cfda7d..9340ccdc5cce8531cf43e5f3c096d8c4eeb9d13d 100644 (file)
@@ -2,7 +2,12 @@
     @if(count($entities) > 0)
         @foreach($entities as $index => $entity)
 
-            @include('entities.list-item', ['entity' => $entity, 'showPath' => true])
+            @include('entities.list-item', [
+            'entity' => $entity,
+            'showPath' => true,
+            'locked' => $permission !== 'view' && !userCan($permission, $entity)
+            ])
+        
             @if($index !== count($entities) - 1)
                 <hr>
             @endif
index 5e16e5333e184f2f37a8f99b9e241a5deb0fd151..9b562703cc4346a70281302cfb804e46b1e494a3 100644 (file)
@@ -38,6 +38,13 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
 Route::get('/status', [StatusController::class, 'show']);
 Route::get('/robots.txt', [HomeController::class, 'robots']);
 
+Route::get('/test', function() {
+    $book = \BookStack\Entities\Models\Book::query()->where('slug', '=', 'k5TrhXxaNb')->firstOrFail();
+    $builder= app()->make(\BookStack\Auth\Permissions\JointPermissionBuilder::class);
+    $builder->rebuildForEntity($book);
+    return 'finished';
+})->withoutMiddleware('web');
+
 // Authenticated routes...
 Route::middleware('auth')->group(function () {