Mini ERP locale per la gestione di ricavi, costi, agenti, partecipanti e ripartizione utili.
- Backend: Python + Flask
- Database: SQLite (file locale)
- ORM: SQLAlchemy
- Autenticazione: Flask-Login
- Frontend: Jinja2 + CSS custom
cd small_erp
python3 -m venv venv
source venv/bin/activate # macOS/Linux
# oppure: venv\Scripts\activate # Windowspip install -r requirements.txtcp .env.example .env
# Modifica .env con i tuoi valori:
# - SECRET_KEY: chiave segreta per le sessioni
# - SUPERADMIN_PASSWORD: password dell'admin iniziale
# - DATABASE_PATH: percorso del file SQLite (default: erp.db)python init_db.pyQuesto crea il database SQLite e il superadmin admin.
La password reale del superadmin viene letta da .env (SUPERADMIN_PASSWORD) al login e non viene salvata nel database.
python seed_demo.pyInserisce agenti, attività, costi, partecipanti e log di audit di esempio.
python run.pyL'applicazione sarà disponibile su http://127.0.0.1:5000
Le credenziali del superadmin sono configurate nel file .env (variabile SUPERADMIN_PASSWORD).
Per sicurezza, l'hash della password del superadmin nel DB e solo un placeholder tecnico e non rappresenta la password reale.
Gli utenti demo (mario.rossi, laura.bianchi) vengono creati solo eseguendo seed_demo.py.
Importante: cambiare le password al primo accesso. Non usare le credenziali di default in produzione.
- Dashboard: panoramica KPI del mese corrente
- Attività di Ricavo: gestione completa con costi, partecipanti e breakdown economico
- Agenti: anagrafica agenti con percentuale commissione predefinita
- Report Mensili: report aggregati per mese con riepilogo per agente e partecipante
- Gestione Utenti: creazione/modifica/disattivazione utenti con ruoli
- Audit Log: tracciamento completo di tutte le operazioni (solo superadmin)
- Superadmin: accesso completo, gestione utenti, audit log (password da
.env) - Utenti: funzionalità operative (attività, agenti, report)
small_erp/
├── app/
│ ├── __init__.py # App factory Flask
│ ├── models.py # Modelli SQLAlchemy
│ ├── services.py # Logica di calcolo centralizzata
│ ├── audit_service.py # Servizio audit log
│ ├── auth.py # Autenticazione
│ ├── dashboard.py # Dashboard
│ ├── activities.py # CRUD attività, costi, partecipanti
│ ├── agents.py # CRUD agenti
│ ├── users.py # Gestione utenti
│ ├── reports.py # Report mensili
│ ├── audit.py # Visualizzazione audit log
│ ├── templates/ # Template Jinja2
│ └── static/css/ # Stili CSS
├── init_db.py # Inizializzazione DB + superadmin
├── seed_demo.py # Dati demo
├── run.py # Entry point
├── requirements.txt
├── .env.example
└── README.md
Questo progetto usa pytest come suite ufficiale di test.
cd /opt/small_erp && python -m pytest tests/ -v --tb=short# Tutti i test con coverage
python -m pytest tests/ -v --cov=app --cov-report=term-missing
# Solo un file
python -m pytest tests/test_services.py -v
# Solo un marker
python -m pytest tests/ -m unit -vunitintegrationauthroutesmodelsservicesslow
Le fixture condivise sono in tests/conftest.py e includono: app, client, superadmin_user, admin_user, operator_user, agent, revenue_activity, activity_cost, activity_participant, audit_log.
- La suite include test su modelli, servizi, autenticazione, route e audit.
- I moduli critici (
app/audit_service.py,app/models.py) hanno copertura molto alta.
Il sistema di logging applicativo salva i file nella directory logs/ (configurabile con LOG_DIR nel file .env).
logs/app.log: log applicativi generalilogs/error.log: solo errori e criticitàlogs/debug.log: dettaglio completo
- Rotazione giornaliera a mezzanotte
- Retention predefinita: 30 giorni
# Segui i log in tempo reale
tail -f logs/app.log
# Cerca errori
grep "ERROR" logs/error.log
# Elenca file log
ls -lah logs/Data: 3 aprile 2026
Eseguito su: Small ERP (Flask 3.1.0 / SQLite / Python 3.12)
Metodologia: Code review statico + penetration test manuale con test suite automatizzata
Test di sicurezza: tests/test_security.py (27 test case)
| # | ID | Severità | Vulnerabilità | Stato | File |
|---|---|---|---|---|---|
| 1 | AUTH-01 | CRITICA | Open redirect su /login?next= |
Corretta | app/auth.py |
| 2 | AUTH-02 | ALTA | Session fixation (nessuna rigenerazione sessione al login) | Corretta | app/auth.py |
| 3 | AUTH-03 | MEDIA | Logout via GET (CSRF logout forzato con <img src="/logout">) |
Corretta | app/auth.py, templates/base.html |
| 4 | CRYPTO-01 | CRITICA | SECRET_KEY debole/hardcoded (admin123$APA, fallback dev-fallback-key) |
Corretta | app/__init__.py, .env |
| 5 | CRYPTO-02 | ALTA | Cookie di sessione senza flag HttpOnly, SameSite, timeout assente |
Corretta | app/__init__.py |
| 6 | AUTHZ-01 | CRITICA | IDOR su attività: qualsiasi utente poteva edit/delete attività di altri | Corretta | app/activities.py |
| 7 | AUTHZ-02 | ALTA | Privilege escalation: ruoli rimossi, solo superadmin gestisce utenti | Corretta | app/users.py |
| 8 | INJ-01 | MEDIA | SQL wildcard injection nei filtri audit (% e _ non sanitizzati) |
Corretta | app/audit.py |
| 9 | INJ-02 | MEDIA | Concatenazione stringa data utente senza validazione nel filtro audit | Corretta | app/audit.py |
| 10 | INJ-03 | ALTA | Reflected XSS via messaggi di errore (eccezione Python nel flash) | Corretta | app/activities.py, app/agents.py |
| 11 | INPUT-01 | MEDIA | Nessun limite su valori decimali negativi o overflow | Corretta | app/activities.py |
| 12 | CONF-01 | CRITICA | Debug mode attivo di default (debug=True in run.py) |
Corretta | run.py |
| 13 | CONF-02 | MEDIA | Header HTTP di sicurezza assenti | Corretta | app/__init__.py |
| 14 | CONF-03 | MEDIA | Password policy debole (min 6 char, nessun requisito complessità) | Corretta | app/users.py |
| 15 | CONF-04 | BASSA | Credenziali demo stampate in chiaro nell'output di seed_demo.py | Corretta | seed_demo.py |
| 16 | CONF-05 | BASSA | Database SQLite non cifrato (encryption at rest) | Nota | — |
- Prima:
redirect(request.args.get('next'))senza validazione - Dopo: Funzione
_is_safe_url()verifica che l'URL sia relativo (nonetloc, noscheme) - Test:
TestOpenRedirect(3 test case)
- Prima:
login_user(user)senza invalidare la sessione esistente - Dopo:
session.clear()prima dilogin_user(),session.permanent = Truecon timeout 30 min - Test:
TestSessionFixation(1 test case)
- Prima:
@auth_bp.route('/logout')accettava GET → un tag<img>poteva forzare il logout - Dopo:
@auth_bp.route('/logout', methods=['POST'])+ form con CSRF token nel template - Test:
TestLogoutMethod(2 test case)
- Prima:
os.environ.get('SECRET_KEY', 'dev-fallback-key')e.envcontenevaadmin123$APA - Dopo: Validazione obbligatoria ≥16 char, errore in produzione se mancante, generato nuovo token 64 hex
- Test:
TestSecretKey(1 test case)
- Prima: Nessun flag di sicurezza configurato
- Dopo:
SESSION_COOKIE_HTTPONLY=True,SESSION_COOKIE_SAMESITE='Lax',PERMANENT_SESSION_LIFETIME=1800 - Test:
TestSessionCookieFlags(3 test case)
- Prima: Qualsiasi utente autenticato poteva modificare/eliminare qualsiasi attività
- Dopo:
_can_modify_activity()verifica checurrent_usersia il creatore o admin - Test:
TestActivityIDOR(3 test case)
- Prima: Un admin poteva assegnare ruolo
admincreando un account equivalente - Dopo: Sistema ruoli rimosso. Solo il superadmin può gestire utenti e audit log
- Prima:
ilike(f'%{username}%')vulnerabile a wildcard DoS; date concatenate come stringhe - Dopo: Caratteri
%e_rimossi dall'input; date validate condatetime.fromisoformat() - Test:
TestAuditFilterInjection,TestAuditDateValidation(4 test case)
- Prima:
flash(f"Errore: {e}")iniettava il testo dell'eccezione Python nella pagina - Dopo: Messaggi generici hardcoded, dettaglio solo nei log server
- Test:
TestXSSErrorMessages(1 test case)
- Prima:
Decimal(user_input)senza limiti, valori negativi e overflow accettati - Dopo:
_validate_decimal()rifiuta negativi e valori > 999.999.999,99;InvalidOperationcatturata - Test:
TestDecimalValidation(2 test case)
- Prima:
app.run(debug=True)hardcoded → shell interattiva Werkzeug esposta - Dopo:
debug=os.environ.get('FLASK_DEBUG', 'false').lower() == 'true'
- Prima: Nessun header di sicurezza
- Dopo:
@app.after_requestaggiunge:X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-origin
- Test:
TestSecurityHeaders(1 test case)
- Prima: Minimo 6 caratteri, nessun requisito di complessità
- Dopo: Minimo 12 caratteri + almeno 1 maiuscola + 1 minuscola + 1 numero
- Test:
TestPasswordPolicy(4 test case)
| Priorità | Raccomandazione | Motivazione |
|---|---|---|
| Alta | Rate limiting su /login |
Nessuna protezione contro brute force; installare flask-limiter con limite 5 tentativi/minuto |
| Alta | Account lockout | Dopo N tentativi falliti, bloccare l'account per X minuti |
| Media | SESSION_COOKIE_SECURE=True |
Da abilitare quando l'app è servita via HTTPS (non abilitato ora per non rompere HTTP locale) |
| Media | Strict-Transport-Security header |
Da aggiungere solo dopo deploy HTTPS |
| Media | Content-Security-Policy header |
Richiede analisi degli asset inline (script/style) prima di attivarlo |
| Media | Encryption at rest (CONF-05) | SQLite non supporta cifratura nativa; valutare migrazione a PostgreSQL o sqlcipher |
| Media | CORS configuration | Se si espongono API REST, configurare flask-cors con whitelist di origini |
| Bassa | Dependency pinning | Creare requirements.lock con pip freeze per bloccare le versioni transitive |
| Bassa | Force password change | Forzare il cambio password al primo accesso per account creati da admin |
| Bassa | Log degli accessi in lettura | Attualmente solo le operazioni CRUD sono loggate, non le letture di dati sensibili |