Vai al contenuto

Sicurezza

Questa pagina raccoglie autenticazione, autorizzazione e protezioni trasversali. I permessi di team sono descritti in Team e ruoli.

Account

Stati

User ha tre stati: STATUS_ACTIVE (10), STATUS_INACTIVE (9, non attivato) e STATUS_DELETED (0, disattivato). Login, ricerca per nome utente e token API considerano solo gli account attivi: disattivare un utente chiude subito anche il suo accesso all'API.

Registrazione

SignupForm::signup() crea l'utente già attivo con UserRegistration, che gli assegna il ruolo RBAC user, crea lo spazio personale e avvisa gli amministratori; poi invia l'email di benvenuto ed entra. Lo stesso servizio crea gli account dal pannello, con il ruolo scelto e una password temporanea. Il flusso di verifica dell'email esiste ancora nel codice ma non si usa, perché richiede account non attivati.

Login

  • "Ricordami" dura 30 giorni.
  • LoginThrottle blocca per 15 minuti dopo 5 tentativi falliti per coppia nome utente + IP, o dopo 20 per IP. Durante il blocco la password non viene nemmeno verificata.
  • I tentativi falliti finiscono nel registro operazioni con il motivo interno.
  • Il login del pannello accetta solo admin e superAdmin.

Verifica in due passaggi

Facoltativa, con codici TOTP (RFC 6238: SHA-1, 6 cifre, 30 secondi), attivabile dal profilo (TwoFactorService, Totp):

  • il segreto si salva cifrato con params['totpEncryptionKey'] (in Docker TOTP_ENC_KEY). Senza chiave la funzione resta spenta; cambiare la chiave rende illeggibili i segreti salvati, quindi chi l'ha attivata dovrà essere azzerato;
  • con la verifica attiva LoginForm non apre la sessione: salva in sessione chi ha superato la password (5 minuti) e rimanda a site/two-factor, sia nel frontend sia nel pannello. Lì TwoFactorForm accetta il codice dell'app (uno step di tolleranza, mai lo stesso step due volte grazie a totp_last_step) o uno dei 10 codici di recupero monouso, salvati come hash sha256;
  • i codici sbagliati hanno un contatore LoginThrottle separato (totp:<nome>), che il login con password non azzera;
  • attivare, disattivare o azzerare rigenera auth_key, così i cookie "ricordami" emessi prima non valgono più. Ogni passaggio finisce nel registro operazioni come "Verifica in due passaggi";
  • il super amministratore la azzera dal pannello (non sugli account protetti altrui); php yii account/reset-two-factor <email> è l'uscita di emergenza;
  • i token API restano invariati: si generano solo da una sessione web già autenticata.

Password

  • Hash bcrypt di Yii; unica regola la lunghezza minima, params['user.passwordMinLength'] (8).
  • Il recupero password non rivela se l'account esiste; il token dura un'ora.
  • Reimpostare la password (dal recupero o dal pannello) rigenera auth_key, che chiude le sessioni "ricordami".

Account protetti

Gli account in params['protectedAccounts'] (per email, perché l'id cambia fra installazioni) sono garantiti da AccountProtection:

  • restano sempre super amministratori: ProtectedDbManager ignora la revoca;
  • restano sempre attivi: User::beforeSave lo impedisce;
  • l'email non si cambia, se non modificando prima la configurazione;
  • nessuno può generare loro una password temporanea dal pannello.

AccountProtectionBootstrap riallinea l'account prima di ogni azione web; php yii account/protect fa lo stesso da console.

RBAC

authManager è common\rbac\ProtectedDbManager, un DbManager che in più protegge gli account protetti e registra ogni assegnazione e revoca.

Ruoli e permessi

Lo stato attuale nasce dalla migrazione m260704_121829_rebuild_rbac_roles.

  • Ruoli globali predefiniti: superAdmin, admin, user, alternativi fra loro (GlobalRoleManager::BUILTIN).
  • Permessi globali (regola globalAdmin, sempre vera): admin.panel, tag.global, user.manage, user.deactivate, team.create (oggi solo del super amministratore).
  • Ruoli di team RBAC: teamViewer ⊂ teamMember ⊂ teamEditor ⊂ teamAdmin, con i permessi di team (regola teamMember).
  • Ruoli globali personalizzati: ruoli RBAC custom:<slug> che si sommano al ruolo predefinito e possono concedere admin.panel, tag.global, user.manage, user.deactivate. Si gestiscono dal pannello, alla voce "Ruoli globali"; l'etichetta sta in data['label'], il nome tecnico non cambia.

Come si decide un permesso di team

Yii::$app->user->can('task.update', ['team_id' => $teamId]);

Di solito attraverso TaskAccess::ensureCan(). La regola TeamMemberRule:

  1. senza team_id nega;
  2. se l'utente non è membro, concede solo board.view e solo agli overseer;
  3. se il team è nel cestino nega;
  4. altrimenti chiede a TeamPermissionResolver per il ruolo del membro.

Per questo admin e superAdmin, pur ereditando teamAdmin, non hanno accesso ai team di cui non sono membri.

Altre politiche

Classe Regola
TaskAccess permessi sulle task; ensureActionable() blocca le task bloccate; ensureCanDelete()
TagPolicy etichette globali con tag.global, di bacheca con tag.manage
TrashPolicy chi elimina task, chi vede e svuota il cestino (vedi Cestino)
TeamVisibility quali team vede un utente (vedi Team e ruoli)

API

  • I token personali sono 48 caratteri casuali; nel database c'è solo lo SHA-256. Lo stesso vale per i token del feed iCal e per i token master.
  • I token master (prefisso gtm_) operano per conto dell'utente indicato in X-Act-As, solo sulle rotte abilitate e mai oltre i permessi di quell'utente. Vedi API HTTP.
  • I token OAuth del connettore MCP (gto_ di accesso, un'ora; gtr_ di rinnovo, 30 giorni) valgono quanto il token personale di chi ha dato il consenso. Si salvano solo gli SHA-256; codice di autorizzazione e refresh sono monouso e il rinnovo è atomico, così due richieste con lo stesso segreto non ottengono entrambe un token. Un utente disattivato li perde subito; dal profilo l'utente vede le app collegate e le revoca.
  • Il consenso OAuth richiede PKCE S256 e un indirizzo di ritorno registrato e uguale; i client si registrano solo con indirizzi https: o http verso localhost, al massimo 20 l'ora per IP. Un client sconosciuto o un indirizzo sbagliato non provocano un redirect: l'errore si mostra sulla pagina.
  • Nel consenso l'utente sceglie cosa concedere all'app (sola lettura, lavoro quotidiano, tutto) e il token è legato al connettore per cui è stato chiesto: non vale sull'altro connettore né, da solo, sull'API. Un refresh già sostituito che si ripresenta revoca l'intera autorizzazione. Dettagli in Connettore MCP.
  • Le operazioni pesanti chieste da Claude (scritture dell'amministrazione, cestino, stati, membri, ruoli e permessi dei team, webhook) partono solo dopo che l'utente le approva in GesTask, una volta e per dieci minuti (Operazioni da approvare).
  • I client OAuth che si presentano con un Client ID Metadata Document vengono letti con la stessa protezione SSRF dei webhook.
  • Dall'API e dal connettore non si toccano credenziali: niente password, verifica in due passaggi, token personali o master, propri o altrui.
  • Limite di 120 richieste al minuto per utente, con gli header X-Rate-Limit-*. Per il connettore MCP conta ogni tool eseguito.
  • L'API non ha sessione, CSRF né cookie; accetta solo le rotte dichiarate.

Header HTTP

SecurityHeadersBootstrap imposta, senza sovrascrivere quelli già presenti:

  • X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff;
  • Referrer-Policy: strict-origin-when-cross-origin;
  • Permissions-Policy: camera=(), microphone=(), geolocation=();
  • Content-Security-Policy: frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self';
  • Strict-Transport-Security sulle richieste HTTPS.

Note

La CSP non limita ancora gli script, perché le viste usano JavaScript in linea. Restringerla richiede prima di spostare quel codice in file.

Sessioni e CSRF

  • Frontend e pannello hanno sessioni, cookie d'identità (httpOnly) e parametri CSRF separati: un login nel frontend non apre il pannello.
  • Le azioni che modificano dati accettano solo POST (VerbFilter).
  • cookieValidationKey sta nei main-local.php, generati da php init.

Allegati

AttachmentStorage salva i file in storage/attachments/tasks/<uuid-task>/<nome-casuale>.<estensione>, fuori dalle cartelle web: nessun file caricato è raggiungibile o eseguibile direttamente.

  • Sono ammesse solo le estensioni in elenco (niente HTML, SVG o JavaScript) e il tipo MIME inviato al browser dipende dall'estensione.
  • Dimensione massima params['attachments']['maxSize'] (20 MB). Va tenuta sotto upload_max_filesize e post_max_size di PHP.
  • Il download richiede board.view sul team e passa da AttachmentDownload: nosniff, e per tutto ciò che non è PDF una CSP default-src 'none'; sandbox. Solo PDF e immagini si aprono nel browser; ogni download si registra.
  • Gli avatar sono un'eccezione: stanno in frontend/web/uploads/avatars, pubblici, ricodificati in WebP.

Altre protezioni

  • SSRF: ogni richiesta verso indirizzi scelti dagli utenti (webhook, Slack, Web Push) passa da OutboundUrlGuard (vedi Webhook).
  • CSV: CsvSafety antepone ' alle celle che iniziano con =, +, -, @, tabulazione o ritorno a capo, contro l'esecuzione di formule nei fogli di calcolo. Vale per tutte le esportazioni.
  • Identificatori: URL e API usano solo uuid; gli id numerici non escono.
  • Registro operazioni: vedi Registro operazioni.