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.
This commit is contained in:
Dan Brown 2022-07-13 15:23:03 +01:00
parent 2989852520
commit 4fb85a9a5c
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
10 changed files with 47 additions and 15 deletions

View File

@ -72,6 +72,7 @@ class PermissionApplicator
$action = $permission; $action = $permission;
} }
// TODO - Use a non-query based check
$hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0; $hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0;
$this->clean(); $this->clean();
@ -163,14 +164,14 @@ class PermissionApplicator
/** /**
* Add restrictions for a generic entity. * 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) { if ($entity instanceof Page) {
// Prevent drafts being visible to others. // Prevent drafts being visible to others.
$this->enforceDraftVisibilityOnQuery($query); $this->enforceDraftVisibilityOnQuery($query);
} }
return $this->entityRestrictionQuery($query, $action); return $this->entityRestrictionQuery($query, 'view');
} }
/** /**

View File

@ -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 protected function permissions(): Collection
{ {

View File

@ -7,10 +7,10 @@ use Illuminate\Support\Facades\DB;
class Popular extends EntityQuery 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() $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')) ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
->groupBy('viewable_id', 'viewable_type') ->groupBy('viewable_id', 'viewable_type')
->orderBy('view_count', 'desc'); ->orderBy('view_count', 'desc');

View File

@ -54,7 +54,7 @@ class SearchRunner
* *
* @return array{total: int, count: int, has_more: bool, results: Entity[]} * @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()); $entityTypes = array_keys($this->entityProvider->all());
$entityTypesToSearch = $entityTypes; $entityTypesToSearch = $entityTypes;
@ -75,7 +75,7 @@ class SearchRunner
} }
$entityModelInstance = $this->entityProvider->get($entityType); $entityModelInstance = $this->entityProvider->get($entityType);
$searchQuery = $this->buildQuery($searchOpts, $entityModelInstance, $action); $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance);
$entityTotal = $searchQuery->count(); $entityTotal = $searchQuery->count();
$searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count); $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count);
@ -159,7 +159,7 @@ class SearchRunner
/** /**
* Create a search query for an entity. * 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(); $entityQuery = $entityModelInstance->newQuery();
@ -193,7 +193,7 @@ class SearchRunner
} }
} }
return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery, $action); return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery);
} }
/** /**

View File

@ -12,7 +12,6 @@ use Illuminate\Http\Request;
class SearchController extends Controller class SearchController extends Controller
{ {
protected $searchRunner; protected $searchRunner;
protected $entityContextManager;
public function __construct(SearchRunner $searchRunner) public function __construct(SearchRunner $searchRunner)
{ {
@ -79,12 +78,12 @@ class SearchController extends Controller
// Search for entities otherwise show most popular // Search for entities otherwise show most popular
if ($searchTerm !== false) { if ($searchTerm !== false) {
$searchTerm .= ' {type:' . implode('|', $entityTypes) . '}'; $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 { } 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]);
} }
/** /**

View 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 { .entity-list-item-path-sep {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;

View File

@ -45,6 +45,12 @@ $fs-s: 12px;
--color-chapter: #af4d0d; --color-chapter: #af4d0d;
--color-book: #077b70; --color-book: #077b70;
--color-bookshelf: #a94747; --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; $positive: #0f7d15;

View 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"> <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($showPath ?? false)
@if($entity->relationLoaded('book') && $entity->book) @if($entity->relationLoaded('book') && $entity->book)
<span class="text-book">{{ $entity->book->getShortName(42) }}</span> <span class="text-book">{{ $entity->book->getShortName(42) }}</span>

View File

@ -2,7 +2,12 @@
@if(count($entities) > 0) @if(count($entities) > 0)
@foreach($entities as $index => $entity) @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) @if($index !== count($entities) - 1)
<hr> <hr>
@endif @endif

View File

@ -38,6 +38,13 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
Route::get('/status', [StatusController::class, 'show']); Route::get('/status', [StatusController::class, 'show']);
Route::get('/robots.txt', [HomeController::class, 'robots']); 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... // Authenticated routes...
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {