hosseinhezami/laravel-permission-manager

The most advanced enterprise-grade permission management system for Laravel. RBAC + ABAC + Role Hierarchy + Multi-Tenancy + Audit Logging.

Maintainers

Package info

github.com/hosseinhezami/laravel-permission-manager

Documentation

pkg:composer/hosseinhezami/laravel-permission-manager

Transparency log

Statistics

Installs: 3 407

Dependents: 0

Suggesters: 0

Stars: 59

Open Issues: 0

v2.0.1 2026-08-21 11:22 UTC

This package is auto-updated.

Last update: 2026-08-31 15:22:01 UTC


README

The most advanced, enterprise-grade permission management system for Laravel applications.

RBAC + ABAC + Role Hierarchy + Multi-Tenancy + Audit Logging + Condition Engine

Latest Version on Packagist Total Downloads Stars License PHP Version Laravel Tests Coverage

๐Ÿ“‹ Table of Contents

โœจ Why This Package?

Most Laravel permission packages only offer basic RBAC. This package goes far beyond that, combining RBAC + ABAC + Policy-based authorization + Role Hierarchy + Multi-Tenancy into a single, cohesive engine.

// Basic RBAC
$user->hasRole('admin');
$user->hasPermissionTo('users.edit');

// Direct permissions with expiry
$user->givePermissionTo('reports.export', expiresAt: now()->addDay());

// Role hierarchy - editor inherits all permissions from viewer
$editor->inheritFrom('viewer');

// Explicit deny overrides everything
$user->denyPermissionTo('users.delete');

// ABAC - contextual permissions
$user->canPermission('posts.update', $post);
// Only if $post->owner_id === $user->id AND $post->status === 'draft'

// Multi-tenancy - different roles per team
$user->assignRoleForTeam('admin', $engineeringTeam);
$user->assignRoleForTeam('editor', $marketingTeam);

// Audit trail - who did what, when
PermissionAudit::byActor($admin->id)->action('granted')->get();

// Explain API - debug why access was denied
PermissionManager::explain($user, 'orders.delete');

๐Ÿš€ Features

Core Authorization Engine

  • โœ… RBAC โ€” Role-Based Access Control with multiple roles per user
  • โœ… Direct Permissions โ€” Assign permissions directly to users without roles
  • โœ… Explicit Allow/Deny โ€” Fine-grained control with precedence rules
  • โœ… Role Hierarchy โ€” Multi-level inheritance with cycle detection
  • โœ… Wildcard Permissions โ€” Flexible pattern matching (users.*, *.edit, !users.delete)
  • โœ… Temporary Permissions โ€” Time-based access with automatic expiry
  • โœ… Permission Groups & Sets โ€” Organize permissions logically
  • โœ… Multi-Guard Support โ€” Isolated permissions per guard (web, api, admin)
  • โœ… Super Admin Bypass โ€” Configurable root access
  • โœ… Authorization Result โ€” Detailed reasons for allow/deny decisions

Enterprise Features

  • ๐Ÿข Teams / Multi-Tenancy โ€” Different roles per team/tenant
  • ๐Ÿง  ABAC (Attribute-Based Access Control) โ€” Context-aware permissions
  • ๐Ÿ“‹ Condition Engine โ€” JSON-based rules without eval()
  • ๐Ÿ“ Audit Logging โ€” Track all permission changes
  • ๐Ÿ” Authorization Audit Trail โ€” Log access attempts (optional)
  • ๐ŸŽฏ Policy Integration โ€” Native Laravel Gate::before() integration
  • ๐ŸŒ Route Sync โ€” Auto-generate permissions from routes
  • ๐Ÿ“ฆ Resource Generator โ€” Auto-create CRUD permissions for models

Developer Experience

  • ๐ŸŽจ Advanced Blade Directives โ€” @role, @permission, @hasAnyRole, @unlessPermission
  • ๐Ÿ›ก๏ธ Middleware DSL โ€” pm:permission:any:edit,view, role:admin|manager
  • ๐Ÿ’ป Rich Facade API โ€” PermissionManager::user(), ::role(), ::explain()
  • ๐Ÿ–ฅ๏ธ Powerful CLI โ€” permission:doctor, permission:why, permission:tree
  • ๐Ÿงช Testing Helpers โ€” actingAsRole(), assertHasPermission()
  • โšก Smart Caching โ€” Tagged cache with hierarchical invalidation
  • ๐Ÿ“ค Import/Export โ€” JSON, CSV, YAML support
  • ๐ŸŽฏ Permission Snapshot โ€” Debug user's complete authorization state

๐Ÿ“ฆ Installation

Step 1: Install via Composer

composer require hosseinhezami/laravel-permission-manager

Step 2: Publish Configuration & Migrations

php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="config"
php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="migrations"

Step 3: Run Migrations

php artisan migrate

This creates 14 tables:

  • roles, permissions, permission_groups, permission_sets, permission_set_items
  • role_permissions, role_inherits
  • user_roles, user_permissions
  • teams, team_user
  • permission_conditions, permission_audits, authorization_logs

Step 4: Add Trait to User Model

namespace App\Models;

use HosseinHezami\PermissionManager\Traits\PermissionTrait;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use PermissionTrait;
}

Step 5: (Optional) Quick Install Command

php artisan permission-manager:install --migrate

โšก Quick Start (5 minutes)

1. Create a Role

use HosseinHezami\PermissionManager\Models\Role;

$admin = Role::create([
    'name' => 'Administrator',
    'slug' => 'admin',
    'description' => 'Full system access',
]);

Or via CLI:

php artisan role:create admin "Administrator" "Full system access"

2. Create Permissions

use HosseinHezami\PermissionManager\Models\Permission;

Permission::create(['route' => 'users.view']);
Permission::create(['route' => 'users.edit']);
Permission::create(['route' => 'users.delete']);

Or sync all your routes automatically:

php artisan permission:sync-routes

3. Assign Permissions to Role

$admin->assignPermission(['users.view', 'users.edit']);
// Or with wildcard
$admin->assignPermission('users.*');

4. Assign Role to User

$user = User::find(1);
$user->assignRole('admin');

// Check permissions
$user->hasPermissionTo('users.edit'); // true
$user->hasPermissionTo('users.delete'); // false

5. Protect Your Routes

// Single permission
Route::get('/users', [UserController::class, 'index'])
    ->middleware('pm:permission:users.view');

// Any of these permissions
Route::get('/users', [UserController::class, 'index'])
    ->middleware('pm:permission:any:users.view,users.list');

// All of these permissions
Route::post('/users', [UserController::class, 'store'])
    ->middleware('pm:permission:all:users.view,users.create');

// Role check
Route::get('/admin', function () { /* ... */ })
    ->middleware('pm:role:admin|manager');

6. Use in Blade

@role('admin')
    <a href="{{ route('admin.dashboard') }}">Dashboard</a>
@endrole

@permission('users.edit')
    <button>Edit User</button>
@endpermission

@unlesspermission('users.delete')
    <span class="text-muted">Delete disabled</span>
@endunlesspermission

๐ŸŽฏ Core Concepts

Roles & Permissions

// Create role
$editor = Role::create(['name' => 'Editor', 'slug' => 'editor']);

// Create permission
$permission = Permission::create(['route' => 'posts.publish']);

// Assign permission to role
$editor->assignPermission('posts.publish');

// Assign role to user
$user->assignRole('editor');

// Check
$user->hasRole('editor'); // true
$user->hasPermissionTo('posts.publish'); // true

Direct Permissions

Sometimes you need to grant a permission to a specific user without creating a role:

// Give a one-time permission
$user->givePermissionTo('reports.export');

// Check direct permission
$user->hasDirectPermission('reports.export'); // true

// Revoke
$user->revokePermissionTo('reports.export');

Why it matters: Perfect for exceptional cases, temporary access, or overriding role-based permissions.

Allow / Deny System

The deny permission has higher priority than allow. This is powerful for exceptions:

// Role grants all posts.* permissions
$editor->assignPermission('posts.*');

// But deny deleting posts specifically
$user->denyPermissionTo('posts.delete');

// Result
$user->hasPermissionTo('posts.edit'); // true (from role)
$user->hasPermissionTo('posts.delete'); // false (explicit deny overrides)

Resolution Order (highest to lowest priority)

1. Super Admin bypass (if configured)
2. Explicit User DENY
3. Explicit Role DENY  
4. Explicit User ALLOW
5. Role ALLOW
6. Inherited Role permissions
7. Default: DENY

Role Hierarchy

Roles can inherit permissions from other roles, creating a hierarchy:

// Create hierarchy: super-admin > admin > editor > viewer
$superAdmin = Role::create(['name' => 'Super Admin', 'slug' => 'super-admin']);
$admin = Role::create(['name' => 'Admin', 'slug' => 'admin']);
$editor = Role::create(['name' => 'Editor', 'slug' => 'editor']);
$viewer = Role::create(['name' => 'Viewer', 'slug' => 'viewer']);

// Assign specific permissions
$viewer->assignPermission('posts.view');
$editor->assignPermission('posts.edit');
$admin->assignPermission('posts.delete');
$superAdmin->assignPermission('system.config');

// Build hierarchy
$admin->inheritFrom('editor');       // admin gets editor's permissions
$editor->inheritFrom('viewer');      // editor gets viewer's permissions
$superAdmin->inheritFrom('admin');   // super-admin gets admin's permissions

// A user with super-admin role now has ALL permissions
$user->assignRole('super-admin');

$user->hasPermissionTo('posts.view');     // โœ… from viewer (via editor โ†’ admin)
$user->hasPermissionTo('posts.edit');     // โœ… from editor (via admin)
$user->hasPermissionTo('posts.delete');   // โœ… from admin
$user->hasPermissionTo('system.config');  // โœ… from super-admin

Cycle Detection

The system automatically prevents circular inheritance:

$roleA->inheritFrom('roleB');
$roleB->inheritFrom('roleA'); // โŒ Throws CyclicRoleInheritanceException

Wildcard Permissions

Use wildcards for flexible permission matching:

// Match any route starting with 'users.'
$role->assignPermission('users.*');

// Now the user has:
$user->hasPermissionTo('users.view');    // true
$user->hasPermissionTo('users.edit');    // true
$user->hasPermissionTo('users.delete');  // true

// Match routes ending with '.edit'
$role->assignPermission('*.edit');
$user->hasPermissionTo('posts.edit');    // true
$user->hasPermissionTo('users.edit');    // true

// Global wildcard (DANGEROUS - use carefully!)
$role->assignPermission('*'); // All permissions

// Negation - deny specific pattern
$role->assignPermission('!users.delete');

Wildcard Examples

Pattern Matches Doesn't Match
users.* users.edit, users.delete posts.edit
*.edit users.edit, posts.edit users.view
users.{id}.edit users.42.edit users.edit
* everything nothing
!users.delete everything except users.delete users.delete

๐Ÿข Enterprise Features

Teams / Multi-Tenancy

Perfect for SaaS applications where users belong to multiple teams:

use HosseinHezami\PermissionManager\Models\Team;

// Create teams
$engineering = Team::createTeam(['name' => 'Engineering', 'slug' => 'engineering']);
$marketing = Team::createTeam(['name' => 'Marketing', 'slug' => 'marketing']);

// User joins both teams
$user->joinTeam($engineering);
$user->joinTeam($marketing);

// Assign different roles per team
$user->assignRoleForTeam('admin', $engineering);
$user->assignRoleForTeam('editor', $marketing);

// Check team-specific roles
$user->hasRoleForTeam('admin', $engineering);   // true
$user->hasRoleForTeam('admin', $marketing);     // false
$user->hasRoleForTeam('editor', $marketing);    // true

Using Team Context

Set the current team context via middleware:

// In routes/api.php
Route::middleware(['pm.team:header,X-Team-Id'])->group(function () {
    // All permissions in this group are scoped to the team from X-Team-Id header
});

Or programmatically:

use HosseinHezami\PermissionManager\Facades\PermissionManager;

PermissionManager::setTeam($currentTeam);
// All subsequent permission checks are scoped to this team

ABAC & Condition Engine

Attribute-Based Access Control lets you define dynamic conditions:

use HosseinHezami\PermissionManager\Models\PermissionCondition;

$permission = Permission::create(['route' => 'posts.update']);

// Only allow updates if user is the post owner AND status is draft
PermissionCondition::create([
    'permission_id' => $permission->id,
    'name' => 'owner-and-draft',
    'conditions' => [
        'all' => [
            ['field' => 'user.id', 'operator' => '=', 'value' => 'resource.owner_id'],
            ['field' => 'resource.status', 'operator' => '=', 'value' => 'draft'],
        ],
    ],
]);

// Usage
$user->givePermissionTo('posts.update');

$post = Post::find(1);
$user->canPermission('posts.update', $post);
// Returns true only if $user->id === $post->owner_id AND $post->status === 'draft'

Supported Operators

Operator Description Example
= == Equal user.id = resource.owner_id
!= !== Not equal user.role != "banned"
> >= Greater than user.level >= resource.required_level
< <= Less than user.failed_attempts < 3
in In array resource.status in ["draft", "pending"]
not_in Not in array user.id not_in [1, 2, 3]
contains String contains user.email contains "@company.com"
starts_with String starts with resource.path starts_with "admin/"
ends_with String ends with resource.mime ends_with "pdf"
exists Is not null resource.published_at exists
not_exists Is null resource.deleted_at not_exists

Logical Operators

// AND logic (all must match)
['all' => [
    ['field' => 'user.id', 'operator' => '=', 'value' => 'resource.owner_id'],
    ['field' => 'resource.status', 'operator' => '=', 'value' => 'draft'],
]]

// OR logic (any must match)
['any' => [
    ['field' => 'user.id', 'operator' => '=', 'value' => 'resource.owner_id'],
    ['field' => 'user.is_admin', 'operator' => '=', 'value' => true],
]]

// NOT logic
['not' => [
    'field' => 'resource.status',
    'operator' => '=',
    'value' => 'archived',
]]

// Complex nesting
['all' => [
    ['field' => 'resource.status', 'operator' => '!=', 'value' => 'archived'],
    ['any' => [
        ['field' => 'user.id', 'operator' => '=', 'value' => 'resource.owner_id'],
        ['field' => 'user.role', 'operator' => '=', 'value' => 'admin'],
    ]],
]]

Temporary Permissions

Grant time-limited access:

// Give permission that expires in 24 hours
$user->givePermissionTo(
    'reports.export',
    'allow',
    now()->addDay()
);

// Permission is valid until expiration
$user->hasPermissionTo('reports.export'); // true now
// After 24 hours: false (automatically)

// Prune expired permissions (run as scheduled job)
php artisan permission:prune --days=7

Audit Logging

Track who changed what and when:

use HosseinHezami\PermissionManager\Models\PermissionAudit;

// Automatically logged when you:
$user->assignRole('admin');
$role->assignPermission('users.delete');
$user->givePermissionTo('reports.export');

// Query audit log
$recentChanges = PermissionAudit::latest()
    ->limit(50)
    ->get();

// Filter by actor (who made the change)
$adminChanges = PermissionAudit::byActor($adminId)->get();

// Filter by action
$grants = PermissionAudit::action('granted')->get();

// Each audit includes:
// - actor_id: who made the change
// - action: what happened (granted, revoked, created, etc.)
// - subject_type: user, role, permission
// - subject_id: the target of the action
// - ip_address: where it came from
// - user_agent: browser info
// - metadata: extra data

Enable Audit Logging

// config/permission-manager.php
'audit' => [
    'enabled' => true,
    'log_mutations' => true,
],

Authorization Audit Trail

Optionally log every permission check (useful for security audits):

// config/permission-manager.php
'authorization_logging' => [
    'enabled' => true,
    'denied_only' => true,  // Only log failed attempts
    'sample_rate' => 0.1,   // Log 10% of checks (for high-traffic apps)
],

Multi-Guard

Isolate permissions by authentication guard:

// Create web-only role
Role::create([
    'name' => 'Web Admin',
    'slug' => 'web-admin',
    'guard_name' => 'web',
]);

// Create API-only role
Role::create([
    'name' => 'API Admin',
    'slug' => 'api-admin',
    'guard_name' => 'api',
]);

// Query by guard
$webRoles = Role::forGuard('web')->get();
$apiRoles = Role::forGuard('api')->get();

// Permissions are also guard-scoped
Permission::forGuard('api')->get();

๐Ÿ›ก๏ธ Middleware DSL

Advanced middleware with a powerful DSL:

Permission Checks

// Single permission
Route::get('/users', fn() => '...')->middleware('pm:permission:users.view');

// ANY of these (OR logic)
Route::get('/users', fn() => '...')
    ->middleware('pm:permission:any:users.view,users.list,users.index');

// ALL of these (AND logic)
Route::post('/users', fn() => '...')
    ->middleware('pm:permission:all:users.view,users.create');

// NOT this permission
Route::get('/public', fn() => '...')
    ->middleware('pm:permission:not:admin.panel');

Role Checks

// Single role
Route::get('/admin', fn() => '...')->middleware('pm:role:admin');

// ANY of these roles (OR logic)
Route::get('/staff', fn() => '...')
    ->middleware('pm:role:any:admin,manager,editor');

// ALL of these roles (AND logic) - user must have ALL
Route::get('/privileged', fn() => '...')
    ->middleware('pm:role:all:verified,premium');

Combined Middleware

// Multiple directives (AND logic between them)
Route::get('/reports', fn() => '...')
    ->middleware([
        'pm:role:admin',
        'pm:permission:reports.view',
    ]);

Dedicated Role Middleware

// Using pipe (|) for OR
Route::get('/staff', fn() => '...')
    ->middleware('role:admin|manager|editor');

// Using comma (,) for AND
Route::get('/premium', fn() => '...')
    ->middleware('role:verified,premium');

๐ŸŽจ Blade Directives

Role Directives

@role('admin')
    <span>Welcome, Administrator!</span>
@endrole

@hasanyrole(['admin', 'editor'])
    <span>You can edit content</span>
@endhasanyrole

@hasallroles(['verified', 'premium'])
    <span>Premium Verified User</span>
@endhasallroles

@unlessrole('banned')
    <span>You are not banned</span>
@endunlessrole

Permission Directives

@permission('users.edit')
    <button>Edit User</button>
@endpermission

@hasanypermission(['users.edit', 'users.delete'])
    <span>You can modify users</span>
@endhasanypermission

@hasallpermissions(['users.view', 'users.edit', 'users.delete'])
    <span>Full user management access</span>
@endhasallpermissions

@unlesspermission('users.delete')
    <span>Delete permission required</span>
@endunlesspermission

@cannotpermission('users.delete')
    <span>Cannot delete users</span>
@endcannotpermission

Contextual Permission Directive

@canpermission('posts.update', $post)
    <a href="{{ route('posts.edit', $post) }}">Edit Post</a>
@endcanpermission

Legacy Directives (Backward Compatible)

@hasRole('admin') ... @endHasRole
@hasPermission('users.edit') ... @endHasPermission

๐Ÿ” Laravel Gate & Policy Integration

Automatic Gate Integration

All permissions are automatically registered with Laravel's Gate:

// All these work out of the box:
$user->can('users.edit');
$user->cannot('users.delete');

// In Blade
@can('users.edit')
    <button>Edit</button>
@endcan

// In Controllers
Gate::authorize('users.edit');
Gate::allows('users.edit');
Gate::denies('users.delete');

Custom Abilities with Policies

Define custom abilities with complex logic:

use HosseinHezami\PermissionManager\Facades\PermissionManager;

PermissionManager::define('posts.update', function ($user, $post) {
    return $post->user_id === $user->id 
        || $user->hasPermissionTo('posts.edit.any');
});

// Usage
$user->canPermission('posts.update', $post);

Explain API

Get detailed explanations of authorization decisions:

$result = PermissionManager::explain($user, 'orders.delete');

// Returns:
[
    'allowed' => false,
    'ability' => 'orders.delete',
    'reason' => 'explicit_user_deny',
    'source' => 'direct_permission',
    'metadata' => [
        'matched_pattern' => 'orders.delete',
        'permission_id' => 42,
    ],
    'user' => [
        'id' => 1,
        'roles' => ['admin', 'editor'],
        'direct_permissions' => ['orders.view', '!orders.delete'],
    ],
]

๐Ÿ’ป Facade API

Managing Roles

use HosseinHezami\PermissionManager\Facades\PermissionManager;

// List all roles
$roles = PermissionManager::roles()->list();

// Create role
PermissionManager::roles()->create([
    'slug' => 'editor',
    'name' => 'Editor',
    'description' => 'Can edit content',
]);

// Role operations via proxy
PermissionManager::role('admin')
    ->assignPermission('users.*')
    ->assignPermissionSet('content-manager')
    ->inheritFrom('editor');

// Update, delete
PermissionManager::role('editor')->update(['name' => 'Content Editor']);
PermissionManager::role('editor')->delete();

Managing Permissions

// List all permissions
$permissions = PermissionManager::permissions()->list();

// Create
PermissionManager::permissions()->create('users.view');
PermissionManager::permissions()->create(['users.edit', 'users.delete']);

// Delete
PermissionManager::permissions()->delete('users.delete');

// Sync with routes
PermissionManager::permissions()->sync();

// Get grouped by category
$grouped = PermissionManager::permissions()->getAllGrouped();

// Permission sets
PermissionManager::permissions()->createSet([
    'name' => 'Content Manager',
    'slug' => 'content-manager',
]);

Managing Users

// User operations
PermissionManager::user($userId)
    ->assignRole('admin')
    ->assignRole(['editor', 'manager'])
    ->givePermissionTo('reports.export');

// Queries
$roles = PermissionManager::user($userId)->roles();
$permissions = PermissionManager::user($userId)->permissions();
$canEdit = PermissionManager::user($userId)->hasPermission('users.edit');

Team Context

// Set current team
PermissionManager::setTeam($team);
PermissionManager::setTeam(42); // By ID
PermissionManager::setTeam('engineering'); // By slug

// Clear team context
PermissionManager::clearTeam();

// Get current team context
$teamContext = PermissionManager::teamContext();

Cache Management

// Clear all permission cache
PermissionManager::cache()->flushAll();

// Clear specific user cache
PermissionManager::cache()->flushUser($userId);

// Clear specific role cache
PermissionManager::cache()->flushRole($roleId);

๐Ÿ–ฅ๏ธ Artisan Commands

Role Management

# List all roles
php artisan roles:list

# Create a role
php artisan role:create admin "Administrator" "Full access"

# Update a role
php artisan role:update admin --name="Super Admin"

# Delete a role
php artisan role:delete admin

Permission Management

# List permissions
php artisan permissions:list

# Create permissions
php artisan permission:create "users.edit"
php artisan permission:create "users.create,users.edit,users.delete"

# Delete permissions
php artisan permission:delete "users.delete"

# Sync routes with permissions
php artisan permission:sync-routes
php artisan permission:sync-routes --strategy=controller-action
php artisan permission:sync-routes --prefix=admin

# Generate CRUD permissions for a resource
php artisan permission:generate-resource users
php artisan permission:generate-resource Post --actions=view,create,update

Assignment Commands

# Assign permissions to role
php artisan role:assign-permission admin "users.*"
php artisan role:assign-permission admin "users.create,users.edit"

# Revoke permissions
php artisan role:revoke-permission admin "users.delete"

# Assign roles to user
php artisan user:assign-role 1 admin
php artisan user:assign-role 1 "admin,editor"

# Revoke roles from user
php artisan user:revoke-role 1 admin

Import / Export

# Export roles to JSON
php artisan role:export roles.json

# Import roles from JSON
php artisan role:import roles.json

Diagnostic & Debug Commands โญ

# ๐Ÿฉบ System health check
php artisan permission:doctor
# Detects:
# - Orphan permissions (not assigned to any role)
# - Orphan roles (not assigned to any user)
# - Cyclic role inheritance
# - Duplicate roles/permissions
# - Expired permissions

# ๐ŸŒณ View role hierarchy tree
php artisan permission:tree
php artisan permission:tree --role=admin

# โ“ Why was access denied?
php artisan permission:why 42 users.delete
# Output:
# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
# โ”‚  Permission Decision Explanation                โ”‚
# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
#   User:    42
#   Ability: users.delete
#   โœ— DENIED
#   Reason:  explicit_user_deny
#   Source:  direct_permission
#   Details:
#     - matched_pattern: users.delete
#     - permission_id: 15

# ๐Ÿ“‹ Explain permission check (JSON output)
php artisan permission:explain 42 users.edit --json

# โœ… Validate configuration
php artisan permission:validate

# โœ‚๏ธ Prune expired permissions
php artisan permission:prune --days=30
php artisan permission:prune --dry-run

# โšก Check single permission
php artisan permission:check 42 users.edit

Cache Commands

# Warm up cache (preload all permissions)
php artisan permission:cache:warm

# Clear cache
php artisan permission:cache:clear
php artisan permission:cache:clear --user=42
php artisan permission:cache:clear --role=5

๐Ÿงช Testing Helpers

The package provides powerful testing helpers for your application tests:

Setup

use HosseinHezami\PermissionManager\Testing\InteractsWithPermissions;
use HosseinHezami\PermissionManager\Testing\PermissionAssertions;

class PostControllerTest extends TestCase
{
    use InteractsWithPermissions;
    use PermissionAssertions;
    
    // ...
}

Creating Test Data

// Create user with roles
$admin = $this->createUserWithRoles(['admin', 'editor']);

// Create role with permissions
$role = $this->createRoleWithPermissions('editor', [
    'posts.view',
    'posts.edit',
    'posts.publish',
]);

// Act as a user with specific roles
$user = $this->actingAsRole(['admin']);

// Act as a user with specific permissions
$user = $this->actingAsWithPermissions(['posts.view', 'posts.edit']);

Testing Permissions

// Grant / deny permissions in tests
$this->grantPermission($user, 'posts.edit');
$this->denyPermission($user, 'posts.delete');

// Clear cache
$this->clearPermissionCache();

Assertions

public function test_admin_can_edit_posts()
{
    $admin = $this->createUserWithRoles(['admin']);
    
    $this->assertHasRole($admin, 'admin');
    $this->assertHasPermission($admin, 'posts.edit');
    $this->assertDoesNotHavePermission($admin, 'system.config');
    
    $this->assertHasAnyRole($admin, ['admin', 'editor']);
    $this->assertHasAllRoles($admin, ['admin']);
    
    $this->assertHasAnyPermission($admin, ['posts.edit', 'posts.view']);
    $this->assertHasAllPermissions($admin, ['posts.edit']);
    
    $this->assertIsNotSuperAdmin($admin);
    
    // Test contextual permissions
    $post = Post::factory()->create(['user_id' => $admin->id]);
    $this->assertCanPermission($admin, 'posts.update', $post);
}

Testing HTTP Routes

public function test_unauthorized_user_gets_403()
{
    $user = $this->createUser();
    
    $this->actingAs($user)
        ->get('/admin/users')
        ->assertStatus(403);
}

public function test_authorized_user_gets_access()
{
    $user = $this->actingAsRole(['admin']);
    
    $this->get('/admin/users')
        ->assertStatus(200);
}

โš™๏ธ Configuration

Full configuration file (config/permission-manager.php):

return [
    // Model classes
    'models' => [
        'role' => \HosseinHezami\PermissionManager\Models\Role::class,
        'permission' => \HosseinHezami\PermissionManager\Models\Permission::class,
        'user' => config('auth.providers.users.model'),
        'team' => \HosseinHezami\PermissionManager\Models\Team::class,
    ],

    // Table names
    'tables' => [
        'roles' => 'roles',
        'permissions' => 'permissions',
        'permission_groups' => 'permission_groups',
        'permission_sets' => 'permission_sets',
        'permission_set_items' => 'permission_set_items',
        'role_permissions' => 'role_permissions',
        'role_inherits' => 'role_inherits',
        'user_roles' => 'user_roles',
        'user_permissions' => 'user_permissions',
        'teams' => 'teams',
        'team_user' => 'team_user',
        'permission_conditions' => 'permission_conditions',
        'permission_audits' => 'permission_audits',
        'authorization_logs' => 'authorization_logs',
    ],

    // Cache settings
    'cache_duration' => 60, // minutes
    'cache' => [
        'enabled' => true,
        'prefix' => 'pm',
        'use_tags' => false, // Enable for Redis/Memcached
        'ttl' => 60,
    ],

    // Feature toggles
    'wildcards' => true,
    'log_denials' => false,
    'direct_permissions' => ['enabled' => true],
    'teams' => [
        'enabled' => true,
        'team_foreign_key' => 'team_id',
    ],

    // Super Admin
    'super_admin' => [
        'enabled' => true,
        'role_slug' => 'super-admin',
        'bypass_all' => true,
    ],

    // Audit
    'audit' => [
        'enabled' => true,
        'log_mutations' => true,
    ],

    // Authorization logging (use with caution in production)
    'authorization_logging' => [
        'enabled' => false,
        'denied_only' => true,
        'sample_rate' => 1.0,
    ],

    // ABAC
    'conditions' => ['enabled' => true],

    // Route sync
    'route_sync' => [
        'enabled' => true,
        'strategy' => 'route-name', // route-name | controller-action | resource-action
    ],
];

๐Ÿ“Š Comparison with Spatie

Feature Laravel Permission Manager Spatie Permission Winner
RBAC โœ… โœ… Tie
Direct Permissions โœ… โœ… Tie
Wildcard Permissions โœ… (advanced) โœ… LPM
Role Hierarchy โœ… Multi-level โŒ LPM
Explicit Deny โœ… โŒ LPM
Temporary Permissions โœ… โŒ LPM
Teams / Multi-Tenancy โœ… โœ… Tie
Multi-Guard โœ… Real isolation โœ… Tie
ABAC (Condition Engine) โœ… โŒ LPM
Audit Logging โœ… Built-in โŒ LPM
Authorization Audit Trail โœ… โŒ LPM
Route Sync โœ… โŒ LPM
Resource Generator โœ… โŒ LPM
Explain API โœ… โŒ LPM
CLI Doctor โœ… โŒ LPM
Permission Tree โœ… โŒ LPM
Gate Integration โœ… Native โœ… Tie
Middleware DSL โœ… Advanced โœ… Tie
Cache Tags โœ… โœ… Tie
Admin UI ๐Ÿ”„ Roadmap โŒ Tie

๐Ÿ—บ๏ธ Roadmap

โœ… v2.0 (Current - Released)

  • Core Authorization Engine
  • Direct Permissions + Allow/Deny
  • Role Hierarchy with Cycle Detection
  • Teams / Multi-Tenancy
  • ABAC Condition Engine
  • Audit Logging
  • Multi-Guard Support
  • Advanced Middleware DSL
  • Blade Directives
  • Gate/Policy Integration
  • Smart Cache Engine
  • CLI Diagnostic Tools
  • Testing Helpers
  • 141 passing tests

๐ŸŽฏ v2.1 (Planned)

  • Permission Aliases / Bundles
  • Time-based Permissions (business hours)
  • Sanctum / Passport Token Abilities integration
  • Field-Level Permissions
  • Advanced Export formats (Excel, YAML)
  • Laravel Octane support
  • Performance optimizations

๐Ÿ”ฎ v3.0 (Vision)

  • Admin Panel UI (Livewire + Filament)
  • Permission Graph visualization
  • Permission recommendation engine (AI)
  • OAuth scope mapping
  • WebSocket-based permission events
  • GraphQL directives

๐Ÿ—๏ธ Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   Your App                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ Blade    โ”‚   โ”‚Middlewareโ”‚  โ”‚ Controllers    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚       โ”‚              โ”‚                โ”‚             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
                       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚         AuthorizationManager (Core Engine)          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ Wildcard   โ”‚  โ”‚ Condition  โ”‚  โ”‚ Policy       โ”‚   โ”‚
โ”‚  โ”‚ Matcher    โ”‚  โ”‚ Evaluator  โ”‚  โ”‚ Resolver     โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ Role       โ”‚  โ”‚ Permission โ”‚  โ”‚ Context      โ”‚   โ”‚
โ”‚  โ”‚ Resolver   โ”‚  โ”‚ Resolver   โ”‚  โ”‚ Resolver     โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
                       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Data Layer                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ Roles  โ”‚ โ”‚Permissionsโ”‚ โ”‚  Teams  โ”‚ โ”‚  Audits  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿค Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details.

Development Setup

# Clone repository
git clone https://github.com/hosseinhezami/laravel-permission-manager.git
cd laravel-permission-manager

# Install dependencies
composer install

# Run tests
composer test

# Run specific test suites
composer test-unit
composer test-feature
composer test-integration

Running Tests

# All tests
composer test

# With coverage
composer test-coverage

๐Ÿ“„ License

The MIT License (MIT). Please see License File for more information.

๐Ÿ’– Support

โญ Show Your Support

If this package helps you, please:

  • โญ Star the repository on GitHub
  • ๐Ÿ“ข Share it with your colleagues
  • ๐Ÿ› Report bugs
  • ๐Ÿ’ก Suggest new features
  • ๐Ÿค Submit pull requests

Made with โค๏ธ by Hossein Hezami

๐Ÿ“š Complete API Reference (click to expand)

User Methods (via PermissionTrait)

// Roles
$user->roles();
$user->assignRole($role);
$user->revokeRole($role);
$user->hasRole($role);
$user->hasAnyRole($roles);
$user->hasAllRoles($roles);
$user->lacksRole($role);

// Permissions
$user->permissions($directOnly = false);
$user->hasPermissionTo($permission, $requireAll = true);
$user->hasAnyPermission($permissions);
$user->hasAllPermissions($permissions);
$user->lacksPermissionTo($permission);

// Direct Permissions
$user->directPermissions();
$user->givePermissionTo($permission, $effect = 'allow', $expiresAt = null);
$user->denyPermissionTo($permission);
$user->revokePermissionTo($permission);
$user->hasDirectPermission($ability);

// Contextual
$user->canPermission($ability, $resource = null);
$user->authorizePermission($ability, $arguments = null);

// Teams
$user->teams();
$user->joinTeam($team);
$user->leaveTeam($team);
$user->belongsToTeam($team);
$user->assignRoleForTeam($roles, $team);
$user->revokeRoleForTeam($roles, $team);
$user->hasRoleForTeam($role, $team);

// Super Admin
$user->isSuperAdmin();

// Snapshot & Cache
$user->getPermissionSnapshot();
$user->forgetCachedPermissions();

Role Methods

// Create & Find
Role::create($data);
Role::findBySlug($slug);

// Permissions
$role->permissions();
$role->assignPermission($routes);
$role->revokePermission($routes);
$role->hasPermissionTo($route);
$role->getAllPermissions();
$role->getAllPermissionModels();

// Hierarchy
$role->inherits();
$role->children();
$role->inheritFrom($role);
$role->removeInheritance($role);

// Users
$role->users();

// Scopes
Role::forGuard($guard);

// Cache
$role->forgetCachedPermissions();

Permission Methods

// Create & Find
Permission::create($data);
Permission::findByRoute($route);

// Groups
$permission->group();
$permission->assignToGroup($group);
$permission->removeFromGroup();

// Conditions
$permission->conditions();
$permission->activeConditions();
$permission->hasConditions();

// Roles
$permission->roles();

// Scopes
Permission::forGuard($guard);
Permission::inGroup($group);

PermissionManager Facade

// Roles
PermissionManager::roles()->list();
PermissionManager::roles()->create($data);
PermissionManager::role($slug)->assignPermission($permission);
PermissionManager::role($slug)->revokePermission($permission);
PermissionManager::role($slug)->update($params);
PermissionManager::role($slug)->delete();
PermissionManager::role($slug)->inheritFrom($role);
PermissionManager::role($slug)->assignPermissionSet($set);

// Permissions
PermissionManager::permissions()->list();
PermissionManager::permissions()->create($routes);
PermissionManager::permissions()->delete($routes);
PermissionManager::permissions()->sync();
PermissionManager::permissions()->syncRoutesWithOptions($options);
PermissionManager::permissions()->groups();
PermissionManager::permissions()->sets();
PermissionManager::permissions()->createSet($data);
PermissionManager::permissions()->generateResource($resource, $actions);

// Users
PermissionManager::user($userId)->assignRole($roles);
PermissionManager::user($userId)->revokeRole($roles);
PermissionManager::user($userId)->roles();
PermissionManager::user($userId)->permissions();
PermissionManager::user($userId)->hasRole($role);
PermissionManager::user($userId)->hasPermission($permission);
PermissionManager::user($userId)->givePermissionTo($permission);
PermissionManager::user($userId)->denyPermissionTo($permission);
PermissionManager::user($userId)->joinTeam($team);
PermissionManager::user($userId)->leaveTeam($team);
PermissionManager::user($userId)->canPermission($ability, $resource);

// Teams
PermissionManager::setTeam($team);
PermissionManager::clearTeam();
PermissionManager::teamContext();

// Authorization
PermissionManager::check($user, $ability, $resource);
PermissionManager::explain($user, $ability, $resource);
PermissionManager::snapshot($user);
PermissionManager::define($ability, $callback);

// Cache
PermissionManager::cache()->flushAll();
PermissionManager::cache()->flushUser($userId);
PermissionManager::cache()->flushRole($roleId);
๐Ÿ“– Usage Examples (click to expand)

E-commerce Platform

// Setup roles
$customer = Role::create(['name' => 'Customer', 'slug' => 'customer']);
$seller = Role::create(['name' => 'Seller', 'slug' => 'seller']);
$admin = Role::create(['name' => 'Admin', 'slug' => 'admin']);

// Hierarchy: admin > seller > customer
$seller->inheritFrom('customer');
$admin->inheritFrom('seller');

// Customer permissions
$customer->assignPermission([
    'products.view',
    'orders.create',
    'orders.view.own',
]);

// Seller gets customer permissions + more
$seller->assignPermission([
    'products.manage.own',
    'orders.view.all',
    'analytics.view.own',
]);

// Conditional permission: sellers can only edit their own products
PermissionCondition::create([
    'permission_id' => Permission::findByRoute('products.edit')->id,
    'conditions' => [
        'field' => 'user.id',
        'operator' => '=',
        'value' => 'resource.seller_id',
    ],
]);

Multi-tenant SaaS

// Each tenant is a team
$companyA = Team::createTeam(['name' => 'Company A']);
$companyB = Team::createTeam(['name' => 'Company B']);

$user->joinTeam($companyA);
$user->joinTeam($companyB);

// Different roles per tenant
$user->assignRoleForTeam('admin', $companyA);
$user->assignRoleForTeam('viewer', $companyB);

// In controllers
public function index(Request $request)
{
    PermissionManager::setTeam($request->user()->currentTeam);
    
    if (auth()->user()->hasPermissionTo('reports.view')) {
        // Can view reports in this tenant
    }
}

Time-limited Contractor Access

$contractor = User::find($contractorId);

// Give access for 30 days
$contractor->givePermissionTo(
    'projects.access',
    'allow',
    now()->addDays(30)
);

// Automatically expires - no cron job needed for checks
// Run pruning weekly: php artisan permission:prune --days=7

Approval Workflow with Conditions

// Define: managers can approve expenses under $1000
// Directors can approve any amount

Permission::create(['route' => 'expenses.approve']);

// Manager condition
PermissionCondition::create([
    'permission_id' => Permission::findByRoute('expenses.approve')->id,
    'name' => 'manager-limit',
    'conditions' => [
        'all' => [
            ['field' => 'user.role', 'operator' => '=', 'value' => 'manager'],
            ['field' => 'resource.amount', 'operator' => '<=', 'value' => 1000],
        ],
    ],
]);

If this package saved you time, consider giving it a โญ on GitHub!

Bug Report ยท Feature Request ยท Documentation