antonioprimera/laravel-basic-permissions

A simple package enabling the use of roles and permissions from a config file in a Laravel app.

v3.13 2024-09-15 14:46 UTC

This package is auto-updated.

Last update: 2024-10-15 14:57:44 UTC


README

Overview

This package allows you to use simple roles with permissions in your application. Roles are configured in a 'permissions.php' config file and contain a list of roles, with their permissions (allowed actions) and optionally a set of roles they inherit.

Here's a simple example of a permissions config file:

return [
    'roles' => [
        'super-admin' 	=> [			//role name
            'permissions' => '*'        //permissions or wildcard
        ],
		
        'store-admin'	=> [			//role name
            'permissions' => [			//permission list (keyword: permissions)
                'store' => [			//scope
                    'manage'			//permission
                ],
                'items' => '*'
            ],
            
            'roles' => [				//inherited role list (keyword: roles / inherits)
                'customer',				//role
                'store-clerk',			//role
            ],
        ],
        
        'store-clerk' => [
            'permissions' => [
                'greet-customers',
                //... other permissions
            ],
        ],
        
        //other roles
    ],
];

You could add the 'store-admin' role to a user:

$user->setRole('store-admin');

You can check different permissions on the user like so:

$user->hasPermission('store:manage');           //true
$user->hasPermission('store:delete');           //false

$user->hasPermission('items.any-permission');   //true
$user->hasPermission('greet-customers');        //true (inherited from role: store-clerk)

Installation

Install the package in your Laravel project via composer:

composer require antonioprimera/laravel-basic-permissions

The package exposes a migration, which adds a role column to the users table, so you should run the artisan migrate command:

php artisan migrate

Add the AntonioPrimera\BasicPermissions\ActorInterface interface and the AntonioPrimera\BasicPermissions\ActorRolesAndPermissions trait to your User model.

Next, you should add the custom attribute cast to your User model like so:

protected $casts = [
    'role' => \AntonioPrimera\BasicPermissions\RoleCast::class,
    //...other attribute casts
];

This will cast the role attribute into a Role instance.

Usage

The basic usage is demonstrated in the example in the Overview section above.

Setting a role

Just add one of your configured roles, or the pre-configured super-admin role to a User model (or any other actor implementing the ActorInterface from this package).

$user->setRole('store-admin');

If you use the custom RoleCast class for the role attribute, you can set the role by assigning a Role instance to the role attribute of the model:

$user->role = new \AntonioPrimera\BasicPermissions\Role('store-admin');
$user->save();

Querying a role permission

In your Laravel Policies, you can check whether the role assigned to an actor allows a specific action, meaning that it has a specific permission (either directly, or inherited from another role):

if ($user->hasPermission('store:manage'))
    //do something

Querying several permissions at once

You can check whether an actor is allowed all permissions from a list of permissions:

//provide the list of permissions as an array
if ($user->hasAllPermissions(['store:manage', 'items:move', 'items:sell']))
    //do something only if the actor has all the above permissions

//OR

//provide the list of permissions as separate arguments
if ($user->hasAllPermissions('store:manage', 'items:move', 'items:sell'))
    //do something only if the actor has all the above permissions

You can check whether an actor is allowed at least one permission from a list of permissions:

//provide the list of permissions as an array
if ($user->hasAnyPermission(['store:manage', 'items:move', 'items:sell']))
    //do something if the actor has at least one of the above permissions

//OR

//provide the list of permissions as separate arguments
if ($user->hasAnyPermission('store:manage', 'items:move', 'items:sell'))
    //do something if the actor has at least one of the above permissions

Configuration

You can configure your roles in your permissions.php config file, under the roles key, like the example from the Overview section above.

Each role should have a list of permissions, maintained in its permissions or actions attribute and can optionally have a roles or inherits attribute, specifying a list of inherited roles.

Permissions

Permissions can be an array of simple strings (like ['greet-customer', ...] in the example above), or nested in arrays (like 'store:manage' in the example above). Nested permissions are addressed via their column (':') separated path.

A star ('*') wildcard can be used for any permission level, which can be substituted with any permission (either direct or nested). See the example from the Overview section above, where the 'store-admin' role has any permission under the 'items' permission set. Also, you can see that the 'super-admin' role should be allowed any action, because any permission query for this role will return true. Note: the wildcard permission should be provided as a stand-alone string, not inside an array

Permission sets are like permission white-lists. A permission, which is not specifically added or inherited, will not be allowed. For example, you can not deny a specific inherited permission for a role.

Inherited roles

Once a role is defined, additionally to its own permissions, it can also inherit the permissions of other roles, which, may also inherit other permissions themselves (any inheritance depth). Although, the application will not break if you define roles with circular inheritance (e.g. role_1, inherits role_2, which inherits role_1), this might have unexpected outcomes, so please try to avoid it.

You can use the roles array key to define the array of inherited roles. It's a matter of preference, which key you use - their scope is identical.

Permission Labels

In order to display the roles for humans, you can add a label attribute to each role. This label can be used in dropdown menus or when displaying the role in the UI. If no label is provided, the role name will be used instead.

The role label can be provided as a string or as an array with translations. When a translation is provided, the application will try to use the current locale to display the label. If no translation is found for the current locale, the fallback locale will be used instead.

Here's an example of a role with a label in English and Spanish:

'store-admin'	=> [
    'label' => [
        'en' => 'Store Admin',
        'es' => 'Administrador de Tienda',
    ],
    //...other role attributes
],

Permission Description

You can add a description to each permission, which can be used to display a tooltip or a help text in the UI. The description can be provided as a string or as an array with translations. When a translation is provided, the application will try to use the current locale to display the description. If no translation is found for the current locale, the fallback locale will be used instead.

Here's an example of a permission with a description in English and Spanish:

'store:manage'	=> [
    'description' => [
        'en' => 'Allows the user to manage the store',
        'es' => 'Permite al usuario administrar la tienda',
    ],
    //...other permission attributes
],

String and Html representations

The role can be cast to string, which will return the role label. If no label is provided, the role name will be returned instead.

The role can also be cast to Html, which is useful when displaying the role in a blade file. The Html representation will return the role label. If no label is provided, the role name will be returned instead.

(string) $role; //returns the role label, or the role name if no label is provided

//in a blade file:
{{ $role }} //returns the role label, or the role name if no label is provided

Testing specific permissions

Because Testing is (or should be) an important part of the development process, this package has some built-in helpers, to enable easy testing. Simply put, you can add and remove specific transient permissions to the actor instance. As the label implies, these permissions are transient and will not be saved to the DB or to any config. They will be gone the next time you fetch the actor from the DB (or create a new instance of this actor).

Assign a transient / temporary permission

In your tests, you might want to check that a specific action can be done only by actors with a specific permission. To add a single permission to the actor, you can use the following syntax:

$user->assignTemporaryPermission('some:permission:name');

All checks for this, permission will return true, until this user is destroyed and re-read from the DB. Please be aware that the transient permissions are saved on the instance, so a $user->refresh() call will not reset these transient permissions.

Remove a transient / temporary permission

You can remove a temporary (transient) permission from an actor, with the following method:

$user->removeTemporaryPermission('some:permission:name');

Clear all transient / temporary permissions

You can remove all temporary (transient) permissions from an actor, with the following method:

$user->clearTemporaryPermissions();