Newer
Older
smart-home-server / server / SHServ / Integrations / GAuth / PermissionResolver.php
<?php

declare(strict_types=1);

namespace SHServ\Integrations\GAuth;

final class PermissionResolver
{
    /**
     * Resolve effective permissions for a user.
     * Algorithm: union(role_defaults, group_permissions, user_grants) minus explicit_denies.
     */
    public function resolve(int $userId, string $systemRole): array
    {
        // Fast path: superadmin
        if ($systemRole === 'superadmin') {
            return $this->getAllPermissionSlugs();
        }

        $effective = [];

        // 1. Role defaults
        $rolePerms = $this->getRolePermissions($systemRole);
        foreach ($rolePerms as $slug => $granted) {
            if ($granted) {
                $effective[$slug] = true;
            }
        }

        // 2. Group permissions (union)
        $groupPerms = $this->getGroupPermissions($userId);
        foreach ($groupPerms as $slug => $granted) {
            if ($granted) {
                $effective[$slug] = true;
            }
        }

        // 3. User-level overrides
        $userOverrides = $this->getUserOverrides($userId);
        foreach ($userOverrides as $slug => $granted) {
            if ($granted) {
                $effective[$slug] = true;
            } else {
                unset($effective[$slug]);
            }
        }

        return array_keys($effective);
    }

    /**
     * Check if user has a specific permission.
     */
    public function has(int $userId, string $systemRole, string $permissionSlug): bool
    {
        if ($systemRole === 'superadmin') {
            return true;
        }
        $perms = $this->resolve($userId, $systemRole);
        return in_array($permissionSlug, $perms, true);
    }

    private function getRolePermissions(string $roleSlug): array
    {
        $tb = app()->thin_builder;
        $result = $tb->select('shserv_roles', ['default_permissions'], [['slug', '=', $roleSlug]]);
        if (!$result) {
            return [];
        }

        $perms = json_decode($result[0]['default_permissions'] ?? '[]', true);
        $map = [];
        foreach ($perms as $slug) {
            $map[$slug] = true;
        }

        // Handle wildcard '*' for superadmin
        if (in_array('*', $perms, true)) {
            $all = $tb->select('shserv_permissions', ['slug']);
            if ($all) {
                foreach ($all as $row) {
                    $map[$row['slug']] = true;
                }
            }
        }

        return $map;
    }

    private function getGroupPermissions(int $userId): array
    {
        $tb = app()->thin_builder;
        $result = $tb->query("
            SELECT p.permission_slug, p.granted
            FROM shserv_group_permissions p
            JOIN shserv_group_members m ON m.group_id = p.group_id
            WHERE m.user_id = {$userId}
        ");

        if (!$result) {
            return [];
        }

        $map = [];
        foreach ($result as $row) {
            $map[$row['permission_slug']] = (bool) $row['granted'];
        }
        return $map;
    }

    private function getUserOverrides(int $userId): array
    {
        $tb = app()->thin_builder;
        $result = $tb->select('shserv_user_permissions', ['permission_slug', 'granted'], [['user_id', '=', $userId]]);
        if (!$result) {
            return [];
        }

        $map = [];
        foreach ($result as $row) {
            $map[$row['permission_slug']] = (bool) $row['granted'];
        }
        return $map;
    }

    private function getAllPermissionSlugs(): array
    {
        $tb = app()->thin_builder;
        $result = $tb->select('shserv_permissions', ['slug']);
        if (!$result) {
            return [];
        }
        return array_column($result, 'slug');
    }
}