craftcamp/abac-bundle
Symfony bundle integrating CraftCamp’s PHP ABAC library for attribute-based access control. Define policy rules based on user and resource attributes (roles as attributes too) and enforce permissions via a security service that can return denied attributes for debugging.
Installation:
composer require craftcamp/abac-bundle
Register the bundle in config/bundles.php (Symfony 4+):
return [
// ...
CraftCamp\AbacBundle\CraftCampAbacBundle::class => ['all' => true],
];
Basic Configuration (config/packages/craftcamp_abac.yaml):
craftcamp_abac:
configuration_files:
- '%kernel.project_dir%/config/abac/attributes.yml'
- '%kernel.project_dir%/config/abac/policy_rules.yml'
Define Attributes (config/abac/attributes.yml):
user:
class: App\Entity\User
type: user
fields:
role: { name: "Role" }
is_active: { name: "Active" }
Define a Rule (config/abac/policy_rules.yml):
rules:
can_edit_post:
attributes:
user.role:
comparison_type: string
comparison: isEqual
value: "editor"
First Usage (Controller):
use PhpAbac\Abac;
class PostController extends AbstractController
{
public function edit(Abac $abac, Post $post)
{
$access = $abac->enforce('can_edit_post', $this->getUser(), $post);
if ($access !== true) {
throw $this->createAccessDeniedException();
}
// ...
}
}
public function update(Abac $abac, $entityId)
{
$entity = $this->entityManager->find($entityId);
$access = $abac->enforce('can_update_entity', $this->getUser(), $entity);
if ($access !== true) {
$this->denyAccessWithDetails($access);
}
// Proceed with update
}
private function denyAccessWithDetails(array $rejectedAttributes)
{
$this->addFlash('error', 'Access denied. Missing: ' . implode(', ', $rejectedAttributes));
return $this->redirectToRoute('homepage');
}
Use dynamic for runtime values (e.g., environment variables):
rules:
can_access_during_maintenance:
attributes:
environment.is_maintenance:
comparison_type: boolean
comparison: isEqual
value: dynamic
Override in PHP:
$abac->getConfiguration()->setDynamicValue('environment.is_maintenance', getenv('MAINTENANCE_MODE') === 'true');
Combine with Symfony’s Voter for hybrid role/ABAC checks:
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
class AbacVoter implements VoterInterface
{
public function __construct(private Abac $abac) {}
public function vote(Authenticated $token, $subject, array $attributes)
{
$rule = $attributes[0] ?? 'default_rule';
return $this->abac->enforce($rule, $token->getUser(), $subject);
}
}
Leverage caching for performance-critical rules:
craftcamp_abac:
cache_options:
cache_folder: '%kernel.cache_dir%/abac'
ttl: 3600 # Cache rules for 1 hour
Map Eloquent/Laravel models to ABAC attributes:
# config/abac/attributes.yml
post:
class: App\Models\Post
type: resource
fields:
author.id: { name: "Author ID" }
published_at:
name: "Published Date"
getter: getPublishedAtTimestamp # Custom getter
Service Provider Setup:
// config/app.php
'providers' => [
// ...
CraftCamp\AbacBundle\CraftCampAbacServiceProvider::class,
],
Middleware for Global Checks:
namespace App\Http\Middleware;
use Closure;
use PhpAbac\Abac;
class AbacMiddleware
{
public function __construct(private Abac $abac) {}
public function handle($request, Closure $next)
{
$rule = $request->route()->getName() . '_rule';
if ($this->abac->enforce($rule, auth()->user(), $request->route()->parameter('resource')) !== true) {
abort(403);
}
return $next($request);
}
}
Dynamic Rule Loading: Load rules from a database or API:
$rules = RuleRepository::fetchForRoute($request->route()->getName());
$abac->getConfiguration()->setRules($rules);
Event-Driven Rule Updates: Use Laravel events to update ABAC rules:
// In a service
event(new RulesUpdated($newRules));
// Event listener
public function handle(RulesUpdated $event)
{
$abac = app(Abac::class);
$abac->getConfiguration()->setRules($event->getRules());
}
Attribute Naming Conflicts:
user.role) are unique across configurations.user.admin_role or user.permissions[*] for arrays.Dynamic Value Timing:
enforce() is called.Caching Stale Rules:
CacheManagerInterface to clear specific rules.Case Sensitivity:
snake_case or camelCase) in your team’s conventions.Circular Dependencies:
rule_a depends on rule_b, which depends on rule_a).Performance with Complex Rules:
isIn/isNotIn arrays can slow down enforcement.isEqual checks with logical operators).Enable Debug Mode:
craftcamp_abac:
debug: true # Logs rule evaluation steps
Check logs for:
Inspect Configuration: Dump the loaded configuration to verify setup:
$config = $abac->getConfiguration();
dump($config->getAttributes(), $config->getRules());
Test Rules Isolated: Use a test controller to validate rules independently:
public function testRule(Abac $abac)
{
$user = new User(); // Mock user
$resource = new Post(); // Mock resource
$result = $abac->enforce('test_rule', $user, $resource);
dump($result); // true or array of rejected attributes
}
Common Errors:
attributes.yml.comparison_type matches the attribute’s data type (e.g., datetime for dates).enforce() matches the YAML key.Custom Comparison Operators:
Extend PhpAbac\Manager\ComparisonManagerInterface to add operators like isBetween:
class CustomComparisonManager implements ComparisonManagerInterface
{
public function addComparison(string $name, callable $callable)
{
$this->comparisons[$name] = $callable;
}
}
Register in services.yaml:
PhpAbac\Manager\ComparisonManagerInterface: '@App\Service\CustomComparisonManager'
Attribute Transformers: Transform raw attributes before comparison (e.g., normalize dates):
$ab
How can I help you explore Laravel packages today?