/authority-controller

Authorization PHP package for Laravel 4.

Primary LanguagePHPMIT LicenseMIT

AuthorityController Build Status

AuthorityController 1.2 is an PHP authorization library for Laravel 4.1 which restricts what resources a given user is allowed to access.

All permissions are defined in a single location:

app/config/packages/efficiently/authority-controller/config.php

and not duplicated across controllers, routes, views, and database queries.

For Laravel 4.0 supports see AuthorityController 1.0 branch

Demo application

You can see in action this package with this Laravel 4.1 demo application.

Origins and Inspirations

It's an extension of the authority-l4 package.

And a port of the best Ruby authorization library: CanCan.

Authority ports some features of CanCan and this package ports almost all the other features.

Installation

  1. Add authority-controller package to your composer.json file to require AuthorityController:
composer require efficiently/authority-controller:1.2.*
  1. Add the service provider to app/config/app.php:
    'Efficiently\AuthorityController\AuthorityControllerServiceProvider',
  1. Add the aliases (facades) to your Laravel app config file:
    'Params'    => 'Efficiently\AuthorityController\Facades\Params',
    'Authority' => 'Efficiently\AuthorityController\Facades\Authority',
  1. This will allow you to access the Authority class through the static interface you are used to with Laravel components.
Authority::can('update', 'SomeModel');
  1. Run this command:
php artisan package:install efficiently/authority-controller
  1. Then provide a version constraint for the efficiently/authority-controller requirement:
1.2.*

Configuration

Create Roles and Permissions Tables

We have provided a basic table structure to get you started in creating your roles and permissions.

Run the Authority migrations

php artisan migrate --package=machuga/authority-l4

This will create the following tables

  • roles
  • role_user
  • permissions

To utilize these tables, you can add the following methods to your User model. You will also need to create Role and Permission Model stubs.

    //app/models/User.php
    public function roles()
    {
        return $this->belongsToMany('Role');
    }

    public function permissions()
    {
        return $this->hasMany('Permission');
    }

    public function hasRole($key)
    {
        foreach($this->roles as $role){
            if($role->name === $key)
            {
                return true;
            }
        }
        return false;
    }

    //app/models/Role.php
    class Role extends Eloquent {}

    //app/models/Permission.php
    class Permission extends Eloquent {}
Init resource filters and controller methods

In your app/controllers/BaseController.php file:

class BaseController extends \Controller
{
    use Efficiently\AuthorityController\ControllerAdditions;
    //code...
}

Getting Started

AuthorityController expects that Auth::user() return the current authenticated user. First, set up some authentication (from Scratch or with Confide package).

Defining Authority rules

User permissions are defined in an AuthorityController configuration file.

You can publish the AuthorityController default configuration file with the command below:

  php artisan config:publish efficiently/authority-controller

This will place a copy of the configuration file at app/config/packages/efficiently/authority-controller. The config file includes an initialize function, which is a great place to setup your rules and aliases.

// app/config/packages/efficiently/authority-controller/config.php

return [

    'initialize' => function($authority) {
        $user = Auth::guest() ? new User : $authority->getCurrentUser();

        // Action aliases. For example:
        $authority->addAlias('moderate', ['read', 'update', 'delete']);

        // Define abilities for the passed in user here. For example:
        if ($user->hasRole('admin')) {
            $authority->allow('manage', 'all');
        } else {
            $authority->allow('read', 'all');
        }

    }

];

See Defining Authority rules for details.

Check Authority rules & Authorization

The current user's permissions can then be checked using the Authority::can() and Authority::cannot() methods in the view and controller.

@if (Authority::can('update', $article))
    {{ link_to_route("articles.edit", "Edit", $article->id) }}
@endif

See Checking Authority rules for more information

The authorize() method in the controller will throw an exception if the user is not able to perform the given action.

public function show($id)
{
    $this->article = Article::find($id);
    $this->authorize('read', $this->article);
}

Setting this for every action can be tedious, therefore the loadAndAuthorizeResource() method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for every action.

class ArticlesController extends \BaseController
{

    public function __construct()
    {
        $this->loadAndAuthorizeResource();
    }

    public function show($id)
    {
        // $this->article is already loaded and authorized
    }
}

See Authorizing Controller Actions for more information.

Exception Handling

The Efficiently\AuthorityController\Exceptions\AccessDenied exception is thrown when calling authorize() in the controller and the user is not able to perform the given action. A message can optionally be provided.

Authority::authorize('read', 'Product', 'Unable to read this product.');

You can catch the exception and modify its behavior in the app/start/global.php file. For example here we set the error message to a flash and redirect to the home page.

App::error(function(Efficiently\AuthorityController\Exceptions\AccessDenied $e, $code, $fromConsole)
{
    $msg = $e->getMessage();
    if ($fromConsole) {
      return 'Error '.$code.': '.$msg."\n";
    }
    Log::error('Access denied! '.$msg);
    return Redirect::route('home')->with('flash_alert', $msg);
});

See Exception Handling for more information.

Documentations

Wiki Docs
Authority Docs

Authority introduction.

Authority-L4 general usage.

CanCan Wiki Docs

Because AuthorityController is a CanCan port, you can also read the Wiki docs of CanCan here.

Controller additions

Your controllers have a $params property:

class ProductsController extends \BaseController
{
    //code...

    public function update($id)
    {
        $this->params['id'] == $id;//-> true
        $this->params['product'];//-> ["name" => "Best movie"]
        $this->params['controller'];//-> 'products'
        $this->params['action'];//-> 'update'
        //code...
    }

    //code...
}

Changelog

1.2.1

  • Fix composer.json file.

1.2.0

  • Security fix: conditional callback was never evaluated when an actual instance object was present.
  • Non backwards compatible: Deny rules override prior rules and Allow rules don't override prior rules but instead are logically or'ed (fix #5). Match more CanCan default behavior unlike machuga\authority package. Read the Wiki doc for more information: Authority-Precedence.
  • Support PHP 5.4, 5.5, 5.6 and HipHop Virtual Machine (hhvm).
  • Update Parameters class to allow custom routes with id and parent_id routes's parameters (fix #6).

1.1.3

  • Upgrade Authority-L4 package to fix Laravel 4.1 support.

1.1.2

  • Tweak the mock system who simulates Eloquent's constructor method.

1.1.1

  • Less intrusive parameters injection in the controllers
    • Check if the current resolved controller responds to paramsBeforeFilter method. Otherwise the application crash.
    • Use the Controller alias of the current Laravel application instead of a hardcoded class name.

1.1.0

  • First beta release for Laravel 4.1 compatibility.
  • Non backwards compatible with Laravel 4.0.

1.0.0

  • First stable release, only compatible with Laravel 4.0.
  • For Laravel 4.1 supports, see AuthorityController 1.1 branch.
  • Fix AccessDenied class, the exception message didn't fallback to the default message if it was empty.

0.10.0

  • Non backwards compatible: Params::get('controller') behaviour is now like Rails. It returns controller name in snake_case and in plural.

0.9.0

  • First beta release

Missing features

  1. In ControllerResource class, the #load_collection method, who uses in the User model #accessible_by method. Looks complicated. Instead, use specific query scopes with collectionScope option to filtering your data in your collection (e.g. index) controller actions. Because you'll allowing/denying access by roles or check user's authorizations on each record of the collection.
  2. In Ability class, the #attributes_for method. Looks useless with Authority because rules conditions are only possible by Closure not by associative array. And CanCan handles #attribute_for only for Hash (associative array) conditions.
  3. #skip_* methods in ControllerAdditions.
  4. For allow() and deny() methods of Authority, the third argument isn't an optional hash (associative array) of conditions but an anonymous function (Closure):
$authority->allow('update', 'Product', function($self, $product) {
    return $product->available === true;
});

Good to know

Compatibility

It's only compatible with PHP >= 5.4 and Laravel 4.1 framework.

This is beta-quality software

It works well according to our tests. The internal API may change and other features will be added. We are working to make AuthorityController production quality software.

Differences between CanCan and AuthorityController

See Wiki page Differences between CanCan and AuthorityController

Philosophy

It's following the D.R.W.J.P.I. principle:

Don't Reinvent the Wheel, Just Port It ! -- (c) 2013 A.D.

Questions or Problems?

If you have any issues with AuthorityController, please add an issue on GitHub or fork the project and send a pull request.

To get the tests running you should install PHPUnit and run phpunit tests.

Special Thanks

AuthorityController was heavily inspired by CanCan and uses Authority-L4.