mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Fixed collapsed perm. gen for book sub-items.
Also converted the existing "JointPermission" usage to the new collapsed permission system.
This commit is contained in:
parent
7330139555
commit
451e4ac452
18
app/Auth/Permissions/CollapsedPermission.php
Normal file
18
app/Auth/Permissions/CollapsedPermission.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property ?int $role_id
|
||||
* @property ?int $user_id
|
||||
* @property string $entity_type
|
||||
* @property int $entity_id
|
||||
* @property bool $view
|
||||
*/
|
||||
class CollapsedPermission extends Model
|
||||
{
|
||||
protected $table = 'entity_permissions_collapsed';
|
||||
}
|
@ -13,13 +13,13 @@ use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Joint permissions provide a pre-query "cached" table of view permissions for all core entity
|
||||
* types for all roles in the system. This class generates out that table for different scenarios.
|
||||
* Collapsed permissions act as a "flattened" view of entity-level permissions in the system
|
||||
* so inheritance does not have to managed as part of permission querying.
|
||||
*/
|
||||
class JointPermissionBuilder
|
||||
class CollapsedPermissionBuilder
|
||||
{
|
||||
/**
|
||||
* Re-generate all entity permission from scratch.
|
||||
* Re-generate all collapsed permissions from scratch.
|
||||
*/
|
||||
public function rebuildForAll()
|
||||
{
|
||||
@ -27,26 +27,26 @@ class JointPermissionBuilder
|
||||
|
||||
// Chunk through all books
|
||||
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) {
|
||||
$this->buildJointPermissionsForBooks($books);
|
||||
$this->buildForBooks($books, false);
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
Bookshelf::query()->withTrashed()
|
||||
->select(['id', 'owned_by'])
|
||||
->select(['id'])
|
||||
->chunk(50, function (EloquentCollection $shelves) {
|
||||
$this->generateCollapsedPermissions($shelves->all());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a particular entity.
|
||||
* Rebuild the collapsed permissions for a particular entity.
|
||||
*/
|
||||
public function rebuildForEntity(Entity $entity)
|
||||
{
|
||||
$entities = [$entity];
|
||||
if ($entity instanceof Book) {
|
||||
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
|
||||
$this->buildJointPermissionsForBooks($books, true);
|
||||
$this->buildForBooks($books, true);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -66,7 +66,7 @@ class JointPermissionBuilder
|
||||
}
|
||||
}
|
||||
|
||||
$this->buildJointPermissionsForEntities($entities);
|
||||
$this->buildForEntities($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,20 +75,20 @@ class JointPermissionBuilder
|
||||
protected function bookFetchQuery(): Builder
|
||||
{
|
||||
return Book::query()->withTrashed()
|
||||
->select(['id', 'owned_by'])->with([
|
||||
->select(['id'])->with([
|
||||
'chapters' => function ($query) {
|
||||
$query->withTrashed()->select(['id', 'owned_by', 'book_id']);
|
||||
$query->withTrashed()->select(['id', 'book_id']);
|
||||
},
|
||||
'pages' => function ($query) {
|
||||
$query->withTrashed()->select(['id', 'owned_by', 'book_id', 'chapter_id']);
|
||||
$query->withTrashed()->select(['id', 'book_id', 'chapter_id']);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build joint permissions for the given book and role combinations.
|
||||
* Build collapsed permissions for the given books.
|
||||
*/
|
||||
protected function buildJointPermissionsForBooks(EloquentCollection $books, bool $deleteOld = false)
|
||||
protected function buildForBooks(EloquentCollection $books, bool $deleteOld)
|
||||
{
|
||||
$entities = clone $books;
|
||||
|
||||
@ -103,27 +103,27 @@ class JointPermissionBuilder
|
||||
}
|
||||
|
||||
if ($deleteOld) {
|
||||
$this->deleteManyJointPermissionsForEntities($entities->all());
|
||||
$this->deleteForEntities($entities->all());
|
||||
}
|
||||
|
||||
$this->generateCollapsedPermissions($entities->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a collection of entities.
|
||||
* Rebuild the collapsed permissions for a collection of entities.
|
||||
*/
|
||||
protected function buildJointPermissionsForEntities(array $entities)
|
||||
protected function buildForEntities(array $entities)
|
||||
{
|
||||
$this->deleteManyJointPermissionsForEntities($entities);
|
||||
$this->deleteForEntities($entities);
|
||||
$this->generateCollapsedPermissions($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the entity jointPermissions for a list of entities.
|
||||
* Delete the stored collapsed permissions for a list of entities.
|
||||
*
|
||||
* @param Entity[] $entities
|
||||
*/
|
||||
protected function deleteManyJointPermissionsForEntities(array $entities)
|
||||
protected function deleteForEntities(array $entities)
|
||||
{
|
||||
$simpleEntities = $this->entitiesToSimpleEntities($entities);
|
||||
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
|
||||
@ -141,6 +141,9 @@ class JointPermissionBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given list of entities into "SimpleEntityData" representations
|
||||
* for faster usage and property access.
|
||||
*
|
||||
* @param Entity[] $entities
|
||||
*
|
||||
* @return SimpleEntityData[]
|
||||
@ -154,7 +157,6 @@ class JointPermissionBuilder
|
||||
$simple = new SimpleEntityData();
|
||||
$simple->id = $attrs['id'];
|
||||
$simple->type = $entity->getMorphClass();
|
||||
$simple->owned_by = $attrs['owned_by'] ?? 0;
|
||||
$simple->book_id = $attrs['book_id'] ?? null;
|
||||
$simple->chapter_id = $attrs['chapter_id'] ?? null;
|
||||
$simpleEntities[] = $simple;
|
||||
@ -171,7 +173,7 @@ class JointPermissionBuilder
|
||||
protected function generateCollapsedPermissions(array $originalEntities)
|
||||
{
|
||||
$entities = $this->entitiesToSimpleEntities($originalEntities);
|
||||
$jointPermissions = [];
|
||||
$collapsedPermData = [];
|
||||
|
||||
// Fetch related entity permissions
|
||||
$permissions = $this->getEntityPermissionsForEntities($entities);
|
||||
@ -181,12 +183,12 @@ class JointPermissionBuilder
|
||||
|
||||
// Create Joint Permission Data
|
||||
foreach ($entities as $entity) {
|
||||
array_push($jointPermissions, ...$this->createCollapsedPermissionData($entity, $permissionMap));
|
||||
array_push($collapsedPermData, ...$this->createCollapsedPermissionData($entity, $permissionMap));
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($jointPermissions) {
|
||||
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
|
||||
DB::table('entity_permissions_collapsed')->insert($jointPermissionChunk);
|
||||
DB::transaction(function () use ($collapsedPermData) {
|
||||
foreach (array_chunk($collapsedPermData, 1000) as $dataChunk) {
|
||||
DB::table('entity_permissions_collapsed')->insert($dataChunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -198,8 +200,8 @@ class JointPermissionBuilder
|
||||
{
|
||||
$chain = [
|
||||
$entity->type . ':' . $entity->id,
|
||||
$entity->chapter_id ? null : ('chapter:' . $entity->chapter_id),
|
||||
$entity->book_id ? null : ('book:' . $entity->book_id),
|
||||
$entity->chapter_id ? ('chapter:' . $entity->chapter_id) : null,
|
||||
$entity->book_id ? ('book:' . $entity->book_id) : null,
|
||||
];
|
||||
|
||||
$permissionData = [];
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
class JointPermission extends Model
|
||||
{
|
||||
protected $primaryKey = null;
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get the role that this points to.
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity this points to.
|
||||
*/
|
||||
public function entity(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Entity::class, 'entity');
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
/**
|
||||
* Holds the "cached" user-specific permissions for entities in the system.
|
||||
* These only exist to indicate resolved permissions active via user-specific
|
||||
* entity permissions, not for all permission combinations for all users.
|
||||
*
|
||||
* @property int $user_id
|
||||
* @property int $entity_id
|
||||
* @property string $entity_type
|
||||
* @property boolean $has_permission
|
||||
*/
|
||||
class JointUserPermission extends Model
|
||||
{
|
||||
protected $primaryKey = null;
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get the entity this points to.
|
||||
*/
|
||||
public function entity(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Entity::class, 'entity');
|
||||
}
|
||||
}
|
@ -11,13 +11,13 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PermissionsRepo
|
||||
{
|
||||
protected JointPermissionBuilder $permissionBuilder;
|
||||
protected $systemRoles = ['admin', 'public'];
|
||||
protected CollapsedPermissionBuilder $permissionBuilder;
|
||||
protected array $systemRoles = ['admin', 'public'];
|
||||
|
||||
/**
|
||||
* PermissionsRepo constructor.
|
||||
*/
|
||||
public function __construct(JointPermissionBuilder $permissionBuilder)
|
||||
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
|
||||
{
|
||||
$this->permissionBuilder = $permissionBuilder;
|
||||
}
|
||||
@ -138,7 +138,7 @@ class PermissionsRepo
|
||||
}
|
||||
|
||||
$role->entityPermissions()->delete();
|
||||
$role->jointPermissions()->delete();
|
||||
$role->collapsedPermissions()->delete();
|
||||
Activity::add(ActivityType::ROLE_DELETE, $role);
|
||||
$role->delete();
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ class SimpleEntityData
|
||||
{
|
||||
public int $id;
|
||||
public string $type;
|
||||
public int $owned_by;
|
||||
public ?int $book_id;
|
||||
public ?int $chapter_id;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
@ -39,14 +39,6 @@ class Role extends Model implements Loggable
|
||||
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related JointPermissions.
|
||||
*/
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The RolePermissions that belong to the role.
|
||||
*/
|
||||
@ -63,6 +55,14 @@ class Role extends Model implements Loggable
|
||||
return $this->hasMany(EntityPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related entity collapsed permissions.
|
||||
*/
|
||||
public function collapsedPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(CollapsedPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this role has a permission.
|
||||
*/
|
||||
|
@ -5,6 +5,8 @@ namespace BookStack\Auth;
|
||||
use BookStack\Actions\Favourite;
|
||||
use BookStack\Api\ApiToken;
|
||||
use BookStack\Auth\Access\Mfa\MfaValue;
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Entities\Tools\SlugGenerator;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Interfaces\Sluggable;
|
||||
@ -298,6 +300,22 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
}, 'activities', 'users.id', '=', 'activities.user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity permissions assigned to this specific user.
|
||||
*/
|
||||
public function entityPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(EntityPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related entity collapsed permissions.
|
||||
*/
|
||||
public function collapsedPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(CollapsedPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url for editing this user.
|
||||
*/
|
||||
|
@ -153,6 +153,8 @@ class UserRepo
|
||||
$user->apiTokens()->delete();
|
||||
$user->favourites()->delete();
|
||||
$user->mfaValues()->delete();
|
||||
$user->collapsedPermissions()->delete();
|
||||
$user->entityPermissions()->delete();
|
||||
$user->delete();
|
||||
|
||||
// Delete user profile images
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@ -22,12 +22,12 @@ class RegeneratePermissions extends Command
|
||||
*/
|
||||
protected $description = 'Regenerate all system permissions';
|
||||
|
||||
protected JointPermissionBuilder $permissionBuilder;
|
||||
protected CollapsedPermissionBuilder $permissionBuilder;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(JointPermissionBuilder $permissionBuilder)
|
||||
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
|
||||
{
|
||||
$this->permissionBuilder = $permissionBuilder;
|
||||
parent::__construct();
|
||||
|
@ -7,10 +7,9 @@ use BookStack\Actions\Comment;
|
||||
use BookStack\Actions\Favourite;
|
||||
use BookStack\Actions\Tag;
|
||||
use BookStack\Actions\View;
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\JointUserPermission;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\PermissionApplicator;
|
||||
use BookStack\Entities\Tools\SlugGenerator;
|
||||
use BookStack\Interfaces\Deletable;
|
||||
@ -188,19 +187,11 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity jointPermissions this is connected to.
|
||||
* Get the entity collapsed permissions this is connected to.
|
||||
*/
|
||||
public function jointPermissions(): MorphMany
|
||||
public function collapsedPermissions(): MorphMany
|
||||
{
|
||||
return $this->morphMany(JointPermission::class, 'entity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the join user permissions for this entity.
|
||||
*/
|
||||
public function jointUserPermissions(): MorphMany
|
||||
{
|
||||
return $this->morphMany(JointUserPermission::class, 'entity');
|
||||
return $this->morphMany(CollapsedPermission::class, 'entity');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -301,7 +292,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
*/
|
||||
public function rebuildPermissions()
|
||||
{
|
||||
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
|
||||
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity(clone $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -372,7 +372,7 @@ class TrashCan
|
||||
$entity->permissions()->delete();
|
||||
$entity->tags()->delete();
|
||||
$entity->comments()->delete();
|
||||
$entity->jointPermissions()->delete();
|
||||
$entity->collapsedPermissions()->delete();
|
||||
$entity->searchTerms()->delete();
|
||||
$entity->deletions()->delete();
|
||||
$entity->favourites()->delete();
|
||||
|
@ -55,8 +55,9 @@ function hasAppAccess(): bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has a permission. If an ownable element
|
||||
* is passed in the jointPermissions are checked against that particular item.
|
||||
* Check if the current user has a permission.
|
||||
* Checks a generic role permission or, if an ownable model is passed in, it will
|
||||
* check against the given entity model, taking into account entity-level permissions.
|
||||
*/
|
||||
function userCan(string $permission, Model $ownable = null): bool
|
||||
{
|
||||
|
@ -13,6 +13,9 @@ class CreateCollapsedRolePermissionsTable extends Migration
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// TODO - Drop joint permissions
|
||||
// TODO - Run collapsed table rebuild.
|
||||
|
||||
Schema::create('entity_permissions_collapsed', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('role_id')->nullable();
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use BookStack\Api\ApiToken;
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
@ -69,7 +69,7 @@ class DummyContentSeeder extends Seeder
|
||||
]);
|
||||
$token->save();
|
||||
|
||||
app(JointPermissionBuilder::class)->rebuildForAll();
|
||||
app(CollapsedPermissionBuilder::class)->rebuildForAll();
|
||||
app(SearchIndex::class)->indexAllEntities();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
@ -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)->rebuildForEntity($largeBook);
|
||||
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity($largeBook);
|
||||
app()->make(SearchIndex::class)->indexEntities($all);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace Tests\Commands;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
@ -13,15 +12,23 @@ class RegeneratePermissionsCommandTest extends TestCase
|
||||
public function test_regen_permissions_command()
|
||||
{
|
||||
DB::rollBack();
|
||||
JointPermission::query()->truncate();
|
||||
$page = Page::first();
|
||||
$page = $this->entities->page();
|
||||
$editor = $this->users->editor();
|
||||
$this->permissions->addEntityPermission($page, ['view'], null, $editor);
|
||||
CollapsedPermission::query()->truncate();
|
||||
|
||||
$this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
|
||||
$this->assertDatabaseMissing('entity_permissions_collapsed', ['entity_id' => $page->id]);
|
||||
|
||||
$exitCode = Artisan::call('bookstack:regenerate-permissions');
|
||||
$this->assertTrue($exitCode === 0, 'Command executed successfully');
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
|
||||
$this->assertDatabaseHas('entity_permissions_collapsed', [
|
||||
'entity_id' => $page->id,
|
||||
'user_id' => $editor->id,
|
||||
'view' => 1,
|
||||
]);
|
||||
|
||||
CollapsedPermission::query()->truncate();
|
||||
DB::beginTransaction();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace Tests\Helpers;
|
||||
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
@ -70,7 +69,6 @@ class PermissionsProvider
|
||||
public function regenerateForEntity(Entity $entity): void
|
||||
{
|
||||
$entity->rebuildPermissions();
|
||||
$entity->load('jointPermissions');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
|
Loading…
Reference in New Issue
Block a user