Zum Hauptinhalt springen
Schneespur
Dokumentation durchblättern

Module entwickeln

Berechtigungen & Rollen

Wie Schneespur Zugriffe steuert: granulare Berechtigungen, benannte Rollen und der Admin-Bypass — und wie ein Modul eigene Berechtigungen registriert und prüft.

Schneespur steuert Zugriffe über ein rollenbasiertes Berechtigungssystem. Ein Modul muss sich nicht um Login, Sitzungen oder Benutzerverwaltung kümmern — es meldet nur seine eigenen Berechtigungen an und prüft sie an den richtigen Stellen. Diese Seite zeigt, wie das zusammenhängt und warum die Trennung in Berechtigungen, Rollen und Benutzer sinnvoll ist.

Die drei Bausteine

Das System kennt drei Ebenen, die aufeinander aufbauen:

  • Berechtigungen (Permissions) — granulare Einzelfähigkeiten, z. B. customers.view oder settings.edit. Eine Berechtigung beschreibt genau eine erlaubte Aktion.
  • Rollen (Roles) — benannte Sammlungen von Berechtigungen, z. B. „Admin” oder „Dispatcher”. Eine Rolle bündelt das, was eine Funktion im Betrieb tatsächlich tun darf.
  • Benutzer (Users) — bekommen eine oder mehrere Rollen zugewiesen.

Der Vorteil dieser Aufteilung: Berechtigungen sind fein und stabil, Rollen sind das, was sich im Alltag ändert. Wer einem Mitarbeiter mehr Rechte geben will, ändert dessen Rolle — nicht jede einzelne Berechtigung von Hand.

Wie die Autorisierung abläuft

Der Weg von einer registrierten Berechtigung bis zur Prüfung im Controller verläuft über mehrere Stationen:

  1. Die PermissionRegistry hält alle verfügbaren Berechtigungen (Core und Module).
  2. Berechtigungen liegen in der Datenbanktabelle permissions.
  3. Rollen liegen in der Tabelle roles, verknüpft über die Pivot-Tabelle role_permission.
  4. Benutzer erhalten Rollen über die Pivot-Tabelle role_user.
  5. Das Trait HasRoles am User-Modell liefert Methoden wie hasPermission() und hasRole().
  6. Gate::before() gewährt Admin-Benutzern bedingungslos alle Berechtigungen.

Weil die PermissionRegistry Core- und Modul-Berechtigungen gemeinsam hält, taucht eine von Ihrem Modul registrierte Berechtigung an genau denselben Stellen auf wie eine Core-Berechtigung — in der Rollenverwaltung, in den Prüfungen, überall.

Der Admin-Bypass

In AppServiceProvider::boot() hängt sich eine Prüfung vor alle anderen:

Gate::before(function ($user) {
    if ($user->isAdmin()) {
        return true; // Admins bypass all permission checks
    }
});

Damit bestehen Admin-Benutzer jede Gate::allows()- und @can()-Prüfung automatisch.

Das ist bewusst so: Wenn Sie in Ihrem Modul eine neue Berechtigung einführen, müssen Sie Admins nicht gesondert berücksichtigen — sie haben ohnehin Zugriff. Sie prüfen einfach auf die fein gewählte Berechtigung, und der Bypass erledigt den Admin-Fall.

Modul-Berechtigungen registrieren

Eigene Berechtigungen meldet ein Modul über die PermissionRegistry an, typischerweise in der boot()-Phase des ServiceProviders:

$permissions = app(PermissionRegistry::class);

$permissions->registerPermission(
    slug: 'documents.view',
    label: 'View documents',
    group: 'documents',
    module: 'documents', // identifies which module owns this permission
);

$permissions->registerPermission(
    slug: 'documents.upload',
    label: 'Upload documents',
    group: 'documents',
    module: 'documents',
);

$permissions->registerPermission(
    slug: 'documents.delete',
    label: 'Delete documents',
    group: 'documents',
    module: 'documents',
);

Der Parameter module hat einen praktischen Zweck: Er ordnet jede Berechtigung ihrem Modul zu. Wird das Modul entfernt, räumt removeByModule() genau diese Berechtigungen wieder auf — es bleiben keine verwaisten Einträge in der Tabelle permissions zurück.

Als Konvention bilden die Slugs das Muster gruppe.aktion ab (etwa documents.upload). Das hält verwandte Berechtigungen in der Oberfläche beieinander und macht sie im Code lesbar.

Core-Berechtigungen

Diese Berechtigungen meldet die Anwendung selbst an. Sie stehen jedem Modul zur Prüfung zur Verfügung — eine Navigation darf sich etwa an customers.view orientieren, ohne dass Ihr Modul diese Berechtigung selbst definieren müsste:

GruppeSlugBedeutung
dashboarddashboard.viewDashboard ansehen
customerscustomers.viewKunden ansehen
customerscustomers.editKunden bearbeiten
customerscustomers.deleteKunden löschen
driversdrivers.viewFahrer ansehen
driversdrivers.editFahrer bearbeiten
driversdrivers.deleteFahrer löschen
vehiclesvehicles.viewFahrzeuge ansehen
vehiclesvehicles.editFahrzeuge bearbeiten
vehiclesvehicles.deleteFahrzeuge löschen
jobsjobs.viewAufträge ansehen
jobsjobs.editAufträge bearbeiten
jobsjobs.deleteAufträge löschen
workshiftsworkshifts.viewSchichten ansehen
reportsreports.viewBerichte ansehen
alertsalerts.viewWarnungen ansehen
alertsalerts.resolveWarnungen auflösen
settingssettings.viewEinstellungen ansehen
settingssettings.editEinstellungen bearbeiten
dsgvodsgvo.viewDSGVO-Informationen ansehen
dsgvodsgvo.editDSGVO-Einstellungen bearbeiten
gpsgps.viewGPS-Daten ansehen
helphelp.viewHilfe ansehen
usersusers.viewBenutzer ansehen
usersusers.editBenutzer bearbeiten
usersusers.deleteBenutzer löschen
crontaskscrontasks.viewCron-Aufgaben ansehen
crontaskscrontasks.manageCron-Aufgaben verwalten

Rollen-Vorlagen

Rollen-Vorlagen sind vordefinierte Rollen-Konfigurationen, die ein Administrator beim Anlegen einer neuen Rolle als Ausgangspunkt übernehmen kann. So muss niemand eine sinnvolle Rechtekombination von Grund auf zusammenstellen — Ihr Modul kann eine passende Vorlage gleich mitliefern:

$templates = app(RoleTemplateRegistry::class);

$templates->registerTemplate(
    slug: 'accountant',
    name: 'Buchhaltung',
    description: 'Zugriff auf Dokumente, Berichte und Exporte',
    permissions: [
        'dashboard.view',
        'documents.view',
        'reports.view',
        'customers.view',
    ],
    module: 'documents',
);

Eine Vorlage ist ein Vorschlag, keine feste Rolle: Der Administrator entscheidet, ob und wie er sie anwendet. Auch hier ordnet module die Vorlage Ihrem Modul zu.

Berechtigungen im Modul prüfen

Geprüft wird dort, wo eine Aktion ausgeführt oder eine Schaltfläche angezeigt wird. Drei Stellen sind typisch.

Im Controller

use Illuminate\Support\Facades\Gate;

public function index()
{
    Gate::authorize('documents.view');
    // ... render the view
}

Gate::authorize() bricht mit einem Berechtigungsfehler ab, wenn der Benutzer die Berechtigung nicht hat — die einfachste Absicherung am Einstiegspunkt einer Aktion.

In Blade-Views

@can('documents.upload')
    <button>Upload Document</button>
@endcan

So wird eine Schaltfläche nur den Benutzern angezeigt, die sie auch nutzen dürfen. Das ersetzt aber nicht die Prüfung im Controller: Sichtbarkeit allein ist keine Absicherung — die eigentliche Aktion gehört trotzdem geschützt.

Im Code

if ($user->hasPermission('documents.view')) {
    // User has the permission through one of their roles
}

if ($user->hasRole('admin')) {
    // User has the admin role
}

hasPermission() prüft die Berechtigung über alle Rollen des Benutzers hinweg.

Das HasRoles-Trait

Das User-Modell nutzt das Trait HasRoles, das diese Methoden bereitstellt:

$user->roles();                     // BelongsToMany relationship
$user->hasRole('admin'): bool;      // check role by slug
$user->assignRole('dispatcher');     // add role (slug or Role model)
$user->removeRole('dispatcher');     // remove role
$user->hasPermission('jobs.view'): bool; // check permission via any role
$user->loadPermissions(): array;     // get all permission slugs (cached)
$user->flushPermissionCache(): void; // clear in-memory cache

Die Berechtigungen eines Benutzers werden im Speicher zwischengehalten (loadPermissions() liefert die zwischengespeicherte Liste). Wenn Sie Rollen oder Rechte zur Laufzeit ändern, leert flushPermissionCache() diesen Zwischenspeicher, damit die nächste Prüfung den neuen Stand sieht.

Benutzer-Rollen (Enum)

Das Enum UserRole definiert die Basis-Rollentypen:

enum UserRole: string {
    case Admin = 'admin';
    case Driver = 'driver';
}

Dazu bietet das User-Modell zwei Bequemlichkeitsmethoden:

$user->isAdmin(): bool;
$user->isDriver(): bool;

isAdmin() ist genau die Methode, auf die sich der weiter oben beschriebene Admin-Bypass stützt.

Berechtigungen in Navigation und Widgets

Sowohl die NavigationRegistry als auch die DashboardWidgetRegistry verstehen berechtigungsabhängige Sichtbarkeit. So zeigt die Oberfläche jedem Benutzer nur das, was zu seinen Rechten passt:

// Only users with 'documents.view' see this nav item
$nav->addItem(
    group: 'stammdaten',
    slug: 'documents',
    label: 'Dokumente',
    route: 'admin.documents.index',
    icon: 'heroicon-o-document',
    permission: 'documents.view',
);

// Only users with 'documents.view' see this widget
$widgets->registerWidget('recent-documents', [
    'view' => 'documents::widgets.recent',
    'permission' => 'documents.view',
]);

Das ist dieselbe Idee wie @can in der View, nur eine Ebene höher: Ein Menüpunkt oder Widget verschwindet für alle, denen die nötige Berechtigung fehlt. Auch hier gilt — die verlinkte Aktion selbst muss im Controller geschützt bleiben.

Wie ein Modul Berechtigungen, Rollen-Vorlagen und alle weiteren Registries im ServiceProvider anmeldet, zeigt das ServiceProvider-Muster. Die vollständige API der hier genannten Registries finden Sie unter Registries.