Renamed and cleaned up existing permission service classes use

This commit is contained in:
Dan Brown 2022-07-12 20:15:41 +01:00
parent 2d4f708c79
commit b0a4d3d059
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
22 changed files with 140 additions and 194 deletions

View File

@ -2,7 +2,6 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Models\Entity;
use BookStack\Interfaces\Loggable;
use Illuminate\Database\Eloquent\Builder;
@ -10,13 +9,6 @@ use Illuminate\Support\Facades\Log;
class ActivityLogger
{
protected $permissionService;
public function __construct(PermissionService $permissionService)
{
$this->permissionService = $permissionService;
}
/**
* Add a generic activity event to the database.
*

View File

@ -2,7 +2,7 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
@ -13,11 +13,11 @@ use Illuminate\Database\Eloquent\Relations\Relation;
class ActivityQueries
{
protected $permissionService;
protected PermissionApplicator $permissions;
public function __construct(PermissionService $permissionService)
public function __construct(PermissionApplicator $permissions)
{
$this->permissionService = $permissionService;
$this->permissions = $permissions;
}
/**
@ -25,7 +25,7 @@ class ActivityQueries
*/
public function latest(int $count = 20, int $page = 0): array
{
$activityList = $this->permissionService
$activityList = $this->permissions
->filterRestrictedEntityRelations(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
->with(['user', 'entity'])
@ -78,7 +78,7 @@ class ActivityQueries
*/
public function userActivity(User $user, int $count = 20, int $page = 0): array
{
$activityList = $this->permissionService
$activityList = $this->permissions
->filterRestrictedEntityRelations(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
->where('user_id', '=', $user->id)

View File

@ -2,7 +2,7 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Models\Entity;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
@ -10,12 +10,11 @@ use Illuminate\Support\Facades\DB;
class TagRepo
{
protected $tag;
protected $permissionService;
protected PermissionApplicator $permissions;
public function __construct(PermissionService $ps)
public function __construct(PermissionApplicator $permissions)
{
$this->permissionService = $ps;
$this->permissions = $permissions;
}
/**
@ -51,7 +50,7 @@ class TagRepo
});
}
return $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $this->permissions->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
}
/**
@ -70,7 +69,7 @@ class TagRepo
$query = $query->orderBy('count', 'desc')->take(50);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
$query = $this->permissions->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name');
}
@ -96,7 +95,7 @@ class TagRepo
$query = $query->where('name', '=', $tagName);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
$query = $this->permissions->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value');
}

View File

@ -20,6 +20,82 @@ class JointPermissionBuilder
*/
protected $entityCache;
/**
* Re-generate all entity permission from scratch.
*/
public function rebuildForAll()
{
JointPermission::query()->truncate();
$this->readyEntityCache();
// Get all roles (Should be the most limited dimension)
$roles = Role::query()->with('permissions')->get()->all();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Rebuild the entity jointPermissions for a particular entity.
*
* @throws Throwable
*/
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->with('permissions')->get()->all(), true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildJointPermissionsForEntities($entities);
}
/**
* Build the entity jointPermissions for a particular role.
*/
public function rebuildForRole(Role $role)
{
$roles = [$role];
$role->jointPermissions()->delete();
// Chunk through all books
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Prepare the local entity cache and ensure it's empty.
*
@ -66,29 +142,6 @@ class JointPermissionBuilder
->find($chapterId);
}
/**
* Re-generate all entity permission from scratch.
*/
public function buildJointPermissions()
{
JointPermission::query()->truncate();
$this->readyEntityCache();
// Get all roles (Should be the most limited dimension)
$roles = Role::query()->with('permissions')->get()->all();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
* Get a query for fetching a book with it's children.
*/
@ -105,18 +158,6 @@ class JointPermissionBuilder
]);
}
/**
* Build joint permissions for the given shelf and role combinations.
*
* @throws Throwable
*/
protected function buildJointPermissionsForShelves(EloquentCollection $shelves, array $roles, bool $deleteOld = false)
{
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($shelves->all());
}
$this->createManyJointPermissions($shelves->all(), $roles);
}
/**
* Build joint permissions for the given book and role combinations.
@ -144,39 +185,6 @@ class JointPermissionBuilder
$this->createManyJointPermissions($entities->all(), $roles);
}
/**
* Rebuild the entity jointPermissions for a particular entity.
*
* @throws Throwable
*/
public function buildJointPermissionsForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->with('permissions')->get()->all(), true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildJointPermissionsForEntities($entities);
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
*
@ -189,26 +197,6 @@ class JointPermissionBuilder
$this->createManyJointPermissions($entities, $roles);
}
/**
* Build the entity jointPermissions for a particular role.
*/
public function buildJointPermissionForRole(Role $role)
{
$roles = [$role];
$role->jointPermissions()->delete();
// Chunk through all books
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
* Delete all the entity jointPermissions for a list of entities.
*

View File

@ -12,10 +12,10 @@ use BookStack\Traits\HasOwner;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
class PermissionService
class PermissionApplicator
{
/**
* @var ?array
* @var ?array<int>
*/
protected $userRoles = null;
@ -24,7 +24,6 @@ class PermissionService
*/
protected $currentUserModel = null;
/**
* Get the roles for the current logged in user.
*/

View File

@ -27,7 +27,7 @@ class PermissionsRepo
*/
public function getAllRoles(): Collection
{
return Role::query()->all();
return Role::query()->get();
}
/**
@ -57,7 +57,7 @@ class PermissionsRepo
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
$this->permissionBuilder->buildJointPermissionForRole($role);
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role);
@ -88,7 +88,7 @@ class PermissionsRepo
$role->fill($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$this->permissionBuilder->buildJointPermissionForRole($role);
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
}
@ -102,7 +102,7 @@ class PermissionsRepo
$permissionNameArray = array_values($permissionNameArray);
if ($permissionNameArray) {
$permissions = EntityPermission::query()
$permissions = RolePermission::query()
->whereIn('name', $permissionNameArray)
->pluck('id')
->toArray();

View File

@ -202,7 +202,6 @@ return [
// Custom BookStack
'Activity' => BookStack\Facades\Activity::class,
'Permissions' => BookStack\Facades\Permissions::class,
'Theme' => BookStack\Facades\Theme::class,
],

View File

@ -42,11 +42,11 @@ class RegeneratePermissions extends Command
{
$connection = DB::getDefaultConnection();
if ($this->hasOption('database')) {
if ($this->option('database')) {
DB::setDefaultConnection($this->option('database'));
}
$this->permissionBuilder->buildJointPermissions();
$this->permissionBuilder->rebuildForAll();
DB::setDefaultConnection($connection);
$this->comment('Permissions regenerated');

View File

@ -9,9 +9,10 @@ use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Tools\SearchIndex;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Facades\Permissions;
use BookStack\Interfaces\Deletable;
use BookStack\Interfaces\Favouritable;
use BookStack\Interfaces\Loggable;
@ -76,7 +77,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function scopeHasPermission(Builder $query, string $permission)
{
return Permissions::restrictEntityQuery($query, $permission);
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query, $permission);
}
/**
@ -284,8 +285,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function rebuildPermissions()
{
/** @noinspection PhpUnhandledExceptionInspection */
Permissions::buildJointPermissionsForEntity(clone $this);
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
}
/**
@ -293,7 +293,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function indexForSearch()
{
app(SearchIndex::class)->indexEntity(clone $this);
app()->make(SearchIndex::class)->indexEntity(clone $this);
}
/**
@ -301,7 +301,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function refreshSlug(): string
{
$this->slug = app(SlugGenerator::class)->generate($this);
$this->slug = app()->make(SlugGenerator::class)->generate($this);
return $this->slug;
}

View File

@ -2,8 +2,8 @@
namespace BookStack\Entities\Models;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Tools\PageContent;
use BookStack\Facades\Permissions;
use BookStack\Uploads\Attachment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
@ -51,7 +51,7 @@ class Page extends BookChild
*/
public function scopeVisible(Builder $query): Builder
{
$query = Permissions::enforceDraftVisibilityOnQuery($query);
$query = app()->make(PermissionApplicator::class)->enforceDraftVisibilityOnQuery($query);
return parent::scopeVisible($query);
}

View File

@ -2,14 +2,14 @@
namespace BookStack\Entities\Queries;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\EntityProvider;
abstract class EntityQuery
{
protected function permissionService(): PermissionService
protected function permissionService(): PermissionApplicator
{
return app()->make(PermissionService::class);
return app()->make(PermissionApplicator::class);
}
protected function entityProvider(): EntityProvider

View File

@ -2,7 +2,7 @@
namespace BookStack\Entities\Tools;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\User;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\BookChild;
@ -21,20 +21,14 @@ use SplObjectStorage;
class SearchRunner
{
/**
* @var EntityProvider
*/
protected $entityProvider;
/**
* @var PermissionService
*/
protected $permissionService;
protected EntityProvider $entityProvider;
protected PermissionApplicator $permissions;
/**
* Acceptable operators to be used in a query.
*
* @var array
* @var string[]
*/
protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
@ -46,10 +40,10 @@ class SearchRunner
*/
protected $termAdjustmentCache;
public function __construct(EntityProvider $entityProvider, PermissionService $permissionService)
public function __construct(EntityProvider $entityProvider, PermissionApplicator $permissions)
{
$this->entityProvider = $entityProvider;
$this->permissionService = $permissionService;
$this->permissions = $permissions;
$this->termAdjustmentCache = new SplObjectStorage();
}
@ -199,7 +193,7 @@ class SearchRunner
}
}
return $this->permissionService->enforceEntityRestrictions($entityModelInstance, $entityQuery, $action);
return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery, $action);
}
/**

View File

@ -1,18 +0,0 @@
<?php
namespace BookStack\Facades;
use Illuminate\Support\Facades\Facade;
class Permissions extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'permissions';
}
}

View File

@ -3,7 +3,7 @@
namespace BookStack\Providers;
use BookStack\Actions\ActivityLogger;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Theming\ThemeService;
use BookStack\Uploads\ImageService;
use Illuminate\Support\ServiceProvider;
@ -31,14 +31,6 @@ class CustomFacadeProvider extends ServiceProvider
return $this->app->make(ActivityLogger::class);
});
$this->app->singleton('images', function () {
return $this->app->make(ImageService::class);
});
$this->app->singleton('permissions', function () {
return $this->app->make(PermissionService::class);
});
$this->app->singleton('theme', function () {
return $this->app->make(ThemeService::class);
});

View File

@ -2,7 +2,7 @@
namespace BookStack\Uploads;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
@ -89,9 +89,9 @@ class Attachment extends Model
*/
public function scopeVisible(): Builder
{
$permissionService = app()->make(PermissionService::class);
$permissions = app()->make(PermissionApplicator::class);
return $permissionService->filterRelatedEntity(
return $permissions->filterRelatedEntity(
Page::class,
self::query(),
'attachments',

View File

@ -2,7 +2,7 @@
namespace BookStack\Uploads;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Models\Page;
use BookStack\Exceptions\ImageUploadException;
use Exception;
@ -11,16 +11,16 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
class ImageRepo
{
protected $imageService;
protected $restrictionService;
protected ImageService $imageService;
protected PermissionApplicator $permissions;
/**
* ImageRepo constructor.
*/
public function __construct(ImageService $imageService, PermissionService $permissionService)
public function __construct(ImageService $imageService, PermissionApplicator $permissions)
{
$this->imageService = $imageService;
$this->restrictionService = $permissionService;
$this->permissions = $permissions;
}
/**
@ -74,7 +74,7 @@ class ImageRepo
}
// Filter by page access
$imageQuery = $this->restrictionService->filterRelatedEntity(Page::class, $imageQuery, 'images', 'uploaded_to');
$imageQuery = $this->permissions->filterRelatedEntity(Page::class, $imageQuery, 'images', 'uploaded_to');
if ($whereClause !== null) {
$imageQuery = $imageQuery->where($whereClause);

View File

@ -1,6 +1,6 @@
<?php
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\User;
use BookStack\Model;
use BookStack\Settings\SettingService;
@ -65,9 +65,9 @@ function userCan(string $permission, Model $ownable = null): bool
}
// Check permission on ownable item
$permissionService = app(PermissionService::class);
$permissions = app(PermissionApplicator::class);
return $permissionService->checkOwnableUserAccess($ownable, $permission);
return $permissions->checkOwnableUserAccess($ownable, $permission);
}
/**
@ -76,9 +76,9 @@ function userCan(string $permission, Model $ownable = null): bool
*/
function userCanOnAny(string $permission, string $entityClass = null): bool
{
$permissionService = app(PermissionService::class);
$permissions = app(PermissionApplicator::class);
return $permissionService->checkUserHasPermissionOnAnything($permission, $entityClass);
return $permissions->checkUserHasPermissionOnAnything($permission, $entityClass);
}
/**

View File

@ -69,7 +69,7 @@ class DummyContentSeeder extends Seeder
]);
$token->save();
app(JointPermissionBuilder::class)->buildJointPermissions();
app(JointPermissionBuilder::class)->rebuildForAll();
app(SearchIndex::class)->indexAllEntities();
}
}

View File

@ -35,7 +35,7 @@ class LargeContentSeeder extends Seeder
$largeBook->chapters()->saveMany($chapters);
$all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all()));
app()->make(JointPermissionBuilder::class)->buildJointPermissionsForEntity($largeBook);
app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook);
app()->make(SearchIndex::class)->indexEntities($all);
}
}

View File

@ -4,6 +4,7 @@ namespace Tests\Commands;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Entities\Models\Page;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
@ -11,13 +12,13 @@ class RegeneratePermissionsCommandTest extends TestCase
{
public function test_regen_permissions_command()
{
\DB::rollBack();
DB::rollBack();
JointPermission::query()->truncate();
$page = Page::first();
$this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
$exitCode = \Artisan::call('bookstack:regenerate-permissions');
$exitCode = Artisan::call('bookstack:regenerate-permissions');
$this->assertTrue($exitCode === 0, 'Command executed successfully');
DB::beginTransaction();

View File

@ -89,7 +89,7 @@ class PublicActionTest extends TestCase
foreach (RolePermission::all() as $perm) {
$publicRole->attachPermission($perm);
}
$this->app->make(JointPermissionBuilder::class)->buildJointPermissionForRole($publicRole);
$this->app->make(JointPermissionBuilder::class)->rebuildForRole($publicRole);
/** @var Chapter $chapter */
$chapter = Chapter::query()->first();

View File

@ -3,7 +3,7 @@
namespace Tests;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\Permissions\PermissionsRepo;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
@ -177,7 +177,7 @@ trait SharedTestHelpers
$entity->save();
$entity->load('permissions');
$this->app->make(JointPermissionBuilder::class)->buildJointPermissionsForEntity($entity);
$this->app->make(JointPermissionBuilder::class)->rebuildForEntity($entity);
$entity->load('jointPermissions');
}
@ -209,7 +209,7 @@ trait SharedTestHelpers
/** @var Role $role */
foreach ($roles as $role) {
$role->detachPermission($permission);
$permissionBuilder->buildJointPermissionForRole($role);
$permissionBuilder->rebuildForRole($role);
}
$user->clearPermissionCache();
@ -243,7 +243,7 @@ trait SharedTestHelpers
$chapter = Chapter::factory()->create(array_merge(['book_id' => $book->id], $userAttrs));
$page = Page::factory()->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs));
$this->app->make(JointPermissionBuilder::class)->buildJointPermissionsForEntity($book);
$this->app->make(JointPermissionBuilder::class)->rebuildForEntity($book);
return compact('book', 'chapter', 'page');
}