feat(permissions): add super-admin gate and model wiring
- Register a `Gate::before` hook so the super-admin role bypasses every permission check, replacing the removed filament-shield gate - Add Role::superAdminName() as the single source for the bypass role - Gate module panel access behind per-module `access:<module>` permissions via the PermissionRegistry, falling back to `view:modules` - Bind PermissionRegistry as a singleton - Point config/permission.php at the app's Role and Permission models
This commit is contained in:
parent
d64c4ac577
commit
8bb6914f6c
@ -56,6 +56,16 @@ class Role extends SpatieRole
|
||||
'disable_activity_checks',
|
||||
];
|
||||
|
||||
/**
|
||||
* The name of the role that bypasses every permission check.
|
||||
*
|
||||
* Replaces the removed filament-shield `Utils::getSuperAdminName()`.
|
||||
*/
|
||||
public static function superAdminName(): string
|
||||
{
|
||||
return config('roles.super_admin', 'super_admin');
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
|
||||
@ -5,8 +5,8 @@ namespace App\Models;
|
||||
use App\Enums\JournalType;
|
||||
use App\Enums\UserState;
|
||||
use App\Observers\UserObserver;
|
||||
use App\Services\PermissionRegistry;
|
||||
use App\Traits\JournalTrait;
|
||||
use BezhanSalleh\FilamentShield\Support\Utils;
|
||||
use Database\Factories\UserFactory;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
@ -33,7 +33,6 @@ use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
|
||||
|
||||
@ -487,16 +486,24 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, MustVerif
|
||||
}
|
||||
|
||||
// For modules panels
|
||||
if ($this->hasRole(Utils::getSuperAdminName())) {
|
||||
if ($this->hasRole(Role::superAdminName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Each module panel is gated by its own `access:<module>` permission,
|
||||
// falling back to the generic `view:modules` for unscoped panels.
|
||||
$moduleKey = app(PermissionRegistry::class)->moduleKeyForPanel($panel);
|
||||
|
||||
if ($moduleKey !== null) {
|
||||
return $this->can('access:'.$moduleKey);
|
||||
}
|
||||
|
||||
return $this->can('view:modules');
|
||||
}
|
||||
|
||||
public function hasAdminAccess(): bool
|
||||
{
|
||||
if ($this->hasRole(Utils::getSuperAdminName().'|admin')) {
|
||||
if ($this->hasRole(Role::superAdminName().'|admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -11,10 +11,12 @@ use App\Enums\PirepStatus;
|
||||
use App\Enums\UserState;
|
||||
use App\Http\Composers\PageLinksComposer;
|
||||
use App\Http\Composers\VersionComposer;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Channels\Discord\DiscordWebhook;
|
||||
use App\Policies\Filament\ActivityPolicy;
|
||||
use App\Services\ModuleService;
|
||||
use App\Services\PermissionRegistry;
|
||||
use App\Services\RouteForge\Contracts\LintRule;
|
||||
use App\Services\RouteForge\LintRunner;
|
||||
use App\Services\RouteForge\Rules\ExistingDuplicates;
|
||||
@ -105,7 +107,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
* Str::nanoid() generates an ID via the hidehalo/nanoid client using
|
||||
* the project's alphabet/length; Str::isNanoid() validates one.
|
||||
*/
|
||||
Str::macro('nanoid', fn (int $length = BaseModel::ID_MAX_LENGTH): string => (new NanoidClient($length))->formattedId(BaseModel::ID_ALPHABET, $length));
|
||||
Str::macro('nanoid', fn (int $length = BaseModel::ID_MAX_LENGTH): string => new NanoidClient($length)->formattedId(BaseModel::ID_ALPHABET, $length));
|
||||
|
||||
Str::macro('isNanoid', fn (mixed $value): bool => is_string($value) && preg_match('/^['.BaseModel::ID_ALPHABET.']{'.BaseModel::ID_MAX_LENGTH.'}$/', $value) === 1);
|
||||
|
||||
@ -114,6 +116,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
/**
|
||||
* Gates (i.e. Authentication) definition
|
||||
*/
|
||||
// Super-admins bypass every permission/policy check. Replaces the
|
||||
// removed filament-shield super_admin gate. Return null (not false) so
|
||||
// non-super-admins fall through to the normal checks.
|
||||
Gate::before(static fn (?User $user): ?bool => $user?->hasRole(Role::superAdminName()) ? true : null);
|
||||
|
||||
Gate::define('access_admin', static fn (?User $user): Response => $user?->hasAdminAccess()
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to access this page.'));
|
||||
@ -206,6 +213,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
// nav views) must see the same instance, so it has to be a singleton.
|
||||
$this->app->singleton(ModuleService::class);
|
||||
|
||||
// Permission registry: modules register custom permissions into the
|
||||
// same instance during boot(), so it must be a singleton.
|
||||
$this->app->singleton(PermissionRegistry::class);
|
||||
|
||||
// RouteForge lint catalog: tag every concrete rule class so adding a
|
||||
// rule means appending one entry here, not editing LintRunner. The
|
||||
// bind below materializes the tagged generator into the runner's
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use Spatie\Permission\DefaultTeamResolver;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return [
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user