mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Permissions: Updated generation querying to be more efficient
Query of existing entity permissions during view permission generation could cause timeouts or SQL placeholder limits due to massive whereOr query generation, where an "or where" clause would be created for each entity type/id combo involved, which could be all within 20 books. This updates the query handling to use a query per type involved, with no "or where"s, and to be chunked at large entity counts. Also tweaked role-specific permission regen to chunk books at half-previous rate to prevent such a large scope being involved on each chunk. For #4695
This commit is contained in:
parent
88ee33ee49
commit
02d94c8798
@ -9,11 +9,9 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class EntityPermissionEvaluator
|
||||
{
|
||||
protected string $action;
|
||||
|
||||
public function __construct(string $action)
|
||||
{
|
||||
$this->action = $action;
|
||||
public function __construct(
|
||||
protected string $action
|
||||
) {
|
||||
}
|
||||
|
||||
public function evaluateEntityForUser(Entity $entity, array $userRoleIds): ?bool
|
||||
@ -82,23 +80,25 @@ class EntityPermissionEvaluator
|
||||
*/
|
||||
protected function getPermissionsMapByTypeId(array $typeIdChain, array $filterRoleIds): array
|
||||
{
|
||||
$query = EntityPermission::query()->where(function (Builder $query) use ($typeIdChain) {
|
||||
foreach ($typeIdChain as $typeId) {
|
||||
$query->orWhere(function (Builder $query) use ($typeId) {
|
||||
[$type, $id] = explode(':', $typeId);
|
||||
$query->where('entity_type', '=', $type)
|
||||
->where('entity_id', '=', $id);
|
||||
});
|
||||
$idsByType = [];
|
||||
foreach ($typeIdChain as $typeId) {
|
||||
[$type, $id] = explode(':', $typeId);
|
||||
if (!isset($idsByType[$type])) {
|
||||
$idsByType[$type] = [];
|
||||
}
|
||||
});
|
||||
|
||||
if (!empty($filterRoleIds)) {
|
||||
$query->where(function (Builder $query) use ($filterRoleIds) {
|
||||
$query->whereIn('role_id', [...$filterRoleIds, 0]);
|
||||
});
|
||||
$idsByType[$type][] = $id;
|
||||
}
|
||||
|
||||
$relevantPermissions = $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all();
|
||||
$relevantPermissions = [];
|
||||
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
$idsChunked = array_chunk($ids, 10000);
|
||||
foreach ($idsChunked as $idChunk) {
|
||||
$permissions = $this->getPermissionsForEntityIdsOfType($type, $idChunk, $filterRoleIds);
|
||||
array_push($relevantPermissions, ...$permissions);
|
||||
}
|
||||
}
|
||||
|
||||
$map = [];
|
||||
foreach ($relevantPermissions as $permission) {
|
||||
@ -113,6 +113,26 @@ class EntityPermissionEvaluator
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $ids
|
||||
* @param int[] $filterRoleIds
|
||||
* @return EntityPermission[]
|
||||
*/
|
||||
protected function getPermissionsForEntityIdsOfType(string $type, array $ids, array $filterRoleIds): array
|
||||
{
|
||||
$query = EntityPermission::query()
|
||||
->where('entity_type', '=', $type)
|
||||
->whereIn('entity_id', $ids);
|
||||
|
||||
if (!empty($filterRoleIds)) {
|
||||
$query->where(function (Builder $query) use ($filterRoleIds) {
|
||||
$query->whereIn('role_id', [...$filterRoleIds, 0]);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
|
@ -83,13 +83,13 @@ class JointPermissionBuilder
|
||||
$role->load('permissions');
|
||||
|
||||
// Chunk through all books
|
||||
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
|
||||
$this->bookFetchQuery()->chunk(10, function ($books) use ($roles) {
|
||||
$this->buildJointPermissionsForBooks($books, $roles);
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
Bookshelf::query()->select(['id', 'owned_by'])
|
||||
->chunk(50, function ($shelves) use ($roles) {
|
||||
->chunk(100, function ($shelves) use ($roles) {
|
||||
$this->createManyJointPermissions($shelves->all(), $roles);
|
||||
});
|
||||
}
|
||||
|
@ -28,12 +28,18 @@ class LargeContentSeeder extends Seeder
|
||||
|
||||
/** @var Book $largeBook */
|
||||
$largeBook = Book::factory()->create(['name' => 'Large book' . Str::random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
|
||||
$pages = Page::factory()->count(200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
|
||||
$chapters = Chapter::factory()->count(50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
|
||||
|
||||
$largeBook->pages()->saveMany($pages);
|
||||
$largeBook->chapters()->saveMany($chapters);
|
||||
$all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all()));
|
||||
|
||||
$allPages = [];
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
$pages = Page::factory()->count(100)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id, 'chapter_id' => $chapter->id]);
|
||||
$largeBook->pages()->saveMany($pages);
|
||||
array_push($allPages, ...$pages->all());
|
||||
}
|
||||
|
||||
$all = array_merge([$largeBook], $allPages, array_values($chapters->all()));
|
||||
|
||||
app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook);
|
||||
app()->make(SearchIndex::class)->indexEntities($all);
|
||||
|
Loading…
Reference in New Issue
Block a user