Skip to content

v1.0.0-beta.5

Pre-release
Pre-release
Compare
Choose a tag to compare
@JosephSilber JosephSilber released this 31 Dec 14:52
· 242 commits to master since this release

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 πŸ‘

  • 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'));