Skip to content

Permissions ​

Introduction ​

Access control in kitloom has two layers:

  • kitloom/access — a tiny contract package. Modules use it to declare the permissions that open their admin pages. It has no storage and no opinion about roles.
  • kitloom/permissions (+ -filament / -nova) — the roles engine: roles, the Users → Roles screen, the Role field on the user form, and the actual checks.

Without the roles engine every declared section is open to any user who can enter the admin, so a site works out of the box. Installing kitloom/permissions switches access control on.

Content capabilities ​

Posts, pages, taxonomies, comments, media and users use the WordPress capability names — edit_posts, edit_others_posts, publish_pages, manage_categories, list_users — including meta capabilities for a single record (edit_post, delete_post). A site checks them through Laravel's Gate as usual:

php
Gate::allows('edit_post', $post);
$user->can('publish_posts');

Custom post types and taxonomies can get capabilities of their own (edit_books, manage_genre_terms) — the roles module turns this on.

Declaring a module's permissions ​

Every other admin page asks about itself: Gate::allows(static::class). The module declares which permission opens that page — one permission per menu item — through a PermissionProvider:

php
<?php

namespace Kitloom\Redirects\Filament;

use Kitloom\Access\Contracts\PermissionProvider;
use Kitloom\Access\Permission;
use Kitloom\Redirects\Filament\Resources\NotFoundLogResource;
use Kitloom\Redirects\Filament\Resources\RedirectionResource;

final class RedirectsPermissions implements PermissionProvider
{
    public function permissions(): iterable
    {
        yield new Permission(
            name: 'manage_redirections',
            group: 'Redirections',
            covers: [RedirectionResource::class],
            label: 'Manage redirects',
        );

        yield new Permission(
            name: 'manage_404_log',
            group: 'Redirections',
            covers: [NotFoundLogResource::class],
            label: 'See and clear the 404 log',
        );
    }
}

Tag it in the admin package's provider:

php
use Kitloom\Access\Permission;

$this->app->tag([RedirectsPermissions::class], Permission::PROVIDER_TAG);
ArgumentMeaning
nameCapability name, WordPress style: manage_redirections
groupHeading on the roles screen, usually the module name
coversClasses of the pages, resources or tools this permission opens
labelHuman-readable text next to the checkbox
rolesRoles that receive it the first time the site sees it. Default: ['administrator']
likeRoles that hold this other permission receive it too — for a permission split off an existing one

On a Filament resource guarded by its section, use the GuardedBySection trait from kitloom/wp-schema-filament — every ability (canViewAny, canCreate, canEdit…) then asks Gate::allows(static::class).

A section nobody declared stays open; with the roles module installed it requires manage_options.

Installing the roles engine ​

bash
composer require kitloom/permissions kitloom/permissions-filament
# or
composer require kitloom/permissions kitloom/permissions-nova

Give the user model roles:

php
use Kitloom\Permissions\Concerns\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
}

Run the migrations — they create the role tables, the default roles (administrator, editor, author, contributor, subscriber) and make the first user an administrator if nobody is one:

bash
php artisan migrate

Let the panel in whoever may read, like wp-admin:

php
public function canAccessPanel(Panel $panel): bool
{
    return $this->can('read');
}
php
Gate::define('viewNova', fn (User $user): bool => $user->can('read'));

How roles behave ​

  • A permission a module declares reaches the roles named in roles once, the first time the site sees it. After that, roles belong to the site's managers: a permission they remove is not given back.
  • A permission from a deactivated module is kept, so activating the module restores access as it was.
  • Roles are stored with spatie/laravel-permission, but only behind kitloom's own contracts (RoleRepository, UserRoles, PermissionChecker). Keep it replaceable:
    • check access through the Gate only — Gate::allows(), $user->can();
    • do not call spatie methods (hasRole(), hasPermissionTo()) or its role: / permission: middleware;
    • do not publish spatie's migration — the package ships its own.

To assign a role in code — a seeder, a factory — use the contract:

php
use Kitloom\Permissions\Contracts\UserRoles;

app(UserRoles::class)->setRole($user, 'editor');