is('admin', 'admin/*')) { return $next($request); } if (! self::isRestricted()) { return $next($request); } if (! self::allows((string) $request->ip())) { abort(404); } return $next($request); } /** Whether the owner has switched the restriction on at all. */ public static function isRestricted(): bool { return Settings::bool('console.network_restricted', false); } /** * Every network that may reach the console. * * The VPN is always in it and is not removable — it is the one path that * exists independently of whatever the owner has typed into the list, and * without it a bad entry would leave nobody able to fix the entry. * * @return array */ public static function allowedRanges(): array { $vpn = (array) config('admin_access.trusted_ranges', []); $own = (array) Settings::get('console.allowed_ips', []); return array_values(array_unique(array_filter(array_merge( $vpn, array_map('trim', array_map('strval', $own)), )))); } public static function allows(string $ip): bool { return self::covers($ip, self::allowedRanges()); } /** * Would this address still get in, given that list? * * Split out so the "you are about to lock yourself out" decision can be * made — and tested — without a request: it is the one check whose failure * mode is that nobody can reach the page that would fix it. * * @param array $extra the owner's list AFTER the change */ public static function wouldStillAllow(string $ip, array $extra): bool { $vpn = (array) config('admin_access.trusted_ranges', []); return self::covers($ip, array_merge($vpn, $extra)); } /** * Whether a value is an address or a CIDR range at all. * * Shared by the console and the recovery command on purpose: an entry that * matches nothing is stored happily and reports success, and the operator * then switches the restriction on believing they are covered. That is the * exact situation the recovery command exists to get out of. */ public static function isNetwork(string $value): bool { [$address, $prefix] = array_pad(explode('/', $value, 2), 2, null); if (filter_var($address, FILTER_VALIDATE_IP) === false) { return false; } if ($prefix === null) { return true; } $max = str_contains((string) $address, ':') ? 128 : 32; return ctype_digit((string) $prefix) && (int) $prefix >= 0 && (int) $prefix <= $max; } /** @param array $ranges */ private static function covers(string $ip, array $ranges): bool { $ranges = array_values(array_filter($ranges)); return $ranges !== [] && $ip !== '' && IpUtils::checkIp($ip, $ranges); } }