craftcamp/php-abac
Attribute-based access control (ABAC) for PHP apps. Define policies using attributes and evaluate permissions based on user, resource, action, and context. Framework-agnostic with a simple API, suited for fine-grained authorization beyond roles.
Installation
composer require craftcamp/php-abac
Add to composer.json if using a monorepo or custom package.
Basic Initialization
use CraftCamp\Abac\Abac;
$abac = new Abac();
$abac->addPolicy('admin', [
'actions' => ['create', 'read', 'update', 'delete'],
'resources' => ['posts', 'users'],
]);
First Use Case: Check Access
$isAllowed = $abac->isAllowed('admin', 'create', 'posts');
// Returns `true` if the policy grants access.
Abac (main class for policy management).Policy (defines rules for subjects, actions, and resources).AbacException (handle edge cases like invalid inputs).tests/ directory) for real-world usage.Dynamic Policy Loading
Load policies from a config file (e.g., config/abac.php) during Laravel bootstrapping:
$abac = new Abac();
foreach (config('abac.policies') as $role => $rules) {
$abac->addPolicy($role, $rules);
}
Role-Based Access Control (RBAC) Integration
Combine with Laravel’s Gate or Policy classes:
// In AuthServiceProvider
Gate::define('manage-posts', function ($user) {
$abac = app(Abac::class);
return $abac->isAllowed($user->role, 'update', 'posts');
});
Attribute-Based Policies
Use custom attributes (e.g., department, is_active) for granular control:
$abac->addPolicy('editor', [
'actions' => ['update'],
'resources' => ['posts'],
'conditions' => [
'department' => 'news',
'is_active' => true,
],
]);
Middleware for API Routes Protect API endpoints with ABAC:
Route::get('/posts', function () {
$abac = app(Abac::class);
if (!$abac->isAllowed(auth()->user()->role, 'read', 'posts')) {
abort(403);
}
// ...
})->middleware('auth:sanctum');
Laravel Service Container
Bind Abac to the container for dependency injection:
$app->singleton(Abac::class, function ($app) {
$abac = new Abac();
// Load policies here or defer to config.
return $abac;
});
Caching Policies Cache policies in Redis/Memcached to avoid reprocessing:
$cache = Cache::remember('abac_policies', now()->addHours(1), function () {
return $abac->getPolicies();
});
Overly Complex Conditions
conditions => ['department' => ['news', 'sports']]) may not work as expected.Case Sensitivity
strtolower()) if needed:
$abac->isAllowed(strtolower($user->role), 'read', strtolower($resource));
Policy Overrides
mergePolicy() or track policies explicitly:
$abac->mergePolicy('admin', ['actions' => ['audit']]); // Adds without replacing.
Performance with Large Policies
isAllowed() call.Enable Logging Temporarily add debug output to track policy evaluation:
$abac->setDebug(true);
$abac->isAllowed('user', 'delete', 'posts'); // Logs evaluation steps.
Validate Inputs Ensure subjects/actions/resources are non-empty strings:
if (empty($subject) || empty($action) || empty($resource)) {
throw new AbacException('Missing required parameters.');
}
Custom Condition Handlers
Extend ConditionInterface for complex logic (e.g., date ranges):
class DateCondition implements ConditionInterface {
public function evaluate(array $attributes): bool {
return $attributes['created_at'] > now()->subDays(30);
}
}
Policy Storage Replace in-memory storage with a database or cache layer:
class DatabasePolicyStore implements PolicyStoreInterface {
public function getPolicy(string $subject): ?Policy {
return Policy::fromArray(DB::table('abac_policies')->where('subject', $subject)->first());
}
}
Event Listeners
Trigger events for policy changes (e.g., PolicyAdded, PolicyUpdated):
$abac->on('PolicyAdded', function ($subject) {
Log::info("New policy added for: {$subject}");
});
Default Deny vs. Allow The library defaults to deny if no matching policy exists. Explicitly allow all by default if needed:
$abac->setDefaultAllow(true); // Risky; use cautiously!
Resource Hierarchies
Support hierarchical resources (e.g., posts/*) with a custom ResourceResolver:
$abac->setResourceResolver(function ($resource) {
return explode('/', $resource); // ['posts', '123']
});
How can I help you explore Laravel packages today?