376 lines
11 KiB
Dart
376 lines
11 KiB
Dart
/// Moteur de permissions ultra-performant avec cache intelligent
|
|
/// Vérifications contextuelles et audit trail intégré
|
|
library permission_engine;
|
|
|
|
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import '../models/user.dart';
|
|
import '../models/user_role.dart';
|
|
import '../models/permission_matrix.dart';
|
|
|
|
/// Moteur de permissions haute performance avec cache multi-niveaux
|
|
///
|
|
/// Fonctionnalités :
|
|
/// - Cache mémoire ultra-rapide avec TTL
|
|
/// - Vérifications contextuelles avancées
|
|
/// - Audit trail automatique
|
|
/// - Support des permissions héritées
|
|
/// - Invalidation intelligente du cache
|
|
class PermissionEngine {
|
|
static final PermissionEngine _instance = PermissionEngine._internal();
|
|
factory PermissionEngine() => _instance;
|
|
PermissionEngine._internal();
|
|
|
|
/// Cache mémoire des permissions avec TTL
|
|
static final Map<String, _CachedPermission> _permissionCache = {};
|
|
|
|
/// Cache des permissions effectives par utilisateur
|
|
static final Map<String, _CachedUserPermissions> _userPermissionsCache = {};
|
|
|
|
/// Durée de vie du cache (5 minutes par défaut)
|
|
static const Duration _defaultCacheTTL = Duration(minutes: 5);
|
|
|
|
/// Durée de vie du cache pour les super admins (plus long)
|
|
static const Duration _superAdminCacheTTL = Duration(minutes: 15);
|
|
|
|
/// Compteur de hits/miss du cache pour monitoring
|
|
static int _cacheHits = 0;
|
|
static int _cacheMisses = 0;
|
|
|
|
/// Stream pour les événements d'audit
|
|
static final StreamController<PermissionAuditEvent> _auditController =
|
|
StreamController<PermissionAuditEvent>.broadcast();
|
|
|
|
/// Stream des événements d'audit
|
|
static Stream<PermissionAuditEvent> get auditStream => _auditController.stream;
|
|
|
|
/// Vérifie si un utilisateur a une permission spécifique
|
|
///
|
|
/// [user] - Utilisateur à vérifier
|
|
/// [permission] - Permission à vérifier
|
|
/// [organizationId] - Contexte organisationnel optionnel
|
|
/// [auditLog] - Activer l'audit trail (défaut: true)
|
|
static Future<bool> hasPermission(
|
|
User user,
|
|
String permission, {
|
|
String? organizationId,
|
|
bool auditLog = true,
|
|
}) async {
|
|
final cacheKey = _generateCacheKey(user.id, permission, organizationId);
|
|
|
|
// Vérification du cache
|
|
final cachedResult = _getCachedPermission(cacheKey);
|
|
if (cachedResult != null) {
|
|
_cacheHits++;
|
|
if (auditLog && !cachedResult.result) {
|
|
_logAuditEvent(user, permission, false, 'CACHED_DENIED', organizationId);
|
|
}
|
|
return cachedResult.result;
|
|
}
|
|
|
|
_cacheMisses++;
|
|
|
|
// Calcul de la permission
|
|
final result = await _computePermission(user, permission, organizationId);
|
|
|
|
// Mise en cache
|
|
_cachePermission(cacheKey, result, user.primaryRole);
|
|
|
|
// Audit trail
|
|
if (auditLog) {
|
|
_logAuditEvent(
|
|
user,
|
|
permission,
|
|
result,
|
|
result ? 'GRANTED' : 'DENIED',
|
|
organizationId,
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Vérifie plusieurs permissions en une seule fois
|
|
static Future<Map<String, bool>> hasPermissions(
|
|
User user,
|
|
List<String> permissions, {
|
|
String? organizationId,
|
|
bool auditLog = true,
|
|
}) async {
|
|
final results = <String, bool>{};
|
|
|
|
// Traitement en parallèle pour les performances
|
|
final futures = permissions.map((permission) =>
|
|
hasPermission(user, permission, organizationId: organizationId, auditLog: auditLog)
|
|
.then((result) => MapEntry(permission, result))
|
|
);
|
|
|
|
final entries = await Future.wait(futures);
|
|
for (final entry in entries) {
|
|
results[entry.key] = entry.value;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// Obtient toutes les permissions effectives d'un utilisateur
|
|
static Future<List<String>> getEffectivePermissions(
|
|
User user, {
|
|
String? organizationId,
|
|
}) async {
|
|
final cacheKey = '${user.id}_effective_${organizationId ?? 'global'}';
|
|
|
|
// Vérification du cache utilisateur
|
|
final cachedUserPermissions = _getCachedUserPermissions(cacheKey);
|
|
if (cachedUserPermissions != null) {
|
|
_cacheHits++;
|
|
return cachedUserPermissions.permissions;
|
|
}
|
|
|
|
_cacheMisses++;
|
|
|
|
// Calcul des permissions effectives
|
|
final permissions = user.getEffectivePermissions(organizationId: organizationId);
|
|
|
|
// Mise en cache
|
|
_cacheUserPermissions(cacheKey, permissions, user.primaryRole);
|
|
|
|
return permissions;
|
|
}
|
|
|
|
/// Vérifie si un utilisateur peut effectuer une action sur un domaine
|
|
static Future<bool> canPerformAction(
|
|
User user,
|
|
String domain,
|
|
String action, {
|
|
String scope = 'own',
|
|
String? organizationId,
|
|
}) async {
|
|
final permission = '$domain.$action.$scope';
|
|
return hasPermission(user, permission, organizationId: organizationId);
|
|
}
|
|
|
|
/// Invalide le cache pour un utilisateur spécifique
|
|
static void invalidateUserCache(String userId) {
|
|
final keysToRemove = <String>[];
|
|
|
|
// Invalider le cache des permissions
|
|
for (final key in _permissionCache.keys) {
|
|
if (key.startsWith('${userId}_')) {
|
|
keysToRemove.add(key);
|
|
}
|
|
}
|
|
|
|
for (final key in keysToRemove) {
|
|
_permissionCache.remove(key);
|
|
}
|
|
|
|
// Invalider le cache des permissions utilisateur
|
|
final userKeysToRemove = <String>[];
|
|
for (final key in _userPermissionsCache.keys) {
|
|
if (key.startsWith('${userId}_')) {
|
|
userKeysToRemove.add(key);
|
|
}
|
|
}
|
|
|
|
for (final key in userKeysToRemove) {
|
|
_userPermissionsCache.remove(key);
|
|
}
|
|
|
|
debugPrint('Cache invalidé pour l\'utilisateur: $userId');
|
|
}
|
|
|
|
/// Invalide tout le cache
|
|
static void invalidateAllCache() {
|
|
_permissionCache.clear();
|
|
_userPermissionsCache.clear();
|
|
debugPrint('Cache complet invalidé');
|
|
}
|
|
|
|
/// Obtient les statistiques du cache
|
|
static Map<String, dynamic> getCacheStats() {
|
|
final totalRequests = _cacheHits + _cacheMisses;
|
|
final hitRate = totalRequests > 0 ? (_cacheHits / totalRequests * 100) : 0.0;
|
|
|
|
return {
|
|
'cacheHits': _cacheHits,
|
|
'cacheMisses': _cacheMisses,
|
|
'hitRate': hitRate.toStringAsFixed(2),
|
|
'permissionCacheSize': _permissionCache.length,
|
|
'userPermissionsCacheSize': _userPermissionsCache.length,
|
|
};
|
|
}
|
|
|
|
/// Nettoie le cache expiré
|
|
static void cleanExpiredCache() {
|
|
final now = DateTime.now();
|
|
|
|
// Nettoyer le cache des permissions
|
|
_permissionCache.removeWhere((key, cached) => cached.expiresAt.isBefore(now));
|
|
|
|
// Nettoyer le cache des permissions utilisateur
|
|
_userPermissionsCache.removeWhere((key, cached) => cached.expiresAt.isBefore(now));
|
|
|
|
debugPrint('Cache expiré nettoyé');
|
|
}
|
|
|
|
// === MÉTHODES PRIVÉES ===
|
|
|
|
/// Calcule une permission sans cache
|
|
static Future<bool> _computePermission(
|
|
User user,
|
|
String permission,
|
|
String? organizationId,
|
|
) async {
|
|
// Vérification des permissions publiques
|
|
if (PermissionMatrix.isPublicPermission(permission)) {
|
|
return true;
|
|
}
|
|
|
|
// Vérification utilisateur actif
|
|
if (!user.isActive) return false;
|
|
|
|
// Vérification directe de l'utilisateur
|
|
if (user.hasPermission(permission, organizationId: organizationId)) {
|
|
return true;
|
|
}
|
|
|
|
// Vérifications contextuelles avancées
|
|
return _checkContextualPermissions(user, permission, organizationId);
|
|
}
|
|
|
|
/// Vérifications contextuelles avancées
|
|
static Future<bool> _checkContextualPermissions(
|
|
User user,
|
|
String permission,
|
|
String? organizationId,
|
|
) async {
|
|
// Logique contextuelle future (intégration avec le serveur)
|
|
// Pour l'instant, retourne false
|
|
return false;
|
|
}
|
|
|
|
/// Génère une clé de cache unique
|
|
static String _generateCacheKey(String userId, String permission, String? organizationId) {
|
|
return '${userId}_${permission}_${organizationId ?? 'global'}';
|
|
}
|
|
|
|
/// Obtient une permission depuis le cache
|
|
static _CachedPermission? _getCachedPermission(String key) {
|
|
final cached = _permissionCache[key];
|
|
if (cached != null && cached.expiresAt.isAfter(DateTime.now())) {
|
|
return cached;
|
|
}
|
|
|
|
if (cached != null) {
|
|
_permissionCache.remove(key);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// Met en cache une permission
|
|
static void _cachePermission(String key, bool result, UserRole userRole) {
|
|
final ttl = userRole == UserRole.superAdmin ? _superAdminCacheTTL : _defaultCacheTTL;
|
|
|
|
_permissionCache[key] = _CachedPermission(
|
|
result: result,
|
|
expiresAt: DateTime.now().add(ttl),
|
|
);
|
|
}
|
|
|
|
/// Obtient les permissions utilisateur depuis le cache
|
|
static _CachedUserPermissions? _getCachedUserPermissions(String key) {
|
|
final cached = _userPermissionsCache[key];
|
|
if (cached != null && cached.expiresAt.isAfter(DateTime.now())) {
|
|
return cached;
|
|
}
|
|
|
|
if (cached != null) {
|
|
_userPermissionsCache.remove(key);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// Met en cache les permissions utilisateur
|
|
static void _cacheUserPermissions(String key, List<String> permissions, UserRole userRole) {
|
|
final ttl = userRole == UserRole.superAdmin ? _superAdminCacheTTL : _defaultCacheTTL;
|
|
|
|
_userPermissionsCache[key] = _CachedUserPermissions(
|
|
permissions: permissions,
|
|
expiresAt: DateTime.now().add(ttl),
|
|
);
|
|
}
|
|
|
|
/// Enregistre un événement d'audit
|
|
static void _logAuditEvent(
|
|
User user,
|
|
String permission,
|
|
bool granted,
|
|
String reason,
|
|
String? organizationId,
|
|
) {
|
|
final event = PermissionAuditEvent(
|
|
userId: user.id,
|
|
userEmail: user.email,
|
|
permission: permission,
|
|
granted: granted,
|
|
reason: reason,
|
|
organizationId: organizationId,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
|
|
_auditController.add(event);
|
|
}
|
|
}
|
|
|
|
/// Classe pour les permissions mises en cache
|
|
class _CachedPermission {
|
|
final bool result;
|
|
final DateTime expiresAt;
|
|
|
|
_CachedPermission({required this.result, required this.expiresAt});
|
|
}
|
|
|
|
/// Classe pour les permissions utilisateur mises en cache
|
|
class _CachedUserPermissions {
|
|
final List<String> permissions;
|
|
final DateTime expiresAt;
|
|
|
|
_CachedUserPermissions({required this.permissions, required this.expiresAt});
|
|
}
|
|
|
|
/// Événement d'audit des permissions
|
|
class PermissionAuditEvent {
|
|
final String userId;
|
|
final String userEmail;
|
|
final String permission;
|
|
final bool granted;
|
|
final String reason;
|
|
final String? organizationId;
|
|
final DateTime timestamp;
|
|
|
|
PermissionAuditEvent({
|
|
required this.userId,
|
|
required this.userEmail,
|
|
required this.permission,
|
|
required this.granted,
|
|
required this.reason,
|
|
this.organizationId,
|
|
required this.timestamp,
|
|
});
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'userId': userId,
|
|
'userEmail': userEmail,
|
|
'permission': permission,
|
|
'granted': granted,
|
|
'reason': reason,
|
|
'organizationId': organizationId,
|
|
'timestamp': timestamp.toIso8601String(),
|
|
};
|
|
}
|
|
}
|