Native Laravel Authorization.

Native Laravel Authorization.

README

Build Status
Scrutinizer Code Quality
Total Downloads
Latest Stable Version
License

An easy, native role / permission management system for Laravel.

Authorization automatically adds your database permissions and roles to the IlluminateAuthAccessGate, this means
that you can utilize all native laravel policies and methods for authorization.

This also means you’re not walled into using this package if you decide it’s not for you.

Installation

Insert Authorization in your composer.json file:

"larapacks/authorization": "1.1.*"

Then run composer update.

Insert the service provider in your config/app.php file:

LarapacksAuthorizationAuthorizationServiceProvider::class,

Once that’s complete, publish the migrations using:

php artisan vendor:publish --tag="authorization"

Then run php artisan migrate.

Once you’ve done the migrations, create the following two models and insert the relevant trait:

The Role model:

<?php

namespace App;

use IlluminateDatabaseEloquentModel;
use LarapacksAuthorizationTraitsRolePermissionsTrait;

class Role extends Model
{
    use RolePermissionsTrait;
}

The Permission model:

<?php

namespace App;

use IlluminateDatabaseEloquentModel;
use LarapacksAuthorizationTraitsPermissionRolesTrait;

class Permission extends Model
{
    use PermissionRolesTrait;
}

Now insert the LarapacksAuthorizationTraitsUserRolesTrait onto your AppUser model:

namespace App;

use LarapacksAuthorizationTraitsUserRolesTrait;
use IlluminateFoundationAuthUser as Authenticatable;

class User extends Authenticatable
{
    use UserRolesTrait;
    
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

You’re all set!

Usage

Using authorization is easy, because it’s utilizing native laravel relationships.

Create a permission:

$createUsers = new Permission();

$createUsers->name = 'users.create';
$createUsers->label = 'Create Users';

$createUsers->save();

Grant the permission to a role:

$administrator = new Role();

$administrator->name = 'administrator';
$administrator->label = 'Admin';

$administrator->save();

$administrator->permissions()->save($createUsers);

Now assign the role to the user:

$user->roles()->save($administrator);

You can also create user specific permissions:

$createUsers = new Permission();

$createUsers->name = 'users.create';
$createUsers->label = 'Create Users';

$createUsers->save();

$user->permissions()->save($createUsers);

Performing Authorization (Native)

// Using Laravel's native `can()` method:

if ($user->can('users.create')) {
    // This user can create other users.
}

// Using Laravel's native `authorize()` method in your controllers:

public function create()
{
    $this->authorize('users.create');
    
    User::create(['...']);
}

// Using Laravel's native Gate facade:

if (Gate::allows('users.create')) {
    //
}

// Using Laravel's native `@can` directive in your views:

@can('users.create')
    <!-- This user can create other users. -->
@endcan

Performing Authorization (Package Specific)

Checking for permission:

// Using the permissions name.
if ($user->hasPermission('users.create')) {
    //
}

// Using the permissions model.
if ($user->hasPermission($createUsers)) {
    //
}

Checking for multiple permissions:

if (auth()->user()->hasPermissions(['users.create', 'users.edit'])) {
    // This user has both creation and edit rights.
} else {
    // It looks like the user doesn't have one of the specified permissions.
}

Checking if the user has any permissions:

if (auth()->user()->hasAnyPermissions(['users.create', 'users.edit', 'users.destroy'])) {
    // This user either has create, edit or destroy permissions.
} else {
    // It looks like the user doesn't have any of the specified permissions.
}

Checking if the user has a role:

if (auth()->user()->hasRole('administrator')) {
    // This user is an administrator.
} else {
    // It looks like the user isn't an administrator.
}

Checking if the user has specified roles:

if (auth()->user()->hasRoles(['administrator', 'member'])) {
    // This user is an administrator and a member.
} else {
    // It looks like the user isn't an administrator or member.
}

Checking if the user has any specified roles:

if (auth()->user()->hasAnyRoles(['administrator', 'member', 'guest'])) {
    // This user is either an administrator, member or guest.
} else {
    // It looks like the user doesn't have any of these roles.
}

Middleware

Authorization includes two useful middleware classes you can utilize for your routes.

Insert them into your app/Http/Kernel.php:

/**
 * The application's route middleware.
 *
 * These middleware may be assigned to groups or used individually.
 *
 * @var array
 */
protected $routeMiddleware = [
    'auth' => AppHttpMiddlewareAuthenticate::class,
    'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
    'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
    'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
    'permission' => LarapacksAuthorizationMiddlewarePermissionMiddleware::class, // The permission middleware
    'role' => LarapacksAuthorizationMiddlewareRoleMiddleware::class, // The role middleware
];

Once you’ve done that, you can start using them.

Note: When a user does not meet the requirements using the middleware,
a 403 HTTP exception is thrown.

To guard a route to only allow specific permissions:

Route::get('users', [
    'uses' => '[email protected]',
    'middleware' => 'permission:users.index',
]);

// Multiple permissions:
Route::get('users', [
    'uses' => '[email protected]',
    'middleware' => 'permission:users.index,users.create', // Users must have index **and** create rights to access this route.
]);

To guard a route to allow a specific role:

Route::get('users', [
    'uses' => '[email protected]',
    'middleware' => 'role:administrator',
]);

// Multiple roles:
Route::get('users', [
    'uses' => '[email protected]',
    'middleware' => 'role:administrator,member', // Users must be an administrator **and** a member to access this route.
]);

Closure Permissions

Note: This feature was introduced in v1.1.0.

Problem:

You need database permissions but you need logic in some permissions as well.

For example, if a user creates a post, only that user should be able to edit it, as well as administrators.

To include this logic into gate abilities, your options are:

  • Define gate abilities in the service provider with the logic (which can get cluttered and become an organizational mess)
  • Define policy classes and have a mix of database abilities with policies (more mess)
  • Policies then need to be bound to a model, and if we don’t have a model, we
    need to call the policy() helper method, and throw our own authorization exception (more confusion)

Solution:

Store all abilities in the database including ones that require logic and utilize native laravel methods for all authorization.

Here’s how it’s done:

// Create the closure permission

$permission = new Permission();

$permission->name = "posts.edit";
$permission->label = "Edit Posts";
$permission->closure = function ($user, $post) {
    return $user->id == $post->user_id || $user->hasRole('admin');
};

$permission->save();

Use native laravel authorization:

public function edit($id)
{
    $post = Post::findOrFail($id);
    
    $this->authorize('posts.edit', [$post]);
    
    return view('posts.edit', compact('post'));
}

It’s not necessary to save the permission onto the user because the logic is inside
the permission itself to determine whether or not the user can perform the ability.

That’s great and all, but I need access to be able to bypass these checks if the user is an administrator!

No problem, just go into your native app/Providers/AuthServiceProvider and define that explicitly in the gate.

public function boot(GateContract $gate)
{
    $this->registerPolicies($gate);
    
    $gate->before(function ($user) {
        return ($user->hasRole('administrator') ?: null);
    });
}

Now all your dynamic logic is stored inside the database, and your clean logic is stored inside the AuthServiceProvider.

Neat huh?

Running Tests

To run your applications tests, inside your TestCase::setUp() method, you’ll instantiate
the PermissionRegistrar before running your tests for permissions to register properly:

use LarapacksAuthorizationPermissionRegistrar;
public function setUp()
{
    parent::setUp();
    
    $registrar = app(PermissionResistrar::class);
    
    $registrar->register();
}

Source

Add a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *