diff --git a/app/Admin/Middleware/CheckPermission.php b/app/Admin/Middleware/CheckPermission.php new file mode 100644 index 0000000..88f5191 --- /dev/null +++ b/app/Admin/Middleware/CheckPermission.php @@ -0,0 +1,96 @@ + + */ + protected $exceptPaths = [ + // + ]; + + /** + * @var array + */ + protected $exceptRoutes = [ + // + ]; + + /** + * Handle an incoming request. + * + * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next + */ + public function handle(Request $request, Closure $next): Response + { + if ($this->inExceptArray($request) || $this->checkRoutePermission($request)) { + return $next($request); + } + + return Admin::response()->fail(__('admin.unauthorized')); + } + + protected function checkRoutePermission(Request $request): bool + { + if (is_null($user = Admin::user())) { + return false; + } + + $ability = $this->normalizeRouteAbility( + $original = $request->route()->getName() + ); + + return collect($ability) + ->when($ability !== $original, fn (Collection $collection) => $collection->push($original)) + ->contains(fn ($ability) => $user->can($ability)); + } + + protected function normalizeRouteAbility(string $ability) + { + foreach ($this->resourceAbilityMap() as $method => $map) { + if (str_ends_with($ability, ".{$method}")) { + return preg_replace("/(.*)\.{$method}$/", '${1}.'.$map, $ability); + } + } + + return $ability; + } + + protected function resourceAbilityMap(): array + { + return [ + 'index' => 'list', + 'show' => 'view', + 'store' => 'create', + 'edit' => 'update', + 'destroy' => 'delete', + ]; + } + + protected function inExceptArray(Request $request): bool + { + if (in_array($request->route()->getName(), $this->exceptRoutes)) { + return true; + } + + $permission = Admin::permission(); + + return $request->is( + collect($this->exceptPaths) + ->when(Admin::permission(), fn (Collection $collection, Permission $permission) => $collection->merge($permission->authExcept)->merge($permission->permissionExcept)) + ->map(function ($path) { + $path = trim((string) config('admin.route.prefix'), '/').'/'.trim((string) $path, '/'); + return ltrim($path, '/'); + }) + ); + } +} diff --git a/app/Models/AdminUser.php b/app/Models/AdminUser.php index 68d3112..841bc08 100644 --- a/app/Models/AdminUser.php +++ b/app/Models/AdminUser.php @@ -9,11 +9,74 @@ use Slowlyo\OwlAdmin\Models\AdminUser as Model; class AdminUser extends Model { + /** + * @var \Illuminate\Support\Collection + */ + protected $allAbilities; + public function roles(): BelongsToMany { return $this->belongsToMany(Admin::adminRoleModel(), 'admin_role_users', 'user_id', 'role_id')->withTimestamps(); } + /** + * 获取此用户的全部能力 + */ + public function allAbilities(): Collection + { + if ($this->allAbilities) { + return $this->allAbilities; + } + + $model = Admin::adminPermissionModel(); + $allPermissions = $model::all(['id', 'parent_id', 'slug'])->keyBy('id'); + + /** @var \Illuminate\Database\Eloquent\Collection 此用户的所有角色的权限集合 */ + $rolePermissions = $this->roles + ->pluck('permissions') + ->flatten() + ->keyBy('id'); + + $abilities = $rolePermissions->map(fn ($permission) => $permission->slug); + + foreach ($rolePermissions as $rolePermission) { + if (is_null($rolePermission->parent_id) || $abilities->has($rolePermission->parent_id)) { + continue; + } + + $parent = $allPermissions->get($rolePermission->parent_id); + + while ($parent) { + $abilities->put($parent->id, $parent->slug); + + if (is_null($parent->parent_id)) { + break; + } + + $parent = $allPermissions->get($parent->parent_id); + }; + } + + unset($allPermissions, $rolePermissions); + + return $this->allAbilities = $abilities; + } + + public function can($abilities, $arguments = []): bool + { + if (empty($abilities)) { + return true; + } + + if ($this->isAdministrator()) { + return true; + } + + return collect($abilities)->every( + fn ($ability) => $this->allAbilities()->contains($ability) + ); + } + public function allMenus(): Collection { $model = Admin::adminMenuModel(); diff --git a/config/admin.php b/config/admin.php index e7e05c4..c99622d 100644 --- a/config/admin.php +++ b/config/admin.php @@ -21,7 +21,13 @@ return [ 'prefix' => 'admin-api', 'domain' => null, 'namespace' => 'App\\Admin\\Controllers', - 'middleware' => ['admin'], + 'middleware' => [ + 'admin.auth', + 'admin.bootstrap', + \App\Admin\Middleware\CheckPermission::class, + 'sanctum', + 'substitute', + ], // 不包含额外路由, 配置后, 不会追加新增/详情/编辑页面路由 'without_extra_routes' => [ '/dashboard',