📦 Marketplace⭐ GitHub
📦

monkeyslegion-permissions

v1.0.0MITFoundation

by Jorge Peraza

MonkeysLegion Permissions v1

Enterprise RBAC, policy gates, composable rules, approval workflows, LDAP/SAML sync, and Apex AI fuzzy authorization for MonkeysLegion. Ground-up build for PHP 8.4 with property hooks, backed enums, and zero magic.

Features

FeatureStatusAuthorizer PipelineGate → PolicyResolver → RuleEngine → DecisionAttribute-First Security#[RequiresRole], #[RequiresPermission], #[Authorize], #[Gate]Policy SystemModel-to-policy mapping with before() / after() hooksComposable RulesPredicate engine with AND/OR/NOT/CUSTOM operators, priority-based executionDatabase StoreRole, Permission, UserRole, UserPermission, StoredRule entitiesApproval WorkflowsPermissionRequest → review → approve/deny with expiryCache LayerTransparent permission caching with TTL and tag-based invalidationLDAP/SAML SyncRoleSyncAdapterInterface with additive/replace/merge/intersect strategiesApex AI IntegrationLLM-backed fuzzy policy evaluation with confidence thresholdsPSR-15 MiddlewareAuthorizationMiddleware — attribute-aware request authorizationEvent System11 audit events for authorization, roles, permissions, and syncCLI Commandspermissions:install, permissions:entitiesPHP 8.4 NativeProperty hooks, backed enums, asymmetric visibility

Requirements

  • PHP 8.4 or higher

  • monkeyscloud/monkeyslegion-di (Container)

  • monkeyscloud/monkeyslegion-events (Event dispatching)

  • psr/http-message ^2.0

  • psr/http-server-middleware ^1.0

Installation

composer require monkeyscloud/monkeyslegion-permissions

Quick Setup

# Full install: config + entities + migration
php ml permissions:install

Or just copy entity stubs

php ml permissions:entities php ml permissions:entities --force # overwrite existing

Architecture

┌─────────────────────────────────────────────────────────┐
│                   HTTP Request                           │
└───────────────────────┬─────────────────────────────────┘
                        ▼
┌─────────────────────────────────────────────────────────┐
│          AuthorizationMiddleware (PSR-15)                │
│  Reads: #[RequiresRole] #[RequiresPermission] #[Gate]   │
└───────────────────────┬─────────────────────────────────┘
                        ▼
┌─────────────────────────────────────────────────────────┐
│                    Authorizer                            │
│  ┌──────┐  ┌────────────┐  ┌────────────┐  ┌────────┐  │
│  │ Gate │→ │PolicyResolve│→ │ RuleEngine │→ │Decision│  │
│  └──────┘  └────────────┘  └────────────┘  └────────┘  │
└─────────────────────────────────────────────────────────┘
                        ▼
┌──────────┐  ┌───────────┐  ┌──────────┐  ┌───────────┐
│ Database │  │   Cache   │  │  Events  │  │  Apex AI  │
│  Store   │  │   Layer   │  │ Dispatch │  │  (opt.)   │
└──────────┘  └───────────┘  └──────────┘  └───────────┘

The package is organized into clear namespaces:

  • Apex/: AI-powered fuzzy policy evaluation (ApexPolicyAdapter, ApexGateRegistrar)

  • Attributes/: Controller/method attributes (#[RequiresRole], #[RequiresPermission], #[Authorize], #[Gate])

  • Cache/: Transparent permission caching (CacheLayer)

  • Contracts/: Core interfaces (AuthorizerInterface)

  • Entity/: Database entities (Role, Permission, UserRole, UserPermission, StoredRule, PermissionRequest)

  • Event/: 11 audit events for all authorization operations

  • Exceptions/: AuthorizationException

  • Gate/: Ability definitions and closures

  • Middleware/: PSR-15 AuthorizationMiddleware

  • Policy/: Model-to-policy mapping (Policy, PolicyResolver)

  • Provider/: DI service provider (PermissionsProvider)

  • Rule/: Composable predicate engine (Rule, Predicate, RuleEngine)

  • Store/: Database persistence (DatabaseStore, PermissionStoreInterface)

  • Sync/: LDAP/SAML role synchronization (RoleSyncManager, adapters)

  • Workflow/: Approval request pipeline (RequestManager)

Configuration

cp vendor/monkeyscloud/monkeyslegion-permissions/config/permissions.mlc config/permissions.mlc
permissions {
    # Super-admin role (has all permissions)
    super_admin_role = ${PERMISSIONS_SUPER_ADMIN:-super-admin}

# Table names
tables {
    roles            = ${PERMISSIONS_TABLE_ROLES:-perm_roles}
    permissions      = ${PERMISSIONS_TABLE_PERMISSIONS:-perm_permissions}
    user_roles       = ${PERMISSIONS_TABLE_USER_ROLES:-perm_user_roles}
    user_permissions = ${PERMISSIONS_TABLE_USER_PERMISSIONS:-perm_user_permissions}
    stored_rules     = ${PERMISSIONS_TABLE_STORED_RULES:-perm_stored_rules}
    requests         = ${PERMISSIONS_TABLE_REQUESTS:-perm_permission_requests}
}

# Permission caching
cache {
    enabled = ${PERMISSIONS_CACHE_ENABLED:-true}
    ttl     = ${PERMISSIONS_CACHE_TTL:-3600}
    prefix  = ${PERMISSIONS_CACHE_PREFIX:-perm:}
}

# Apex AI integration (optional)
apex {
    confidence_threshold = ${PERMISSIONS_APEX_THRESHOLD:-0.7}
    model = ${PERMISSIONS_APEX_MODEL:-}
}

}

Attribute-Based Authorization

use MonkeysLegion\Permissions\Attributes\RequiresRole;
use MonkeysLegion\Permissions\Attributes\RequiresPermission;
use MonkeysLegion\Permissions\Attributes\Authorize;

#[RequiresRole('admin')] class AdminController { #[RequiresPermission('users.create')] public function createUser(): Response { // Only accessible to admins with users.create permission }

#[Authorize('update-user', subject: 'user')]
public function updateUser(int $id): Response
{
    // Delegates to a policy: UserPolicy::update($currentUser, $user)
}

}

Gate (Ability Definitions)

use MonkeysLegion\Permissions\Gate;

$gate = new Gate();

// Define abilities with closures $gate->define('edit-post', fn($user, $post) => $user->id === $post->author_id);

// Check $gate->allows($user, 'edit-post', $post); // true/false $gate->denies($user, 'edit-post', $post); // true/false $gate->authorize($user, 'edit-post', $post); // throws AuthorizationException

Policy System

use MonkeysLegion\Permissions\Policy\Policy;

class PostPolicy extends Policy { public function update(object $user, object $post): bool { return $user->id === $post->author_id; }

public function delete(object $user, object $post): bool
{
    return $user->role === 'admin';
}

// Optional: runs before any check
public function before(object $user, string $ability): ?bool
{
    // Super-admins can do anything
    if ($user->role === 'super-admin') {
        return true;
    }
    return null; // fall through to specific check
}

}

Composable Rules

use MonkeysLegion\Permissions\Rule\Rule;
use MonkeysLegion\Permissions\Rule\Predicate;
use MonkeysLegion\Permissions\Rule\PredicateType;
use MonkeysLegion\Permissions\Rule\RuleEngine;

// Build rules with predicates $rule = new Rule( name: 'business-hours-only', key: 'business_hours', predicates: [ new Predicate(PredicateType::Custom, fn() => date('H') >= 9 && date('H') < 17), ], priority: 10, );

$engine = new RuleEngine(); $engine->addRule($rule);

$result = $engine->evaluate($context); // RuleResult: allowed, denied, or abstained

Approval Workflows

use MonkeysLegion\Permissions\Workflow\RequestManager;

$manager = $container->get(RequestManager::class);

// User requests a permission $request = $manager->request( userId: 'user-42', targetKey: 'reports.export', reason: 'Need to export Q4 reports for client presentation', );

// Admin reviews $manager->approve($request, reviewedBy: 'admin-1', note: 'Approved for Q4'); // or $manager->deny($request, reviewedBy: 'admin-1', note: 'Use dashboard instead');

LDAP/SAML Sync

use MonkeysLegion\Permissions\Sync\RoleSyncManager;
use MonkeysLegion\Permissions\Sync\SyncStrategy;

$sync = $container->get(RoleSyncManager::class);

// Sync roles from external identity provider $result = $sync->sync( userId: 'user-42', externalRoles: ['engineering', 'team-lead'], strategy: SyncStrategy::Merge, );

// SyncResult: added, removed, unchanged counts

Strategies:

StrategyBehaviorAdditiveOnly add new roles, never removeReplaceExternal roles replace all local rolesMergeUnion of external and local rolesIntersectLocalKeep only roles that exist both externally and locally

Apex AI Fuzzy Authorization

use MonkeysLegion\Permissions\Apex\ApexGateRegistrar;

$registrar = $container->get(ApexGateRegistrar::class);

// Register AI-powered policy $registrar->register( gateName: 'content-appropriate', prompt: 'Is the following content appropriate for a professional workplace?', confidenceThreshold: 0.8, );

// Now use it like any other gate $authorizer->authorize($user, 'content-appropriate', ['content' => $postBody]);

Database Entities

The package includes 8 entity stubs ready for database persistence:

EntityPurposeRoleRoles with key, name, level, metadataPermissionPermissions with key, name, groupUserRoleUser ↔ Role assignments with expiryUserPermissionDirect user ↔ permission grants with expiryStoredRulePersistent composable rules with predicatesPermissionRequestApproval workflow requestsRequestStatusEnum: pending, approved, denied, expiredRequestTypeEnum: permission, role

Events

EventWhen DispatchedAuthorizationCheckedAny authorization check completesAuthorizationDeniedAuthorization deniedPermissionGrantedPermission assigned to userPermissionRevokedPermission removed from userPermissionRequestedUser requests a permissionRequestApprovedRequest approved by reviewerRequestDeniedRequest denied by reviewerRoleAssignedRole assigned to userRoleRevokedRole removed from userRolesSyncedExternal role sync completedApexPolicyEvaluatedAI policy evaluation completedPolicyEvaluatedStandard policy evaluation completed

$dispatcher->listen(AuthorizationDenied::class, function (AuthorizationDenied $event) {
    $logger->warning("Access denied: user {$event->userId} → {$event->ability}");
});

Middleware Setup

// In your middleware pipeline
$pipeline->pipe(AuthorizationMiddleware::class);

// The middleware: // 1. Reads #[RequiresRole], #[RequiresPermission], #[Authorize], #[Gate] from the route // 2. Checks permissions via Authorizer // 3. Returns 403 on denial // 4. Dispatches audit events

Cache Layer

use MonkeysLegion\Permissions\Cache\CacheLayer;

$cache = new CacheLayer($cacheStore, prefix: 'perm:', ttl: 3600);

// Permissions are cached transparently $hasPermission = $cache->remember("user:42:posts.edit", function () use ($store) { return $store->hasPermission('user-42', 'posts.edit'); });

// Invalidate on changes $cache->forget("user:42:*");

Security Posture

  • Deny by default — no permission means no access

  • Super-admin bypass — configurable super-admin role

  • Expiring grants — UserRole and UserPermission support expires_at

  • Audit trail — 12 event types for full observability

  • AI confidence gates — configurable threshold prevents uncertain AI decisions

  • Cache invalidation — automatic cache flush on permission changes

Testing

composer test
composer phpstan

License

MIT © MonkeysCloud