Bouncer Versions Save

Laravel Eloquent roles and abilities.

v1.0.0-rc.6

4 years ago

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();

v1.0.0-rc.5

5 years ago

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

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.

v1.0.0-rc.4

5 years ago

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

v1.0.0-rc.3

5 years ago

This is a tiny release coming at the heels of v1.0.0-rc.2, which had a bug in the migration that tried to set a default value for a JSON column.

As the MySQL docs says:

The BLOB, TEXT, GEOMETRY, and JSON data types cannot be assigned a default value.

The default value has now been removed.

v1.0.0-rc.2

5 years ago

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.

v1.0.0-rc.1

6 years ago

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.

v1.0.0-beta.5

6 years ago

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

v1.0.0-beta.4

6 years ago

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,
    ]);
    

v1.0.0-beta.3

6 years ago

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

v1.0.0-beta.2

7 years ago

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.