Roles and Permissions
Qore uses Spatie Permissions for permission checking (docs: https://spatie.be/docs/laravel-permission).
However, there are a few extra helpers and functionalities
like Scoped permissions.
Permissions are stored in the permissions
table per tenant.
Creating permissions
Let's create permissions in our seeder. Because permissions offer a bit more than a name
, there is
a PermissionsCreator
available:
class PermissionsSeeder extends Seeder
{
public function run()
{
/** @var PermissionCreator $creator */
$creator = app(PermissionCreator::class);
$creator->cruda('users');
}
}
If you'd prefer to have full control over creating a permission:
$permission = new Permission();
$permission->name = $name;
$permission->guard_name = 'web';
$permission->resource = $resource;
$permission->scope = $scope;
$permission->for_view_any = $forViewAny;
$permission->for_view = $forView;
$permission->for_create = $forCreate;
$permission->for_update = $forUpdate;
$permission->for_delete = $forDelete;
$permission->save();
If you'd like to have more info about all the columns, see: Scoped permissions.
Creating roles
You may create a role:
$admin = app(RoleCreator::class)->create('admin');
Assigning permissions to a role:
foreach (Permission::all() as $permission) {
$admin->givePermissionTo($permission->id);
}
You may also lock a role so it cannot be deleted:
$admin = app(RoleCreator::class)->create('admin', true);
Or copy all permissions from another role:
app(RoleCreator::class)->create('sales', true, $admin->id);
Creating policies
The QoreUser model uses a trait called HasExtensivePermissions
.
This trait makes it easy to write policies:
class EmployeePolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return $user->canViewAny('employees');
}
public function view(User $user, Employee $model)
{
return $user->canView('employees', $model);
}
public function create(User $user)
{
return $user->canCreate('employees');
}
public function update(User $user, Employee $model)
{
return $user->canUpdate('employees', $model);
}
public function delete(User $user, Employee $model)
{
return $user->canDelete('employees', $model);
}
}
These methods will check whether the user has the given permission, or fall back on Scoped permissions.
Usage
Back-end
You can authorize in a few different ways, depending on the situation.
Via the AuthorizesRequest
trait
class MyController
{
use AuthorizesRequests;
public function myMethod(): JsonResponse
{
$this->authorize('update', $myModel);
// OR:
$this->authorize('manage', MyModel::class);
}
}
Via direct permissions:
!auth()->user()->can('manage permissions')
// OR at least one
auth()->user()->canAny('view users', 'create users', 'update users')
Via direct permission scopes:
auth()->user()->hasPermissionScope(MyPermissionScope::class, $model);
// OR if you do not have a model:
auth()->user()->hasPermissionScope(MyPermissionScope::class);
Front-end
In Vue components you can check if you have a permission:
this.$can('update users')
Or if you have at least one of the given permissions:
this.$canAny('update users', 'delete users')
Resource permissions
By default, a resource will apply the following permissions to be used in the front-end:
'permissions' => [
'view_any' => $this->canViewAny(),
'create' => $this->canCreate(),
'update_any' => $this->canUpdateAny(),
'delete_any' => $this->canDeleteAny(),
'clickable_row' => $this->hasClickableRow(),
'create_button' => $this->hasCreateButton()
],
You may override any of these methods to define your own logic. The default implementation is:
public function canViewAny(): bool
{
return auth()->user()->can('viewAny', $this->model());
}
public function canView(Model $model): bool
{
return auth()->user()->can('view', $model);
}
public function canCreate(): bool
{
return auth()->user()->can('create', $this->model());
}
public function canUpdateAny(): bool
{
return auth()->user()->hasPermissionTo("update {$this->name()}");
}
public function canUpdate(Model $model): bool
{
return auth()->user()->can('update', $model);
}
public function canDeleteAny(): bool
{
return auth()->user()->hasPermissionTo("delete {$this->name()}");
}
public function canDelete(Model $model): bool
{
return auth()->user()->can('delete', $model);
}
// Whether you can click on a table row
public function hasClickableRow(): bool
{
return $this->canViewAny();
}
// Whether a create button is shown
public function hasCreateButton(): bool
{
return $this->canCreate();
}
Qore will cache the permissions for the front-end automatically on production, and caching can be enabled locally for performance. You can find more information below.
Front-end cache
When you do a full page refresh, the GlobalsController
will determine which resources are visible for the user. This means however that if you have a lot of resources, a lot of Gate::checks(...)
will be executed,
and can lead to long page reloads. For this reason, you can choose to enable front-end caching to make development a bit less slow.
You can enable caching on your Resource (which is enabled by default on Production) by overriding the following method:
protected function shouldCachePermissions(): bool
{
return App::isProduction();
}
Or just change the permission cache time:
protected function permissionsCacheDuration(): int|Carbon
{
return now()->addMinutes(1);
}
Overriding default behaviour
Role & Permission management is handled by routes and controllers inside Qore.
You can override these routes if necessary. For example, add the following to routes/confirmed.php
:
Route::prefix('permissions')->group(function () {
Route::get('/roles', [MyRoleAndPermissionController::class, 'roles']);
Route::delete('/roles/{id}', [MyRoleAndPermissionController::class, 'deleteRole']);
Route::put('/roles/{id}', [MyRoleAndPermissionController::class, 'updateRole']);
Route::get('/roles/create', [MyRoleAndPermissionController::class, 'createRoleForm']);
Route::post('/roles/create', [MyRoleAndPermissionController::class, 'storeRole']);
Route::get('/user/edit', [MyRoleAndPermissionController::class, 'editUserForm']);
Route::post('/user/edit', [MyRoleAndPermissionController::class, 'updateUser']);
});
Then your controller may look like this:
class MyRoleAndPermissionController extends RoleAndPermissionController
{
public function updateUser(Request $request): JsonResponse
{
//
}
}
Custom permission sections
When configuring permissions in the interface, there are a few sections added by Qore: table
, settings
and mailing
.
These sections exist because some permissions have their resource
column filled with these.
These are configured in config/components.php
. If you need to add more sections, publish the file:
php artisan vendor:publish --tag=qore.system.config --force
And then edit the following:
'permissions' => [
'sections' => [
'tables' => [
'singular_name' => 'Table',
'icon' => 'filter_alt',
],
'settings' => [
'singular_name' => 'Settings',
'icon' => 'settings',
],
'mails' => [
'singular_name' => 'Mailing',
'icon' => 'mail',
],
]
]