diff --git a/app/Http/Controllers/AlertRuleController.php b/app/Http/Controllers/AlertRuleController.php index ed52113c7ac8..036fe3cfd848 100644 --- a/app/Http/Controllers/AlertRuleController.php +++ b/app/Http/Controllers/AlertRuleController.php @@ -6,6 +6,7 @@ use App\Models\AlertRule; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Str; use LibreNMS\Alerting\QueryBuilderParser; use LibreNMS\Util\Time; @@ -17,6 +18,12 @@ class AlertRuleController extends Controller */ public function store(AlertRuleRequest $request): JsonResponse { + if (Gate::denies('create', AlertRule::class)) { + return response()->json([ + 'status' => 'error', + 'message' => 'You are not authorized to create alert rules', + ], 403); + } try { $alertRule = new AlertRule; $this->fillAlertRule($alertRule, $request); @@ -69,6 +76,12 @@ public function show(AlertRule $alertRule): JsonResponse */ public function update(AlertRuleRequest $request, AlertRule $alertRule): JsonResponse { + if (Gate::denies('update', AlertRule::class)) { + return response()->json([ + 'status' => 'error', + 'message' => 'You are not authorized to update alert rules', + ], 403); + } try { $this->fillAlertRule($alertRule, $request); diff --git a/app/Policies/AlertPolicy.php b/app/Policies/AlertPolicy.php index ff0464f63d28..2c1785bc05cf 100644 --- a/app/Policies/AlertPolicy.php +++ b/app/Policies/AlertPolicy.php @@ -23,28 +23,35 @@ public function viewAny(User $user): bool */ public function view(User $user, Alert $alert): bool { + if ($this->hasGlobalPermission($user, 'viewAny')) { + return true; + } + return $this->hasGlobalPermission($user, 'view') - || Permissions::canAccessDevice($alert->device_id, $user); + && Permissions::canAccessDevice($alert->device_id, $user); } - public function detail(User $user): bool + public function detail(User $user, Alert $alert): bool { - return $this->hasGlobalPermission($user, 'detail'); + return $this->hasGlobalPermission($user, 'detail') && + Permissions::canAccessDevice($alert->device_id, $user); } /** * Determine whether the user can update the model. */ - public function update(User $user): bool + public function update(User $user, Alert $alert): bool { - return $this->hasGlobalPermission($user, 'update'); + return $this->hasGlobalPermission($user, 'update') && + Permissions::canAccessDevice($alert->device_id, $user); } /** * Determine whether the user can delete the model. */ - public function delete(User $user): bool + public function delete(User $user, Alert $alert): bool { - return $this->hasGlobalPermission($user, 'delete'); + return $this->hasGlobalPermission($user, 'delete') && + Permissions::canAccessDevice($alert->device_id, $user); } } diff --git a/app/Policies/AlertRulePolicy.php b/app/Policies/AlertRulePolicy.php index 82bf3a645d61..529a8a1243c2 100644 --- a/app/Policies/AlertRulePolicy.php +++ b/app/Policies/AlertRulePolicy.php @@ -2,8 +2,11 @@ namespace App\Policies; +use App\Facades\Permissions; +use App\Models\AlertRule; use App\Models\User; + class AlertRulePolicy { use ChecksGlobalPermissions; @@ -19,32 +22,74 @@ public function viewAny(User $user): bool /** * Determine whether the user can view the model. */ - public function view(User $user): bool + public function view(User $user, AlertRule $alertRule): bool { - return $this->hasGlobalPermission($user, 'view'); + if ($this->hasGlobalPermission($user, 'viewAny')) { + return true; + } + + if (! $this->hasGlobalPermission($user, 'view')) { + return false; + } + + foreach ($alertRule->devices()->pluck('device_id') as $deviceId) { + if (Permissions::canAccessDevice($deviceId, $user)) { + return true; + } + } + + return false; } /** * Determine whether the user can create models. */ - public function create(User $user): bool + public function create(User $user, AlertRule $alertRule): bool { - return $this->hasGlobalPermission($user, 'create'); + if (! $this->hasGlobalPermission($user, 'create')) { + return false; + } + + foreach ($alertRule->devices()->pluck('device_id') as $deviceId) { + if (Permissions::canAccessDevice($deviceId, $user)) { + return true; + } + } + + return false; } /** * Determine whether the user can update the model. */ - public function update(User $user): bool + public function update(User $user, AlertRule $alertRule): bool { - return $this->hasGlobalPermission($user, 'update'); + if (! $this->hasGlobalPermission($user, 'update')) { + return false; + } + + foreach ($alertRule->devices()->pluck('device_id') as $deviceId) { + if (Permissions::canAccessDevice($deviceId, $user)) { + return true; + } + } + + return false; } /** * Determine whether the user can delete the model. */ - public function delete(User $user): bool + public function delete(User $user, AlertRule $alertRule): bool { - return $this->hasGlobalPermission($user, 'delete'); + if (! $this->hasGlobalPermission($user, 'delete')) { + return false; + } + + foreach ($alertRule->devices()->pluck('device_id') as $deviceId) { + if (Permissions::canAccessDevice($deviceId, $user)) { + return true; + } + } } } diff --git a/app/Policies/AlertSchedulePolicy.php b/app/Policies/AlertSchedulePolicy.php index f0a58e39a9a0..8ac445ac57c3 100644 --- a/app/Policies/AlertSchedulePolicy.php +++ b/app/Policies/AlertSchedulePolicy.php @@ -21,6 +21,10 @@ public function viewAny(User $user): bool */ public function view(User $user): bool { + if ($this->hasGlobalPermission($user, 'viewAny')) { + return true; + } + return $this->hasGlobalPermission($user, 'view'); } diff --git a/app/Policies/AlertTemplatePolicy.php b/app/Policies/AlertTemplatePolicy.php index 48342d01bc10..7afdca95ac8a 100644 --- a/app/Policies/AlertTemplatePolicy.php +++ b/app/Policies/AlertTemplatePolicy.php @@ -21,6 +21,10 @@ public function viewAny(User $user): bool */ public function view(User $user): bool { + if ($this->hasGlobalPermission($user, 'viewAny')) { + return true; + } + return $this->hasGlobalPermission($user, 'view'); } diff --git a/app/Policies/AlertTransportPolicy.php b/app/Policies/AlertTransportPolicy.php index 7bcfa4f26ee5..ab4c38ddde6d 100644 --- a/app/Policies/AlertTransportPolicy.php +++ b/app/Policies/AlertTransportPolicy.php @@ -21,6 +21,10 @@ public function viewAny(User $user): bool */ public function view(User $user): bool { + if ($this->hasGlobalPermission($user, 'viewAny')) { + return true; + } + return $this->hasGlobalPermission($user, 'view'); } diff --git a/app/Policies/ApplicationPolicy.php b/app/Policies/ApplicationPolicy.php index ee50cc4a9a22..2fa021d440c1 100644 --- a/app/Policies/ApplicationPolicy.php +++ b/app/Policies/ApplicationPolicy.php @@ -2,6 +2,8 @@ namespace App\Policies; +use App\Facades\Permissions; +use App\Models\Application; use App\Models\User; class ApplicationPolicy @@ -19,16 +21,21 @@ public function viewAny(User $user): bool /** * Determine whether the user can view the model. */ - public function view(User $user): bool + public function view(User $user, Application $application): bool { - return $this->hasGlobalPermission($user, 'view'); + if ($this->hasGlobalPermission($user, 'viewAny')) { + return true; + } + + return $this->hasGlobalPermission($user, 'view') && Permissions::canAccessDevice($application->device_id, $user); } /** * Determine whether the user can update the model. */ - public function update(User $user): bool + public function update(User $user, Application $application): bool { - return $this->hasGlobalPermission($user, 'update'); + return $this->hasGlobalPermission($user, 'update') && + Permissions::canAccessDevice($application->device_id, $user); } } diff --git a/database/migrations/2026_03_18_000001_add_alert_template_viewany_permission.php b/database/migrations/2026_03_18_000001_add_alert_template_viewany_permission.php new file mode 100644 index 000000000000..69c3255b620a --- /dev/null +++ b/database/migrations/2026_03_18_000001_add_alert_template_viewany_permission.php @@ -0,0 +1,42 @@ + [ + 'name' => $name, + 'guard_name' => 'web', + 'created_at' => $now, + 'updated_at' => $now, + ], self::PERMISSIONS); + + DB::table('permissions')->insertOrIgnore($insertData); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + DB::table('permissions') + ->whereIn('name', self::PERMISSIONS) + ->where('guard_name', 'web') + ->delete(); + } +}; + diff --git a/includes/html/forms/schedule-maintenance.inc.php b/includes/html/forms/schedule-maintenance.inc.php index 29b3fdb9abdb..74bd8bbce596 100644 --- a/includes/html/forms/schedule-maintenance.inc.php +++ b/includes/html/forms/schedule-maintenance.inc.php @@ -15,6 +15,7 @@ use App\Facades\LibrenmsConfig; use App\Models\AlertSchedule; use App\Models\UserPref; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Str; use LibreNMS\Enum\MaintenanceBehavior; @@ -196,8 +197,8 @@ 'schedule_id' => $alert_schedule->schedule_id ?? null, ]; } elseif ($sub_type == 'parse-maintenance') { - Gate::authorize('view', AlertSchedule::class); $alert_schedule = AlertSchedule::findOrFail($_POST['schedule_id']); + Gate::authorize('view', $alert_schedule); $items = []; foreach (dbFetchRows('SELECT `alert_schedulable_type`, `alert_schedulable_id` FROM `alert_schedulables` WHERE `schedule_id`=?', [$alert_schedule->schedule_id]) as $target) { diff --git a/includes/html/forms/show-alert-transport.inc.php b/includes/html/forms/show-alert-transport.inc.php index b7f24d287402..16e9d4ae81bd 100644 --- a/includes/html/forms/show-alert-transport.inc.php +++ b/includes/html/forms/show-alert-transport.inc.php @@ -15,7 +15,7 @@ header('Content-type: application/json'); -if (Gate::denies('view', AlertTransport::class)) { +if (Gate::denies('viewAny', AlertTransport::class)) { exit(json_encode([ 'status' => 'error', 'message' => 'You need permission', diff --git a/includes/html/pages/alert-schedule.inc.php b/includes/html/pages/alert-schedule.inc.php index d41085e6812c..ebb69a01821b 100644 --- a/includes/html/pages/alert-schedule.inc.php +++ b/includes/html/pages/alert-schedule.inc.php @@ -13,6 +13,7 @@ */ use App\Models\AlertSchedule; +use Illuminate\Support\Facades\Gate; $pagetitle[] = 'Alert Schedule'; $no_refresh = true; diff --git a/includes/html/pages/alert-transports.inc.php b/includes/html/pages/alert-transports.inc.php index d9ca23322477..3f8a4b04f032 100644 --- a/includes/html/pages/alert-transports.inc.php +++ b/includes/html/pages/alert-transports.inc.php @@ -3,7 +3,7 @@ use App\Models\AlertTransport; use Illuminate\Support\Facades\Gate; -if (Gate::allows('create', AlertTransport::class)) { +if (Gate::allows('viewAny', AlertTransport::class)) { // handle OAuth requests $request = request(); // grab the Request object diff --git a/includes/html/print-alert-rules.php b/includes/html/print-alert-rules.php index bccda8ed88de..cce23acf9596 100644 --- a/includes/html/print-alert-rules.php +++ b/includes/html/print-alert-rules.php @@ -26,6 +26,7 @@ use App\Facades\DeviceCache; use App\Models\AlertRule; +use Illuminate\Support\Facades\Gate; use LibreNMS\Alerting\QueryBuilderParser; use LibreNMS\Enum\AlertState; @@ -35,14 +36,10 @@ $no_refresh = true; -?> -
-
- -
-
- isset($rule['default']) && $rule['default']); $default_extra = [ @@ -75,6 +72,14 @@ unset($qb); } +?> +
+
+ +
+
+ +hasGlobalAdmin() require_once 'includes/html/modal/alert_rule_collection.inc.php'; // Also dies if !Auth::user()->hasGlobalAdmin() @@ -408,8 +413,12 @@ $enabled_msg = htmlentities((string) $rule['name']) . ' is ON'; } + $disabled_attr = ''; + if (! Gate::allows('update', AlertRule::class)) { + $disabled_attr = 'disabled'; + } echo "
"; - echo ""; + echo ""; echo '
'; echo ''; @@ -455,7 +464,7 @@ '; -if ($count < 1) { +if ($count < 1 && Gate::allows('create', AlertRule::class)) { echo '
diff --git a/includes/html/print-alert-templates.php b/includes/html/print-alert-templates.php index f4190da15173..79021891388d 100644 --- a/includes/html/print-alert-templates.php +++ b/includes/html/print-alert-templates.php @@ -3,8 +3,8 @@ use App\Models\AlertRule; use App\Models\AlertTemplate; use App\Models\AlertTemplateMap; -use Illuminate\Support\Facades\Gate as Gate; - +use Illuminate\Support\Facades\Gate; +Gate::authorize('viewAny', AlertTemplate::class); $no_refresh = true; include 'includes/html/modal/alert_template.inc.php'; @@ -87,10 +87,18 @@ if (row.old_template == "1") { response = " "; } + if(row.id == 0) { - response = response + " " + ""; + + response = response + " " + } else { - response = response + " " + ""; + + response = response + " " + + + response = response + ""; + } return response; }, diff --git a/includes/html/print-alert-transports.php b/includes/html/print-alert-transports.php index fcf0d5a80704..3d131e260d83 100644 --- a/includes/html/print-alert-transports.php +++ b/includes/html/print-alert-transports.php @@ -52,7 +52,9 @@ if (Gate::allows('delete', AlertTransport::class)) { echo ""; } - echo " "; + if (Gate::allows('update', AlertTransport::class)) { + echo " "; + } echo '
'; } echo ''; diff --git a/includes/html/table/alerts.inc.php b/includes/html/table/alerts.inc.php index 60388dca17c7..5e816e6537e1 100644 --- a/includes/html/table/alerts.inc.php +++ b/includes/html/table/alerts.inc.php @@ -14,6 +14,8 @@ * @author LibreNMS Contributors */ +use App\Models\Alert; +use Illuminate\Support\Facades\Gate; use LibreNMS\Util\Time; $where = ' `devices`.`disabled` = 0'; @@ -131,11 +133,13 @@ [$fault_detail, $max_row_length] = alert_details($log); $info = json_decode((string) $alert['info'], true); - $alert_to_ack = ''; - $alert_to_nack = ''; - $alert_to_unack = ''; - - $ack_ico = $alert_to_ack; + $alert_to_ack = ''; + if (Gate::allows('update', Alert::class)) { + $alert_to_ack = ''; + $alert_to_nack = ''; + $alert_to_unack = ''; + } + $ack_ico = $alert_to_ack; if ((int) $alert['state'] === 0) { $msg = ''; @@ -193,6 +197,11 @@ $note_class = 'warning'; } + $note = ''; + if (Gate::allows('update', Alert::class)) { + $note = ""; + } + $response[] = [ 'id' => $rulei++, 'rule' => '' . htmlentities((string) $alert['name']) . '', @@ -206,7 +215,7 @@ 'alert_id' => $alert['id'], 'ack_ico' => $ack_ico, 'proc' => $has_proc, - 'notes' => "", + 'notes' => $note, ]; } diff --git a/lang/en/permissions.php b/lang/en/permissions.php index 9f0652f841fa..a7bbe6975f76 100644 --- a/lang/en/permissions.php +++ b/lang/en/permissions.php @@ -42,6 +42,7 @@ 'alert-template' => [ 'title' => 'Alert Templates', + 'viewAny' => ['label' => 'View Alert Templates', 'description' => 'View the list of all alert templates'], 'view' => ['label' => 'View Alert Templates', 'description' => 'View alert templates'], 'create' => ['label' => 'Create Alert Templates', 'description' => 'Create new alert templates'], 'update' => ['label' => 'Edit Alert Templates', 'description' => 'Modify existing alert templates'], @@ -50,6 +51,7 @@ 'alert-transport' => [ 'title' => 'Alert Transports', + 'viewAny' => ['label' => 'View Alert Transports', 'description' => 'View the list of all alert transports'], 'view' => ['label' => 'View Alert Transports', 'description' => 'View alert transports'], 'create' => ['label' => 'Create Alert Transports', 'description' => 'Create new alert transports'], 'update' => ['label' => 'Edit Alert Transports', 'description' => 'Modify existing alert transports'], diff --git a/resources/definitions/permissions.yaml b/resources/definitions/permissions.yaml index 3ca9cc43880c..b90d6886c5fe 100644 --- a/resources/definitions/permissions.yaml +++ b/resources/definitions/permissions.yaml @@ -21,12 +21,14 @@ groups: - alert-schedule.delete alert-template: + - alert-template.viewAny - alert-template.view - alert-template.create - alert-template.update - alert-template.delete alert-transport: + - alert-transport.viewAny - alert-transport.view - alert-transport.create - alert-transport.update diff --git a/resources/views/layouts/menu.blade.php b/resources/views/layouts/menu.blade.php index ef185ef0e509..962706f98a00 100644 --- a/resources/views/layouts/menu.blade.php +++ b/resources/views/layouts/menu.blade.php @@ -167,7 +167,7 @@ class="tw:md:hidden tw:2xl:inline-block">{{ __('Overview') }} @admin - @can('viewAny', \App\Models\DeviceGroup::class) + @can(['viewAny', 'view'], \App\Models\DeviceGroup::class)
  • {{ __('Manage Groups') }}
  • @@ -560,20 +560,26 @@ class="fa fa-exclamation-circle text-{{ $alert_menu_class }} fa-fw fa-lg" aria-hidden="true"> {{ __('Alert History') }}
  • {{ __('Statistics') }}
  • - @admin -
  • {{ __('Alert Rules') }}
  • -
  • {{ __('Scheduled Maintenance') }}
  • -
  • {{ __('Alert Templates') }} -
  • -
  • + @endcanany + @canany(['viewAny', 'view'], \App\Models\AlertTransport::class) +
  • {{ __('Alert Transports') }}
  • - @endadmin + @endcanany @includeIf('menu.custom') diff --git a/routes/web.php b/routes/web.php index 707cbaaf3126..e457d4769398 100644 --- a/routes/web.php +++ b/routes/web.php @@ -187,6 +187,12 @@ Route::post('unregister', [PushNotificationController::class, 'unregister'])->name('push.unregister'); }); + Route::post('alert/transports/{transport}/test', [AlertTransportController::class, 'test'])->name('alert.transports.test'); + Route::resource('alert-rule', AlertRuleController::class)->only(['show', 'store', 'update', 'destroy']); + Route::put('alert-rule/{alert_rule}/toggle', [AlertRuleController::class, 'toggle'])->name('alert-rule.toggle'); + Route::get('alert-rule-from-template/{template_id}', [AlertRuleTemplateController::class, 'template'])->name('alert-rule-template'); + Route::get('alert-rule-from-rule/{alert_rule}', [AlertRuleTemplateController::class, 'rule'])->name('alert-rule-template.rule'); + // admin pages Route::middleware('can:admin')->group(function (): void { Route::get('settings/{tab?}/{section?}', [SettingsController::class, 'index'])->name('settings'); @@ -195,11 +201,6 @@ Route::resource('roles', RoleController::class); - Route::post('alert/transports/{transport}/test', [AlertTransportController::class, 'test'])->name('alert.transports.test'); - Route::resource('alert-rule', AlertRuleController::class)->only(['show', 'store', 'update', 'destroy']); - Route::put('alert-rule/{alert_rule}/toggle', [AlertRuleController::class, 'toggle'])->name('alert-rule.toggle'); - Route::get('alert-rule-from-template/{template_id}', [AlertRuleTemplateController::class, 'template'])->name('alert-rule-template'); - Route::get('alert-rule-from-rule/{alert_rule}', [AlertRuleTemplateController::class, 'rule'])->name('alert-rule-template.rule'); Route::get('alertlog/{alertLog}/details', Ajax\AlertDetailsController::class)->name('alertlog.details'); Route::get('plugin/settings', App\Http\Controllers\PluginAdminController::class)->name('plugin.admin');