Bouncer Role and Ability Management

repository·master·Indexed 25 days ago

https://github.com/josephsilber/bouncer

A framework-agnostic role and ability management system for applications using Eloquent models. Bouncer provides a fluent API to grant permissions to users, roles, or specific model instances and integrates seamlessly with Laravel's authorization gates. It supports fine-grained control, ownership logic, and multi-tenancy scoping.

Tokens
8.9K
Snippets
25
Records
56
Agent score
86%

What's inside Bouncer

  1. Implement Multi-tenancy with Bouncer Scope

    master

    Bouncer supports multi-tenant applications by scoping roles and abilities to a specific tenant.

    1. Publish the scope middleware:
    php artisan vendor:publish --tag="bouncer.middleware"
    1. Configure the middleware: Edit the published middleware (typically app/Http/Middleware/ScopeBouncer.php) to define how the tenant ID is retrieved from the request:
    public function handle($request, Closure $next)
    {
        $tenantId = $request->user()->account_id;
    
        Bouncer::scope()->to($tenantId);
    
        return $next($request);
    }
    1. Register the middleware in your app/Http/Kernel.php within the web middleware group:
    protected $middlewareGroups = [
        'web' => [
            // ...
            \App\Http\Middleware\ScopeBouncer::class,
        ]
    ];
  2. Check a user's roles

    master

    While it is recommended to check for abilities rather than roles, you can check if a user has specific roles using the Bouncer facade or the user model.

    Via Facade:

    • Bouncer::is($user)->a('role'); (or an('role') if starting with a vowel)
    • Bouncer::is($user)->notA('role'); (or notAn('role'))
    • Bouncer::is($user)->a('role1', 'role2'); (Checks if user has any of these)
    • Bouncer::is($user)->all('role1', 'role2'); (Checks if user has all of these)
    • Bouncer::is($user)->notAn('role1', 'role2'); (Checks if user has none of these)

    Via User Model:

    • $user->isAn('role');
    • $user->isA('role');
    • $user->isNotAn('role');
    • $user->isNotA('role');
    • $user->isAll('role1', 'role2');
    // Facade checks
    Bouncer::is($user)->a('moderator');
    Bouncer::is($user)->an('admin');
    Bouncer::is($user)->notA('moderator');
    Bouncer::is($user)->a('moderator', 'editor');
    Bouncer::is($user)->all('editor', 'moderator');
    Bouncer::is($user)->notAn('editor', 'moderator');
    
    // User model checks
    $user->isAn('admin');
    $user->isA('subscriber');
    $user->isNotAn('admin');
    $user->isNotA('subscriber');
    $user->isAll('editor', 'moderator');
  3. Create roles and abilities

    master

    Bouncer allows you to create roles and abilities on the fly. Simply pass the name of the role or ability to Bouncer::allow(), and Bouncer will create the underlying models if they do not exist.

    To add extra attributes (like a title), use the role() and ability() methods to manually create the models first.

    Note: These examples use the Bouncer facade. If you do not use facades, inject an instance of Silber\Bouncer\Bouncer into your class.

    // Simple creation
    Bouncer::allow('admin')->to('ban-users');
    
    // Creation with additional attributes
    $admin = Bouncer::role()->firstOrCreate([
        'name' => 'admin',
        'title' => 'Administrator',
    ]);
    
    $ban = Bouncer::ability()->firstOrCreate([
        'name' => 'ban-users',
        'title' => 'Ban users',
    ]);
    
    Bouncer::allow($admin)->to($ban);
  4. Restrict abilities to models and implement ownership

    master

    You can restrict an ability to a specific model type or a specific model instance. Additionally, you can use toOwn to allow users to manage their own models based on a user_id comparison.

    Restricting to Model/Instance:

    • Bouncer::allow($user)->to('edit', Post::class); (Model type)
    • Bouncer::allow($user)->to('edit', $post); (Specific instance)

    Ownership:

    • Bouncer::allow($user)->toOwn(Post::class); (Grants all abilities on owned models)
    • Bouncer::allow($user)->toOwn(Post::class)->to('view'); (Restricts ownership to specific abilities)
    • Bouncer::allow($user)->toOwnEverything(); (Grants ownership of all model types)
    • Bouncer::allow($user)->toOwnEverything()->to('view'); (Restricts ownership to specific abilities)

    Note: Ownership logic compares the model's user_id to the logged-in user's id.

    // Restrict to model type
    Bouncer::allow($user)->to('edit', Post::class);
    
    // Restrict to specific instance
    Bouncer::allow($user)->to('edit', $post);
    
    // Ownership
    Bouncer::allow($user)->toOwn(Post::class);
    Bouncer::allow($user)->toOwn(Post::class)->to('view');
    Bouncer::allow($user)->toOwnEverything();
    Bouncer::allow($user)->toOwnEverything()->to('view');
  5. Install Bouncer in a non-Laravel app

    master

    To use Bouncer in a non-Laravel environment using Eloquent Capsule:

    1. Install via Composer:
      composer require silber/bouncer
    2. Set up Eloquent Capsule:
      use Illuminate\Database\Capsule\Manager as Capsule;
      
      $capsule = new Capsule;
      $capsule->addConnection([/* connection config */]);
      $capsule->setAsGlobal();
    3. Run migrations using a tool like Vagabond or by executing the raw SQL found in the repository's migrations/sql/MySQL.sql file.
    4. Add the HasRolesAndAbilities trait to your User model:
      use Illuminate\Database\Eloquent\Model;
      use Silber\Bouncer\Database\HasRolesAndAbilities;
      
      class User extends Model
      {
          use HasRolesAndAbilities;
      }
    5. Initialize Bouncer:
      use Silber\Bouncer\Bouncer;
      
      // For a request with a specific user
      $bouncer = Bouncer::create($user);
      
      // Or with default settings
      $bouncer = Bouncer::create();
    6. Define the user model:
      $bouncer->useUserModel(User::class);
    composer require silber/bouncer
  6. Refresh the Bouncer cache

    master

    Bouncer caches queries for the current request. If cross-request caching is enabled, you can refresh the cache manually.

    • Bouncer::refresh();: Fully refreshes the entire cache. Uses cache tags if available.
    • Bouncer::refreshFor($user);: Refreshes the cache only for a specific user.

    Note on Multi-tenancy: When using multi-tenancy, refreshFor($user) only refreshes the cache within the current scope's context.

  7. Assign and retract roles from a user

    master

    You can assign roles to a user using the Bouncer facade or directly on the user model. To remove a role, use the retract method.

    Via Facade:

    • Bouncer::assign('role-name')->to($user);
    • Bouncer::retract('role-name')->from($user);

    Via User Model:

    • $user->assign('role-name');
    • $user->retract('role-name');
    // Assigning
    Bouncer::assign('admin')->to($user);
    $user->assign('admin');
    
    // Retracting
    Bouncer::retract('admin')->from($user);
    $user->retract('admin');
  8. Authorize users with Bouncer and Laravel Gate

    master

    Bouncer integrates directly with Laravel's Gate. You can use the Bouncer facade for convenience or use the standard $user->can() method on the user model.

    Via Bouncer Facade:

    • Bouncer::can($ability, $model = null);
    • Bouncer::canAny($abilities, $model = null);
    • Bouncer::cannot($ability, $model = null);
    • Bouncer::authorize($ability, $model = null);

    Via User Model:

    • $user->can($ability, $model = null);

    In Blade Templates: Use the standard Laravel @can directive:

    @can('update', $post)
        <!-- Content -->
    @endcan
    // Facade passthroughs
    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);
    
    // User model
    $user->can($ability, $model);
  9. Grant and remove abilities for a user

    master

    Abilities can be granted directly to a user without a role. You can also remove specific abilities using disallow.

    Granting:

    • Bouncer::allow($user)->to('ability-name');
    • $user->allow('ability-name');

    Removing:

    • Bouncer::disallow($user)->to('ability-name');
    • $user->disallow('ability-name');

    Important Notes:

    • If a user has a role that grants an ability, disallow on the user will not stop them from having that ability. You must instead disallow the ability from the role: Bouncer::disallow('role-name')->to('ability-name');.
    • To remove an ability for a specific model type, pass the class name: Bouncer::disallow($user)->to('delete', Post::class);.
    • To remove an ability for a specific model instance, pass the instance: Bouncer::disallow($user)->to('delete', $post); (Note: this will not remove a model-type level disallow).
  10. Query users by their roles

    master

    You can use Eloquent-style queries to find users based on their assigned roles.

    • User::whereIs('role-name')->get(); (Find users with a specific role)
    • User::whereIs('role1', 'role2')->get(); (Find users with ANY of the given roles)
    • User::whereIsAll('role1', 'role2')->get(); (Find users with ALL of the given roles)
  11. Retrieve roles and abilities for a user

    master

    You can retrieve a user's roles or abilities directly from the user model.

    • $user->getRoles();: Returns a collection of the user's roles.
    • $user->getAbilities();: Returns a collection of all allowed abilities (including those from roles).
    • $user->getForbiddenAbilities();: Returns a collection of abilities explicitly forbidden for the user.
    $roles = $user->getRoles();
    $abilities = $user->getAbilities();
    $forbiddenAbilities = $user->getForbiddenAbilities();
  12. Install Bouncer in a Laravel app

    master

    To integrate Bouncer into a Laravel application, follow these steps:

    1. Install the package via Composer:
      composer require silber/bouncer
    2. Add the HasRolesAndAbilities trait to your User model:
      use Silber\Bouncer\Database\HasRolesAndAbilities;
      
      class User extends Model
      {
          use HasRolesAndAbilities;
      }
    3. Publish the Bouncer migrations:
      php artisan vendor:publish --tag="bouncer.migrations"
    4. Run the migrations:
      php artisan migrate

    When using the Bouncer facade, ensure you import it at the top of your file:

    use Bouncer;
    composer require silber/bouncer