From 7c6bf992014b424715b9ca347b3c71bdf6ff6b5e Mon Sep 17 00:00:00 2001 From: jwed Date: Tue, 28 Jul 2026 19:19:39 -0400 Subject: [PATCH] Delete the bespoke page-grant middleware (A2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This app attached an Enforce*PageGrant middleware at runtime via a RouteMatched listener, consulting its own private *_page_grants table to decide whether the request could proceed. Safe to remove because the middleware only ever NARROWED routes that already carry a `permission:` gate. Three independent audits agreed on that for all eleven apps: no route relied on it as its sole authorization, so deleting it cannot widen access — the permission the route already declares still decides. Two cases were not simply narrowing, and both were handled first: - unifi's guest-Wi-Fi card poll was `auth`-only and leaked the live guest password to any signed-in account. Closed in 9cbc8e6 before this, so this commit does not reopen it. - google's middleware had NO permission-holder bypass, so it could deny a legitimate google.manage holder. Deleting it restores their access. What is deliberately NOT done here: the grant tables, models and the fold in User::pageGrantedPermissions() all remain, so a grant row can still ADD access. It can no longer 404 anybody. The fold goes last, together with the characterisation tests that pin it. Verified: pages in all four de-gated apps return 200, the affected suites pass, and dashboard:doctor's advisories drop from 18 to 7. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RDA3fQbsa3gaHCFoV1Q8Gg --- src/Http/Middleware/EnforceUnifiPageGrant.php | 51 ------------------- src/UnifiServiceProvider.php | 16 ------ 2 files changed, 67 deletions(-) delete mode 100644 src/Http/Middleware/EnforceUnifiPageGrant.php diff --git a/src/Http/Middleware/EnforceUnifiPageGrant.php b/src/Http/Middleware/EnforceUnifiPageGrant.php deleted file mode 100644 index ce6297c..0000000 --- a/src/Http/Middleware/EnforceUnifiPageGrant.php +++ /dev/null @@ -1,51 +0,0 @@ -user() is still null. - */ -class EnforceUnifiPageGrant -{ - public function handle(Request $request, Closure $next): Response - { - $user = $request->user(); - $routeName = $request->route()?->getName(); - - if (! $routeName || ! $user || $user->is_super_admin) { - return $next($request); - } - - try { - $item = NavItem::where('route_name', $routeName)->first(); - if ($item) { - // Permission holders keep access — grants extend, never revoke. - $holdsPermission = $item->required_permission - && UnifiServiceProvider::realPermissionKeysFor($user)->contains($item->required_permission); - - if (! $holdsPermission && ! UnifiPageGrant::userCanAccess($user, $item)) { - // 404 instead of 403 — don't leak that the page exists. - abort(404); - } - } - } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { - throw $e; - } catch (\Throwable) { - // unifi_page_grants may not exist yet on first install — fail - // open in that narrow window (permission middleware still - // guards the route). - } - - return $next($request); - } -} diff --git a/src/UnifiServiceProvider.php b/src/UnifiServiceProvider.php index 00deb6d..a2e08d2 100644 --- a/src/UnifiServiceProvider.php +++ b/src/UnifiServiceProvider.php @@ -4,8 +4,6 @@ namespace Dashboard\Unifi; use App\Models\NavItem; use Dashboard\Unifi\Models\UnifiPageGrant; -use Illuminate\Routing\Events\RouteMatched; -use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; class UnifiServiceProvider extends ServiceProvider @@ -55,20 +53,6 @@ class UnifiServiceProvider extends ServiceProvider // Sidebar will fall back to legacy permission filter. } - // Per-page enforcement for unifi pages. Settings stays on its - // permission:unifi.settings route middleware (the Access tab - // itself lives there and must not be able to lock itself out). - // The user-dependent check lives in middleware appended to the - // matched route: RouteMatched fires before the session/auth - // middleware run, so request->user() is null here and any check - // at this point silently fails open. - Event::listen(RouteMatched::class, function (RouteMatched $event) { - $routeName = $event->route->getName(); - if (! $routeName || ! str_starts_with($routeName, 'unifi.')) return; - if (str_starts_with($routeName, 'unifi.settings')) return; - - $event->route->middleware(\Dashboard\Unifi\Http\Middleware\EnforceUnifiPageGrant::class); - }); if ($this->app->runningInConsole()) { $this->commands([