Skip to main content

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();
info

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();
}
caution

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',
],
]
]