From: Dan Brown Date: Wed, 13 Jul 2022 14:23:03 +0000 (+0100) Subject: Started removal of non-view permission queries X-Git-Tag: v22.07~1^2~25^2~7 X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/4fb85a9a5cd75f1f0a0f605a916d4c3a746ee672 Started removal of non-view permission queries 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. --- diff --git a/app/Auth/Permissions/PermissionApplicator.php b/app/Auth/Permissions/PermissionApplicator.php index 3dc529e32..40a7f6116 100644 --- a/app/Auth/Permissions/PermissionApplicator.php +++ b/app/Auth/Permissions/PermissionApplicator.php @@ -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'); } /** diff --git a/app/Auth/User.php b/app/Auth/User.php index 4e2183244..c060d5ec8 100644 --- a/app/Auth/User.php +++ b/app/Auth/User.php @@ -163,7 +163,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon } /** - * Get all permissions belonging to a the current user. + * Get all permissions belonging to the current user. */ protected function permissions(): Collection { diff --git a/app/Entities/Queries/Popular.php b/app/Entities/Queries/Popular.php index e6b22a1c9..66006df1b 100644 --- a/app/Entities/Queries/Popular.php +++ b/app/Entities/Queries/Popular.php @@ -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'); diff --git a/app/Entities/Tools/SearchRunner.php b/app/Entities/Tools/SearchRunner.php index 5d94379c9..1dcc2eb44 100644 --- a/app/Entities/Tools/SearchRunner.php +++ b/app/Entities/Tools/SearchRunner.php @@ -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); } /** diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 6b2be5a2d..4a002298c 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -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]); } /** diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 19060fbbf..5e251f9c7 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -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; diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss index 6b57147ef..3cb2dd4ed 100644 --- a/resources/sass/_variables.scss +++ b/resources/sass/_variables.scss @@ -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; diff --git a/resources/views/entities/list-item.blade.php b/resources/views/entities/list-item.blade.php index 44e06753d..5314c8446 100644 --- a/resources/views/entities/list-item.blade.php +++ b/resources/views/entities/list-item.blade.php @@ -1,7 +1,13 @@ -@component('entities.list-item-basic', ['entity' => $entity]) +@component('entities.list-item-basic', ['entity' => $entity, 'classes' => (($locked ?? false) ? 'disabled ' : '') . ($classes ?? '') ])
+ @if($locked ?? false) +
+ @icon('lock')You don't have the required permissions to select this item. +
+ @endif + @if($showPath ?? false) @if($entity->relationLoaded('book') && $entity->book) {{ $entity->book->getShortName(42) }} diff --git a/resources/views/search/parts/entity-ajax-list.blade.php b/resources/views/search/parts/entity-ajax-list.blade.php index a4eedf75e..9340ccdc5 100644 --- a/resources/views/search/parts/entity-ajax-list.blade.php +++ b/resources/views/search/parts/entity-ajax-list.blade.php @@ -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)
@endif diff --git a/routes/web.php b/routes/web.php index 5e16e5333..9b562703c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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 () {