jellis / check by jellis

A very easy-to-implement user access control package designed for use with Laravel and Eloquent
34
2
0
Package Data
Maintainer Username: jellis
Maintainer Contact: jellis@jellis.com.au (Joshua Ellis)
Package Create Date: 2016-07-03
Package Last Update: 2017-01-29
Language: PHP
License: MIT
Last Refreshed: 2024-05-10 15:13:06
Package Statistics
Total Downloads: 34
Monthly Downloads: 0
Daily Downloads: 0
Total Stars: 2
Total Watchers: 0
Total Forks: 0
Total Open Issues: 0

NOTE

Ensure you don't use the RoutAwareModel for your Authenticatable User model - it becomes a circular operation when applying the global scopes and you'll get a bad gateway (502) error.

What's it all about?

The purpose of the project was to create a syntactically simple way to implement context-based user access control. What does that mean, exactly? Good question...

Context-based access control

I wanted to start with the idea that I could use a really straight-forward syntax for my "things" (whatever they might be). The first concept I came up with was Check::can('post.edit'). Because I was a fan of naming my routes, this made good sense from a flow point-of-view. Because I have my routes named, I figured I'd be able to implement middleware that would also leverage the access control system.

Adding context to the access control wasn't a trivial task. Each model will have its own context. Say in a Post model, "owning" a post might mean that there is a user_id field on the Post that is equal to the current user, but in a User model, "owning" might mean that users are in the same company as you. So, how do I have a simple syntax for implementing and checking permissions, but also giving context when the need arises?

Using the Jeffrey Way school of thought, I started with how I wanted to define things... I really wanted my Role classes to be so simple it's almost stupid.

    $permissions = [
        'post' => [
            'index', 'create', 'store', 'view', 'edit:own', 'update:own',
        ]
    ];

After starting with those two ideas, I set to work and actually managed to implement them. What we have is, I think, a simple, fluent way of managing user access.

Route Aware Models

If you have, say, a listing page for your users where they can see all posts, but can only edit their own, you'd simply have to do the following.

Register the service provider config/app.php

    'providers' => [
        ...
        Jellis\Check\Providers\CheckServiceProvider::class,
        ...
    ],

Register the facade in config/app.php

    'aliases' => [
        ...
        'Check' => Jellis\Check\Facades\Check::class,
        ...
    ],

Name the route and assign the middleware

Route::get('post', ['uses' => 'PostController@index', 'as' => 'post.index', 'middleware' => 'check']);

Create a role (assuming "member" for this user)

<?php

namespace App\Roles;

use Jellis\Check\Roles\Base;

class Member extends Base {

    protected $permissions = [
        'post' => [
            'index', 'view', 'create', 'store', 'view', 'edit:own', 'update:own',
        ],
    ];

}

Configure the model to do its thing


namespace App\Models;

use Jellis\Check\RouteAwareModel;

class Post extends RouteAwareModel
{

    protected $table = 'posts';

    ...

    /**
     * This is to check against a given model
     */
    public function allowOwnOnly()
    {
        return $this->user_id == \Auth::id();
    }

    /**
     * This is to restrict things coming out of the database
     */
    public function restrictOwnOnly(Builder $builder)
    {
        $builder->where('user_id', Auth::id());
    }

}

You need to implement the getRole() method on the user model


    class User extends Model {
        ...
        public function getRole()
        {
            return $this->role; // Or however you determine what a user's role is right now
        }
        ...
    }

Register the middleware in Kernel.php

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'check' => \Jellis\Check\Middleware\Checker::class,
    ];

Retrieve some records in your controller

class PostController extends Controller {

    public function index()
    {
        // You could check stuff here if you need to
        $myThing = Check::can('my.thing');

        // Or you can do a contextual check, say, on a post
        $post = Post::find(1);

        if (Check::can('post.edit', $post)) {
            // Do some thing
        }

        // In this instance, let's pass it to the view
        $posts = Post::all();

        return view('post.index', compact('posts'));
    }
}

And in the view you can do things like

@foreach($posts as $post)
    <p>{{ $post->title }}@check('post.edit', $post)<strong>You can edit</strong>@endcheck</p>
@endforeach

So you're a super admin??

Who really wants to be putting all of those routes in for super admin? Not me.

When defining your SuperAdmin role, just override the can() method

class SuperAdmin extends Base
{
    /**
     * Can do all the things all the time
     *
     * @param string $action
     * @param Model $model
     * @return bool
     */
    public function check($action, Model $model = null)
    {
        return true;
    }
}

TODO

  1. Implement ability to define a permission for multiple contexts edit:own|company
  2. Implement multiple contexts on the scope for checking access rights
  3. Implement multiple contexts on the scope for pulling records from the model
  4. Allow ability to wildcard a thing post.*, whilst still retaining scope ability post.*:own