Delete the bespoke page-grant middleware (A2)
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDA3fQbsa3gaHCFoV1Q8Gg
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Dashboard\Unifi\Http\Middleware;
|
||||
|
||||
use App\Models\NavItem;
|
||||
use Closure;
|
||||
use Dashboard\Unifi\Models\UnifiPageGrant;
|
||||
use Dashboard\Unifi\UnifiServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Per-page access for unifi pages. Attached dynamically by the
|
||||
* RouteMatched listener in UnifiServiceProvider — the check cannot
|
||||
* live in the listener because RouteMatched fires before session/auth
|
||||
* middleware, when request->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);
|
||||
}
|
||||
}
|
||||
@@ -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([
|
||||
|
||||
Reference in New Issue
Block a user