Updated main permission check methods to support our new enum.
use BookStack\App\AppVersion;
use BookStack\App\Model;
use BookStack\Facades\Theme;
+use BookStack\Permissions\Permission;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Settings\SettingService;
use BookStack\Users\Models\User;
* Check if the current user has a permission. If an ownable element
* 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)) {
return user()->can($permission);
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity;
use BookStack\Permissions\Models\EntityPermission;
+use BookStack\Permissions\Permission;
use BookStack\Users\Models\Role;
use BookStack\Users\Models\User;
use Illuminate\Http\Request;
foreach ($permissions as $roleId => $info) {
$entityPermissionData = ['role_id' => $roleId];
- foreach (EntityPermission::PERMISSIONS as $permission) {
- $entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
+ foreach (Permission::genericForEntity() as $permission) {
+ $permName = $permission->value;
+ $entityPermissionData[$permName] = (($info[$permName] ?? false) === "true");
}
$formatted[] = $entityPermissionData;
}
foreach ($permissions as $requestPermissionData) {
$entityPermissionData = ['role_id' => $requestPermissionData['role_id']];
- foreach (EntityPermission::PERMISSIONS as $permission) {
- $entityPermissionData[$permission] = boolval($requestPermissionData[$permission] ?? false);
+ foreach (Permission::genericForEntity() as $permission) {
+ $permName = $permission->value;
+ $entityPermissionData[$permName] = boolval($requestPermissionData[$permName] ?? false);
}
$formatted[] = $entityPermissionData;
}
*/
class EntityPermission extends Model
{
- public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
-
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
public $timestamps = false;
protected $hidden = ['entity_id', 'entity_type', 'id'];
/**
* @property int $id
* @property string $name
- * @property string $display_name
*/
class RolePermission extends Model
{
--- /dev/null
+<?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,
+ ];
+ }
+}
/**
* 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];
- $fullPermission = count($explodedPermission) > 1 ? $permission : $ownable->getMorphClass() . '-' . $permission;
+ $fullPermission = count($explodedPermission) > 1 ? $permissionName : $ownable->getMorphClass() . '-' . $permissionName;
$user = $this->currentUser();
$userRoleIds = $this->getCurrentUserRoleIds();
*/
protected function ensureValidEntityAction(string $action): void
{
- if (!in_array($action, EntityPermission::PERMISSIONS)) {
- throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
+ $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');
}
}
*/
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']);
}
/**
use BookStack\App\Model;
use BookStack\App\SluggableInterface;
use BookStack\Entities\Tools\SlugGenerator;
+use BookStack\Permissions\Permission;
use BookStack\Translation\LocaleDefinition;
use BookStack\Translation\LocaleManager;
use BookStack\Uploads\Image;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
-use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
/**
* 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);
}
}
/**
- * Clear any cached permissions on this instance.
+ * Clear any cached permissions in this instance.
*/
- public function clearPermissionCache()
+ public function clearPermissionCache(): void
{
$this->permissions = null;
}
/**
* Attach a role to this user.
*/
- public function attachRole(Role $role)
+ public function attachRole(Role $role): void
{
$this->roles()->attach($role->id);
$this->unsetRelation('roles');
/**
* Check if the user has a social account,
- * If a driver is passed it checks for that single account type.
- *
- * @param bool|string $socialDriver
- *
- * @return bool
+ * If a driver is passed, it checks for that single account type.
*/
- public function hasSocialAccount($socialDriver = false)
+ public function hasSocialAccount(string $socialDriver = ''): bool
{
- if ($socialDriver === false) {
+ if (empty($socialDriver)) {
return $this->socialAccounts()->count() > 0;
}
Schema::table('comments', function (Blueprint $table) {
$table->dropColumn('text');
});
+
+ Schema::table('role_permissions', function (Blueprint $table) {
+ $table->dropColumn('display_name');
+ $table->dropColumn('description');
+ });
}
/**
use BookStack\Entities\Models\Entity;
use BookStack\Permissions\Models\EntityPermission;
use BookStack\Permissions\Models\RolePermission;
+use BookStack\Permissions\Permission;
use BookStack\Settings\SettingService;
use BookStack\Users\Models\Role;
use BookStack\Users\Models\User;
protected function actionListToEntityPermissionData(array $actionList, int $roleId = 0): array
{
$permissionData = ['role_id' => $roleId];
- foreach (EntityPermission::PERMISSIONS as $possibleAction) {
- $permissionData[$possibleAction] = in_array($possibleAction, $actionList);
+ foreach (Permission::genericForEntity() as $permission) {
+ $permissionData[$permission->value] = in_array($permission->value, $actionList);
}
return $permissionData;