1264 lines
40 KiB
Markdown
Executable File
1264 lines
40 KiB
Markdown
Executable File
# 🔐 PERMISSIONS - Documentazione Completa NetGescon
|
|
|
|
> **Sistema Permessi dell'interfaccia universale**
|
|
> **📅 Creato:** 21/07/2025
|
|
> **🔄 Ultimo aggiornamento:** 21/07/2025
|
|
> **🎯 Priorità:** ⚡ ALTO
|
|
|
|
---
|
|
|
|
## 📋 **OVERVIEW PERMISSIONS SYSTEM**
|
|
|
|
Il Sistema Permessi NetGescon gestisce **ACCESSO GRANULARE** per:
|
|
- 👤 **User Roles** (Super Admin, Admin, Manager, User, Guest)
|
|
- 🔑 **Permissions** (Create, Read, Update, Delete, Execute)
|
|
- 🏢 **Resource Access** (Stabili, Condomini, Unità, Reports)
|
|
- 📊 **Data Visibility** (Own, Team, Company, All)
|
|
- 🎛️ **UI Components** (Navigation, Actions, Features)
|
|
|
|
### 🎯 **Security Philosophy**
|
|
- **Least Privilege** - Accesso minimo necessario per default
|
|
- **Role-Based Access** - Permessi basati su ruoli definiti
|
|
- **Resource-Level Security** - Controllo granulare per ogni risorsa
|
|
- **UI-Driven Permissions** - Interfaccia che si adatta ai permessi
|
|
|
|
---
|
|
|
|
## 👥 **ROLE SYSTEM**
|
|
|
|
### 🔑 **Role Definitions**
|
|
```php
|
|
// app/Models/User.php
|
|
|
|
class User extends Authenticatable implements HasMedia
|
|
{
|
|
// Role constants
|
|
const ROLE_SUPER_ADMIN = 'super_admin';
|
|
const ROLE_ADMIN = 'admin';
|
|
const ROLE_MANAGER = 'manager';
|
|
const ROLE_USER = 'user';
|
|
const ROLE_GUEST = 'guest';
|
|
|
|
// Permission levels
|
|
const PERMISSION_ALL = 'all';
|
|
const PERMISSION_COMPANY = 'company';
|
|
const PERMISSION_TEAM = 'team';
|
|
const PERMISSION_OWN = 'own';
|
|
const PERMISSION_NONE = 'none';
|
|
|
|
/**
|
|
* Get user role
|
|
*/
|
|
public function getRole()
|
|
{
|
|
return $this->role ?? self::ROLE_USER;
|
|
}
|
|
|
|
/**
|
|
* Check if user has role
|
|
*/
|
|
public function hasRole($role)
|
|
{
|
|
return $this->getRole() === $role;
|
|
}
|
|
|
|
/**
|
|
* Check if user has any of the roles
|
|
*/
|
|
public function hasAnyRole(array $roles)
|
|
{
|
|
return in_array($this->getRole(), $roles);
|
|
}
|
|
|
|
/**
|
|
* Check if user is admin or above
|
|
*/
|
|
public function isAdmin()
|
|
{
|
|
return $this->hasAnyRole([
|
|
self::ROLE_SUPER_ADMIN,
|
|
self::ROLE_ADMIN
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Check if user is manager or above
|
|
*/
|
|
public function isManager()
|
|
{
|
|
return $this->hasAnyRole([
|
|
self::ROLE_SUPER_ADMIN,
|
|
self::ROLE_ADMIN,
|
|
self::ROLE_MANAGER
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get permission level for resource
|
|
*/
|
|
public function getPermissionLevel($resource)
|
|
{
|
|
$permissions = $this->permissions ?? [];
|
|
return $permissions[$resource] ?? $this->getDefaultPermissionLevel();
|
|
}
|
|
|
|
/**
|
|
* Get default permission level based on role
|
|
*/
|
|
public function getDefaultPermissionLevel()
|
|
{
|
|
return match($this->getRole()) {
|
|
self::ROLE_SUPER_ADMIN => self::PERMISSION_ALL,
|
|
self::ROLE_ADMIN => self::PERMISSION_COMPANY,
|
|
self::ROLE_MANAGER => self::PERMISSION_TEAM,
|
|
self::ROLE_USER => self::PERMISSION_OWN,
|
|
self::ROLE_GUEST => self::PERMISSION_NONE,
|
|
default => self::PERMISSION_NONE
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### 🏢 **Role Matrix**
|
|
```php
|
|
// config/roles.php
|
|
|
|
return [
|
|
'roles' => [
|
|
'super_admin' => [
|
|
'name' => 'Super Amministratore',
|
|
'description' => 'Accesso completo al sistema',
|
|
'color' => 'danger',
|
|
'icon' => 'fas fa-crown',
|
|
'permissions' => [
|
|
'users' => 'all',
|
|
'companies' => 'all',
|
|
'stabili' => 'all',
|
|
'condomini' => 'all',
|
|
'unita' => 'all',
|
|
'reports' => 'all',
|
|
'settings' => 'all',
|
|
'logs' => 'all'
|
|
]
|
|
],
|
|
|
|
'admin' => [
|
|
'name' => 'Amministratore',
|
|
'description' => 'Gestione completa della company',
|
|
'color' => 'warning',
|
|
'icon' => 'fas fa-user-shield',
|
|
'permissions' => [
|
|
'users' => 'company',
|
|
'stabili' => 'company',
|
|
'condomini' => 'company',
|
|
'unita' => 'company',
|
|
'reports' => 'company',
|
|
'settings' => 'company'
|
|
]
|
|
],
|
|
|
|
'manager' => [
|
|
'name' => 'Manager',
|
|
'description' => 'Gestione team e risorse assegnate',
|
|
'color' => 'info',
|
|
'icon' => 'fas fa-user-tie',
|
|
'permissions' => [
|
|
'users' => 'team',
|
|
'stabili' => 'team',
|
|
'condomini' => 'team',
|
|
'unita' => 'team',
|
|
'reports' => 'team'
|
|
]
|
|
],
|
|
|
|
'user' => [
|
|
'name' => 'Utente',
|
|
'description' => 'Accesso alle proprie risorse',
|
|
'color' => 'success',
|
|
'icon' => 'fas fa-user',
|
|
'permissions' => [
|
|
'stabili' => 'own',
|
|
'condomini' => 'own',
|
|
'unita' => 'own',
|
|
'reports' => 'own'
|
|
]
|
|
],
|
|
|
|
'guest' => [
|
|
'name' => 'Ospite',
|
|
'description' => 'Accesso solo lettura limitato',
|
|
'color' => 'secondary',
|
|
'icon' => 'fas fa-eye',
|
|
'permissions' => [
|
|
'stabili' => 'none',
|
|
'condomini' => 'none',
|
|
'unita' => 'none',
|
|
'reports' => 'none'
|
|
]
|
|
]
|
|
],
|
|
|
|
'resources' => [
|
|
'users' => 'Gestione Utenti',
|
|
'companies' => 'Gestione Aziende',
|
|
'stabili' => 'Gestione Stabili',
|
|
'condomini' => 'Gestione Condomini',
|
|
'unita' => 'Unità Immobiliari',
|
|
'reports' => 'Reports e Analytics',
|
|
'settings' => 'Impostazioni Sistema',
|
|
'logs' => 'Logs di Sistema'
|
|
],
|
|
|
|
'actions' => [
|
|
'create' => 'Creare',
|
|
'read' => 'Visualizzare',
|
|
'update' => 'Modificare',
|
|
'delete' => 'Eliminare',
|
|
'execute' => 'Eseguire'
|
|
]
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 🛡️ **PERMISSION MIDDLEWARE**
|
|
|
|
### 🔐 **Role Middleware**
|
|
```php
|
|
// app/Http/Middleware/CheckRole.php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class CheckRole
|
|
{
|
|
/**
|
|
* Handle an incoming request.
|
|
*/
|
|
public function handle(Request $request, Closure $next, ...$roles)
|
|
{
|
|
if (!Auth::check()) {
|
|
return redirect('login');
|
|
}
|
|
|
|
$user = Auth::user();
|
|
|
|
if (!$user->hasAnyRole($roles)) {
|
|
abort(403, 'Non hai i permessi per accedere a questa risorsa.');
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 🔑 **Permission Middleware**
|
|
```php
|
|
// app/Http/Middleware/CheckPermission.php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class CheckPermission
|
|
{
|
|
/**
|
|
* Handle an incoming request.
|
|
*/
|
|
public function handle(Request $request, Closure $next, string $resource, string $action = 'read')
|
|
{
|
|
if (!Auth::check()) {
|
|
return redirect('login');
|
|
}
|
|
|
|
$user = Auth::user();
|
|
|
|
if (!$this->hasPermission($user, $resource, $action)) {
|
|
if ($request->expectsJson()) {
|
|
return response()->json([
|
|
'message' => 'Non hai i permessi per eseguire questa azione.'
|
|
], 403);
|
|
}
|
|
|
|
abort(403, 'Non hai i permessi per eseguire questa azione.');
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
|
|
/**
|
|
* Check if user has permission for resource action
|
|
*/
|
|
private function hasPermission($user, $resource, $action)
|
|
{
|
|
$permissionLevel = $user->getPermissionLevel($resource);
|
|
|
|
// Super admin has all permissions
|
|
if ($user->hasRole(User::ROLE_SUPER_ADMIN)) {
|
|
return true;
|
|
}
|
|
|
|
// No permission
|
|
if ($permissionLevel === User::PERMISSION_NONE) {
|
|
return false;
|
|
}
|
|
|
|
// All permission
|
|
if ($permissionLevel === User::PERMISSION_ALL) {
|
|
return true;
|
|
}
|
|
|
|
// Check specific action permissions
|
|
return $this->checkActionPermission($user, $resource, $action, $permissionLevel);
|
|
}
|
|
|
|
/**
|
|
* Check action-specific permissions
|
|
*/
|
|
private function checkActionPermission($user, $resource, $action, $permissionLevel)
|
|
{
|
|
// Define action requirements
|
|
$actionPermissions = [
|
|
'create' => ['company', 'team', 'own'],
|
|
'read' => ['company', 'team', 'own'],
|
|
'update' => ['company', 'team', 'own'],
|
|
'delete' => ['company', 'team'],
|
|
'execute' => ['company', 'team']
|
|
];
|
|
|
|
$requiredLevels = $actionPermissions[$action] ?? ['company'];
|
|
|
|
return in_array($permissionLevel, $requiredLevels);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎛️ **UI PERMISSION GATES**
|
|
|
|
### 🎨 **Blade Directives**
|
|
```php
|
|
// app/Providers/AppServiceProvider.php
|
|
|
|
use Illuminate\Support\Facades\Blade;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
public function boot()
|
|
{
|
|
// Role-based directives
|
|
Blade::if('role', function ($role) {
|
|
return Auth::check() && Auth::user()->hasRole($role);
|
|
});
|
|
|
|
Blade::if('anyrole', function (...$roles) {
|
|
return Auth::check() && Auth::user()->hasAnyRole($roles);
|
|
});
|
|
|
|
// Permission-based directives
|
|
Blade::if('can', function ($resource, $action = 'read') {
|
|
if (!Auth::check()) return false;
|
|
|
|
$user = Auth::user();
|
|
$permissionLevel = $user->getPermissionLevel($resource);
|
|
|
|
if ($user->hasRole(User::ROLE_SUPER_ADMIN)) return true;
|
|
if ($permissionLevel === User::PERMISSION_NONE) return false;
|
|
if ($permissionLevel === User::PERMISSION_ALL) return true;
|
|
|
|
// Check action permissions
|
|
$actionPermissions = [
|
|
'create' => ['company', 'team', 'own'],
|
|
'read' => ['company', 'team', 'own'],
|
|
'update' => ['company', 'team', 'own'],
|
|
'delete' => ['company', 'team'],
|
|
'execute' => ['company', 'team']
|
|
];
|
|
|
|
$requiredLevels = $actionPermissions[$action] ?? ['company'];
|
|
return in_array($permissionLevel, $requiredLevels);
|
|
});
|
|
|
|
// Admin check
|
|
Blade::if('admin', function () {
|
|
return Auth::check() && Auth::user()->isAdmin();
|
|
});
|
|
|
|
// Manager check
|
|
Blade::if('manager', function () {
|
|
return Auth::check() && Auth::user()->isManager();
|
|
});
|
|
}
|
|
```
|
|
|
|
### 🧩 **Permission Components**
|
|
```blade
|
|
<!-- resources/views/components/permission-gate.blade.php -->
|
|
@props([
|
|
'resource' => '',
|
|
'action' => 'read',
|
|
'role' => null,
|
|
'fallback' => null
|
|
])
|
|
|
|
@php
|
|
$hasPermission = false;
|
|
|
|
if ($role) {
|
|
$hasPermission = Auth::check() && Auth::user()->hasAnyRole(is_array($role) ? $role : [$role]);
|
|
} elseif ($resource) {
|
|
$hasPermission = Auth::check() && Auth::user()->hasPermission($resource, $action);
|
|
}
|
|
@endphp
|
|
|
|
@if($hasPermission)
|
|
{{ $slot }}
|
|
@elseif($fallback)
|
|
{!! $fallback !!}
|
|
@endif
|
|
```
|
|
|
|
```blade
|
|
<!-- resources/views/components/role-badge.blade.php -->
|
|
@props(['user' => null])
|
|
|
|
@php
|
|
$user = $user ?? Auth::user();
|
|
$roleConfig = config('roles.roles')[$user->getRole()] ?? null;
|
|
@endphp
|
|
|
|
@if($roleConfig)
|
|
<span class="badge bg-{{ $roleConfig['color'] }} badge-role"
|
|
data-bs-toggle="tooltip"
|
|
title="{{ $roleConfig['description'] }}">
|
|
<i class="{{ $roleConfig['icon'] }} me-1"></i>
|
|
{{ $roleConfig['name'] }}
|
|
</span>
|
|
@endif
|
|
```
|
|
|
|
---
|
|
|
|
## 🔧 **JAVASCRIPT PERMISSIONS**
|
|
|
|
### ⚡ **Permission Manager**
|
|
```javascript
|
|
// /public/js/permissions/permission-manager.js
|
|
|
|
class PermissionManager {
|
|
constructor() {
|
|
this.permissions = {};
|
|
this.user = null;
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
// Load user permissions from API or meta tags
|
|
await this.loadPermissions();
|
|
|
|
// Apply UI permissions
|
|
this.applyUIPermissions();
|
|
|
|
// Setup dynamic permission checks
|
|
this.setupDynamicChecks();
|
|
}
|
|
|
|
async loadPermissions() {
|
|
try {
|
|
// Try to load from meta tag first
|
|
const permissionsMeta = document.querySelector('meta[name="user-permissions"]');
|
|
if (permissionsMeta) {
|
|
this.permissions = JSON.parse(permissionsMeta.content);
|
|
return;
|
|
}
|
|
|
|
// Fallback to API
|
|
const response = await fetch('/api/user/permissions');
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
this.permissions = data.permissions;
|
|
this.user = data.user;
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to load permissions:', error);
|
|
this.permissions = {};
|
|
}
|
|
}
|
|
|
|
applyUIPermissions() {
|
|
// Hide/show elements based on permissions
|
|
document.querySelectorAll('[data-permission]').forEach(element => {
|
|
const permission = element.dataset.permission;
|
|
const action = element.dataset.action || 'read';
|
|
|
|
if (!this.can(permission, action)) {
|
|
element.style.display = 'none';
|
|
element.setAttribute('aria-hidden', 'true');
|
|
}
|
|
});
|
|
|
|
// Hide/show based on roles
|
|
document.querySelectorAll('[data-role]').forEach(element => {
|
|
const roles = element.dataset.role.split(',');
|
|
|
|
if (!this.hasAnyRole(roles)) {
|
|
element.style.display = 'none';
|
|
element.setAttribute('aria-hidden', 'true');
|
|
}
|
|
});
|
|
|
|
// Disable buttons/links
|
|
document.querySelectorAll('[data-permission-action]').forEach(element => {
|
|
const permission = element.dataset.permissionAction;
|
|
const action = element.dataset.action || 'execute';
|
|
|
|
if (!this.can(permission, action)) {
|
|
element.disabled = true;
|
|
element.classList.add('disabled');
|
|
element.setAttribute('aria-disabled', 'true');
|
|
|
|
// Remove click handlers
|
|
element.onclick = (e) => {
|
|
e.preventDefault();
|
|
this.showPermissionDenied();
|
|
return false;
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
setupDynamicChecks() {
|
|
// Intercept form submissions
|
|
document.addEventListener('submit', (e) => {
|
|
const form = e.target;
|
|
const permission = form.dataset.permission;
|
|
const action = form.dataset.action || 'create';
|
|
|
|
if (permission && !this.can(permission, action)) {
|
|
e.preventDefault();
|
|
this.showPermissionDenied();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Intercept navigation
|
|
document.addEventListener('click', (e) => {
|
|
const link = e.target.closest('a[data-permission]');
|
|
if (link) {
|
|
const permission = link.dataset.permission;
|
|
const action = link.dataset.action || 'read';
|
|
|
|
if (!this.can(permission, action)) {
|
|
e.preventDefault();
|
|
this.showPermissionDenied();
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Intercept AJAX requests
|
|
const originalFetch = window.fetch;
|
|
window.fetch = (...args) => {
|
|
const [url, options = {}] = args;
|
|
|
|
// Check if request has permission requirements
|
|
if (options.permission) {
|
|
const action = options.action || 'read';
|
|
if (!this.can(options.permission, action)) {
|
|
return Promise.reject(new Error('Permission denied'));
|
|
}
|
|
}
|
|
|
|
return originalFetch.apply(this, args);
|
|
};
|
|
}
|
|
|
|
can(resource, action = 'read') {
|
|
// Super admin can do everything
|
|
if (this.hasRole('super_admin')) {
|
|
return true;
|
|
}
|
|
|
|
const permissionLevel = this.permissions[resource];
|
|
|
|
if (!permissionLevel || permissionLevel === 'none') {
|
|
return false;
|
|
}
|
|
|
|
if (permissionLevel === 'all') {
|
|
return true;
|
|
}
|
|
|
|
// Check action-specific permissions
|
|
const actionPermissions = {
|
|
'create': ['company', 'team', 'own'],
|
|
'read': ['company', 'team', 'own'],
|
|
'update': ['company', 'team', 'own'],
|
|
'delete': ['company', 'team'],
|
|
'execute': ['company', 'team']
|
|
};
|
|
|
|
const requiredLevels = actionPermissions[action] || ['company'];
|
|
return requiredLevels.includes(permissionLevel);
|
|
}
|
|
|
|
hasRole(role) {
|
|
return this.user && this.user.role === role;
|
|
}
|
|
|
|
hasAnyRole(roles) {
|
|
return this.user && roles.includes(this.user.role);
|
|
}
|
|
|
|
isAdmin() {
|
|
return this.hasAnyRole(['super_admin', 'admin']);
|
|
}
|
|
|
|
isManager() {
|
|
return this.hasAnyRole(['super_admin', 'admin', 'manager']);
|
|
}
|
|
|
|
showPermissionDenied() {
|
|
if (window.NetGesconNotifications) {
|
|
NetGesconNotifications.error('Non hai i permessi per eseguire questa azione.');
|
|
} else {
|
|
alert('Non hai i permessi per eseguire questa azione.');
|
|
}
|
|
}
|
|
|
|
// Permission-based navigation
|
|
navigate(url, permission = null, action = 'read') {
|
|
if (permission && !this.can(permission, action)) {
|
|
this.showPermissionDenied();
|
|
return false;
|
|
}
|
|
|
|
window.location.href = url;
|
|
return true;
|
|
}
|
|
|
|
// Dynamic permission checking for AJAX
|
|
async checkPermissionAPI(resource, action = 'read') {
|
|
try {
|
|
const response = await fetch('/api/check-permission', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
},
|
|
body: JSON.stringify({ resource, action })
|
|
});
|
|
|
|
return response.ok;
|
|
} catch (error) {
|
|
console.error('Permission check failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Refresh permissions
|
|
async refreshPermissions() {
|
|
await this.loadPermissions();
|
|
this.applyUIPermissions();
|
|
}
|
|
}
|
|
|
|
// Initialize permission manager
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.PermissionManager = new PermissionManager();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 📱 **RESPONSIVE PERMISSIONS**
|
|
|
|
### 🎛️ **Permission-Aware Navigation**
|
|
```blade
|
|
<!-- resources/views/components/navigation/sidebar.blade.php -->
|
|
<nav class="netgescon-sidebar" id="sidebar">
|
|
<div class="sidebar-content">
|
|
|
|
<!-- Dashboard - Available to all authenticated users -->
|
|
<div class="nav-section">
|
|
<a href="{{ route('admin.dashboard') }}"
|
|
class="nav-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
|
<i class="fas fa-tachometer-alt nav-icon"></i>
|
|
<span class="nav-text">Dashboard</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Gestione Stabili -->
|
|
<x-permission-gate resource="stabili" action="read">
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">Gestione Immobiliare</div>
|
|
|
|
<!-- Stabili -->
|
|
<a href="{{ route('admin.stabili.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.stabili.*') ? 'active' : '' }}">
|
|
<i class="fas fa-building nav-icon"></i>
|
|
<span class="nav-text">Stabili</span>
|
|
<x-permission-gate resource="stabili" action="create">
|
|
<span class="nav-badge bg-primary">+</span>
|
|
</x-permission-gate>
|
|
</a>
|
|
|
|
<!-- Condomini -->
|
|
<a href="{{ route('admin.condomini.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.condomini.*') ? 'active' : '' }}"
|
|
data-permission="condomini"
|
|
data-action="read">
|
|
<i class="fas fa-home nav-icon"></i>
|
|
<span class="nav-text">Condomini</span>
|
|
</a>
|
|
|
|
<!-- Unità Immobiliari -->
|
|
<a href="{{ route('admin.unita.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.unita.*') ? 'active' : '' }}"
|
|
data-permission="unita"
|
|
data-action="read">
|
|
<i class="fas fa-door-open nav-icon"></i>
|
|
<span class="nav-text">Unità</span>
|
|
</a>
|
|
</div>
|
|
</x-permission-gate>
|
|
|
|
<!-- Reports e Analytics -->
|
|
<x-permission-gate resource="reports" action="read">
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">Reports</div>
|
|
|
|
<a href="{{ route('admin.reports.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.reports.*') ? 'active' : '' }}">
|
|
<i class="fas fa-chart-bar nav-icon"></i>
|
|
<span class="nav-text">Analytics</span>
|
|
</a>
|
|
|
|
<x-permission-gate resource="reports" action="execute">
|
|
<a href="{{ route('admin.reports.financial') }}"
|
|
class="nav-link">
|
|
<i class="fas fa-euro-sign nav-icon"></i>
|
|
<span class="nav-text">Report Finanziari</span>
|
|
</a>
|
|
</x-permission-gate>
|
|
</div>
|
|
</x-permission-gate>
|
|
|
|
<!-- Gestione Utenti - Solo per Admin -->
|
|
<x-permission-gate role="admin">
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">Amministrazione</div>
|
|
|
|
<a href="{{ route('admin.users.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.users.*') ? 'active' : '' }}">
|
|
<i class="fas fa-users nav-icon"></i>
|
|
<span class="nav-text">Utenti</span>
|
|
</a>
|
|
|
|
<a href="{{ route('admin.settings.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.settings.*') ? 'active' : '' }}"
|
|
data-permission="settings"
|
|
data-action="read">
|
|
<i class="fas fa-cog nav-icon"></i>
|
|
<span class="nav-text">Impostazioni</span>
|
|
</a>
|
|
</div>
|
|
</x-permission-gate>
|
|
|
|
<!-- System Logs - Solo per Super Admin -->
|
|
<x-permission-gate role="super_admin">
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">Sistema</div>
|
|
|
|
<a href="{{ route('admin.logs.index') }}"
|
|
class="nav-link {{ request()->routeIs('admin.logs.*') ? 'active' : '' }}">
|
|
<i class="fas fa-file-alt nav-icon"></i>
|
|
<span class="nav-text">Logs</span>
|
|
</a>
|
|
</div>
|
|
</x-permission-gate>
|
|
|
|
</div>
|
|
</nav>
|
|
```
|
|
|
|
### 🎯 **Permission-Based Actions**
|
|
```blade
|
|
<!-- resources/views/admin/stabili/index.blade.php -->
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Gestione Stabili</h1>
|
|
<p class="text-muted">Gestisci tutti gli stabili del tuo portafoglio</p>
|
|
</div>
|
|
|
|
<div class="page-actions">
|
|
<!-- Create Action - Only if user can create -->
|
|
<x-permission-gate resource="stabili" action="create">
|
|
<a href="{{ route('admin.stabili.create') }}"
|
|
class="btn btn-primary">
|
|
<i class="fas fa-plus mr-2"></i>
|
|
Nuovo Stabile
|
|
</a>
|
|
</x-permission-gate>
|
|
|
|
<!-- Export Action - Only if user can execute -->
|
|
<x-permission-gate resource="stabili" action="execute">
|
|
<button class="btn btn-outline-secondary"
|
|
onclick="exportStabili()"
|
|
data-permission-action="stabili"
|
|
data-action="execute">
|
|
<i class="fas fa-download mr-2"></i>
|
|
Esporta
|
|
</button>
|
|
</x-permission-gate>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Table with Permission-Based Actions -->
|
|
<div class="netgescon-card">
|
|
<div class="table-responsive">
|
|
<table class="table netgescon-table" id="stabili-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Nome</th>
|
|
<th>Indirizzo</th>
|
|
<th>Unità</th>
|
|
<th>Valore</th>
|
|
<th class="text-center">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($stabili as $stabile)
|
|
<tr>
|
|
<td>
|
|
<strong>{{ $stabile->nome }}</strong>
|
|
<x-role-badge :user="$stabile->responsible" />
|
|
</td>
|
|
<td>{{ $stabile->indirizzo_completo }}</td>
|
|
<td>{{ $stabile->unita_count }}</td>
|
|
<td>{{ number_format($stabile->valore, 0, ',', '.') }} €</td>
|
|
<td class="text-center">
|
|
<div class="btn-group">
|
|
<!-- View Action - Always available if user can read -->
|
|
<a href="{{ route('admin.stabili.show', $stabile) }}"
|
|
class="btn btn-sm btn-outline-primary"
|
|
data-bs-toggle="tooltip"
|
|
title="Visualizza">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
|
|
<!-- Edit Action - Only if user can update -->
|
|
<x-permission-gate resource="stabili" action="update">
|
|
<a href="{{ route('admin.stabili.edit', $stabile) }}"
|
|
class="btn btn-sm btn-outline-secondary"
|
|
data-bs-toggle="tooltip"
|
|
title="Modifica">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
</x-permission-gate>
|
|
|
|
<!-- Delete Action - Only if user can delete -->
|
|
<x-permission-gate resource="stabili" action="delete">
|
|
<button class="btn btn-sm btn-outline-danger"
|
|
onclick="deleteStabile({{ $stabile->id }})"
|
|
data-permission-action="stabili"
|
|
data-action="delete"
|
|
data-bs-toggle="tooltip"
|
|
title="Elimina">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</x-permission-gate>
|
|
|
|
<!-- Reports Action - Only if user can execute reports -->
|
|
<x-permission-gate resource="reports" action="execute">
|
|
<button class="btn btn-sm btn-outline-info"
|
|
onclick="generateReport({{ $stabile->id }})"
|
|
data-permission-action="reports"
|
|
data-action="execute"
|
|
data-bs-toggle="tooltip"
|
|
title="Report">
|
|
<i class="fas fa-chart-bar"></i>
|
|
</button>
|
|
</x-permission-gate>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 **DYNAMIC PERMISSIONS**
|
|
|
|
### 📡 **Real-time Permission Updates**
|
|
```javascript
|
|
// /public/js/permissions/dynamic-permissions.js
|
|
|
|
class DynamicPermissions {
|
|
constructor() {
|
|
this.websocket = null;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 5;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Connect to WebSocket for real-time updates
|
|
this.connectWebSocket();
|
|
|
|
// Setup periodic permission refresh
|
|
this.setupPeriodicRefresh();
|
|
|
|
// Listen for permission change events
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
connectWebSocket() {
|
|
if (!window.WebSocket) return;
|
|
|
|
try {
|
|
const wsUrl = `wss://${window.location.host}/ws/permissions`;
|
|
this.websocket = new WebSocket(wsUrl);
|
|
|
|
this.websocket.onopen = () => {
|
|
console.log('Permission WebSocket connected');
|
|
this.reconnectAttempts = 0;
|
|
};
|
|
|
|
this.websocket.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
this.handlePermissionUpdate(data);
|
|
};
|
|
|
|
this.websocket.onclose = () => {
|
|
console.log('Permission WebSocket disconnected');
|
|
this.attemptReconnect();
|
|
};
|
|
|
|
this.websocket.onerror = (error) => {
|
|
console.error('Permission WebSocket error:', error);
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('Failed to connect to permission WebSocket:', error);
|
|
}
|
|
}
|
|
|
|
attemptReconnect() {
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
this.reconnectAttempts++;
|
|
const delay = Math.pow(2, this.reconnectAttempts) * 1000;
|
|
|
|
setTimeout(() => {
|
|
console.log(`Attempting to reconnect permission WebSocket (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
this.connectWebSocket();
|
|
}, delay);
|
|
}
|
|
}
|
|
|
|
setupPeriodicRefresh() {
|
|
// Refresh permissions every 5 minutes
|
|
setInterval(() => {
|
|
this.refreshPermissions();
|
|
}, 5 * 60 * 1000);
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Listen for custom permission events
|
|
document.addEventListener('netgescon:permission:changed', (e) => {
|
|
this.handlePermissionUpdate(e.detail);
|
|
});
|
|
|
|
// Listen for role changes
|
|
document.addEventListener('netgescon:role:changed', (e) => {
|
|
this.handleRoleChange(e.detail);
|
|
});
|
|
|
|
// Listen for user context changes
|
|
document.addEventListener('netgescon:user:context:changed', (e) => {
|
|
this.refreshPermissions();
|
|
});
|
|
}
|
|
|
|
handlePermissionUpdate(data) {
|
|
const { userId, permissions, changes } = data;
|
|
|
|
// Check if update is for current user
|
|
const currentUserId = window.NetGesconApp?.user?.id;
|
|
if (userId && userId !== currentUserId) {
|
|
return;
|
|
}
|
|
|
|
// Update local permissions
|
|
if (permissions) {
|
|
window.PermissionManager.permissions = permissions;
|
|
window.PermissionManager.applyUIPermissions();
|
|
}
|
|
|
|
// Handle specific changes
|
|
if (changes) {
|
|
changes.forEach(change => {
|
|
this.handleSpecificChange(change);
|
|
});
|
|
}
|
|
|
|
// Show notification if permissions were revoked
|
|
const revokedPermissions = changes?.filter(c => c.type === 'revoked') || [];
|
|
if (revokedPermissions.length > 0) {
|
|
this.showPermissionRevokedNotification(revokedPermissions);
|
|
}
|
|
|
|
// Dispatch event
|
|
document.dispatchEvent(new CustomEvent('netgescon:permissions:updated', {
|
|
detail: { permissions, changes }
|
|
}));
|
|
}
|
|
|
|
handleSpecificChange(change) {
|
|
const { resource, action, type, oldLevel, newLevel } = change;
|
|
|
|
switch (type) {
|
|
case 'granted':
|
|
this.enablePermissionElements(resource, action);
|
|
break;
|
|
|
|
case 'revoked':
|
|
this.disablePermissionElements(resource, action);
|
|
break;
|
|
|
|
case 'modified':
|
|
this.updatePermissionLevel(resource, oldLevel, newLevel);
|
|
break;
|
|
}
|
|
}
|
|
|
|
enablePermissionElements(resource, action) {
|
|
const selector = `[data-permission="${resource}"][data-action="${action}"], [data-permission-action="${resource}"][data-action="${action}"]`;
|
|
const elements = document.querySelectorAll(selector);
|
|
|
|
elements.forEach(element => {
|
|
element.style.display = '';
|
|
element.disabled = false;
|
|
element.classList.remove('disabled');
|
|
element.removeAttribute('aria-hidden');
|
|
element.removeAttribute('aria-disabled');
|
|
});
|
|
}
|
|
|
|
disablePermissionElements(resource, action) {
|
|
const selector = `[data-permission="${resource}"][data-action="${action}"], [data-permission-action="${resource}"][data-action="${action}"]`;
|
|
const elements = document.querySelectorAll(selector);
|
|
|
|
elements.forEach(element => {
|
|
element.style.display = 'none';
|
|
element.disabled = true;
|
|
element.classList.add('disabled');
|
|
element.setAttribute('aria-hidden', 'true');
|
|
element.setAttribute('aria-disabled', 'true');
|
|
});
|
|
}
|
|
|
|
updatePermissionLevel(resource, oldLevel, newLevel) {
|
|
// Update all elements related to this resource
|
|
const elements = document.querySelectorAll(`[data-permission="${resource}"]`);
|
|
|
|
elements.forEach(element => {
|
|
const action = element.dataset.action || 'read';
|
|
const hasPermission = window.PermissionManager.can(resource, action);
|
|
|
|
if (hasPermission) {
|
|
this.enablePermissionElements(resource, action);
|
|
} else {
|
|
this.disablePermissionElements(resource, action);
|
|
}
|
|
});
|
|
}
|
|
|
|
handleRoleChange(data) {
|
|
const { userId, oldRole, newRole } = data;
|
|
|
|
// Check if change is for current user
|
|
const currentUserId = window.NetGesconApp?.user?.id;
|
|
if (userId && userId !== currentUserId) {
|
|
return;
|
|
}
|
|
|
|
// Update user role
|
|
if (window.PermissionManager.user) {
|
|
window.PermissionManager.user.role = newRole;
|
|
}
|
|
|
|
// Refresh all permissions
|
|
this.refreshPermissions();
|
|
|
|
// Show role change notification
|
|
this.showRoleChangeNotification(oldRole, newRole);
|
|
|
|
// Potentially redirect if role was downgraded
|
|
if (this.isRoleDowngrade(oldRole, newRole)) {
|
|
this.handleRoleDowngrade(newRole);
|
|
}
|
|
}
|
|
|
|
async refreshPermissions() {
|
|
try {
|
|
await window.PermissionManager.refreshPermissions();
|
|
console.log('Permissions refreshed successfully');
|
|
} catch (error) {
|
|
console.error('Failed to refresh permissions:', error);
|
|
}
|
|
}
|
|
|
|
showPermissionRevokedNotification(revokedPermissions) {
|
|
const resources = revokedPermissions.map(p => p.resource).join(', ');
|
|
|
|
if (window.NetGesconNotifications) {
|
|
NetGesconNotifications.warning(
|
|
`I tuoi permessi per ${resources} sono stati modificati. Alcune funzionalità potrebbero non essere più disponibili.`,
|
|
{ duration: 10000 }
|
|
);
|
|
}
|
|
}
|
|
|
|
showRoleChangeNotification(oldRole, newRole) {
|
|
const roleNames = {
|
|
'super_admin': 'Super Amministratore',
|
|
'admin': 'Amministratore',
|
|
'manager': 'Manager',
|
|
'user': 'Utente',
|
|
'guest': 'Ospite'
|
|
};
|
|
|
|
const message = `Il tuo ruolo è stato cambiato da ${roleNames[oldRole]} a ${roleNames[newRole]}.`;
|
|
|
|
if (window.NetGesconNotifications) {
|
|
NetGesconNotifications.info(message, { duration: 8000 });
|
|
}
|
|
}
|
|
|
|
isRoleDowngrade(oldRole, newRole) {
|
|
const roleHierarchy = {
|
|
'super_admin': 5,
|
|
'admin': 4,
|
|
'manager': 3,
|
|
'user': 2,
|
|
'guest': 1
|
|
};
|
|
|
|
return (roleHierarchy[newRole] || 0) < (roleHierarchy[oldRole] || 0);
|
|
}
|
|
|
|
handleRoleDowngrade(newRole) {
|
|
// If user was downgraded to guest, redirect to limited dashboard
|
|
if (newRole === 'guest') {
|
|
setTimeout(() => {
|
|
window.location.href = '/dashboard/limited';
|
|
}, 3000);
|
|
}
|
|
|
|
// If user lost admin privileges while on admin page, redirect
|
|
if (!['super_admin', 'admin'].includes(newRole) && window.location.pathname.includes('/admin')) {
|
|
setTimeout(() => {
|
|
window.location.href = '/dashboard';
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
// Manual permission check for critical actions
|
|
async verifyPermission(resource, action) {
|
|
try {
|
|
const response = await fetch('/api/verify-permission', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
},
|
|
body: JSON.stringify({ resource, action })
|
|
});
|
|
|
|
return response.ok;
|
|
} catch (error) {
|
|
console.error('Permission verification failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize dynamic permissions
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.DynamicPermissions = new DynamicPermissions();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 **FILES CORRELATI**
|
|
|
|
### 🔐 **Permission System Files**
|
|
- `/app/Http/Middleware/CheckRole.php` - Role middleware
|
|
- `/app/Http/Middleware/CheckPermission.php` - Permission middleware
|
|
- `/app/Models/User.php` - User model with permissions
|
|
- `/config/roles.php` - Role and permission configuration
|
|
|
|
### 🧩 **Blade Components**
|
|
- `/resources/views/components/permission-gate.blade.php`
|
|
- `/resources/views/components/role-badge.blade.php`
|
|
- `/resources/views/components/navigation/sidebar.blade.php`
|
|
|
|
### ⚡ **JavaScript Files**
|
|
- `/public/js/permissions/permission-manager.js`
|
|
- `/public/js/permissions/dynamic-permissions.js`
|
|
|
|
### 🎨 **CSS Files**
|
|
- `/public/css/components/permissions.css`
|
|
- `/public/css/components/role-badges.css`
|
|
|
|
---
|
|
|
|
## 🧪 **TESTING CHECKLIST**
|
|
|
|
### ✅ **Role-Based Testing**
|
|
- [ ] Super Admin - Accesso completo
|
|
- [ ] Admin - Gestione company
|
|
- [ ] Manager - Gestione team
|
|
- [ ] User - Solo risorse proprie
|
|
- [ ] Guest - Solo lettura limitata
|
|
|
|
### ✅ **Permission Testing**
|
|
- [ ] Create permissions funzionali
|
|
- [ ] Read permissions funzionali
|
|
- [ ] Update permissions funzionali
|
|
- [ ] Delete permissions funzionali
|
|
- [ ] Execute permissions funzionali
|
|
|
|
### ✅ **UI Testing**
|
|
- [ ] Navigation basata su permessi
|
|
- [ ] Buttons e actions nascosti/disabilitati
|
|
- [ ] Forms permission-aware
|
|
- [ ] Real-time permission updates
|
|
|
|
---
|
|
|
|
## 🎯 **NEXT STEPS**
|
|
|
|
### 🔮 **Permission System Evolution**
|
|
- [ ] **Fine-Grained Permissions** - Permessi a livello di campo
|
|
- [ ] **Temporary Permissions** - Permessi con scadenza
|
|
- [ ] **Permission Inheritance** - Ereditarietà basata su gerarchia
|
|
- [ ] **Audit Trail** - Log completo delle modifiche permessi
|
|
- [ ] **Permission Templates** - Template predefiniti per ruoli
|
|
|
|
---
|
|
|
|
*📝 Documento mantenuto da: GitHub Copilot AI Assistant*
|
|
*🔗 Componente: Permission System NetGescon Interface v2.1.0*
|