The home card used to be grant-gated by the shell's card table, which left the poll endpoint behind `auth` alone — and that endpoint returns a LIVE CREDENTIAL: the guest network's current rotating password and its join QR code. "Any logged-in user" was never an adequate gate for that; the card table was doing the work, and the card table is gone. New key rather than reuse: unifi.guest_wifi exists precisely so front-desk staff can be given the guest password WITHOUT any other network access. Reusing unifi.settings would have meant handing out controller configuration to get a Wi-Fi password read. Authorised inside the controller rather than by route middleware, so the endpoint stays outside the unifi page-permission groups — it is a home-page widget, not a unifi page. Nobody holds the new key until it is granted, so the card is invisible until then. That is the intended direction: capability is assigned deliberately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDA3fQbsa3gaHCFoV1Q8Gg
96 lines
4.1 KiB
PHP
96 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace Dashboard\Unifi;
|
|
|
|
use App\Models\NavItem;
|
|
use Illuminate\Support\ServiceProvider;
|
|
|
|
class UnifiServiceProvider extends ServiceProvider
|
|
{
|
|
public function register(): void
|
|
{
|
|
$this->mergeConfigFrom(__DIR__ . '/../config/unifi.php', 'unifi');
|
|
|
|
$this->app->singleton(Services\UnifiApiClient::class, function ($app) {
|
|
return new Services\UnifiApiClient();
|
|
});
|
|
}
|
|
|
|
public function boot(): void
|
|
{
|
|
$this->loadRoutesFrom(__DIR__ . '/routes/unifi.php');
|
|
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
|
|
|
|
// "/" home card: guest Wi-Fi network + current rotating password +
|
|
// QR (re-renders when the password changes). Self-hides until enabled
|
|
// and a password has been rotated.
|
|
//
|
|
// The ONE card in the platform that earned a NEW permission key rather
|
|
// than reusing one. It cannot be universal — the payload is a live
|
|
// credential, and publishing it to every student and parent is the exact
|
|
// hole GuestCardController::data was written to close. And no existing
|
|
// unifi key fits: unifi.stats is the network dashboards, unifi.settings is
|
|
// the controller connection, and handing the front desk either one to let
|
|
// them read a guest password would widen access, not reuse it. So the
|
|
// granularity goes in the catalogue (same move as the papercut/inventory
|
|
// split), and the key is grantable to non-unifi staff — which is the whole
|
|
// point of a home-page card.
|
|
//
|
|
// Declared in composer.json extra.dashboard.permissions, so the catalogue
|
|
// row appears when unifi is installed/updated — NOT seeded to any group.
|
|
try {
|
|
app(\App\Support\DashboardCardRegistry::class)->register(
|
|
'unifi.guest_wifi',
|
|
'Guest Wi-Fi',
|
|
'UnifiGuestWifi',
|
|
fn (\App\Models\User $user) => \Dashboard\Unifi\Http\Controllers\GuestCardController::resolve($user),
|
|
'The guest network name, current password, and a join QR code — updates automatically when the password rotates.',
|
|
permission: 'unifi.guest_wifi',
|
|
);
|
|
} catch (\Throwable) {
|
|
// Older shell without the card registry — skip.
|
|
}
|
|
|
|
// Tell the shell's nav sidebar which unifi nav items the user
|
|
// can see: pages whose required_permission the user holds through
|
|
// groups, plus pages granted via the Settings → Access tab. The
|
|
// shell also folds these grants back into User::can()/allPermissions
|
|
// so the route-level permission middleware passes for grantees.
|
|
if ($this->app->runningInConsole()) {
|
|
$this->commands([
|
|
Console\CheckWebhooks::class,
|
|
Console\CaptureSnapshots::class,
|
|
Console\CleanupSnapshots::class,
|
|
Console\RebootAllAps::class,
|
|
Console\RotatePasswords::class,
|
|
Console\SyncPpskSchedules::class,
|
|
]);
|
|
$this->publishes([
|
|
__DIR__ . '/../config/unifi.php' => config_path('unifi.php'),
|
|
], 'unifi-config');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Group + direct permissions only. Deliberately NOT allPermissions():
|
|
* the shell folds page-grant permissions back into allPermissions/can,
|
|
* so using it here would turn a single granted page into every page
|
|
* carrying the same permission.
|
|
*/
|
|
public static function realPermissionKeysFor(\App\Models\User $user): \Illuminate\Support\Collection
|
|
{
|
|
if ($user->is_super_admin) {
|
|
return \App\Models\Permission::pluck('key');
|
|
}
|
|
|
|
$direct = $user->directPermissions()->get();
|
|
|
|
return \App\Models\Permission::whereHas('groups', fn ($q) => $q->whereIn('group_id', $user->groups()->pluck('groups.id')))
|
|
->pluck('key')
|
|
->merge($direct->where('granted', true)->pluck('permission_key'))
|
|
->diff($direct->where('granted', false)->pluck('permission_key'))
|
|
->unique()
|
|
->values();
|
|
}
|
|
}
|