Granulares Permissions-System¶
Übersicht¶
Das System verwendet ein zweistufiges Berechtigungssystem:
- Rollen (UserRoleEnum) - Grundlegende Zugriffsebene
- Granulare Permissions - Individuelle Berechtigungen pro User
Rollen (UserRoleEnum)¶
1️⃣ User (0) - Vertriebsmitarbeiter¶
Basis-Zugriff: - ✅ Alle Daten lesen (Kunden, Artikel, Bestellungen, Statistiken) - ❌ Standardmäßig KEINE Schreibrechte
Individuelle Berechtigungen:
- Schreibrechte werden über permissions-Objekt pro User konfiguriert
- Kann in der Benutzerverwaltung individuell eingestellt werden
2️⃣ Admin (1) - Geschäftsführer/Manager¶
Vollzugriff auf: - ✅ Alle Kunden (erstellen, bearbeiten, löschen) - ✅ Alle Artikel (erstellen, bearbeiten, löschen) - ✅ Alle Bestellungen (erstellen, bearbeiten, löschen) - ✅ Kategorien (Artikel, Kunden, Package Sizes) - ✅ Statistiken aller Mitarbeiter - ✅ Push-Benachrichtigungen versenden - ✅ System-Einstellungen ändern
KANN NICHT: - ❌ Benutzer-Rollen ändern - ❌ User-Permissions ändern - ❌ Benutzer erstellen/löschen
3️⃣ SuperAdmin (2) - IT/Owner¶
Vollzugriff auf ALLES: - ✅ Alle Admin-Rechte - ✅ Benutzer erstellen, bearbeiten, löschen - ✅ Benutzer-Rollen vergeben - ✅ User-Permissions konfigurieren - ✅ Connector-Credentials verwalten
Granulare Permissions (UserPermissions)¶
Jeder User hat ein permissions-Objekt mit folgenden Flags:
Kunden-Berechtigungen¶
bool canCreateCustomers // Kunden anlegen
bool canEditCustomers // Kunden bearbeiten
bool canDeleteCustomers // Kunden löschen
Artikel-Berechtigungen¶
bool canCreateArticles // Artikel anlegen
bool canEditArticles // Artikel bearbeiten
bool canDeleteArticles // Artikel löschen
Bestellungen-Berechtigungen¶
bool canCreateOrders // Bestellungen anlegen
bool canEditOrders // Bestellungen bearbeiten
bool canDeleteOrders // Bestellungen löschen
Standard-Permissions nach Rolle¶
User (Standard: Nur Lesen)¶
Admin (Standard: Alles)¶
UserPermissions.admin()
canCreateCustomers: true
canEditCustomers: true
canDeleteCustomers: true
canCreateArticles: true
canEditArticles: true
canDeleteArticles: true
canCreateOrders: true
canEditOrders: true
canDeleteOrders: true
SuperAdmin (Standard: Alles)¶
Firestore Security Rules¶
System Settings¶
// Nur Admin und SuperAdmin können ändern
allow read: if isAuthenticated();
allow write: if isAdmin(); // userRole >= 1
Users Collection¶
// Lesen: Alle
allow read: if isAuthenticated();
// User kann eigene Daten ändern (NICHT userRole/permissions)
allow update: if isAuthenticated() && request.auth.uid == userId
&& !('userRole' in request.resource.data.diff(resource.data))
&& !('permissions' in request.resource.data.diff(resource.data));
// Nur SuperAdmin kann User verwalten
allow create, delete: if isSuperAdmin();
allow update: if isSuperAdmin(); // Überschreibt obige Regel für SuperAdmin
Customers, Articles, Orders¶
// Lesen: Alle
allow read: if isAuthenticated();
// Schreiben: Admin ODER User mit entsprechender Permission
allow create: if isAdmin() || hasPermission('canCreateCustomers');
allow update: if isAdmin() || hasPermission('canEditCustomers');
allow delete: if isAdmin() || hasPermission('canDeleteCustomers');
Verwendung im Code¶
Permissions prüfen¶
// Im User-Objekt
if (user.permissions.canCreateCustomers) {
// Zeige "Kunde anlegen" Button
}
if (user.permissions.hasAnyArticlePermission) {
// User hat mindestens eine Artikel-Berechtigung
}
// Rolle prüfen
if (user.userRole == UserRoleEnum.admin || user.userRole == UserRoleEnum.superadmin) {
// Zeige Admin-Bereich
}
User erstellen mit Custom Permissions¶
final newUser = User(
firstName: 'Max',
lastName: 'Mustermann',
email: 'max@example.com',
userRole: UserRoleEnum.user,
language: LanguageEnum.de,
permissions: UserPermissions(
canCreateOrders: true, // Darf Bestellungen erstellen
canEditOrders: true, // Darf Bestellungen bearbeiten
canEditCustomers: true, // Darf Kunden bearbeiten
// Rest bleibt false
),
);
Permissions aktualisieren¶
final updatedUser = currentUser.copyWith(
permissions: currentUser.permissions.copyWith(
canDeleteArticles: true, // Artikel-Löschrecht hinzufügen
),
);
Migration bestehender User¶
Bestehende User ohne permissions-Feld erhalten automatisch Standard-Permissions basierend auf ihrer Rolle:
// Im User.fromMap Constructor
permissions: json['permissions'] != null
? UserPermissions.fromMap(json['permissions'])
: null, // null wird zu Standard-Permissions der Rolle
Benutzerverwaltung UI¶
In der Benutzerverwaltung sollte es pro User folgende Einstellungen geben:
Rolle auswählen: - [ ] User (Standard: Nur Lesen) - [ ] Admin (Vollzugriff außer User-Verwaltung) - [ ] SuperAdmin (Vollzugriff auf alles)
Für User-Rolle - Individuelle Berechtigungen:
Kunden: - [ ] Anlegen - [ ] Bearbeiten - [ ] Löschen
Artikel: - [ ] Anlegen - [ ] Bearbeiten - [ ] Löschen
Bestellungen: - [ ] Anlegen - [ ] Bearbeiten - [ ] Löschen
⚠️ Hinweis: Permissions werden bei Admin/SuperAdmin ignoriert, da diese Rollen bereits Vollzugriff haben.
Sicherheitshinweise¶
- Permissions nur durch SuperAdmin änderbar
-
Firestore Rules blockieren Permission-Änderungen durch normale User
-
Admin vs SuperAdmin Unterschied
- Admins können Geschäftsdaten UND System-Settings verwalten
-
Nur SuperAdmins können User-Berechtigungen verwalten
-
Cloud Functions umgehen Rules
- Backend-Funktionen haben Admin-Zugriff unabhängig von Rules
-
Zusätzliche Prüfungen in Functions einbauen bei kritischen Operationen
-
Default Permissions
- Neue User starten immer mit
readOnly()Permissions -
Muss explizit durch SuperAdmin erweitert werden
-
Source-System Integration
- Wenn ein Quellsystem (Customer/Article/Order) aktiviert wird, werden automatisch alle entsprechenden Permissions von ALLEN Usern entfernt
- Dies verhindert, dass User Daten ändern die aus einem externen System synchronisiert werden
- Im User-Editor werden die Checkboxen disabled und eine orange Warnung angezeigt
Source-System Automatisierung¶
Beispiel-Szenarien¶
Szenario 1: Vertriebsmitarbeiter (nur Bestellungen)¶
User(
userRole: UserRoleEnum.user,
permissions: UserPermissions(
canCreateOrders: true,
canEditOrders: true,
// Kann keine Kunden/Artikel anlegen
),
)
Szenario 2: Junior Produktmanager¶
User(
userRole: UserRoleEnum.user,
permissions: UserPermissions(
canCreateArticles: true,
canEditArticles: true,
// Kann Artikel nicht löschen
),
)
Szenario 3: Customer Service¶
User(
userRole: UserRoleEnum.user,
permissions: UserPermissions(
canCreateCustomers: true,
canEditCustomers: true,
canEditOrders: true,
// Kann nichts löschen
),
)
Szenario 4: Geschäftsführer¶
User(
userRole: UserRoleEnum.admin,
// Permissions werden ignoriert - hat alle Rechte
// Kann System-Settings ändern
// Kann KEINE User-Rollen vergeben
)
Szenario 5: IT-Administrator¶
User(
userRole: UserRoleEnum.superadmin,
// Permissions werden ignoriert - hat alle Rechte
// Kann System-Settings ändern
// Kann User-Rollen vergeben
// Kann User-Permissions konfigurieren
)
Firestore Security Rules - Rollen-basierter Zugriff¶
User Rollen¶
Basierend auf UserRoleEnum in /lib/models/all_enums/user_role_enum.dart:
Rollen in Firestore Rules¶
Die Rolle wird im users Document als Index gespeichert:
// Firestore Document: /users/{userId}
{
"firstName": "Max",
"lastName": "Mustermann",
"email": "max@example.com",
"userRole": 2, // ← 0=user, 1=admin, 2=superadmin
...
}
Helper Functions¶
// Prüft ob User SuperAdmin ist (userRole == 2)
function isSuperAdmin() {
return isAuthenticated() && getUserData().userRole == 2;
}
// Prüft ob User mindestens Admin ist (userRole >= 1)
function isAdmin() {
return isAuthenticated() && getUserData().userRole >= 1;
}
// Holt die User-Daten aus der users Collection
function getUserData() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
}
Berechtigungen nach Rolle¶
🔵 User (index 0)¶
- ✅ Lesen: Articles, Customers, Orders, eigene User-Daten
- ✅ Schreiben: Articles, Customers, Orders erstellen/bearbeiten
- ✅ Lesen: System Settings
- ❌ Schreiben: System Settings
- ❌ Schreiben: Andere User
🟡 Admin (index 1)¶
- ✅ Alle Berechtigungen von User
- ✅ Zusätzlich: (kann erweitert werden)
🔴 SuperAdmin (index 2)¶
- ✅ Alle Berechtigungen von Admin
- ✅ System Settings schreiben (einzige Rolle mit Zugriff!)
- ✅ Alle User bearbeiten
- ✅ Connector Credentials verwalten
Geschützte Collections¶
System Settings - NUR SuperAdmin¶
match /system_settings/{settingId} {
allow read: if isAuthenticated(); // Alle können lesen
allow write: if isSuperAdmin(); // Nur SuperAdmin schreiben
match /push_notification_settings/{notificationId} {
allow read: if isAuthenticated();
allow write: if isSuperAdmin();
}
}
Users Collection¶
match /users/{userId} {
allow read: if isAuthenticated(); // Alle können alle User lesen
allow write: if isOwner(userId) || // Eigene Daten
isSuperAdmin(); // Oder SuperAdmin
}
Warum können alle User lesen?
- Die isSuperAdmin() Funktion muss die Rolle aus /users/{uid} lesen
- Wenn User ihre eigene Rolle nicht lesen können, funktioniert die Funktion nicht
- Nur userRole, email, name sind sichtbar (keine sensitiven Daten)
Testing¶
Test 1: Normal User versucht System Settings zu ändern¶
// User Document: userRole = 0
await db.collection('system_settings').doc('test').update({value: 'test'});
// ❌ FEHLER: Missing or insufficient permissions
Test 2: SuperAdmin ändert System Settings¶
// User Document: userRole = 2
await db.collection('system_settings').doc('test').update({value: 'test'});
// ✅ ERFOLG
Test 3: User ändert eigene Daten¶
// Eigene UID = 'abc123'
await db.collection('users').doc('abc123').update({firstName: 'Max'});
// ✅ ERFOLG
Test 4: User ändert fremde User-Daten¶
// Fremde UID = 'xyz789'
await db.collection('users').doc('xyz789').update({firstName: 'Max'});
// ❌ FEHLER: Missing or insufficient permissions
Ersten SuperAdmin erstellen¶
Option 1: Firebase Console (manuell)¶
- Gehe zu Firebase Console → Firestore
- Navigiere zu
usersCollection - Finde deinen User (nach UID oder Email)
- Bearbeite das Feld
userRole→ setze auf2
Option 2: Cloud Function (einmalig)¶
// functions/setup_first_admin.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.makeFirstSuperAdmin = functions.https.onRequest(async (req, res) => {
const email = 'admin@example.com';
// Finde User by Email
const userSnapshot = await db.collection('users')
.where('email', '==', email)
.limit(1)
.get();
if (userSnapshot.empty) {
return res.status(404).send('User not found');
}
const userId = userSnapshot.docs[0].id;
// Setze SuperAdmin Rolle
await db.collection('users').doc(userId).update({
userRole: 2 // SuperAdmin
});
res.send(`User ${email} is now SuperAdmin`);
});
Deployment:
firebase deploy --only functions:makeFirstSuperAdmin
# Aufrufen (einmalig!)
curl https://YOUR-REGION-YOUR-PROJECT.cloudfunctions.net/makeFirstSuperAdmin
# Danach löschen:
# Lösche die Function aus functions/index.js
firebase deploy --only functions
Option 3: Admin SDK Script (lokal)¶
// scripts/make_superadmin.js
const admin = require('firebase-admin');
const serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
async function makeSuperAdmin(email) {
const snapshot = await db.collection('users')
.where('email', '==', email)
.limit(1)
.get();
if (snapshot.empty) {
console.log('User not found');
return;
}
const userId = snapshot.docs[0].id;
await db.collection('users').doc(userId).update({ userRole: 2 });
console.log(`✅ ${email} is now SuperAdmin`);
}
makeSuperAdmin('admin@example.com');
Ausführen:
Performance-Hinweis¶
⚠️ Die getUserData() Funktion macht einen zusätzlichen Firestore-Read!
Bei jedem Schreibzugriff auf system_settings wird:
1. Der Request geprüft
2. Ein zusätzlicher Read auf /users/{uid} gemacht
3. Die userRole geprüft
Kosten: - Jeder Schreibversuch = 1 Write + 1 Read - Bei vielen System Settings Writes → höhere Kosten
Alternative: Custom Claims (komplexer, aber performanter) - User-Rolle wird im Auth Token gespeichert - Keine zusätzlichen Firestore Reads - Siehe: https://firebase.google.com/docs/auth/admin/custom-claims
Migration auf Production Rules¶
# 1. Backup aktuelle Rules
cp firestore.rules firestore.rules.backup
# 2. Production Rules aktivieren
cp firebase_config/firestore.rules.production firestore.rules
# 3. Ersten SuperAdmin erstellen (siehe oben)
# 4. Deployen
firebase deploy --only firestore:rules
# 5. Testen mit verschiedenen User-Rollen
Troubleshooting¶
"Missing or insufficient permissions" für normale Admins¶
→ Nur SuperAdmin (userRole=2) kann System Settings ändern → Prüfe in Firebase Console: users/{uid}/userRole sollte 2 sein
"getUserData() returns null"¶
→ Der User existiert nicht in der users Collection
→ Stelle sicher, dass bei der Registrierung ein User-Document erstellt wird
"isSuperAdmin() always returns false"¶
→ Prüfe ob request.auth.uid mit der Document-ID in users übereinstimmt
→ Prüfe ob das Feld userRole als Number (nicht String) gespeichert ist
Firebase Security Rules & Cloud Functions¶
Wichtige Unterscheidung¶
🔒 Security Rules gelten für:¶
- Client Apps (Web, iOS, Android)
- Direkte Firebase SDK Zugriffe
- Firestore REST API (mit User-Authentifizierung)
🔓 Security Rules gelten NICHT für:¶
- Cloud Functions (nutzen Admin SDK)
- Firebase Admin SDK (Server-seitig)
- Service Accounts
Wie Cloud Functions auf Firestore zugreifen¶
❌ FALSCH - Client SDK (unterliegt Security Rules)¶
// In Client-Apps - WIRD von Security Rules geprüft
import { getFirestore } from 'firebase/firestore';
const db = getFirestore();
await db.collection('orders').add({...}); // ← Unterliegt Security Rules
✅ RICHTIG - Admin SDK in Cloud Functions (umgeht Security Rules)¶
// In Cloud Functions - UMGEHT Security Rules
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
await db.collection('orders').add({...}); // ← Voller Admin-Zugriff!
Deine Functions nutzen Admin SDK¶
Schaue in /functions/index.js:
const admin = require('firebase-admin');
admin.initializeApp();
// Diese Zeile gibt deinen Functions VOLLEN Zugriff
const db = admin.firestore();
Bedeutung:
- Deine Cloud Functions können IMMER auf Firestore zugreifen
- Auch wenn Security Rules allow read, write: if false; haben
- Auch wenn keine Authentifizierung vorhanden ist
Beispiel: Statistik-Berechnung¶
// functions/internal_statistic_calculation.js
const admin = require('firebase-admin');
const db = admin.firestore();
// Cloud Function schreibt Statistiken
exports.calculateStatistics = functions.pubsub
.schedule('every 24 hours')
.onRun(async (context) => {
// ✅ Funktioniert IMMER - auch wenn Security Rules
// für normale User "write: if false" haben
await db.collection('articleSalesStatistics').doc('today').set({
totalSales: 1000,
calculatedAt: admin.firestore.FieldValue.serverTimestamp()
});
});
Security Rules:
match /articleSalesStatistics/{statId} {
allow read: if isAuthenticated();
allow write: if false; // ← Normale User können NICHT schreiben
// Aber Cloud Functions KÖNNEN trotzdem schreiben!
}
Best Practices für Production¶
1. Statistics Collections - Nur Functions schreiben¶
match /articleSalesStatistics/{statId} {
allow read: if isAuthenticated();
allow write: if false; // Nur Cloud Functions (Admin SDK) können schreiben
}
2. Orders - User können erstellen, Functions können alles¶
match /orders/{orderId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated();
allow update: if isAuthenticated() &&
(!resource.data.keys().hasAny(['sourceSystem']));
allow delete: if false; // Nur Cloud Functions können löschen
}
3. Audit Logs - Nur Functions schreiben¶
match /auditLogs/{logId} {
allow read: if request.auth != null &&
request.auth.token.admin == true; // Nur Admins
allow write: if false; // Nur Cloud Functions
}
Wie testest du das?¶
1. Security Rules testen (Client-Perspektive)¶
# Emulator starten
firebase emulators:start --only firestore
# In deiner App testen
# Sollte FEHLSCHLAGEN wenn Rules es verbieten
2. Cloud Function testen¶
# Function lokal ausführen
firebase emulators:start --only functions,firestore
# Function aufrufen
# Sollte FUNKTIONIEREN auch wenn Rules es verbieten
Zusammenfassung¶
| Zugriff über | Security Rules | Beispiel |
|---|---|---|
| Flutter App | ✅ Ja | FirebaseFirestore.instance.collection('orders') |
| Web App | ✅ Ja | getFirestore().collection('orders') |
| Cloud Function | ❌ Nein | admin.firestore().collection('orders') |
| Admin SDK (Server) | ❌ Nein | admin.firestore() |
| REST API (mit Auth) | ✅ Ja | https://firestore.googleapis.com/v1/... |
Merke:
- Wenn admin.firestore() → Voller Zugriff, Rules egal
- Wenn Firebase SDK → Security Rules gelten
- Cloud Functions = Vertrauenswürdiger Server-Code = Voller Zugriff