mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-10-23 18:48:37 +03:00
Permissions: Removed unused role-perm columns, added permission enum
Updated main permission check methods to support our new enum.
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
use BookStack\App\AppVersion;
|
use BookStack\App\AppVersion;
|
||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
use BookStack\Facades\Theme;
|
use BookStack\Facades\Theme;
|
||||||
|
use BookStack\Permissions\Permission;
|
||||||
use BookStack\Permissions\PermissionApplicator;
|
use BookStack\Permissions\PermissionApplicator;
|
||||||
use BookStack\Settings\SettingService;
|
use BookStack\Settings\SettingService;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
@@ -39,7 +40,7 @@ function user(): User
|
|||||||
* Check if the current user has a permission. If an ownable element
|
* Check if the current user has a permission. If an ownable element
|
||||||
* is passed in the jointPermissions are checked against that particular item.
|
* is passed in the jointPermissions are checked against that particular item.
|
||||||
*/
|
*/
|
||||||
function userCan(string $permission, ?Model $ownable = null): bool
|
function userCan(string|Permission $permission, ?Model $ownable = null): bool
|
||||||
{
|
{
|
||||||
if (is_null($ownable)) {
|
if (is_null($ownable)) {
|
||||||
return user()->can($permission);
|
return user()->can($permission);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use BookStack\Entities\Models\Bookshelf;
|
|||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Facades\Activity;
|
use BookStack\Facades\Activity;
|
||||||
use BookStack\Permissions\Models\EntityPermission;
|
use BookStack\Permissions\Models\EntityPermission;
|
||||||
|
use BookStack\Permissions\Permission;
|
||||||
use BookStack\Users\Models\Role;
|
use BookStack\Users\Models\Role;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -93,8 +94,9 @@ class PermissionsUpdater
|
|||||||
|
|
||||||
foreach ($permissions as $roleId => $info) {
|
foreach ($permissions as $roleId => $info) {
|
||||||
$entityPermissionData = ['role_id' => $roleId];
|
$entityPermissionData = ['role_id' => $roleId];
|
||||||
foreach (EntityPermission::PERMISSIONS as $permission) {
|
foreach (Permission::genericForEntity() as $permission) {
|
||||||
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
|
$permName = $permission->value;
|
||||||
|
$entityPermissionData[$permName] = (($info[$permName] ?? false) === "true");
|
||||||
}
|
}
|
||||||
$formatted[] = $entityPermissionData;
|
$formatted[] = $entityPermissionData;
|
||||||
}
|
}
|
||||||
@@ -108,8 +110,9 @@ class PermissionsUpdater
|
|||||||
|
|
||||||
foreach ($permissions as $requestPermissionData) {
|
foreach ($permissions as $requestPermissionData) {
|
||||||
$entityPermissionData = ['role_id' => $requestPermissionData['role_id']];
|
$entityPermissionData = ['role_id' => $requestPermissionData['role_id']];
|
||||||
foreach (EntityPermission::PERMISSIONS as $permission) {
|
foreach (Permission::genericForEntity() as $permission) {
|
||||||
$entityPermissionData[$permission] = boolval($requestPermissionData[$permission] ?? false);
|
$permName = $permission->value;
|
||||||
|
$entityPermissionData[$permName] = boolval($requestPermissionData[$permName] ?? false);
|
||||||
}
|
}
|
||||||
$formatted[] = $entityPermissionData;
|
$formatted[] = $entityPermissionData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
*/
|
*/
|
||||||
class EntityPermission extends Model
|
class EntityPermission extends Model
|
||||||
{
|
{
|
||||||
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
|
|
||||||
|
|
||||||
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
|
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
protected $hidden = ['entity_id', 'entity_type', 'id'];
|
protected $hidden = ['entity_id', 'entity_type', 'id'];
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property string $display_name
|
|
||||||
*/
|
*/
|
||||||
class RolePermission extends Model
|
class RolePermission extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
151
app/Permissions/Permission.php
Normal file
151
app/Permissions/Permission.php
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Permissions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum to represent the permissions which may be used in checks.
|
||||||
|
* These generally align with RolePermission names, although some are abstract or truncated as some checks
|
||||||
|
* are performed across a range of different items which may be subject to inheritance and other complications.
|
||||||
|
*
|
||||||
|
* We use and still allow the string values in usage to allow for compatibility with scenarios where
|
||||||
|
* users have customised their instance with additional permissions via the theme system.
|
||||||
|
* This enum primarily exists for alignment within the codebase.
|
||||||
|
*/
|
||||||
|
enum Permission: string
|
||||||
|
{
|
||||||
|
// Generic Actions
|
||||||
|
// Used for more abstract entity permission checks
|
||||||
|
case View = 'view';
|
||||||
|
case Create = 'create';
|
||||||
|
case Update = 'update';
|
||||||
|
case Delete = 'delete';
|
||||||
|
|
||||||
|
// System Permissions
|
||||||
|
case AccessApi = 'access-api';
|
||||||
|
case ContentExport = 'content-export';
|
||||||
|
case ContentImport = 'content-import';
|
||||||
|
case EditorChange = 'editor-change';
|
||||||
|
case ReceiveNotifications = 'receive-notifications';
|
||||||
|
case RestrictionsManageAll = 'restrictions-manage-all';
|
||||||
|
case RestrictionsManageOwn = 'restrictions-manage-own';
|
||||||
|
case SettingsManage = 'settings-manage';
|
||||||
|
case TemplatesManage = 'templates-manage';
|
||||||
|
case UserRolesManage = 'user-roles-manage';
|
||||||
|
case UsersManage = 'users-manage';
|
||||||
|
|
||||||
|
// Entity permissions
|
||||||
|
// Each has 'all' or 'own' in it's RolePermission, with the base non-suffix name being used
|
||||||
|
// in actual checking logic, with the permission system handling the assessment of the underlying RolePermission.
|
||||||
|
case AttachmentCreate = 'attachment-create';
|
||||||
|
case AttachmentCreateAll = 'attachment-create-all';
|
||||||
|
case AttachmentCreateOwn = 'attachment-create-own';
|
||||||
|
|
||||||
|
case AttachmentDelete = 'attachment-delete';
|
||||||
|
case AttachmentDeleteAll = 'attachment-delete-all';
|
||||||
|
case AttachmentDeleteOwn = 'attachment-delete-own';
|
||||||
|
|
||||||
|
case AttachmentUpdate = 'attachment-update';
|
||||||
|
case AttachmentUpdateAll = 'attachment-update-all';
|
||||||
|
case AttachmentUpdateOwn = 'attachment-update-own';
|
||||||
|
|
||||||
|
case BookCreate = 'book-create';
|
||||||
|
case BookCreateAll = 'book-create-all';
|
||||||
|
case BookCreateOwn = 'book-create-own';
|
||||||
|
|
||||||
|
case BookDelete = 'book-delete';
|
||||||
|
case BookDeleteAll = 'book-delete-all';
|
||||||
|
case BookDeleteOwn = 'book-delete-own';
|
||||||
|
|
||||||
|
case BookUpdate = 'book-update';
|
||||||
|
case BookUpdateAll = 'book-update-all';
|
||||||
|
case BookUpdateOwn = 'book-update-own';
|
||||||
|
|
||||||
|
case BookView = 'book-view';
|
||||||
|
case BookViewAll = 'book-view-all';
|
||||||
|
case BookViewOwn = 'book-view-own';
|
||||||
|
|
||||||
|
case BookshelfCreate = 'bookshelf-create';
|
||||||
|
case BookshelfCreateAll = 'bookshelf-create-all';
|
||||||
|
case BookshelfCreateOwn = 'bookshelf-create-own';
|
||||||
|
|
||||||
|
case BookshelfDelete = 'bookshelf-delete';
|
||||||
|
case BookshelfDeleteAll = 'bookshelf-delete-all';
|
||||||
|
case BookshelfDeleteOwn = 'bookshelf-delete-own';
|
||||||
|
|
||||||
|
case BookshelfUpdate = 'bookshelf-update';
|
||||||
|
case BookshelfUpdateAll = 'bookshelf-update-all';
|
||||||
|
case BookshelfUpdateOwn = 'bookshelf-update-own';
|
||||||
|
|
||||||
|
case BookshelfView = 'bookshelf-view';
|
||||||
|
case BookshelfViewAll = 'bookshelf-view-all';
|
||||||
|
case BookshelfViewOwn = 'bookshelf-view-own';
|
||||||
|
|
||||||
|
case ChapterCreate = 'chapter-create';
|
||||||
|
case ChapterCreateAll = 'chapter-create-all';
|
||||||
|
case ChapterCreateOwn = 'chapter-create-own';
|
||||||
|
|
||||||
|
case ChapterDelete = 'chapter-delete';
|
||||||
|
case ChapterDeleteAll = 'chapter-delete-all';
|
||||||
|
case ChapterDeleteOwn = 'chapter-delete-own';
|
||||||
|
|
||||||
|
case ChapterUpdate = 'chapter-update';
|
||||||
|
case ChapterUpdateAll = 'chapter-update-all';
|
||||||
|
case ChapterUpdateOwn = 'chapter-update-own';
|
||||||
|
|
||||||
|
case ChapterView = 'chapter-view';
|
||||||
|
case ChapterViewAll = 'chapter-view-all';
|
||||||
|
case ChapterViewOwn = 'chapter-view-own';
|
||||||
|
|
||||||
|
case CommentCreate = 'comment-create';
|
||||||
|
case CommentCreateAll = 'comment-create-all';
|
||||||
|
case CommentCreateOwn = 'comment-create-own';
|
||||||
|
|
||||||
|
case CommentDelete = 'comment-delete';
|
||||||
|
case CommentDeleteAll = 'comment-delete-all';
|
||||||
|
case CommentDeleteOwn = 'comment-delete-own';
|
||||||
|
|
||||||
|
case CommentUpdate = 'comment-update';
|
||||||
|
case CommentUpdateAll = 'comment-update-all';
|
||||||
|
case CommentUpdateOwn = 'comment-update-own';
|
||||||
|
|
||||||
|
case ImageCreate = 'image-create';
|
||||||
|
case ImageCreateAll = 'image-create-all';
|
||||||
|
case ImageCreateOwn = 'image-create-own';
|
||||||
|
|
||||||
|
case ImageDelete = 'image-delete';
|
||||||
|
case ImageDeleteAll = 'image-delete-all';
|
||||||
|
case ImageDeleteOwn = 'image-delete-own';
|
||||||
|
|
||||||
|
case ImageUpdate = 'image-update';
|
||||||
|
case ImageUpdateAll = 'image-update-all';
|
||||||
|
case ImageUpdateOwn = 'image-update-own';
|
||||||
|
|
||||||
|
case PageCreate = 'page-create';
|
||||||
|
case PageCreateAll = 'page-create-all';
|
||||||
|
case PageCreateOwn = 'page-create-own';
|
||||||
|
|
||||||
|
case PageDelete = 'page-delete';
|
||||||
|
case PageDeleteAll = 'page-delete-all';
|
||||||
|
case PageDeleteOwn = 'page-delete-own';
|
||||||
|
|
||||||
|
case PageUpdate = 'page-update';
|
||||||
|
case PageUpdateAll = 'page-update-all';
|
||||||
|
case PageUpdateOwn = 'page-update-own';
|
||||||
|
|
||||||
|
case PageView = 'page-view';
|
||||||
|
case PageViewAll = 'page-view-all';
|
||||||
|
case PageViewOwn = 'page-view-own';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the generic permissions which may be queried for entities.
|
||||||
|
*/
|
||||||
|
public static function genericForEntity(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::View,
|
||||||
|
self::Create,
|
||||||
|
self::Update,
|
||||||
|
self::Delete,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,11 +24,12 @@ class PermissionApplicator
|
|||||||
/**
|
/**
|
||||||
* Checks if an entity has a restriction set upon it.
|
* Checks if an entity has a restriction set upon it.
|
||||||
*/
|
*/
|
||||||
public function checkOwnableUserAccess(Model&OwnableInterface $ownable, string $permission): bool
|
public function checkOwnableUserAccess(Model&OwnableInterface $ownable, string|Permission $permission): bool
|
||||||
{
|
{
|
||||||
$explodedPermission = explode('-', $permission);
|
$permissionName = is_string($permission) ? $permission : $permission->value;
|
||||||
|
$explodedPermission = explode('-', $permissionName);
|
||||||
$action = $explodedPermission[1] ?? $explodedPermission[0];
|
$action = $explodedPermission[1] ?? $explodedPermission[0];
|
||||||
$fullPermission = count($explodedPermission) > 1 ? $permission : $ownable->getMorphClass() . '-' . $permission;
|
$fullPermission = count($explodedPermission) > 1 ? $permissionName : $ownable->getMorphClass() . '-' . $permissionName;
|
||||||
|
|
||||||
$user = $this->currentUser();
|
$user = $this->currentUser();
|
||||||
$userRoleIds = $this->getCurrentUserRoleIds();
|
$userRoleIds = $this->getCurrentUserRoleIds();
|
||||||
@@ -235,8 +236,13 @@ class PermissionApplicator
|
|||||||
*/
|
*/
|
||||||
protected function ensureValidEntityAction(string $action): void
|
protected function ensureValidEntityAction(string $action): void
|
||||||
{
|
{
|
||||||
if (!in_array($action, EntityPermission::PERMISSIONS)) {
|
$allowed = Permission::genericForEntity();
|
||||||
|
foreach ($allowed as $permission) {
|
||||||
|
if ($permission->value === $action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
|
throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ class Role extends Model implements Loggable
|
|||||||
*/
|
*/
|
||||||
public function permissions(): BelongsToMany
|
public function permissions(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id');
|
return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id')
|
||||||
|
->select(['id', 'name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use BookStack\Api\ApiToken;
|
|||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
use BookStack\App\SluggableInterface;
|
use BookStack\App\SluggableInterface;
|
||||||
use BookStack\Entities\Tools\SlugGenerator;
|
use BookStack\Entities\Tools\SlugGenerator;
|
||||||
|
use BookStack\Permissions\Permission;
|
||||||
use BookStack\Translation\LocaleDefinition;
|
use BookStack\Translation\LocaleDefinition;
|
||||||
use BookStack\Translation\LocaleManager;
|
use BookStack\Translation\LocaleManager;
|
||||||
use BookStack\Uploads\Image;
|
use BookStack\Uploads\Image;
|
||||||
@@ -26,7 +27,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
@@ -156,8 +156,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
/**
|
/**
|
||||||
* Check if the user has a particular permission.
|
* Check if the user has a particular permission.
|
||||||
*/
|
*/
|
||||||
public function can(string $permissionName): bool
|
public function can(string|Permission $permission): bool
|
||||||
{
|
{
|
||||||
|
$permissionName = is_string($permission) ? $permission : $permission->value;
|
||||||
return $this->permissions()->contains($permissionName);
|
return $this->permissions()->contains($permissionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,9 +182,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear any cached permissions on this instance.
|
* Clear any cached permissions in this instance.
|
||||||
*/
|
*/
|
||||||
public function clearPermissionCache()
|
public function clearPermissionCache(): void
|
||||||
{
|
{
|
||||||
$this->permissions = null;
|
$this->permissions = null;
|
||||||
}
|
}
|
||||||
@@ -191,7 +192,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
/**
|
/**
|
||||||
* Attach a role to this user.
|
* Attach a role to this user.
|
||||||
*/
|
*/
|
||||||
public function attachRole(Role $role)
|
public function attachRole(Role $role): void
|
||||||
{
|
{
|
||||||
$this->roles()->attach($role->id);
|
$this->roles()->attach($role->id);
|
||||||
$this->unsetRelation('roles');
|
$this->unsetRelation('roles');
|
||||||
@@ -207,15 +208,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the user has a social account,
|
* Check if the user has a social account,
|
||||||
* If a driver is passed it checks for that single account type.
|
* If a driver is passed, it checks for that single account type.
|
||||||
*
|
|
||||||
* @param bool|string $socialDriver
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function hasSocialAccount($socialDriver = false)
|
public function hasSocialAccount(string $socialDriver = ''): bool
|
||||||
{
|
{
|
||||||
if ($socialDriver === false) {
|
if (empty($socialDriver)) {
|
||||||
return $this->socialAccounts()->count() > 0;
|
return $this->socialAccounts()->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ return new class extends Migration
|
|||||||
Schema::table('comments', function (Blueprint $table) {
|
Schema::table('comments', function (Blueprint $table) {
|
||||||
$table->dropColumn('text');
|
$table->dropColumn('text');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Schema::table('role_permissions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('display_name');
|
||||||
|
$table->dropColumn('description');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5,6 +5,7 @@ namespace Tests\Helpers;
|
|||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Permissions\Models\EntityPermission;
|
use BookStack\Permissions\Models\EntityPermission;
|
||||||
use BookStack\Permissions\Models\RolePermission;
|
use BookStack\Permissions\Models\RolePermission;
|
||||||
|
use BookStack\Permissions\Permission;
|
||||||
use BookStack\Settings\SettingService;
|
use BookStack\Settings\SettingService;
|
||||||
use BookStack\Users\Models\Role;
|
use BookStack\Users\Models\Role;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
@@ -139,8 +140,8 @@ class PermissionsProvider
|
|||||||
protected function actionListToEntityPermissionData(array $actionList, int $roleId = 0): array
|
protected function actionListToEntityPermissionData(array $actionList, int $roleId = 0): array
|
||||||
{
|
{
|
||||||
$permissionData = ['role_id' => $roleId];
|
$permissionData = ['role_id' => $roleId];
|
||||||
foreach (EntityPermission::PERMISSIONS as $possibleAction) {
|
foreach (Permission::genericForEntity() as $permission) {
|
||||||
$permissionData[$possibleAction] = in_array($possibleAction, $actionList);
|
$permissionData[$permission->value] = in_array($permission->value, $actionList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $permissionData;
|
return $permissionData;
|
||||||
|
|||||||
Reference in New Issue
Block a user