Eloquent roles and abilities.

Overview

Bouncer

Build Status Total Downloads License

Bouncer is an elegant, framework-agnostic approach to managing roles and abilities for any app using Eloquent models.

Table of Contents

Click to expand

Introduction

Bouncer is an elegant, framework-agnostic approach to managing roles and abilities for any app using Eloquent models. With an expressive and fluent syntax, it stays out of your way as much as possible: use it when you want, ignore it when you don't.

For a quick, glanceable list of Bouncer's features, check out the cheat sheet.

Bouncer works well with other abilities you have hard-coded in your own app. Your code always takes precedence: if your code allows an action, Bouncer will not interfere.

Once installed, you can simply tell the bouncer what you want to allow at the gate:

// Give a user the ability to create posts
Bouncer::allow($user)->to('create', Post::class);

// Alternatively, do it through a role
Bouncer::allow('admin')->to('create', Post::class);
Bouncer::assign('admin')->to($user);

// You can also grant an ability only to a specific model
Bouncer::allow($user)->to('edit', $post);

When you check abilities at Laravel's gate, the bouncer will automatically be consulted. If he sees an ability that has been granted to the current user (whether directly, or through a role) he'll authorize the check.

Installation

Note: Bouncer requires PHP 7.2+ and Laravel/Eloquent 6.0+

If you're not up to date, use Bouncer RC6. It supports all the way back to PHP 5.5 & Laravel 5.1, and has no known bugs.

Installing Bouncer in a Laravel app

  1. Install Bouncer with composer:
$ composer require silber/bouncer v1.0.0-rc.10
  1. Add Bouncer's trait to your user model:

    use Silber\Bouncer\Database\HasRolesAndAbilities;
    
    class User extends Model
    {
        use HasRolesAndAbilities;
    }
  2. Now, to run Bouncer's migrations, first publish the migrations into your app's migrations directory, by running the following command:

    php artisan vendor:publish --tag="bouncer.migrations"
    
  3. Finally, run the migrations:

    php artisan migrate
    

Facade

Whenever you use the Bouncer facade in your code, remember to add this line to your namespace imports at the top of the file:

use Bouncer;

For more information about Laravel Facades, refer to the Laravel documentation.

Installing Bouncer in a non-Laravel app

  1. Install Bouncer with composer:

    $ composer require silber/bouncer v1.0.0-rc.10
    
  2. Set up the database with the Eloquent Capsule component:

    use Illuminate\Database\Capsule\Manager as Capsule;
    
    $capsule = new Capsule;
    
    $capsule->addConnection([/* connection config */]);
    
    $capsule->setAsGlobal();

    Refer to the Eloquent Capsule documentation for more details.

  3. Run the migrations by either of the following methods:

  4. Add Bouncer's trait to your user model:

    use Illuminate\Database\Eloquent\Model;
    use Silber\Bouncer\Database\HasRolesAndAbilities;
    
    class User extends Model
    {
        use HasRolesAndAbilities;
    }
  5. Create an instance of Bouncer:

    use Silber\Bouncer\Bouncer;
    
    $bouncer = Bouncer::create();
    
    // If you are in a request with a current user
    // that you'd wish to check permissions for,
    // pass that user to the "create" method:
    $bouncer = Bouncer::create($user);

    If you're using dependency injection in your app, you may register the Bouncer instance as a singleton in the container:

    use Silber\Bouncer\Bouncer;
    use Illuminate\Container\Container;
    
    Container::getInstance()->singleton(Bouncer::class, function () {
        return Bouncer::create();
    });

    You can now inject Bouncer into any class that needs it.

    The create method creates a Bouncer instance with sensible defaults. To fully customize it, use the make method to get a factory instance. Call create() on the factory to create the Bouncer instance:

    use Silber\Bouncer\Bouncer;
    
    $bouncer = Bouncer::make()
             ->withCache($customCacheInstance)
             ->create();

    Check out the Factory class to see all the customizations available.

  6. Set which model is used as the user model throughout your app:

    $bouncer->useUserModel(User::class);

    For additional configuration, check out the Configuration section below.

Enabling cache

By default, Bouncer's queries are cached for the current request. For better performance, you may want to enable cross-request caching.

Usage

Adding roles and abilities to users is made extremely easy. You do not have to create a role or an ability in advance. Simply pass the name of the role/ability, and Bouncer will create it if it doesn't exist.

Note: the examples below all use the Bouncer facade. If you don't use facades, you can instead inject an instance of Silber\Bouncer\Bouncer into your class.

Creating roles and abilities

Let's create a role called admin and give it the ability to ban-users from our site:

Bouncer::allow('admin')->to('ban-users');

That's it. Behind the scenes, Bouncer will create both a Role model and an Ability model for you.

If you want to add additional attributes to the role/ability, such as a human-readable title, you can manually create them using the role and ability methods on the Bouncer class:

$admin = Bouncer::role()->firstOrCreate([
    'name' => 'admin',
    'title' => 'Administrator',
]);

$ban = Bouncer::ability()->firstOrCreate([
    'name' => 'ban-users',
    'title' => 'Ban users',
]);

Bouncer::allow($admin)->to($ban);

Assigning roles to a user

To now give the admin role to a user, simply tell the bouncer that the given user should be assigned the admin role:

Bouncer::assign('admin')->to($user);

Alternatively, you can call the assign method directly on the user:

$user->assign('admin');

Giving a user an ability directly

Sometimes you might want to give a user an ability directly, without using a role:

Bouncer::allow($user)->to('ban-users');

Here too you can accomplish the same directly off of the user:

$user->allow('ban-users');

Restricting an ability to a model

Sometimes you might want to restrict an ability to a specific model type. Simply pass the model name as a second argument:

Bouncer::allow($user)->to('edit', Post::class);

If you want to restrict the ability to a specific model instance, pass in the actual model instead:

Bouncer::allow($user)->to('edit', $post);

Allowing a user or role to "own" a model

Use the toOwn method to allow users to manage their own models:

Bouncer::allow($user)->toOwn(Post::class);

Now, when checking at the gate whether the user may perform an action on a given post, the post's user_id will be compared to the logged-in user's id (this can be customized). If they match, the gate will allow the action.

The above will grant all abilities on a user's "owned" models. You can restrict the abilities by following it up with a call to the to method:

Bouncer::allow($user)->toOwn(Post::class)->to('view');

// Or pass it an array of abilities:
Bouncer::allow($user)->toOwn(Post::class)->to(['view', 'update']);

You can also allow users to own all types of models in your application:

Bouncer::allow($user)->toOwnEverything();

// And to restrict ownership to a given ability
Bouncer::allow($user)->toOwnEverything()->to('view');

Retracting a role from a user

The bouncer can also retract a previously-assigned role from a user:

Bouncer::retract('admin')->from($user);

Or do it directly on the user:

$user->retract('admin');

Removing an ability

The bouncer can also remove an ability previously granted to a user:

Bouncer::disallow($user)->to('ban-users');

Or directly on the user:

$user->disallow('ban-users');

Note: if the user has a role that allows them to ban-users they will still have that ability. To disallow it, either remove the ability from the role or retract the role from the user.

If the ability has been granted through a role, tell the bouncer to remove the ability from the role instead:

Bouncer::disallow('admin')->to('ban-users');

To remove an ability for a specific model type, pass in its name as a second argument:

Bouncer::disallow($user)->to('delete', Post::class);

Warning: if the user has an ability to delete a specific $post instance, the code above will not remove that ability. You will have to remove the ability separately - by passing in the actual $post as a second argument - as shown below.

To remove an ability for a specific model instance, pass in the actual model instead:

Bouncer::disallow($user)->to('delete', $post);

Note: the disallow method only removes abilities that were previously given to this user/role. If you want to disallow a subset of what a more-general ability has allowed, use the forbid method.

Forbidding an ability

Bouncer also allows you to forbid a given ability, for more fine-grained control. At times you may wish to grant a user/role an ability that covers a wide range of actions, but then restrict a small subset of those actions.

Here are some examples:

  • You might allow a user to generally view all documents, but have a specific highly-classified document that they should not be allowed to view:

    Bouncer::allow($user)->to('view', Document::class);
    
    Bouncer::forbid($user)->to('view', $classifiedDocument);
  • You may wish to allow your superadmins to do everything in your app, including adding/removing users. Then you may have an admin role that can do everything besides managing users:

    Bouncer::allow('superadmin')->everything();
    
    Bouncer::allow('admin')->everything();
    Bouncer::forbid('admin')->toManage(User::class);
  • You may wish to occasionally ban users, removing their permission to all abilities. However, actually removing all of their roles & abilities would mean that when the ban is removed we'll have to figure out what their original roles and abilities were.

    Using a forbidden ability means that they can keep all their existing roles and abilities, but still not be authorized for anything. We can accomplish this by creating a special banned role, for which we'll forbid everything:

    Bouncer::forbid('banned')->everything();

    Then, whenever we want to ban a user, we'll assign them the banned role:

    Bouncer::assign('banned')->to($user);

    To remove the ban, we'll simply retract the role from the user:

    Bouncer::retract('banned')->from($user);

As you can see, Bouncer's forbidden abilities gives you a lot of granular control over the permissions in your app.

Unforbidding an ability

To remove a forbidden ability, use the unforbid method:

Bouncer::unforbid($user)->to('view', $classifiedDocument);

Note: this will remove any previously-forbidden ability. It will not authomatically allow the ability if it's not already allowed by a different regular ability granted to this user/role.

Checking a user's roles

Note: Generally speaking, you should not have a need to check roles directly. It is better to allow a role certain abilities, then check for those abilities instead. If what you need is very general, you can create very broad abilities. For example, an access-dashboard ability is always better than checking for admin or editor roles directly. For the rare occasion that you do want to check a role, that functionality is available here.

The bouncer can check if a user has a specific role:

Bouncer::is($user)->a('moderator');

If the role you're checking starts with a vowel, you might want to use the an alias method:

Bouncer::is($user)->an('admin');

For the inverse, you can also check if a user doesn't have a specific role:

Bouncer::is($user)->notA('moderator');

Bouncer::is($user)->notAn('admin');

You can check if a user has one of many roles:

Bouncer::is($user)->a('moderator', 'editor');

You can also check if the user has all of the given roles:

Bouncer::is($user)->all('editor', 'moderator');

You can also check if a user has none of the given roles:

Bouncer::is($user)->notAn('editor', 'moderator');

These checks can also be done directly on the user:

$user->isAn('admin');
$user->isA('subscriber');

$user->isNotAn('admin');
$user->isNotA('subscriber');

$user->isAll('editor', 'moderator');

Querying users by their roles

You can query your users by whether they have a given role:

$users = User::whereIs('admin')->get();

You may also pass in multiple roles, to query for users that have any of the given roles:

$users = User::whereIs('superadmin', 'admin')->get();

To query for users who have all of the given roles, use the whereIsAll method:

$users = User::whereIsAll('sales', 'marketing')->get();

Getting all roles for a user

You can get all roles for a user directly from the user model:

$roles = $user->getRoles();

Getting all abilities for a user

You can get all abilities for a user directly from the user model:

$abilities = $user->getAbilities();

This will return a collection of the user's allowed abilities, including any abilities granted to the user through their roles.

You can also get a list of abilities that have been explicitly forfidden:

$forbiddenAbilities = $user->getForbiddenAbilities();

Authorizing users

Authorizing users is handled directly at Laravel's Gate, or on the user model ($user->can($ability)).

For convenience, the Bouncer class provides these passthrough methods:

Bouncer::can($ability);
Bouncer::can($ability, $model);

Bouncer::canAny($abilities);
Bouncer::canAny($abilities, $model);

Bouncer::cannot($ability);
Bouncer::cannot($ability, $model);

Bouncer::authorize($ability);
Bouncer::authorize($ability, $model);

These call directly into their equivalent methods on the Gate class.

Blade directives

Bouncer does not add its own blade directives. Since Bouncer works directly with Laravel's gate, simply use its @can directive to check for the current user's abilities:

@can ('update', $post)
    <a href="{{ route('post.update', $post) }}">Edit Post</a>
@endcan

Since checking for roles directly is generally not recommended, Bouncer does not ship with a separate directive for that. If you still insist on checking for roles, you can do so using the general @if directive:

@if ($user->isAn('admin'))
    //
@endif

Refreshing the cache

All queries executed by Bouncer are cached for the current request. If you enable cross-request caching, the cache will persist across different requests.

Whenever you need, you can fully refresh the bouncer's cache:

Bouncer::refresh();

Note: fully refreshing the cache for all users uses cache tags if they're available. Not all cache drivers support this. Refer to Laravel's documentation to see if your driver supports cache tags. If your driver does not support cache tags, calling refresh might be a little slow, depending on the amount of users in your system.

Alternatively, you can refresh the cache only for a specific user:

Bouncer::refreshFor($user);

Multi-tenancy

Bouncer fully supports multi-tenant apps, allowing you to seamlessly integrate Bouncer's roles and abilities for all tenants within the same app.

The scope middleware

To get started, first publish the scope middleware into your app:

php artisan vendor:publish --tag="bouncer.middleware"

The middleware will now be published to app/Http/Middleware/ScopeBouncer.php. This middleware is where you tell Bouncer which tenant to use for the current request. For example, assuming your users all have an account_id attribute, this is what your middleware would look like:

public function handle($request, Closure $next)
{
    $tenantId = $request->user()->account_id;

    Bouncer::scope()->to($tenantId);

    return $next($request);
}

You are of course free to modify this middleware to fit your app's needs, such as pulling the tenant information from a subdomain et al.

Now with the middleware in place, be sure to register it in your HTTP Kernel:

protected $middlewareGroups = [
    'web' => [
        // Keep the existing middleware here, and add this:
        \App\Http\Middleware\ScopeBouncer::class,
    ]
];

All of Bouncer's queries will now be scoped to the given tenant.

Customizing Bouncer's scope

Depending on your app's setup, you may not actually want all of the queries to be scoped to the current tenant. For example, you may have a fixed set of roles/abilities that are the same for all tenants, and only allow your users to control which users are assigned which roles, and which roles have which abilities. To achieve this, you can tell Bouncer's scope to only scope the relationships between Bouncer's models, but not the models themselves:

Bouncer::scope()->to($tenantId)->onlyRelations();

Furthermore, your app might not even allow its users to control which abilities a given role has. In that case, tell Bouncer's scope to exclude role abilities from the scope, so that those relationships stay global across all tenants:

Bouncer::scope()->to($tenantId)->onlyRelations()->dontScopeRoleAbilities();

If your needs are even more specialized than what's outlined above, you can create your own Scope with whatever custom logic you need:

use Silber\Bouncer\Contracts\Scope;

class MyScope implements Scope
{
    // Whatever custom logic your app needs
}

Then, in a service provider, register your custom scope:

Bouncer::scope(new MyScope);

Bouncer will call the methods on the Scope interface at various points in its execution. You are free to handle them according to your specific needs.

Configuration

Bouncer ships with sensible defaults, so most of the time there should be no need for any configuration. For finer-grained control, Bouncer can be customized by calling various configuration methods on the Bouncer class.

If you only use one or two of these config options, you can stick them into your main AppServiceProvider's boot method. If they start growing, you may create a separate BouncerServiceProvider class in your app/Providers directory (remember to register it in the providers config array).

Cache

By default, all queries executed by Bouncer are cached for the current request. For better performance, you may want to use cross-request caching:

Bouncer::cache();

Warning: if you enable cross-request caching, you are responsible to refresh the cache whenever you make changes to user's roles/abilities. For how to refresh the cache, read refreshing the cache.

On the contrary, you may at times wish to completely disable the cache, even within the same request:

Bouncer::dontCache();

This is particularly useful in unit tests, when you want to run assertions against roles/abilities that have just been granted.

Tables

To change the database table names used by Bouncer, pass an associative array to the tables method. The keys should be Bouncer's default table names, and the values should be the table names you wish to use. You do not have to pass in all tables names; only the ones you wish to change.

Bouncer::tables([
    'abilities' => 'my_abilities',
    'permissions' => 'granted_abilities',
]);

Bouncer's published migration uses the table names from this configuration, so be sure to have these in place before actually running the migration file.

Custom models

You can easily extend Bouncer's built-in Role and Ability models:

use Silber\Bouncer\Database\Ability;

class MyAbility extends Ability
{
    // custom code
}
use Silber\Bouncer\Database\Role;

class MyRole extends Role
{
    // custom code
}

Alternatively, you can use Bouncer's IsAbility and IsRole traits without actually extending any of Bouncer's models:

use Illuminate\Database\Eloquent\Model;
use Silber\Bouncer\Database\Concerns\IsAbility;

class MyAbility extends Model
{
    use IsAbility;

    // custom code
}
use Illuminate\Database\Eloquent\Model;
use Silber\Bouncer\Database\Concerns\IsRole;

class MyRole extends Model
{
    use IsRole;

    // custom code
}

If you use the traits instead of extending Bouncer's models, be sure to set the proper $table name and $fillable fields yourself.

Regardless of which method you use, the next step is to actually tell Bouncer to use your custom models:

Bouncer::useAbilityModel(MyAbility::class);
Bouncer::useRoleModel(MyRole::class);

User Model

By default, Bouncer automatically uses the user model of the default auth guard.

If you're using Bouncer with a non-default guard, and it uses a different user model, you should let Bouncer know about the user model you want to use:

Bouncer::useUserModel(\App\Admin::class);

Ownership

In Bouncer, the concept of ownership is used to allow users to perform actions on models they "own".

By default, Bouncer will check the model's user_id against the current user's primary key. If needed, this can be set to a different attribute:

Bouncer::ownedVia('userId');

If different models use different columns for ownership, you can register them separately:

Bouncer::ownedVia(Post::class, 'created_by');
Bouncer::ownedVia(Order::class, 'entered_by');

For greater control, you can pass a closure with your custom logic:

Bouncer::ownedVia(Game::class, function ($game, $user) {
    return $game->team_id == $user->team_id;
});

FAQ

There are some concepts in Bouncer that people keep on asking about, so here's a short list of some of those topics:

Where do I set up my app's roles and abilities?

Seeding the initial roles and abilities can be done in a regular Laravel seeder class. Start by creating a specific seeder file for Bouncer:

php artisan make:seeder BouncerSeeder

Place all of your seeding roles & abilities code in the seeder's run method. Here's an example of what that might look like:

use Bouncer;
use Illuminate\Database\Seeder;

class BouncerSeeder extends Seeder
{
    public function run()
    {
        Bouncer::allow('superadmin')->everything();

        Bouncer::allow('admin')->everything();
        Bouncer::forbid('admin')->toManage(User::class);

        Bouncer::allow('editor')->to('create', Post::class);
        Bouncer::allow('editor')->toOwn(Post::class);

        // etc.
    }
}

To actually run it, pass the seeder's class name to the class option of the db:seed command:

php artisan db:seed --class=BouncerSeeder

Can I use a different set of roles & abilities for the public & dashboard sections of my site, respectively?

Bouncer's scope can be used to section off different parts of the site, creating a silo for each one of them with its own set of roles & abilities:

  1. Create a ScopeBouncer middleware that takes an $identifier and sets it as the current scope:

    use Bouncer, Closure;
    
    class ScopeBouncer
    {
        public function handle($request, Closure $next, $identifier)
        {
            Bouncer::scope()->to($identifier);
    
            return $next($request);
        }
    }
  2. Register this new middleware as a route middleware in your HTTP Kernel class:

    protected $routeMiddleware = [
        // Keep the other route middleware, and add this:
        'scope-bouncer' => \App\Http\Middleware\ScopeBouncer::class,
    ];
  3. In your route service provider, apply this middleware with a different identifier for the public routes and the dashboard routes, respectively:

    Route::middleware(['web', 'scope-bouncer:1'])
         ->namespace($this->namespace)
         ->group(base_path('routes/public.php'));
    
    Route::middleware(['web', 'scope-bouncer:2'])
         ->namespace($this->namespace)
         ->group(base_path('routes/dashboard.php'));

That's it. All roles and abilities will now be separately scoped for each section of your site. To fine-tune the extent of the scope, see Customizing Bouncer's scope.

I'm trying to run the migration, but I'm getting a SQL error that the "specified key was too long"

Starting with Laravel 5.4, the default database character set is now utf8mb4. If you're using older versions of some databases (MySQL below 5.7.7, or MariaDB below 10.2.2) with Larvel 5.4+, you'll get a SQL error when trying to create an index on a string column. To fix this, change Laravel's default string length in your AppServiceProvider:

use Illuminate\Support\Facades\Schema;

public function boot()
{
    Schema::defaultStringLength(191);
}

You can read more in this Laravel News article.

I'm trying to run the migration, but I'm getting a SQL error that there is a "Syntax error or access violation: 1064 ... to use near json not null)"

JSON columns are a relatively new addition to MySQL (5.7.8) and MariaDB (10.2.7). If you're using an older version of these databases, you cannot use JSON columns.

The best solution would be to upgrade your DB. If that's not currently possible, you can change your published migration file to use a text column instead:

- $table->json('options')->nullable();
+ $table->text('options')->nullable();

Console commands

bouncer:clean

The bouncer:clean command deletes unused abilities. Running this command will delete 2 types of unused abilities:

  • Unassigned abilities - abilities that are not assigned to anyone. For example:

    Bouncer::allow($user)->to('view', Plan::class);
    
    Bouncer::disallow($user)->to('view', Plan::class);

    At this point, the "view plans" ability is not assigned to anyone, so it'll get deleted.

    Note: depending on the context of your app, you may not want to delete these. If you let your users manage abilities in your app's UI, you probably don't want to delete unassigned abilities. See below.

  • Orphaned abilities - model abilities whose models have been deleted:

    Bouncer::allow($user)->to('delete', $plan);
    
    $plan->delete();

    Since the plan no longer exists, the ability is no longer of any use, so it'll get deleted.

If you only want to delete one type of unused ability, run it with one of the following flags:

php artisan bouncer:clean --unassigned
php artisan bouncer:clean --orphaned

If you don't pass it any flags, it will delete both types of unused abilities.

To automatically run this command periodically, add it to your console kernel's schedule:

$schedule->command('bouncer:clean')->weekly();

Cheat Sheet

// Adding abilities for users
Bouncer::allow($user)->to('ban-users');
Bouncer::allow($user)->to('edit', Post::class);
Bouncer::allow($user)->to('delete', $post);

Bouncer::allow($user)->everything();
Bouncer::allow($user)->toManage(Post::class);
Bouncer::allow($user)->toManage($post);
Bouncer::allow($user)->to('view')->everything();

Bouncer::allow($user)->toOwn(Post::class);
Bouncer::allow($user)->toOwnEverything();

// Removing abilities uses the same syntax, e.g.
Bouncer::disallow($user)->to('delete', $post);
Bouncer::disallow($user)->toManage(Post::class);
Bouncer::disallow($user)->toOwn(Post::class);

// Adding & removing abilities for roles
Bouncer::allow('admin')->to('ban-users');
Bouncer::disallow('admin')->to('ban-users');

// You can also forbid specific abilities with the same syntax...
Bouncer::forbid($user)->to('delete', $post);

// And also remove a forbidden ability with the same syntax...
Bouncer::unforbid($user)->to('delete', $post);

// Re-syncing a user's abilities
Bouncer::sync($user)->abilities($abilities);

// Assigning & retracting roles from users
Bouncer::assign('admin')->to($user);
Bouncer::retract('admin')->from($user);

// Assigning roles to multiple users by ID
Bouncer::assign('admin')->to([1, 2, 3]);

// Re-syncing a user's roles
Bouncer::sync($user)->roles($roles);

// Checking the current user's abilities
$boolean = Bouncer::can('ban-users');
$boolean = Bouncer::can('edit', Post::class);
$boolean = Bouncer::can('delete', $post);

$boolean = Bouncer::cannot('ban-users');
$boolean = Bouncer::cannot('edit', Post::class);
$boolean = Bouncer::cannot('delete', $post);

// Checking a user's roles
$boolean = Bouncer::is($user)->a('subscriber');
$boolean = Bouncer::is($user)->an('admin');
$boolean = Bouncer::is($user)->notA('subscriber');
$boolean = Bouncer::is($user)->notAn('admin');
$boolean = Bouncer::is($user)->a('moderator', 'editor');
$boolean = Bouncer::is($user)->all('moderator', 'editor');

Bouncer::cache();
Bouncer::dontCache();

Bouncer::refresh();
Bouncer::refreshFor($user);

Some of this functionality is also available directly on the user model:

$user->allow('ban-users');
$user->allow('edit', Post::class);
$user->allow('delete', $post);

$user->disallow('ban-users');
$user->disallow('edit', Post::class);
$user->disallow('delete', $post);

$user->assign('admin');
$user->retract('admin');

$boolean = $user->isAn('admin');
$boolean = $user->isAn('editor', 'moderator');
$boolean = $user->isAll('moderator', 'editor');
$boolean = $user->isNotAn('admin', 'moderator');

// Querying users by their roles
$users = User::whereIs('superadmin')->get();
$users = User::whereIs('superadmin', 'admin')->get();
$users = User::whereIsAll('sales', 'marketing')->get();

$abilities = $user->getAbilities();
$forbidden = $user->getForbiddenAbilities();

Alternative

Among the bajillion packages that Spatie has so graciously bestowed upon the community, you'll find the excellent laravel-permission package. Like Bouncer, it nicely integrates with Laravel's built-in gate and permission checks, but has a different set of design choices when it comes to syntax, DB structure & features.

License

Bouncer is open-sourced software licensed under the MIT license

Comments
  • Call Policy / Gate, if available, even if ability granted

    Call Policy / Gate, if available, even if ability granted

    In our system, admins have access to do everything:

    Bouncer::allow('admin')->everything();
    

    We have a user messaging system and use a MessagePolicy class to check if you are allowed to send a message to a user. In our case, we don't need the "everything" ability to grant access, even though they are an admin - we want the MessagePolicy to always fire but bouncer automatically intersects Gate::authorize('send', $message) with Bouncer granted permission via ability #18 [all abilities]

    Would it be possible to have a way of telling bouncer to ignore certain abilities for one check, maybe a closure?

    Bouncer::ignore('*', function() {
        Gate::authorize('send', $message); // MessagePolicy will be checked and the absolute source of truth.
    });
    
    discussion 
    opened by garygreen 68
  • How can we help Bouncer get to 1.0?

    How can we help Bouncer get to 1.0?

    Hello!

    Do you have a checklist of features or ideas that need to be hashed out before you're comfortable tagging a 1.0 release that anyone might be able to help with?

    I want to start contributing to OSS and Bouncer might be a good place to start.

    discussion 
    opened by marcusmoore 60
  • How should wildcards work?

    How should wildcards work?

    I'm currently working on allowing wildcard abilities. Here's how it works:

    Bouncer::allow($user)->to('*');
    
    Bouncer::allows('*'); // true
    Bouncer::allows('ban-users'); // true
    

    You can also allow all actions on a model:

    Bouncer::allow($user)->to('*', $post);
    
    Bouncer::allows('delete', $post); // true
    Bouncer::allows('*', $post); // true
    
    Bouncer::allows('ban-users'); // false
    Bouncer::allows('*'); // false
    

    You can also allow a specific action on all models:

    Bouncer::allow($user)->to('create', '*');
    
    Bouncer::allows('create', User::class); // true
    Bouncer::allows('create', Post::class); // true
    
    Bouncer::allows('edit', Post::class); // false
    Bouncer::allows('create'); // false
    

    So far so good. What I'm not sure about is the following:

    Bouncer::allow($user)->to('*');
    
    Bouncer::allows('view-dashboard'); // true
    
    Bouncer::allows('delete', $user); // false
    

    As you can see, a wildcard ability does not allow model abilities. To also allow model abilities you need two wildcards:

    Bouncer::allow($user)->to('*', '*');
    
    Bouncer::allows('view-dashboard'); // true
    Bouncer::allows('delete', $user); // true
    

    All of this is already implemented. Now onto the question:

    Which one of these two makes more sense?

    1. A single wildcard only allows simple abilities. Model abilities requires double wildcards.
    2. There's no point in ever only allowing a user all simple abilities. Make a single wildcard allow everything.

    Option 1 is the way it works now.

    Thoughts?

    discussion 
    opened by JosephSilber 34
  • Change requests

    Change requests

    I have a couple requests for your fantastic library.

    1. Make it work for sites that already have a users table and have the code check to see what the user table primary index name is. Our users table has a primary key of user_id which breaks Bouncer. The migrations don't work right and I had to modify the AssignRole.php Conductors file in the assignRole function to work with it. Would be nice if it was more universal.
    2. Right now you have functions for checking to see if a user is in a particular role. But I don't see an option for checking to see if a user has a particular ability. I can fetch all abilities and loop, but something like this would be nice:
    $check = $user->can('ban-users');
    

    Thanks so much!

    opened by emergingdzns 28
  • Subsequent protected routes test fails, but pass independently

    Subsequent protected routes test fails, but pass independently

    I'm upgrading to Laravel 5.6 and v1.0.0-rc.1 from Laravel 5.5. and beta4. I'm noticing all my tests are failing that check permissions except for the first one. If a test file has 5 tests on routes that are protected by can: middleware, the first will pass and every other will fail at the $this->visit('my-url') step. If I run all the tests separately they will pass.

    My version: "silber/bouncer": "v1.0.0-rc.1",

    My routes:

            Route::group([
                'middleware' => ['can:'.Ability::MANAGE_GROUPS],
            ], function () {
                Route::get('groups', 'GroupController@index');
                Route::get('groups/{groupId}', 'GroupController@show');
            });
    

    I've tried adding Bouncer::dontCache(); in my setUp() with no luck. Any ideas on things that might be an issue causing this or something I can look at?

    opened by bkuhl 25
  • Rethinkdb support

    Rethinkdb support

    Hello, I'm trying to use this class using Rethinkdb as database engine. Installation successfully done and no troubles for now, but, I'm trying to seed bouncer and create default privileges to start a new application and after I used databaseseeder for this, I got this error:

      [Symfony\Component\Debug\Exception\FatalErrorException]
      Call to a member function prepare() on null
    

    This is how I'm doing my seeders. User model

    <?php
    
    namespace App;
    
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use Silber\Bouncer\Database\HasRolesAndAbilities;
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Authenticatable
    {
        use HasRolesAndAbilities;
        /**
         * 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',
        ];
    }
    

    PrivilegeSeeder

    <?php
    
    use Illuminate\Database\Seeder;
    
    class PrivilegeSeeder extends Seeder
    {
        /**
         * Run the database seeds.
         *
         * @return void
         */
        public function run()
        {
            Bouncer::allow('root')->to(['configure','vehicles','users','reports','configure-system']);
        }
    }
    

    DatabaseSeeder

    <?php
    
    use Illuminate\Database\Seeder;
    use Illuminate\Database\Eloquent\Model;
    
    class DatabaseSeeder extends Seeder
    {
        /**
         * Run the database seeds.
         *
         * @return void
         */
        public function run()
        {
            Model::unguard();
            $this->call(UserTableSeeder::class);
            $this->call(PrivilegeSeeder::class);
            Model::reguard();
        }
    }
    

    Help will be much apreciated. regards

    opened by hackerunet 23
  • Problem with multiple databases

    Problem with multiple databases

    Hello!

    Thank you for this awesome project, it makes handling roles and abilities a painless task.

    Apparently Bouncer does not support multiple databases...

    For my project I have my users table on a different database, and trust me, I have good reasons to do that.

    The problem is: when I try to assign a role to a user, I get a QueryException, saying that the users table was not found.

    I modified the migrations to set the foreign keys correctly, but I think it would be a bad practice to modify the vendor files and fix the relations in the classes...

    Is there any way you could help me here?

    Thank you!

    opened by renanBritz 23
  • Don't assume model App\User

    Don't assume model App\User

    Model App\User is hard-coded in various classes:

    Silber\Bouncer\Database\Models Silber\Bouncer\Database\Ability Silber\Bouncer\Database\Role

    It is possible to make it configurable?

    opened by jonagoldman 23
  • Cross-request caching does not work as expected

    Cross-request caching does not work as expected

    Bouncer version: 1.0.0-rc3 Laravel version: 5.7.3

    Hi @JosephSilber ,

    I recently upgraded to Laravel 5.7 and I noticed that the cache does not work anymore. What I mean is that even with the cache enabled, Bouncer is still making queries to resolve the abilities at the Gate.

    Here are some thoughts after my investigation:

    1. Bouncer's service provider is registering and booting. The boot method calls registerAtGate, which resolves the Gate singleton and a Sliber\Bouncer\Clipboard instance, and then the Clipboard registers its before/after callbacks in the Gate, via the Clipboard::registerAt() method.

    2. In my code, I'm calling Bouncer::cache() in the AuthServiceProvider's bootmethod. By calling the Bouncer facade, Laravel resolves the Bouncer singleton for the first time, which is created through the Silber\Bouncer\Factory. The factory uses a new Sliber\Bouncer\Clipboard instance, as well as the Gate singleton.

    3. When the create method of the factory is called, this Clipboard instance registers its before/after callbacks in the Gate, via the Clipboard::registerAt() method. Now the Gate has two before callbacks, and two after callbacks.

    4. Then, the cache method is called, which is instantiating a Silber\Bouncer\CachedClipboard instance with the before slot from the previous clipboard. The CachedClipboard instance is then bound into the container, and set to the Bouncer singleton.

    So at this point the Bouncer instance has a CachedClipboard instance, but the Gate has two sets of callbacks which are bound on two Silber\Bouncer\Clipboard instances which are not being used by the Bouncer instance anymore. As a result, for each ability check the queries are performed twice, and the cache is never used.

    However when I use the isA(), isAn() methods on the user, the CachedClipboard is being used.

    I believe this issue is related to #345.

    bug 
    opened by sebdesign 18
  • Clipboard closure arguments in v0.1.3

    Clipboard closure arguments in v0.1.3

    @can('user-edit', $user)
        <a href="#" class="edit">Edit user</a>
    @endcan
    

    Argument 3 passed to Silber\Bouncer\Clipboard::Silber\Bouncer{closure}() must be of the type array, object given.

    Started to throw this exception since v0.1.3.

    bug 
    opened by antonkomarev 17
  • Multi Tenancy: take it for a spin

    Multi Tenancy: take it for a spin

    After many requests to add multi-tenancy support to Bouncer, I've been gathering some use-cases from people. As I see it, there are 2 types of setups that people have:

    1. Everything scoped. In this setup, everything is scoped to the current tenant, so that each tenant can set up their own roles and abilities.

    2. Scoped relationships. In this setup, the roles and abilities are fixed. All tenants have the same set of roles and abilities. Only the relationships are scoped, meaning that the assignment of a given role/ability to a user is restricted to a tenant, but the actual role & ability models are the same across the board.


    I've been working on implementing these 2 into Bouncer, using the following syntax:

    1. Everything scoped:

      Bouncer::scope()->to($tenantId);
      

      This will scope all models and all relationships to the given tenant.

    2. Scoped relationships:

      Bouncer::scope()->to($tenantId)->onlyRelations();
      

      This will only scope the relationships to the given tenant, but will keep the actual roles/abilities global.


    Support for multi tenancy is now in the master branch, which you can install in your project with this command:

    $ composer require silber/bouncer:dev-master
    

    After installation, you should set up a global middleware that calls one of those 2 methods, with the current tenant's ID.

    Please report back here with any bugs/issues/ideas/improvements.

    Thanks loads :pray:

    help wanted discussion announcement 
    opened by JosephSilber 16
  • Batch allow/disallow

    Batch allow/disallow

    Is there a way to mass allow or disallow some ability to an array of users? like

    Bouncer::allow($users)->to('ability', $object)
    Bouncer:: disallow($users)->to('ability', $object)
    
    opened by robertoperez-digital 1
  • Check if a role owns a model

    Check if a role owns a model

    To implement a settings UI it is necessary to find out if a role can own or manage a model. So I'd suggest a method like

    $role->canOwn(Task::class);
    
    $role->canManage(Task::class);
    
    support 
    opened by obrunsmann 1
  • Teams Permissions

    Teams Permissions

    Both spatie/laravel-permissions and LaraTrust have their own optional implementation of Teams Permissions. Are there any plans or ongoing works to supplement the need for Teams-based abilities?

    opened by luchmewep 2
  • Checking if user owns a model via keys of a pivot table

    Checking if user owns a model via keys of a pivot table

    Good day @JosephSilber, I got started with bouncer fairly recently and its a really great tool. It has really been helpful.

    So lately I've been trying to implement something, I've got this user table and a business table, a single owner can own one or multiple businesses and a single business can be owned by one or multiple owners, so basically the relationship between these two tables is "many to many relationship". So I've got this pivot table that maps businesses to users and in my Business.php and User.php Models I've set up the relationships with the BelongsToMany() method.

    Now, my question is, is their anyway for Bouncer to check if a Business model is owned by a given User, given that their is no user_id column on the business table, this would be impossible with something like Bouncer::can($ability, $business), so Is their a way I could do this, by let's say passing the pivot table and the corresponding keys to a method?

    I've tried using the Bouncer::ownedVia() method and passed in a callback function that checks if a user owns a business and so on, but I honestly want to know if their is a better way for this.

    opened by ches-001 1
  • bouncer ability create command added

    bouncer ability create command added

    while using Bouncer package for a multi-tenant role and permission control on a project, I faced that bouncer may need the ability to create and assign to a role commands to help with role permission setting. So I made a laravel artisan command that can create an ability and assign to a role Feature 1: create just an ability Feature 2: create ability and assign to multiple role(s) using the --role option Feature 3: Create an ability using multiple scopes using the --scope option

    opened by hafijul233 0
Releases(v1.0.0)
  • v1.0.0(Feb 22, 2022)

  • v1.0.0-rc.13(Feb 21, 2022)

    This is mainly a housecleaning release, with two breaking changes:

    1. Levels were removed. They were too much of a burden on maintenance, and has actively hindered development on new features. With Bouncer's many features, such as allowing & forbidding permissions, broad model abilities, per-model abilities, and the powerful ownership model, the need for levels is now truly niche, and no longer worth the maintenance cost.

      They were purposefully never documented, and I've been saying in the issues for years that they will eventually be removed.

    2. Bouncer's checks, by default, now run after your policies and gate definitions. This gives your code precedence for all auth checks. If your code explicitly passes/fails a check, that will now be honored and Bouncer won't even run for that.

      This is actually what people expect intuitively, and has been a setting in Bouncer for a long time now. This release just switches the default.

      If you prefer the old order of things, you can tell Bouncer to run before your code:

      Bouncer::runBeforePolicies();
      

      This setting used to be called Bouncer::runAfterPolicies(), as it toggled the inverse. If you have that your code, you can now safely remove it.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.12(Feb 8, 2022)

  • v1.0.0-rc.11(Nov 25, 2021)

    New

    • PHP 8.1 :rocket:

    Fixes

    • Fix for Postgres databases https://github.com/JosephSilber/bouncer/pull/568
    • Fix for Oracle databases https://github.com/JosephSilber/bouncer/pull/564
    • Fix the bouncer:clean command for morphed class names https://github.com/JosephSilber/bouncer/pull/560
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.10(Dec 8, 2020)

  • v1.0.0-rc.9(Sep 8, 2020)

    New

    • Support for Laravel 8.0 :tada:

    Fixes

    • Syncing user roles should not affect other authorities with the same ID. #529
    • Fix Bouncer::canAny() method. #526
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.8(Mar 31, 2020)

    • Adds a Bouncer::canAny() pass-through method to the Gate (#510).
    • When soft deleting a role/ability, pivot records are no longer deleted (#491).
    • Fix for when Laravel is set to use table prefixes (#508).
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.7(Mar 4, 2020)

    New

    • Support for Laravel 7.0 :tada:

    Breaking Changes

    • This release finally drops support for older versions of Laravel. Starting with this release, we support Laravel 6.0 and up.

      If you're still using an older version of Laravel, you can continue using RC6. There are no known bugs.

    • Since we no longer support older versions of Laravel, we no longer need the getClipboardInstance() method (#505).

      There's an extremely low likelihood of this affecting you. This will only affect you if you were calling that method directly (why?), or if you ever had to manually resolve conflicts, such as:

      use Authorizable, HasAbilities {
          Authorizable::getClipboardInstance insteadof HasAbilities;
      }
      

      If you have code like that anywhere, you should remove the explicit insteadof precedence resolution, since that method no longer exists.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.6(Sep 4, 2019)

    New

    • Support for Laravel 6.0 :tada:

    Fixes

    • Syncing abilities should not affect other entities with the same ID. #409
    • Don't hit the DB unnecessarily when checking roles by ID. #418

    Breaking Changes

    NOTE: this will only affect you if you've enabled cross-request caching.

    Bouncer's internal representation of cached roles has changed. If you're using cross-request caching, you should clear the cache after upgrading Bouncer:

    Bouncer::refresh();
    
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.5(Feb 27, 2019)

    New

    • Support for Laravel 5.8 :tada:

    • Allow granting abilities to everyone. https://github.com/JosephSilber/bouncer/commit/0d6e7b65f46180f89fc228e799dc1e684d6dddb9

      Bouncer::allowEveryone()->to('view', Post::class);
      

      See #319 for why this is useful. In short:

      When there are certain abilities you'd like everyone to have, you previously had to add that ability to everyone separately (either directly or through a role). This works, but it means that:

      1. You're bloating up your database.
      2. It's another thing that has to run whenever a new user signs up.
      3. Whenever you change these, you have to remember to also add these permissions for all existing users.

      Using Bouncer to grant these abilities to everyone means there's one less thing to manage, and you can keep your DB much leaner.

      Note: this requires a small change to the DB schema, making two columns nullable. See below in the section on migrations.

    • Allow running a callback with a temporary scope. https://github.com/JosephSilber/bouncer/commit/ebba511b741d51f5c2ea40f37411de76af17bbac

      When applying a global scope in a multi-tenant system, it may sometimes be beneficial to be able to run a single query without the scope, or with a different scope. #368

      Both of these are now possible:

      Bouncer::scope()->onceTo($tenantId, function () {
          // All queries within this closure will run with this
          // temporary $tenantId. After that, every other
          // query will use the global tenant ID.
      });
      
      Bouncer::scope()->removeOnce(function () {
          // All queries within this closure will run without any scope.
      });
      

      It's also now possible to get the current tenant's ID:

      $tenantId = Bouncer::scope()->get();
      

    Fixes

    • Properly scope disallow/forbid when called on a role https://github.com/JosephSilber/bouncer/commit/f14327515690b93d5db27dc6ef036e07f0c9861b

    Breaking Changes

    NOTE: this will only affect you if you're using custom models or custom table names.

    If you're using custom models or custom table names, they will now automatically be registered with the morph map. See #306 and #378 for why this change was necessary.

    What this means is that if you weren't registering your models with the morph map yourself, you'll now have to migrate your DB so that it no longer stores the raw model class names.

    So, if you've registered a custom role with Bouncer:

    Bouncer::useRoleModel(MyRole::class);
    

    ...and have not previously registered it with the morph map yourself, you should migrate your DB to use the morph map's entity type:

    DB::table('permissions')
        ->where(['entity_type' => MyRole::class])
        ->update(['entity_type' => (new MyRole)->getMorphClass()]);
        
    

    Schema Changes

    There are no necessary schema changes in this release. However, in order to use the new allowEveryone() feature, you'll need to change the following 2 columns in the permissions table to be nullable:

    • entity_id
    • entity_type

    A note on version compatibility

    Since this release contains an important bugfix to the multi-tenancy scope system in Bouncer, it still supports Laravel all the way back to 5.1 and PHP all the way back to 5.5.

    If there are no show stopping bugs found with this RC, the next RC will no longer support these older versions of Laravel & PHP. If you're still stuck on these older versions, you can continue using Bouncer with this release until you're ready to upgrade.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.4(Oct 22, 2018)

    This is a small bugfix release, and as such has the same PHP/Laravel versions support as RC3.

    Fixes

    • Fix Bouncer's caching, which actually had an adverse effect on performance. #347

    • Register the clipboard with the container, even when used outside of Laravel. #354

    • Always resolve the clipboard class through its contract. #349

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.3(Sep 9, 2018)

  • v1.0.0-rc.2(Sep 4, 2018)

    New

    • Support for Laravel 5.7

    • Running after policies. https://github.com/JosephSilber/bouncer/commit/1103309a178c6e8db07a2700e67c39164a64da3a

      Note: this only works in Laravel 5.7+

      You can now configure Bouncer to run its checks after your policy checks:

      Bouncer::runAfterPolicies();
      

      This will ensure that even if Bouncer allows a given action, you can still reject it from a policy. For example:

      Bouncer::allow($user)->to('delete', Listing::class);
      
      class ListingPolicy
      {
          public function delete(User $user, Listing $listing)
          {
              if ($listing->isLocked()) {
                  return false;
              }
          }
      }
      

      By default, Bouncer runs before your policies. Since deleting a listing is allowed by Bouncer, the policy is never called, and deleting locked listings is alowed for anyone who can delete listings.

      By configuring Bouncer to run after your policies, you get a chance to reject a given check before it even hits Bouncer, so that no one can delete a locked listing. Since the policy does not return anything for unlocked listings, the gate will then check with Bouncer if the action is allowed.

      See #264 for the history and full discussion around this feature.

      Note: this will be the default (and possibly only) mode in a future version of Bouncer that only supports Laravel 5.7+. If you're on 5.7, you're strongly encouraged to enable this configuration.

    • Optimized checking against many abilities. #276

      By default, Bouncer fetches all of a user's abilities from the DB when running an authorization check. This only happens once, with subsequent checks running against the cached abilities.

      This is perfect for apps that only use a handful of abilities per user. But for apps where a single user may have 100s or even 1000s of abilities, pulling down all of that just to run a single check is not ideal.

      You can configure Bouncer not to cache abilities:

      Bouncer::dontCache();
      

      Now, when running in this mode, Bouncer will not even pull down the abilities; all checks will be done directly in the database.

    Fixes

    • Removing an ability from a user also removed it from a role with the same ID #278

    • Syncing roles/abilities did not account for Bouncer's scope #265

    Migration

    There are two in-progress features that did not make the cut for this release. Part of the groundwork did make it in, and that includes the additions to the schema. As part of this upgrade, 3 new columns were added.

    • If your app is not in production yet, and you don't have any real data yet:

      1. Delete your existing Bouncer migration file.

      2. Generate a new one with:

        php artisan vendor:publish --tag="bouncer.migrations"
        
      3. Rerun all your migrations:

        php artisan migrate:fresh
        
    • If your app is already in production with real data:

      1. Create a new migration file by running:

        php artisan make:migration upgrade_bouncer_to_rc2
        
      2. Open the file, and replace the contents of the up method with this:

        Schema::table('abilities', function (Blueprint $table) {
            $table->json('options')->nullable();
        });
        
        Schema::table('assigned_roles', function (Blueprint $table) {
            $table->integer('restricted_to_id')->unsigned()->nullable();
            $table->string('restricted_to_type')->nullable();
        });
        
      3. Make a backup of your database before running the migration.

      4. Run the migration:

        php artisan migrate
        

    A note on version compatibility

    This version was supposed to drop support for older versions of PHP/Laravel, as outlined in the release notes for RC1. However, since this release includes two important bugfixes, it still supports Laravel all the way back to 5.1 and PHP all the way back to 5.5.

    If there are no show stopping bugs found with this RC, the next RC will no longer support these older versions of Laravel & PHP. If you're still stuck on these older versions, you can continue using Bouncer with this release until you're ready to upgrade.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.1(Feb 1, 2018)

    This release is basically the same as beta 5, with a few minor bug fixes.

    I did manage to sneak in one handy little feature:

    • Automatic titles for roles and abilities.

      If you don't specify a title for a role or ability, Bouncer will intelligently add a meaningful title for you :+1:


    A note on version compatibility

    Bouncer currently supports Laravel all the way back to 5.1 and PHP all the way back to 5.5. This has understandably put a tremendous burden on maintaining Bouncer.

    When Bouncer 1.0 is released, it will only support Laravel 5.5+ and PHP 7.1+.

    If there are no show stopping bugs found with this RC, the next RC will no longer support these older versions of Laravel & PHP. If you're still stuck on these older versions, you can continue using Bouncer with this release until you're ready to upgrade.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-beta.5(Dec 31, 2017)

    New

    • Multi-tenancy support. Bouncer is now fully ready for multi-tenant1 apps 🎉

      To use Bouncer in a multi-tenant app, start by publishing the scope middleware into your app:

      php artisan vendor:publish --tag="bouncer.middleware"
      

      The middleware will now be published to app/Http/Middleware/ScopeBouncer.php. This middleware is where you tell Bouncer which tenant to use for the current request. For example, assuming your users all have an account_id attribute, this is what your middleware would look like:

      public function handle($request, Closure $next)
      {
          $tenantId = $request->user()->account_id;
      
          Bouncer::scope()->to($tenantId);
      
          return $next($request);
      }
      

      You are of course free to modify this middleware to fit your app's needs, such as pulling the tenant information from a subdomain et al.

      Now with the middleware in place, be sure to register it in your HTTP Kernel:

      protected $middlewareGroups = [
          'web' => [
              // Keep the existing middleware here, and add this:
              \App\Http\Middleware\ScopeBouncer::class,
      

      All of Bouncer's queries will now be scoped to the given tenant.

      Depending on your app's setup, you may not actually want all of the queries to be scoped to the current tenants. For example, you may have a fixed set of roles/abilities, and only allow your users to control which users are assigned which roles, and which roles have which abilities. You can tell Bouncer's scope to only scope the relationships between Bouncer's models, but not the models themselves:

      Bouncer::scope()->to($tenantId)->onlyRelations();
      

      Furthermore, your app might not even allow its users to control which abilities a given role has. In that case, tell Bouncer's scope to exclude role abilities from the scope, so that those relationships stay global across all tenants:

      Bouncer::scope()->to($tenantId)->onlyRelations()->dontScopeRoleAbilities();
      

      If your needs are even more specialized than what's outlined above, you can create your own Scope with whatever custom logic you need:

      use Silber\Bouncer\Contracts\Scope;
      
      class MyScope implements Scope
      {
          // Whatever custom logic your app needs
      }
      
      Bouncer::scope(new MyScope);
      

      At various points in its execution, Bouncer will call some of the methods on the Scope interface. You are free to handle that according to your specific needs :+1:

    • Ownership permissions may now be restricted to a given ability:

      // Only allow editors to own posts to edit them, not anything else
      Bouncer::allow('editor')->toOwn(Post::class)->to('edit');
      
      $editor->can('edit', $theirPost); // true
      $editor->can('delete', $theirPost); // false
      
    • New bouncer:clean command, to delete unused abilities. This will delete 2 types of unused abilities:

      • Unassigned abilities - abilities that are not assigned to anyone. For example:

        Bouncer::allow($user)->to('edit', $post);
        
        Bouncer::disallow($user)->to('edit', $post);
        

        At this point, the edit post ability is not assigned to anyone, so it'll get deleted.

        Note: depending on the context of your app, you may not want to delete these. If you let your users manage abilities in your app's UI, you probably don't want to delete unassigned abilities. See below.

      • Orphaned abilities - model abilities whose models have been deleted:

        Bouncer::allow($user)->to('edit', $post);
        
        $post->delete();
        

        Since the post no longer exists, the ability is no longer of any use, so it'll get deleted.

      If you only want to delete one type of unused ability, run it with one of the following flags:

      php artisan bouncer:clean --unassigned
      php artisan bouncer:clean --orphaned
      

      If you don't pass it any of the flags, it will delete both types of unused abilities.

      To run this command automatically, add it to your console kernel's schedule:

      $schedule->command('bouncer:clean')->weekly();
      

    Breaking Changes

    • Schema changes. To add multi-tenancy support, all 4 Bouncer tables got a new scope column. If you're upgrading from an earlier version of Bouncer, you should add this column to all of Bouncer's tables.

      If your app hasn't made it to production yet, the easiest way to upgrade is to just delete the published bouncer migration file, republish the new migration file, then run php artisan migrate:fresh to rerun all migrations from the start.

      If you have a production app that you wish to upgrade, you should add these columns to your tables using the following SQL commands:

      alter table `abilities` add `scope` int null after `only_owned`
      alter table `roles` add `scope` int null after `level`
      alter table `assigned_roles` add `scope` int null after `entity_type`
      alter table `permissions` add `scope` int null after `forbidden`
      

      If you plan on using multi-tenancy in your app, you'll also need to remove the unique indexes on these tables, since role/ability names may be repeated among different tenants.

      For a complete diff of Bouncer's migration file between beta 4 and beta 5, see here (click on the Files Changed tab and scroll down to the migrations/create_bouncer_tables.php file).

    • Removed Bouncer's seeders. The docs for them were removed over a year ago, because this feature was confusing people. Now that Laravel allows you to specify a specific seeder class to run individually, you can just use a regular Laravel seeder to seed Bouncer roles & abilities. Start by creating a seeder file for Bouncer:

      php artisan make:seeder BouncerSeeder
      

      To run these seeds, pass it to the class option of the db:seed class:

      php artisan db:seed --class=BouncerSeeder
      

    2 quick notes:

    • I plan for this to be the last beta of Bouncer. If everything works out as planned, the first RC should be released sometime in January.

    • Bouncer currently supports Laravel all the way back to 5.1 and PHP all the way back to 5.5. This has understandably put a tremendous burden on maintaining Bouncer.

      I plan for Bouncer 1.0 to only support Laravel 5.5+ and PHP 7.1+. The first RC will still support everything Bouncer currently supports, so that people may continue using Bouncer till they get around to upgrading.


    1 Bouncer's scopes are not restricted to multi-tenancy. For example, a common request for a long time has been to have the public portion of the site have different roles/abilities than the dashboard portion. You can now achieve this using Bouncer's new scope:

    1. Create a ScopeBouncer middleware that takes an identifier and sets it as the current scope:

      
      class ScopeBouncer
      {
          public function handle($request, Closure $next, $identifier)
          {
              Bouncer::scope()->to($identifier);
      
              return $next($request);
          }
      }
      
    2. Register this new middleware as a route middleware in your HTTP Kernel class:

      protected $routeMiddleware = [
          // Keep the other route middleware, and add this:
          'scope-bouncer' => \App\Http\Middleware\ScopeBouncer::class,
      ];
      
    3. In your routes service provider, apply this middleware with a different identifier for the public routes and the dashboard routes, respectively:

      Route::middleware(['web', 'scope-bouncer:1'])
           ->namespace($this->namespace)
           ->group(base_path('routes/public.php'));
      
      Route::middleware(['web', 'scope-bouncer:2'])
           ->namespace($this->namespace)
           ->group(base_path('routes/dashboard.php'));
      
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-beta.4(Sep 1, 2017)

    New

    • Support Laravel 5.5 🎉

    • Sync roles (retract any roles not in the list, and assign all roles in the provided list):

      Bouncer::sync($user)->roles(['admin', 'reviewer']);
      
      // You can also pass in role IDs:
      Bouncer::sync($user)->roles([1, 2]);
      
      // Or role models, if you already have them:
      Bouncer::sync($user)->roles([$adminModel, $reviewerModel]);
      
    • Sync abilities (disallow any abilities not in the list, and allow all abilities in the provided list):

      Bouncer::sync($user)->abilities(['access-dashboard', 'ban-users']);
      
      // You can also pass in ability IDs:
      Bouncer::sync($user)->abilities([1, 2]);
      
      // Or ability models, if you already have them:
      Bouncer::sync($user)->abilities($abilityModels);
      
      // Or a map of abilities:
      Bouncer::sync($user)->abilities([
          'create' => User::class,
          'delete' => Post::class,
      ]);
      
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-beta.3(Aug 3, 2017)

    New

    • Support Laravel 5.4.31, which broke Bouncer.

    • Greatly enhanced granting multiple roles/abilities at once:

      // Assign multiple roles:
      Bouncer::assign(['admin', 'editor'])->to($user);
      
      // Allow multiple abilities:
      Bouncer::allow($user)->to(['access-dashboard', 'ban-users']);
      
      // ...also works with model abilities:
      Bouncer::allow($user)->to(['edit', 'delete'], Post::class);
      Bouncer::allow($user)->to(['edit', 'delete'], $post);
      
      // ...and even with multiple models:
      Bouncer::allow($user)->to('delete', [Post::class, Category::class]);
      Bouncer::allow($user)->to(['edit', 'delete'], [Post::class, Category::class]);
      
      // ...and can also take an associative array:
      Bouncer::allow($user)->to([
          'create' => Post::class,
          'view'   => User::class,
          'edit'   => $user,
      ]);
      
    • Added a whereIsNot scope to the hasRoles trait.

    Breaking Changes

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-beta.2(Jan 25, 2017)

    New

    • Support for Laravel 5.4.

    • Added the toEver method to forbid a given ability on all models:

      Bouncer::forbid('editor')->toEver('delete');
      
    • Fluent API for the HasAbilities trait:

      $user->allow()->everything();
      $user->allow()->toAlways('view');
      $user->allow()->toManage(Post::class);
      $user->allow()->toOwn(Profile::class);
      $user->allow()->toOwnEverything();
      

      Also works for disallow(), forbid() and unforbid().

    • Support table prefixes.

      Bouncer will now honor any table prefixes that you've set up in Laravel's config.

    Breaking Changes

    • The namespace for most traits has been changed. If you're using any trait other than HasRolesAndAbilities, you'll have to update its namespace import.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-beta.1(Dec 12, 2016)

    New

    • Forbid abilities. You can now forbid abilities for more granular control. https://github.com/JosephSilber/bouncer/commit/865227ba0d0de74661ffe2e3afc79e1926367c9e

      Bouncer::allow($user)->to('delete', Post::class);
      
      $post1 = Post::where('title', 'Regular post')->first();
      $post2 = Post::where('title', 'Very important post')->first();
      
      Bouncer::forbid($user)->to('delete', $post2);
      
      Bouncer::allows('delete', $post1); // true
      Bouncer::allows('delete', $post2); // false
      

      Here's another example:

      Bouncer::allow('superadmin')->everything();
      
      Bouncer::allow('admin')->everything();
      Bouncer::forbid('admin')->toManage(User::class);
      

      The admin role can now do everything, besides managing users.

    • Easily add a title to an ability. You can now pass additional attributes for the ability model being created. https://github.com/JosephSilber/bouncer/commit/7036b52dc293929ce836bab74194bcc574f37718

      Bouncer::allow($user)->to('edit', Post::class, [
          'title' => 'Edit all posts',
      ]);
      
    • Bouncer factory. It is now easier than ever to use bouncer outside of Laravel. https://github.com/JosephSilber/bouncer/commit/a1b7137423bbe2348848cd066ba6ec4faf8a720a

      $bouncer = Bouncer::create();
      
      // use $bouncer
      $bouncer->allow($user)->to('access-dashboard');
      

      You can also pass along a $user instance to be able to check abilities for that user:

      $bouncer = Bouncer::make()->withUser($user)->create();
      
      $bouncer->allows('access-dashboard');
      

    Breaking Changes

    • Removed exclusivity option. https://github.com/JosephSilber/bouncer/commit/280d7bbfdaca5f82172da7a42994bf4356bdbdc7
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-alpha.3(Sep 21, 2016)

    New

    • Support Laravel 5.4.31, which broke Bouncer.

    • Greatly enhanced granting multiple roles/abilities at once:

      // Assign multiple roles:
      Bouncer::assign(['admin', 'editor'])->to($user);
      
      // Allow multiple abilities:
      Bouncer::allow($user)->to(['access-dashboard', 'ban-users']);
      
      // Also works with model abilities:
      Bouncer::allow($user)->to(['edit', 'delete'], Post::class);
      Bouncer::allow($user)->to(['edit', 'delete'], $post);
      
      // And even with multiple models:
      Bouncer::allow($user)->to('delete', [Post::class, Category::class]);
      Bouncer::allow($user)->to(['edit', 'delete'], [Post::class, Category::class]);
      
      // Go crazy and pass it an associative array with whatever you want:
      Bouncer::allow($user)->to([
          'create' => Post::class,
          'view'   => User::class,
          'edit'   => $user,
      ]);
      
    • Added a whereIsNot scope to the hasRoles trait.

    Breaking Changes

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-alpha.2(Aug 26, 2016)

    New

    • Support for Laravel 5.3

    • Support for PHP 7.1

    • Added can, cannot and cant methods on roles, to check abilities directly on a role. https://github.com/JosephSilber/bouncer/commit/d1b11870117deb0579c92868752444d7ded48d30

    • New IsRole and IsAbility traits, so that custom models don't have to extend Bouncer's models. https://github.com/JosephSilber/bouncer/commit/151094d8be4e7950b427efb6b78b2113a4712064

    • New define method on the Bouncer class, to allow defining callbacks on the gate. https://github.com/JosephSilber/bouncer/commit/9f7d0c3e2a7ef16f8533469cd2a0470f1c8504f1

    • Roles and Abilities now have a title column, to optionally add a display name. https://github.com/JosephSilber/bouncer/commit/558f69321a3b6a9b1285732077846243a2a0c504

      Usage:

      // Creating a role with a title
      
      $role = Bouncer::role()->create([
          'name' => 'site-admin',
          'title' => 'Site Administrator',
      ]);
      
      Bouncer::allow($role)->to('delete', Post::class);
      
      // Creating an ability with a title
      
      $ability = Bouncer::ability()->create([
          'name' => 'ban-users',
          'title' => 'Ban users',
      ]);
      
      Bouncer::allow($user)->to($ability);
      
      // Creating an ability for a model with a title
      
      $ability = Bouncer::ability()->createForModel(Post::class, [
          'name' => 'edit',
          'title' => 'Edit posts',
      ]);
      
      Bouncer::allow($user)->to($ability);
      

    Breaking Changes

    • Removed the Authorize middleware and AuthorizesResources trait, since they'e been merged directly into Laravel https://github.com/JosephSilber/bouncer/commit/0c2ceaa6e8915699de8cc29e92d30d7a50a0efaf

    • Renamed $user->is($role) to $user->isAn($role) and $user->isA($role), for compatibility with Laravel 5.3. https://github.com/JosephSilber/bouncer/commit/145bf653015ce6ba1a9c42999805158ef7c4cc40

    • There are also some schema changes, to prepare for upcoming features. The goal is to not need any more schema changes from this point till the launch of 1.0 (we'll see).

      If you're upgrading from 0.x to alpha 2, follow the upgrade guide in the docs.

      If you're upgrading from alpha 1 to alpha 2, run this migration:

      Schema::table('abilities', function (Blueprint $table) {
          $table->string('name', 150)->change();
          $table->string('entity_type', 150)->nullable()->change();
      
          $table->string('title')->nullable()->after('name');
          $table->boolean('only_owned')->default(false)->after('entity_type');
      
          $table->dropUnique('abilities_name_entity_id_entity_type_unique');
          $table->unique(['name', 'entity_id', 'entity_type', 'only_owned']);
      });
      
      Schema::table('roles', function (Blueprint $table) {
          $table->string('title')->nullable()->after('name');
          $table->integer('level')->unsigned()->nullable()->after('name');
      });
      
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-alpha.1(Mar 27, 2016)

    New

    • Polymorphic structure: Bouncer now uses a new polymorphic database schema, so that you can attach roles and abilities to any model (see here how to upgrade your schema).

    • Wildcard abilities: you can now use wildcards to allow a wide spread of abilities:

      Bouncer::allow($user)->to('edit', '*');
      
      Bouncer::allows('edit', $post) == true;
      

      For more information on wildcards, see this discussion: #56

    • whereAssignedTo query scope: Role::whereAssignedTo($users) will return all roles assigned to those users.

    • whereCannot query scope: User::whereCannot('edit', Post::class) will return all users that can't edit posts.

    Pending

    The following is what's holding up the 1.0 stable release:

    • Wildcards in scopes: currently, not all query scopes handle wildcards properly. We need full wildcard support in all query scopes before 1.0 can be released.
    • Wildcard aliases: we need proper alias methods for most of the wildcard operations. See this discussion for more information.
    • Documentation: there are still a lot of things missing from the documentation. I want to properly flesh it out before the 1.0 release.
    Source code(tar.gz)
    Source code(zip)
  • v0.1.7(Feb 24, 2016)

    • Fix for Laravel 5.1, where the third argument to the gate's before callback may be missing.
    • Added --prefer-lowest to the Travis matrix to catch these incompatibilities in the future.
    Source code(tar.gz)
    Source code(zip)
  • v0.1.6(Feb 21, 2016)

  • v0.1.3(Feb 18, 2016)

    • You can now call Bouncer::exclusive() to have Bouncer deny any abilities that have not been granted via Bouncer. This will cause the Gate to skip any abilities that you have defined in your code.
    • You can now set your own custom table names:
    Bouncer::tables([
        'abilities' => 'my_abilities',
        'roles'     => 'my_roles',
    ]);
    
    Source code(tar.gz)
    Source code(zip)
  • v0.1.2(Jan 10, 2016)

  • v0.1.1(Dec 27, 2015)

  • v0.1.0(Dec 9, 2015)

    You can now scope user queries by whether they have a particular ability:

    $users = User::whereCan('view-dashboard')->get();
    $users = User::whereCan('delete', $post)->get();
    $users = User::whereCan('delete', Post::class)->get();
    

    You can also directly query roles that have specific abilities:

    $roles = Bouncer::role()->whereCan('view-dashboard')->get();
    $roles = Bouncer::role()->whereCan('delete', $post)->get();
    $roles = Bouncer::role()->whereCan('delete', Post::class)->get();
    

    Finally, you can query users on whether they have a specific role:

    $users = User::whereIs('admin')->get();
    $users = User::whereIs('admin', 'moderator')->get();
    $users = User::whereIsAll('reader', 'contributor')->get();
    
    Source code(tar.gz)
    Source code(zip)
  • v0.0.26(Dec 8, 2015)

    Added new notA and notAn methods, so you can now check for the absence of roles:

    Bouncer::is($user)->notA('moderator');
    Bouncer::is($user)->notAn('editor');
    

    Also added the isNot method to the user trait:

    $user->isNot('admin');
    

    Fixes:

    • Model names weren't being passed through properly in the user trait's allow and disallow methods. https://github.com/JosephSilber/bouncer/commit/dcfd741d50130495a1d75ade4403f16b38495496
    • Model abilities were sometimes being conflated. https://github.com/JosephSilber/bouncer/commit/fe480def4eb71acc0194c1ec91a38c749927c58f
    Source code(tar.gz)
    Source code(zip)
  • v0.0.25(Nov 29, 2015)

Owner
Joseph Silber
Jack of all trades, master of some.
Joseph Silber
Tech-Admin is Laravel + Bootstrap Admin Panel With User Management And Access Control based on Roles and Permissions.

Tech-Admin | Laravel 8 + Bootstrap 4 Tech-Admin is Admin Panel With Preset of Roles, Permissions, ACL, User Management, Profile Management. Features M

TechTool India 39 Dec 23, 2022
Handle roles and permissions in your Laravel application

Laratrust (Laravel Package) Version Compatibility Laravel Laratrust 8.x 6.x 7.x 6.x 6.x 6.x 5.6.x - 5.8.x 5.2 5.3.x - 5.5.x 5.1 5.0.x - 5.2.x 4.0. Ins

Santiago GarcĂ­a 2k Dec 30, 2022
Associate users with roles and permissions

Associate users with permissions and roles Sponsor If you want to quickly add authentication and authorization to Laravel projects, feel free to check

Spatie 10.9k Jan 3, 2023
Powerful package for handling roles and permissions in Laravel 5

Roles And Permissions For Laravel 5 Powerful package for handling roles and permissions in Laravel 5 (5.1 and also 5.0). Installation Composer Service

Roman BiÄŤan 1.2k Dec 17, 2022
Laravel Roles and Permissions

Introduction to Laravel Roles and Permission App Starter Kit Roles and sanctions are a paramount part of many web applications. In project, we have op

Brian Kiprono Koech 1 Nov 1, 2021
A Powerful package for handling roles and permissions in Laravel with GUI.

Laravel Roles A Powerful package for handling roles and permissions in Laravel. Supports Laravel 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 6.0, 7.0, and 8.0+. Tab

Jeremy Kenedy 827 Jan 1, 2023
This is a lightweight package that allows you assign roles and permissions to any Laravel model, or on a pivot table (many to many relationship).

Simple Laravel roles and permissions Introduction This package allows you to assign roles and permissions to any laravel model, or on a pivot table (m

null 52 Nov 10, 2022
Roles & Permissions for Laravel 8 / 7 / 6 / 5

Defender Defender is an Access Control List (ACL) Solution for Laravel 5 / 6 / 7 (single auth). (Not compatible with multi-auth) With security and usa

ArtesĂŁos 437 Dec 22, 2022
Laravel Users (Roles & Permissions, Devices, Password Hashing, Password History).

LARAVEL USERS Roles & Permissions Devices Password Hashing Password History Documentation You can find the detailed documentation here in Laravel User

Pharaonic 8 Dec 14, 2022
Proyecto para aprender a utilizar privilegios (roles y permisos) con CRUDBooster

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

Informática DP 3 May 9, 2022
UserFrosting is a secure, modern user management system written in PHP and built on top of the Slim Microframework, Twig templating engine, and Eloquent ORM.

UserFrosting is a secure, modern user management system written in PHP and built on top of the Slim Microframework, Twig templating engine, and Eloquent ORM.

UserFrosting 1.6k Jan 1, 2023
Braindead simple social login with Laravel and Eloquent.

Important: This package is not actively maintained. For bug fixes and new features, please fork. Eloquent OAuth Use the Laravel 4 wrapper for easy int

Adam Wathan 374 Dec 21, 2022
A PHP boilerplate based on Slim Framework, for start projects with Eloquent ORM, Validation, Auth (JWT), Repositories and Transformers ready

A PHP boilerplate based on Slim Framework, for start projects with Eloquent ORM, Validation, Auth (JWT), Repositories and Transformers ready.

Damiano Petrungaro 58 Aug 10, 2022
Set up Laravel Auth guards using Eloquent in seconds

Nightguard Set up Auth guards using Eloquent in seconds. Introduction Laravel guards provide a super convenient way of authorizing different areas of

Luke Downing 10 Mar 18, 2021
Open source social sign on PHP Library. HybridAuth goal is to act as an abstract api between your application and various social apis and identities providers such as Facebook, Twitter and Google.

Hybridauth 3.7.1 Hybridauth enables developers to easily build social applications and tools to engage websites visitors and customers on a social lev

hybridauth 3.3k Dec 23, 2022
PHP library to verify and validate Apple IdentityToken and authenticate a user with Apple ID.

Sign-in with Apple SDK Installation Recommended and easiest way to installing library is through Composer. composer require azimolabs/apple-sign-in-ph

Azimo Labs 79 Nov 8, 2022
PHP Client and Router Library for Autobahn and WAMP (Web Application Messaging Protocol) for Real-Time Application Messaging

Thruway is an open source client and router implementation of WAMP (Web Application Messaging Protocol), for PHP. Thruway uses an event-driven, non-blocking I/O model (reactphp), perfect for modern real-time applications.

Voryx 662 Jan 3, 2023
User registration and login form with validations and escapes for total security made with PHP.

Login and Sign Up with PHP User registration and login form with validations and escapes for total security made with PHP. Validations Required fields

Alexander PĂ©rez 2 Jan 26, 2022
A complete Login and Register page using a Mysql Database and php

Login With Mysql A complete Login and Register page using a Mysql Database ?? Built with ⚙️ ?? Description A login with Frontend, Backend and Database

Marc Medrano 1 Nov 5, 2021