# Chi può vedere cosa

Una dashboard che chiunque abbia l'URL può leggere è uno screenshot con passaggi in più. La ragione per mantenere le dashboard in una piattaforma gestita invece che in una cartella di HTML esportato è che la piattaforma ricorda chi è autorizzato ad aprirle — e continua a ricordarlo anche dopo che la persona che le ha costruite se n'è andata.

Questa pagina è la mappa onesta di quel modello: cosa applica oggi, dove si ferma, e quali parti puoi raggiungere da codice.

## I tre ruoli[​](#i-tre-ruoli "Link diretto a I tre ruoli")

Ogni appartenenza a un account porta esattamente un ruolo:

| Ruolo       | Valore    | Cosa significa                                                                  |
| ----------- | --------- | ------------------------------------------------------------------------------- |
| **Admin**   | `admin`   | Bypassa ogni controllo dei diritti di accesso. Gestisce membri, gruppi e grant. |
| **Creator** | `creator` | Può creare e modificare. Soggetto ai diritti di accesso.                        |
| **Viewer**  | `viewer`  | Sola lettura. Rifiutato su qualsiasi route che modifica.                        |

Ci sono tre cose da sapere su questo prima di progettarci attorno.

**Il ruolo vive nella riga di appartenenza, non nel token.** Il JWT non porta alcuna claim di ruolo, quindi ogni decisione sul ruolo è una lettura dal database sulla coppia account/utente. È un po' più lento e molto più difficile da falsificare.

**Fallisce in chiuso.** Un'appartenenza il cui ruolo salvato non è riconosciuto — un valore legacy, un errore di battitura — si risolve in `viewer`, non in admin.

**Admin è un override, non un rango superiore.** Un admin non è "un creator con più grant": il controllo dei permessi va direttamente ad *allow* prima che venga consultato qualsiasi grant. Non puoi negare l'accesso a un admin da una cartella.

Una chiave API agisce come la persona che l'ha creata

Una chiave API si risolve nell'utente che l'ha creata, **con il ruolo di quell'utente**. Una chiave generata da un admin è una chiave admin, e tutto ciò che segue si applica ad essa esattamente come si applica a quella persona nel browser. Non esiste un ambito di permessi separato e più ristretto per le chiavi, e una chiave non scade quando il ruolo di chi l'ha creata viene declassato — revoca la chiave.

Ventiquattro route sono deliberatamente chiuse alle chiavi e richiedono una vera sessione browser: fatturazione e pagamenti (16 di esse), la gestione delle chiavi API stessa, la lettura del tuo abbonamento, l'eliminazione dell'account, il cambio password, la telemetria e la firma dei token di embed. **Tutto il resto accetta una chiave** — incluse tutte le route di diritti di accesso, gruppi, cartelle, dashboard e dataset.

**Un'eccezione deliberata.** Rendere una dashboard `public` è vincolato da un interruttore a livello di account, `apiKeysMayPublish`, che **ha valore predefinito `false`** e può essere attivato solo da un admin in un browser. Quindi una chiave — e di conseguenza un agente che la possiede — può costruire, pubblicare e condividere dentro il tuo account, ma per impostazione predefinita non può essere ciò che rende qualcosa leggibile da chiunque al mondo. Vedi [Pubblica e condividi](/it/guides/publish-and-share.md#going-public-is-gated-separately-and-an-agent-cannot-do-it-alone).

## Gruppi[​](#gruppi "Link diretto a Gruppi")

Un gruppo è un insieme nominato di utenti dentro un account. Concedi al gruppo; l'appartenenza cambia senza toccare il grant.

Due limiti, entrambi deliberati:

* **I gruppi contengono utenti, non altri gruppi.** Non c'è annidamento, quindi non c'è una gerarchia di gruppi di cui tenere conto.
* **Le letture sono aperte a ogni membro dell'account; le scritture sono solo per gli admin.** Qualsiasi membro può elencare i gruppi e vedere chi ne fa parte. Solo un admin può creare, rinominare, eliminare o modificare l'appartenenza.

I gruppi *sono* presenti nell'SDK:

```
from vibedasher.models import GroupCreate, GroupUpdate



created = client.groups.create(GroupCreate(name="analytics", description="Read-only analysts"))

client.groups.update(created.group.id, GroupUpdate(user_ids_to_add=["<user-id>"]))



for g in client.groups.list().groups:

    print(g.name, g.user_count)
```

```
const created = await client.groups.create({ name: 'analytics', description: 'Read-only analysts' });

await client.groups.update(created.group.id, { userIdsToAdd: ['<user-id>'] });



const { groups } = await client.groups.list();
```

## Diritti di accesso — il grant è sulla cartella[​](#diritti-di-accesso--il-grant-è-sulla-cartella "Link diretto a Diritti di accesso — il grant è sulla cartella")

Questa è la frase più importante di questa pagina, e quella che più probabilmente ti sorprenderà:

> **Un grant nomina una cartella, mai una singola dashboard o un singolo dataset.**

Un diritto di accesso è una riga della forma:

```
(account, folder, resource type, permission, principal) → allow | deny
```

* **tipo di risorsa** — `dataset` o `viz`. Sono i due che un grant può nominare; vedi [Cosa può nominare un grant](#what-a-grant-can-name) per cosa governa tutto il resto.
* **permesso** — `view`, `edit`, `delete`, `create`.
* **principal** — un utente o un gruppo.
* **allow / deny** — i due tipi di accesso.

Una dashboard è coperta da un grant perché *si trova nella* cartella a cui è stato concesso. Spostala in un'altra cartella e sarà invece governata dai grant di quella cartella. Non c'è alcun grant per singola dashboard di cui perdere traccia, il che è la metà positiva di questo design; l'altra metà è che **la cartella è la più piccola unità che puoi concedere**, quindi una cartella che condividi è una cartella che condividi interamente.

### Questo non è un tetto a quanto stretta può essere una condivisione[​](#that-is-not-a-ceiling-on-how-narrow-a-share-can-be "Link diretto a Questo non è un tetto a quanto stretta può essere una condivisione")

La frase sopra riguarda la *concessione*, non la condivisione. Una cartella è la cosa più piccola che puoi nominare in una concessione — e sei tu a decidere cosa c'è in una cartella. Per condividere esattamente una dashboard, dalle una cartella tutta sua: sono tre chiamate che il tuo agente ha già:

```
create_folder(name="Q3 board pack")

move_to_folder(resource_type="viz", resource_id=<vizId>, folder_id=<newFolderId>)

grant_access(folder_id=<newFolderId>, resource_type="viz",

             principal_id=<userId>, principal_type="user", permissions=["view"])
```

`resource_type` è `dataset` o `viz` nello spostamento, `principal_type` è `user` o `group`, e `access_type` vale `allow` per default — `deny` è l'altro valore, e batte ogni allow.

Queste tre sono strumenti MCP, e l'MCP è la via supportata per farlo. **Le rotte di access-rights sono deliberatamente fuori dall'SDK pubblicato e dal riferimento API** — sono impiantistica di controllo accessi di cui non abbiamo fissato la forma, quindi non esiste un metodo di client generato per esse e non va usato nessuno. `create_folder` è quella delle tre con un gemello SDK, `client.folders.create`.

Spostare una dashboard cambia quali concessioni la governano e nient'altro — la dashboard mantiene il suo id, e [il suo link non è toccato](/it/guides/publish-and-share.md). Quindi «condividere per singola dashboard è impossibile» è la conclusione sbagliata da trarre dalla regola della cartella: sono tre chiamate, e la cartella è il modo di esprimerla.

### Le due metà di una concessione su dashboard[​](#both-halves-of-a-dashboard-grant "Link diretto a Le due metà di una concessione su dashboard")

Una dashboard e i dati sottostanti sono governati da **tipi di risorsa distinti**, quindi una condivisione che ne nomina solo uno è fatta a metà. `viz:view` permette a qualcuno di aprire la dashboard; `dataset:view` è ciò che permette alle sue tile di restituire numeri. La sezione seguente spiega perché questo non è un dettaglio rinviabile.

### Come viene effettivamente presa una decisione[​](#come-viene-effettivamente-presa-una-decisione "Link diretto a Come viene effettivamente presa una decisione")

In ordine. La prima regola che corrisponde vince:

1. **Admin** → allow. Incondizionatamente, prima che venga letto qualsiasi grant.
2. **Il principal interno `system`** → allow. Questa è l'identità worker della piattaforma stessa (job di aggiornamento, esecuzioni pianificate); non è un account in cui puoi accedere.
3. **Proprietà** → allow. L'utente proprietario della riga può sempre leggerla e modificarla.
4. Altrimenti, decidono i grant della cartella, e **un singolo deny batte ogni allow.**

Vale la pena precisare deny-wins, perché è più ristretto di quanto sembri. Un deny rimuove una cartella dall'insieme che ti era stato concesso — non sottrae dalle regole 1–3. **Non puoi negare l'accesso a un admin, e non puoi negare a qualcuno una riga di cui è proprietario.** Se il tuo modello mentale è "deny è l'arresto di emergenza", non lo è: è una sottrazione solo all'interno dello strato dei grant.

### Due comportamenti che ti morderanno[​](#due-comportamenti-che-ti-morderanno "Link diretto a Due comportamenti che ti morderanno")

**I grant non si propagano nelle sottocartelle.** Il controllo è un test piatto di appartenenza a un insieme sul `folder_id` proprio della risorsa. Le cartelle hanno effettivamente una relazione genitore/figlio, e l'interfaccia la disegna come un albero — ma nessun codice di autorizzazione la percorre. Un grant su *Marketing* **non** raggiunge *Marketing / Q3*. Concedi ogni cartella che intendi concedere.

**Un deny su `create` prima non faceva nulla — corretto il 2026-09-08.** Il controllo del permesso di creazione leggeva solo le righe `allow`; il diritto di bootstrap di un Creator a creare nella radice dell'account o in una cartella di cui è proprietario, e una riga `allow` esplicita altrove, bypassavano entrambi completamente qualsiasi `deny`. Una riga `create` / `deny` esplicita di un admin ora sovrasta entrambi — la stessa regola "un singolo deny batte ogni allow" che il resto di questa pagina descrive per view/edit/delete. Gli admin restano non negabili, coerentemente con ogni altro permesso qui.

### Chi può modificare un grant[​](#chi-può-modificare-un-grant "Link diretto a Chi può modificare un grant")

Un **admin dell'account**, o il **proprietario della cartella** su cui si trova il grant. La lettura dei grant è aperta a qualsiasi membro dell'account: lo stato di condivisione viene visualizzato nell'app su pagine che qualsiasi membro può aprire, quindi la lettura è deliberatamente non vincolata. Considera l'elenco dei grant stesso come informazione visibile a tutto l'account.

## Cosa può nominare un grant[​](#what-a-grant-can-name "Link diretto a Cosa può nominare un grant")

**Due tipi di risorsa sono assegnabili: `viz` (dashboard) e `dataset`.** Costruisci il tuo modello di governance con questi due, tenuti in cartelle: è la forma che il modello applica ed è la sola che la finestra di dialogo offre. `POST /v1/access-rights` rifiuta qualunque altro tipo di risorsa con un `400`, nel momento in cui lo scrivi anziché nel momento in cui qualcuno ci conta.

Tre cose sono governate da qualcosa che non è un grant, e sapere quale da cosa è tutto il senso di questa sezione:

| Risorsa                             | Cosa la governa                                                                                                                                                        |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cartella**                        | L'appartenenza all'account per le letture; il ruolo per le mutazioni. Una cartella è il contenitore che un grant *nomina*, non una cosa contro cui un grant si scrive. |
| **Pianificazione di aggiornamento** | La proprietà, più il confine dell'account.                                                                                                                             |
| **Domanda**                         | Solo il proprietario. Una domanda non può essere condivisa con nessun meccanismo.                                                                                      |
| **Attività**                        | L'ACL della *dashboard* a cui appartiene.                                                                                                                              |
| **Connessione**                     | Il suo proprietario e gli amministratori dell'account. Qualunque Creator può crearne una.                                                                              |

**Un elenco vuoto ti dice di quale genere di vuoto si tratta.** `GET /v1/access-rights/{folder}/{type}` porta un flag `enforced`, quindi `total: 0` per un tipo non assegnabile significa *nessun diritto di questo tipo può esistere in alcun luogo*, mentre `total: 0` per `viz` significa *nessuno ne possiede uno*. Le righe scritte prima del rifiuto restano elencate, e restano rimovibili, con `enforced: false` accanto.

## Condividere una dashboard senza condividerne i dati non funziona[​](#condividere-una-dashboard-senza-condividerne-i-dati-non-funziona "Link diretto a Condividere una dashboard senza condividerne i dati non funziona")

È la domanda a cui il modello *non* risponde come la maggior parte si aspetta, quindi merita una frase netta: **`viz:view` da solo non dà a nessuno una dashboard funzionante.**

I riquadri di una dashboard prendono i loro numeri con `POST /v1/query`, e quella rotta autorizza **ogni dataset coinvolto tramite l'ACL dei dataset**, in modalità nega-anziché-scartare: un solo dataset che il lettore non può leggere respinge l'intera query con `403 query_dataset_forbidden`. Non viene mai eseguita sul sottoinsieme che *è* invece consentito. Quindi «fai vedere allo stagista questa dashboard ma non i dati dietro» produce una dashboard i cui riquadri falliscono, non una dashboard con i numeri nascosti.

Concedi anche `dataset:view` sulla cartella che contiene i dataset — `viz:view` e `dataset:view` vanno **in coppia**, e una concessione che porta solo la prima produce una dashboard in cui ogni tile restituisce `403`. Se i dataset non devono essere condivisi in blocco, dagli una cartella loro come fa la ricetta sopra. Oppure, se l'obiettivo è davvero mostrare un risultato senza consegnare i dati sottostanti, **pubblica la dashboard o incorporala**. Entrambe queste corsie girano sotto l'identità di servizio della piattaforma e non consultano mai questa ACL: è esattamente per questo che possono mostrare un numero a qualcuno che non può interrogarlo.

## Verificare il proprio lavoro[​](#verificare-il-proprio-lavoro "Link diretto a Verificare il proprio lavoro")

Ogni campo qui sopra descrive una regola; nessuno ti dice con cosa finisce *una persona specifica*, e questo non si legge dall'elenco delle concessioni. I diritti si compongono — appartenenze ai gruppi, un `deny` che batte ogni allow compreso uno ereditato da un gruppo, la separazione per tipo di risorsa e il controllo di ruolo separato sulle mutazioni — in un esito che nessun singolo elenco mostra. Il `deny` ereditato è il caso che conta: non compare mai nell'elenco degli allow che leggi, quindi «perché questa persona non lo vede» non ha risposta dal solo elenco.

`GET /v1/access-rights/{folder}/effective?userId=` lo risolve. Per ogni coppia (tipo di risorsa, permesso) restituisce cosa decide l'ACL, **quale regola ha deciso**, se il ruolo della persona lo consente, se il tipo è applicato, il prodotto di tutto ciò — e **la riga di diritto d'accesso che ha prodotto il verdetto**, cioè l'id da revocare quando la risposta è un deny. Il tuo agente ha la stessa cosa come `describe_effective_access`.

Risponde solo alla domanda sull'ACL, e lo dichiara in un elenco `caveats` presente in ogni risposta: non può vedere le righe che la persona *possiede* (la proprietà è per riga e batte l'ACL della cartella), non sa nulla delle corsie della dashboard pubblica e dell'embed e — come sopra — una dashboard funzionante ha bisogno anche di diritti sui suoi dataset. Puoi sempre chiedere di te stesso; chiedere di qualcun altro richiede la stessa posizione che serve per cambiare l'ACL: un admin dell'account o il proprietario della cartella.

## Raggiungere tutto questo da codice[​](#raggiungere-tutto-questo-da-codice "Link diretto a Raggiungere tutto questo da codice")

| Superficie                                               | Negli SDK?                   |
| -------------------------------------------------------- | ---------------------------- |
| Gruppi (creazione, elenco, modifica, appartenenza)       | **Sì** — `client.groups.*`   |
| Utenti (elenco, modifica, ruolo, appartenenza al gruppo) | **Sì** — `client.users.*`    |
| Account                                                  | **Sì** — `client.accounts.*` |
| **Diritti di accesso (i grant stessi)**                  | **No**                       |

I diritti di accesso sono esclusi dalla superficie SDK generata, quindi non esiste `client.accessRights` né una voce per essi nel **[Riferimento API](/it/reference/vibedasher-sdk-api.md)**. Le route esistono e accettano una chiave API — semplicemente non fanno parte del contratto client pubblicato e versionato, e preferiamo dirtelo piuttosto che lasciarti costruire su una forma a cui non ci siamo impegnati. Gestisci i grant nell'app, oppure dal tuo agente: il server MCP espone strumenti per grant, revoke e list-access-rights che chiamano le stesse route sotto la tua identità, ed ereditano la stessa regola admin-o-proprietario-della-cartella.

## Filtraggio per riga: la corsia dell'embed, e solo quella[​](#row-level-filtering-the-embed-lane-and-only-the-embed-lane "Link diretto a Filtraggio per riga: la corsia dell'embed, e solo quella")

La sicurezza per riga è reale in Vibedasher, ma vive in un solo posto: **le dashboard incorporate dietro un token firmato.**

Quando il tuo server genera un token di embed, può allegare filtri al payload del token. Su ogni query la piattaforma riscrive ogni riferimento a un dataset in una subquery filtrata, quindi il visualizzatore non può fisicamente leggere righe al di fuori della propria porzione. I filtri provengono **esclusivamente** dal token verificato — mai dal corpo della richiesta — quindi un visualizzatore non può ampliare il proprio ambito modificando una richiesta. Un filtro che nomina una colonna che non esiste in nessuno schema di dataset viene rifiutato con un `403` invece di essere ignorato silenziosamente, perché un filtro che silenziosamente non fa nulla è una falla.

Tre limiti da tenere in conto:

* **Si applica solo alla corsia dell'embed.** La corsia di query dell'SDK non ha *alcuna* iniezione obbligatoria di filtri: una chiave API è un chiamante lato server fidato, e limitare i propri utenti è compito tuo, non della piattaforma.
* **L'ACL della cartella non è per riga.** Decide quali dashboard e dataset esistono per te, non quali righe al loro interno puoi leggere. Sono domande diverse e Vibedasher le risolve con meccanismi diversi.
* **Una dashboard che interroga nel browser non può essere ristretta affatto, ed è una scelta di progettazione.** È la decisione da prendere prima di costruire, non dopo. Una dashboard auto-ospitata sulla corsia senza credenziali — l'albero espulso il cui `dataLane` è `wasm`, o qualunque cosa tu abbia costruito da solo sopra un estratto Parquet o JSON — scarica i dati ed esegue ogni query nel browser del visitatore. L'estratto è indirizzato per **dataset e istantanea, non per query**, quindi contiene tutte le righe di quel dataset, e ogni visitatore riceve lo stesso file. Non resta alcun server lungo il percorso per restringerlo: un filtro scritto nella pagina cambia ciò che viene *disegnato*, mai ciò che è stato *consegnato*.

L'ambito per riga e la corsia senza credenziali si escludono a vicenda

Tutto il fascino di quella corsia sta nel fatto che non c'è alcuna chiave nel bundle, nessun token da generare e nessun backend tuo da far girare — e il prezzo è esattamente la stessa proprietà vista dall'altro lato: nulla può dare a un visualizzatore meno righe che a un altro. Non è una lacuna che verrà colmata; è ciò che significa mettere i dati nel browser.

Se alcuni visualizzatori devono vedere meno righe, tieni la query su un server. Due configurazioni lo fanno, ed entrambe tengono la credenziale fuori dalla pagina: **il tuo backend genera un token di embed di breve durata che porta il filtro** (la piattaforma aggiunge quel predicato a ogni query che autorizza, prima di qualsiasi join o aggregazione, così la pagina non può allargarlo), oppure **codice server fidato interroga con una chiave API** e restituisce righe che hai già ristretto. Scegliere prima la corsia veloce e poi "restringerla" con un filtro nella pagina produce una dashboard che sembra giusta, non solleva alcun errore, e consegna tutte le righe.

Vedi **[Incorporare](/it/api/embedding-the-share-iframe.md)** per come un token viene generato e presentato.

## Autenticazione a due fattori[​](#autenticazione-a-due-fattori "Link diretto a Autenticazione a due fattori")

Email + password è l'unico ingresso in Vibedasher che poggia su un solo segreto. Gli accessi con Google e LinkedIn portano il secondo fattore del loro provider; le chiavi API e il token CLI di `vibedasher login` non sono affatto accessi. Il secondo fattore è quindi **opt-in, per utente, solo sull'accesso con password**: un codice TOTP a sei cifre da qualsiasi app di autenticazione (Google Authenticator, 1Password, Authy…), attivato da **Impostazioni → Autenticazione a due fattori** scansionando un codice QR e confermando un codice. L'attivazione consegna **otto codici di recupero monouso**, mostrati una sola volta: un telefono perso accede con uno di questi.

Cosa cambia all'accesso: dopo una password corretta il browser **non riceve una sessione**, riceve una sfida di cinque minuti a scopo unico che solo il passaggio del codice può riscattare. L'API non vede mai quella sfida come una credenziale: porta un'audience che l'API rifiuta in partenza, quindi non può essere riprodotta contro `api.*.vibedasher.com` né infilata nel cookie di sessione. Cinque codici errati bloccano la sfida per quindici minuti; un codice vale una sola volta.

Tre limiti da conoscere:

* **L'attivazione è personale.** Ogni membro la attiva per il proprio account; non è qualcosa che un amministratore accende per tutti.
* **Gli accessi OAuth non ricevono mai la sfida**, per scelta: aggiungere il nostro fattore sopra quello del provider è attrito senza sicurezza.
* **Lo sblocco è un'azione da amministratore.** Un membro senza telefono e senza codice di recupero si rivolge a un **amministratore dell'account**, che reimposta il suo secondo fattore dalla pagina del membro (l'azione viene registrata). Un proprietario che è l'unico amministratore, senza codice di recupero, passa dal supporto.

## Una configurazione pratica[​](#una-configurazione-pratica "Link diretto a Una configurazione pratica")

Per un team di dieci persone con un paio di dataset sensibili:

1. Rendi **admin** una o due persone che amministrano l'account. Ricorda che sono ingovernabili dai grant — è il punto del ruolo, ed è la ragione per mantenere l'elenco breve.
2. Chiunque costruisca è un **creator**; chiunque legga soltanto è un **viewer**. Il ruolo viewer è un rifiuto netto su ogni modifica, quindi è un confine reale, non una convenzione dell'interfaccia.
3. Metti dashboard e dataset in cartelle che rispecchino come vuoi condividerli — **una cartella per pubblico**, non per argomento. La cartella è il grant.
4. Concedi ai **gruppi**, non alle persone. Il grant sopravvive così a chi entra o esce.
5. Non creare cartelle annidate aspettandoti che il grant del genitore le raggiunga. Concedi ognuna.
6. Se una dashboard deve arrivare a persone al di fuori dell'account, pubblicala e incorporala con un token firmato che porti i loro filtri per riga — non dare loro un account.
