| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- <?php
- namespace App\Services\Access;
- use App\Models\Permission;
- use App\Models\Role;
- use App\Models\User;
- use Illuminate\Database\QueryException;
- use Illuminate\Support\Collection;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\Schema;
- class AccessService
- {
- private const CACHE_VERSION_KEY = 'permissions:version';
- public function can(User $user, string $permission): bool
- {
- if ($this->isDirectAdmin($user)) {
- return true;
- }
- $effect = $this->getEffectivePermissions($user)->get($permission);
- return $effect === 'allow';
- }
- public function canAny(User $user, array $permissions): bool
- {
- foreach ($permissions as $permission) {
- if ($this->can($user, trim((string) $permission))) {
- return true;
- }
- }
- return false;
- }
- public function canViewField(User $user, string $module, string $field, ?string $entity = null): bool
- {
- return $this->can($user, $this->fieldPermissionSlug($module, $field, 'view'));
- }
- public function canUpdateField(User $user, string $module, string $field, ?string $entity = null): bool
- {
- return $this->can($user, $this->fieldPermissionSlug($module, $field, 'update'));
- }
- public function filterReadableFields(User $user, string $module, array $fields, ?string $entity = null): array
- {
- return array_values(array_filter(
- $fields,
- fn (string $field): bool => $this->canViewField($user, $module, $field, $entity)
- ));
- }
- public function filterWritableData(User $user, string $module, array $data, ?string $entity = null): array
- {
- return array_filter(
- $data,
- fn (string $field): bool => $this->canUpdateField($user, $module, $field, $entity),
- ARRAY_FILTER_USE_KEY
- );
- }
- public function assertCan(User $user, string $permission): void
- {
- abort_unless($this->can($user, $permission), 403);
- }
- public function routePermission(?string $routeName): array|string|null
- {
- if (!$routeName) {
- return null;
- }
- $exact = config('access_routes.exact.' . $routeName);
- if ($exact) {
- return $exact;
- }
- foreach (config('access_routes.prefixes', []) as $prefix => $permissions) {
- if (!str_starts_with($routeName, $prefix)) {
- continue;
- }
- $suffix = substr($routeName, strlen($prefix));
- return $permissions[$suffix] ?? $permissions['*'] ?? null;
- }
- return null;
- }
- public function canAccessRoute(User $user, ?string $routeName): bool
- {
- $permission = $this->routePermission($routeName);
- if ($permission === null) {
- return false;
- }
- return is_array($permission)
- ? $this->canAny($user, $permission)
- : $this->can($user, $permission);
- }
- public function roleHasPermission(Role $role, string $permission): bool
- {
- if ($role->slug === Role::ADMIN) {
- return true;
- }
- $permissionModel = $role->permissions()
- ->where('slug', $permission)
- ->first();
- return $permissionModel?->pivot?->effect === 'allow';
- }
- public function getEffectivePermissions(User $user): Collection
- {
- if ($this->isDirectAdmin($user)) {
- try {
- return Permission::query()->pluck('slug')->mapWithKeys(fn (string $slug): array => [$slug => 'allow']);
- } catch (QueryException) {
- return collect();
- }
- }
- return Cache::remember(
- $this->cacheKey($user),
- now()->addHour(),
- fn (): Collection => $this->resolveEffectivePermissions($user)
- );
- }
- public function bumpCacheVersion(): void
- {
- Cache::forever(self::CACHE_VERSION_KEY, $this->cacheVersion() + 1);
- }
- private function resolveEffectivePermissions(User $user): Collection
- {
- if (!$this->tablesExist()) {
- return collect();
- }
- $effects = collect();
- $role = $user->roleModel()->with('permissions')->first();
- if ($role) {
- foreach ($role->permissions as $permission) {
- $effects->put($permission->slug, $permission->pivot->effect);
- }
- }
- $userPermissions = $user->permissions()
- ->where(function ($query) {
- $query->whereNull('expires_at')
- ->orWhere('expires_at', '>', now());
- })
- ->get();
- foreach ($userPermissions as $permission) {
- $effects->put($permission->slug, $permission->pivot->effect);
- }
- return $effects;
- }
- private function isDirectAdmin(User $user): bool
- {
- return $user->resolvedRoleSlug() === Role::ADMIN;
- }
- private function fieldPermissionSlug(string $module, string $field, string $action): string
- {
- return "{$module}.fields.{$field}.{$action}";
- }
- private function cacheKey(User $user): string
- {
- return 'permissions:user:' . $user->id . ':v' . $this->cacheVersion();
- }
- private function cacheVersion(): int
- {
- return (int) Cache::get(self::CACHE_VERSION_KEY, 1);
- }
- private function tablesExist(): bool
- {
- return Schema::hasTable('roles')
- && Schema::hasTable('permissions')
- && Schema::hasTable('role_permissions')
- && Schema::hasTable('user_permissions');
- }
- }
|