Aller au contenu principal

Qui peut voir quoi

Un tableau de bord que n'importe qui muni de l'URL peut lire est une capture d'écran avec des étapes en plus. La raison de garder les tableaux de bord dans une plateforme maintenue plutôt que dans un dossier de HTML exporté est que la plateforme se souvient de qui est autorisé à les ouvrir — et continue de s'en souvenir après le départ de la personne qui les a construits.

Cette page est la carte honnête de ce modèle : ce qu'il applique aujourd'hui, où il s'arrête, et quelles parties vous pouvez atteindre depuis le code.

Les trois rôles​

Chaque adhésion à un compte porte exactement un rôle :

RôleValeurCe que cela signifie
AdminadminContourne chaque vérification de droits d'accès. Gère les membres, les groupes et les octrois.
CréateurcreatorPeut créer et modifier. Soumis aux droits d'accès.
VisionneurviewerLecture seule. Refusé sur toute route qui modifie.

Trois choses valent la peine d'être connues avant de concevoir autour de cela.

Le rôle vit sur la ligne d'adhésion, pas dans le jeton. Le JWT ne porte aucune revendication de rôle, donc chaque décision de rôle est une lecture en base de données sur la paire compte/utilisateur. C'est un peu plus lent et beaucoup plus difficile à falsifier.

Il échoue de façon fermée. Une adhésion dont le rôle stocké est non reconnu — une valeur héritée, une faute de frappe — se résout en viewer, pas en admin.

Admin est un contournement, pas un rang supérieur. Un admin n'est pas « un créateur avec plus d'octrois » : la vérification de permission court-circuite vers autoriser avant même la consultation d'un octroi. Vous ne pouvez pas refuser un admin l'accès à un dossier.

Une clé API agit comme la personne qui l'a créée

Une clé API se résout vers son utilisateur créateur, avec le rôle de cet utilisateur. Une clé générée par un admin est une clé admin, et tout ce qui suit s'applique à elle exactement comme cela s'applique à cette personne dans le navigateur. Il n'existe pas de portée de permission séparée et plus étroite pour les clés, et une clé n'expire pas quand le rôle de son créateur est rétrogradé — révoquez la clé.

Vingt-quatre routes sont délibérément fermées aux clés et nécessitent une vraie session de navigateur : la facturation et le paiement (16 d'entre elles), la gestion des clés API elle-même, la lecture de votre abonnement, la suppression de compte, le changement de mot de passe, la télémétrie, et la signature de jeton d'intégration. Tout le reste accepte une clé — y compris chaque route de droits d'accès, de groupe, de dossier, de tableau de bord et de jeu de données.

Une exception délibérée. Basculer un tableau de bord en public est contrôlé par un interrupteur de compte, apiKeysMayPublish, qui vaut false par défaut et ne peut être activé que par un admin dans un navigateur. Donc une clé — et par conséquent un agent qui la détient — peut construire, publier et partager à l'intérieur de votre compte, mais ne peut pas par défaut être ce qui rend quelque chose lisible par le monde entier. Voir Publier et partager.

Groupes​

Un groupe est un sac nommé d'utilisateurs à l'intérieur d'un compte. Vous accordez au groupe ; l'adhésion change sans toucher à l'octroi.

Deux limites, toutes deux délibérées :

  • Les groupes contiennent des utilisateurs, pas d'autres groupes. Il n'y a pas d'imbrication, donc il n'y a pas de hiérarchie de groupes à raisonner.
  • Les lectures sont ouvertes à chaque membre du compte ; les écritures sont réservées aux admins. N'importe quel membre peut lister les groupes et voir qui en fait partie. Seul un admin peut créer, renommer, supprimer, ou changer l'adhésion.

Les groupes sont dans le 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();

Droits d'accès — l'octroi porte sur le dossier​

C'est la phrase la plus importante de cette page, et celle qui a le plus de chances de vous surprendre :

Un octroi nomme un dossier, jamais un tableau de bord ou un jeu de données individuel.

Un droit d'accès est une ligne de la forme :

(account, folder, resource type, permission, principal) → allow | deny
  • resource type — dataset ou viz. Ce sont les deux qu'un octroi peut nommer ; voir Ce qu'un octroi peut nommer pour ce qui gouverne tout le reste.
  • permission — view, edit, delete, create.
  • principal — un utilisateur ou un groupe.
  • allow / deny — les deux types d'accès.

Un tableau de bord est couvert par un octroi parce qu'il se trouve dans le dossier accordé. Déplacez-le vers un autre dossier et il est désormais gouverné par les octrois de ce dossier-là. Il n'y a pas d'octroi par tableau de bord à perdre de vue, ce qui est le bon côté de cette conception ; l'autre côté est que le dossier est la plus petite unité que vous pouvez accorder, donc un dossier que vous partagez est un dossier que vous partagez entièrement.

Ce n'est pas un plafond sur la finesse d'un partage​

La phrase ci-dessus parle de l'autorisation, pas du partage. Un dossier est la plus petite chose que vous pouvez nommer dans une autorisation — et c'est vous qui décidez de son contenu. Pour partager exactement un tableau de bord, donnez-lui un dossier à lui seul, ce qui fait trois appels que votre agent possède déjà :

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 vaut dataset ou viz pour le déplacement, principal_type vaut user ou group, et access_type vaut allow par défaut — deny est l'autre valeur, et elle l'emporte sur toute autorisation.

Ces trois appels sont des outils MCP, et le MCP est la voie prise en charge pour cela. Les routes d'access-rights sont volontairement absentes du SDK publié et de la référence d'API — c'est de la plomberie de contrôle d'accès dont la forme n'est pas engagée, donc aucune méthode de client générée n'existe pour elles et aucune ne devrait être utilisée. create_folder est celui des trois qui a un équivalent SDK, client.folders.create.

Déplacer un tableau de bord change quelles autorisations le régissent et rien d'autre — il garde son id, et son lien n'est pas affecté. Donc « le partage par tableau de bord est impossible » est la mauvaise conclusion à tirer de la règle du dossier : c'est trois appels, et le dossier est la manière de l'exprimer.

Les deux moitiés d'une autorisation de tableau de bord​

Un tableau de bord et les données en dessous sont régis par des types de ressource distincts, donc un partage qui n'en nomme qu'un est à moitié fait. viz:view permet à quelqu'un d'ouvrir le tableau de bord ; dataset:view est ce qui permet à ses tuiles de renvoyer des chiffres. La section suivante explique pourquoi ce n'est pas un détail qu'on peut remettre à plus tard.

Comment une décision est réellement prise​

Dans l'ordre. La première règle qui correspond l'emporte :

  1. Admin → autoriser. Sans condition, avant même la lecture d'un octroi.
  2. Le principal interne system → autoriser. C'est l'identité propre de worker de la plateforme (jobs d'actualisation, exécutions planifiées) ; ce n'est pas un compte sur lequel vous pouvez vous connecter.
  3. Propriété → autoriser. L'utilisateur propriétaire de la ligne peut toujours la lire et la modifier.
  4. Sinon, les octrois de dossier décident, et un seul deny l'emporte sur tous les allow.

Le principe deny-l'emporte mérite d'être précisé, car il est plus étroit qu'il n'y paraît. Un deny retire un dossier de l'ensemble qui vous a été accordé — il ne soustrait pas des règles 1 à 3. Vous ne pouvez pas refuser un admin, et vous ne pouvez pas refuser quelqu'un l'accès à une ligne dont il est propriétaire. Si votre modèle mental est « deny est l'arrêt d'urgence », ce n'est pas le cas : c'est une soustraction à l'intérieur de la seule couche d'octroi.

Deux comportements qui vous mordront​

Les octrois ne se propagent pas dans les sous-dossiers. La vérification est un test d'appartenance à un ensemble plat sur le folder_id propre de la ressource. Les dossiers ont bien une relation parent/enfant, et l'interface la dessine comme un arbre — mais aucun code d'autorisation ne la parcourt. Un octroi sur Marketing n'atteint pas Marketing / T3. Accordez chaque dossier que vous voulez accorder.

Un deny sur create ne faisait autrefois rien — corrigé le 2026-09-08. La vérification de permission de création ne lisait que les lignes allow ; le droit de bootstrap d'un créateur à créer à la racine du compte ou dans un dossier dont il est propriétaire, et une ligne allow explicite ailleurs, contournaient tous deux entièrement un deny. Une ligne explicite create / deny d'un admin l'emporte désormais sur les deux — la même règle « un seul deny l'emporte sur tous les allow » que le reste de cette page décrit pour view/edit/delete. Les admins restent inrefusables, à l'image de toute autre permission ici.

Qui peut modifier un octroi​

Un admin du compte, ou le propriétaire du dossier sur lequel porte l'octroi. La lecture des octrois est ouverte à tout membre du compte : l'état de partage est affiché dans l'application sur des pages que tout membre peut ouvrir, la lecture est donc délibérément non verrouillée. Traitez la liste des octrois elle-même comme une information visible par tout le compte.

Ce qu'un octroi peut nommer​

Deux types de ressources peuvent faire l'objet d'un octroi : viz (tableaux de bord) et dataset. Construisez votre modèle de gouvernance avec ces deux-là, rangés dans des dossiers : c'est la forme que le modèle applique, et la seule que la boîte de dialogue propose. POST /v1/access-rights refuse tout autre type de ressource avec un 400, au moment où vous l'écrivez plutôt qu'au moment où quelqu'un compte dessus.

Trois choses sont gouvernées par autre chose qu'un octroi, et savoir laquelle par quoi est tout l'objet de cette section :

RessourceCe qui la gouverne
DossierL'appartenance au compte pour la lecture ; le rôle pour les mutations. Un dossier est le conteneur qu'un octroi nomme, pas une chose contre laquelle un octroi s'écrit.
Planification d'actualisationLa propriété, plus la frontière du compte.
QuestionPropriétaire uniquement. Une question ne peut être partagée par aucun mécanisme.
TâcheL'ACL du tableau de bord auquel elle appartient.
ConnexionSon propriétaire et les administrateurs du compte. Tout Créateur peut en créer une.

Une liste vide vous dit de quel genre de vide il s'agit. GET /v1/access-rights/{folder}/{type} porte un drapeau enforced : total: 0 pour un type non octroyable signifie aucun droit de ce type ne peut exister où que ce soit, tandis que total: 0 pour viz signifie personne n'en détient. Les lignes écrites avant le refus restent listées, et restent supprimables, avec enforced: false à côté d'elles.

Partager un tableau de bord sans partager ses données ne fonctionne pas​

C'est la question à laquelle le modèle ne répond pas comme la plupart des gens s'y attendent, elle mérite donc une phrase nette : viz:view seul ne donne à personne un tableau de bord qui fonctionne.

Les tuiles d'un tableau de bord vont chercher leurs chiffres avec POST /v1/query, et cette route autorise chaque jeu de données participant via l'ACL des jeux de données, en mode refuser-plutôt-qu'écarter : un seul jeu de données que le lecteur ne peut pas lire rejette la requête entière avec 403 query_dataset_forbidden. Elle n'est jamais exécutée sur le sous-ensemble qu'il a le droit de lire. Ainsi, « que le stagiaire voie ce tableau de bord mais pas les données derrière » produit un tableau de bord dont les tuiles échouent, pas un tableau de bord dont les chiffres sont masqués.

Accordez aussi dataset:view sur le dossier qui contient les jeux de données — viz:view et dataset:view vont par paire, et une autorisation qui ne porte que la première produit un tableau de bord dont chaque tuile renvoie 403. Si les jeux de données ne doivent pas être partagés en bloc, donnez-leur un dossier à eux comme le fait la recette ci-dessus. Ou, si le but est vraiment de montrer un résultat sans livrer les données sous-jacentes, publiez le tableau de bord ou intégrez-le. Ces deux voies s'exécutent sous l'identité de service de la plateforme et ne consultent jamais cette ACL, ce qui est précisément pourquoi elles peuvent montrer un chiffre à quelqu'un qui n'a pas le droit de l'interroger.

Vérifier votre propre travail​

Chaque champ ci-dessus décrit une règle ; aucun ne vous dit ce dont dispose une personne donnée au bout du compte, et cela ne se lit pas sur la liste des octrois. Les droits se composent — appartenances aux groupes, un deny qui l'emporte sur tous les allow y compris un allow hérité d'un groupe, la séparation par type de ressource, et le contrôle de rôle distinct sur les mutations — en un résultat qu'aucune liste ne montre. Le deny hérité est le cas qui compte : il n'apparaît jamais dans la liste des allow que vous lisez, si bien que « pourquoi cette personne ne voit-elle pas ceci ? » est sans réponse à partir de la seule liste.

GET /v1/access-rights/{folder}/effective?userId= le résout. Pour chaque couple (type de ressource, permission), il renvoie ce que décide l'ACL, quelle règle a décidé, si le rôle de la personne le permet, si le type est appliqué, le produit de tout cela — et la ligne de droit d'accès qui a produit le verdict, c'est-à-dire l'identifiant à révoquer quand la réponse est un deny. Votre agent dispose de la même chose sous le nom describe_effective_access.

Il répond uniquement à la question de l'ACL, et le dit dans une liste caveats présente sur chaque réponse : il ne peut pas voir les lignes que la personne possède (la propriété est au niveau de la ligne et l'emporte sur l'ACL du dossier), il ne sait rien des voies de publication ou d'intégration, et — comme expliqué ci-dessus — un tableau de bord qui fonctionne exige aussi des droits sur ses jeux de données. Vous pouvez toujours interroger vos propres droits ; interroger ceux de quelqu'un d'autre exige la même qualité que modifier l'ACL : un admin du compte ou le propriétaire du dossier.

Y accéder depuis le code​

SurfaceDans les SDKs ?
Groupes (création, liste, mise à jour, adhésion)Oui — client.groups.*
Utilisateurs (liste, mise à jour, rôle, adhésion à un groupe)Oui — client.users.*
CompteOui — client.accounts.*
Droits d'accès (les octrois eux-mêmes)Non

Les droits d'accès sont exclus de la surface générée du SDK, il n'y a donc pas de client.accessRights ni d'entrée pour eux dans la Référence API. Les routes existent et acceptent une clé API — elles ne font simplement pas partie du contrat de client publié et versionné, et nous préférons vous le dire plutôt que de vous laisser construire contre une forme sur laquelle nous ne nous sommes pas engagés. Gérez les octrois dans l'application, ou depuis votre agent : le serveur MCP expose des outils d'octroi, de révocation et de liste des droits d'accès qui appellent les mêmes routes sous votre propre identité, et héritent de la même règle admin-ou-propriétaire-du-dossier.

Filtrage au niveau des lignes : la voie d'intégration, et seulement elle​

La sécurité au niveau des lignes est réelle dans Vibedasher, mais elle vit à un seul endroit : les tableaux de bord intégrés derrière un jeton signé.

Quand votre serveur génère un jeton d'intégration, il peut y attacher des filtres. Sur chaque requête, la plateforme réécrit chaque référence de jeu de données en une sous-requête filtrée, de sorte que le visionneur ne peut physiquement pas lire des lignes en dehors de sa tranche. Les filtres proviennent exclusivement du jeton vérifié — jamais du corps de la requête — donc un visionneur ne peut pas élargir sa propre portée en modifiant une requête. Un filtre nommant une colonne qui n'existe dans aucun schéma de jeu de données est rejeté avec un 403 plutôt qu'ignoré silencieusement, car un filtre qui ne fait silencieusement rien est une brèche.

Trois limites à prévoir :

  • Cela ne s'applique qu'à la voie d'intégration. La voie de requête du SDK n'a aucune injection de filtre obligatoire : une clé API est un appelant côté serveur de confiance, et scoper ses propres utilisateurs est votre travail, pas celui de la plateforme.
  • L'ACL de dossier n'est pas au niveau des lignes. Elle décide quels tableaux de bord et jeux de données existent pour vous, pas quelles lignes à l'intérieur vous pouvez lire. Ce sont des questions différentes et Vibedasher y répond avec des mécanismes différents.
  • Un tableau de bord qui exécute ses requêtes dans le navigateur ne peut pas être restreint du tout — c'est voulu. C'est le point à trancher avant de construire, pas après. Un tableau de bord auto-hébergé sur la voie sans identifiants — l'arbre éjecté dont le dataLane vaut wasm, ou tout ce que vous avez construit vous-même au-dessus d'un extrait Parquet ou JSON — télécharge les données et exécute chaque requête dans le navigateur du visiteur. L'extrait est adressé par jeu de données et instantané, pas par requête : il contient donc toutes les lignes de ce jeu de données, et chaque visiteur reçoit le même fichier. Il ne reste aucun serveur sur le chemin pour le restreindre : un filtre écrit dans la page change ce qui est dessiné, jamais ce qui a été livré.
La portée par ligne et la voie sans identifiants s'excluent mutuellement

Tout l'intérêt de cette voie est qu'il n'y a pas de clé dans le bundle, pas de jeton à générer et pas de backend à faire tourner de votre côté — et le prix, c'est exactement la même propriété vue de l'autre côté : rien ne peut donner moins de lignes à un visionneur qu'à un autre. Ce n'est pas un manque qui sera comblé ; c'est ce que signifie mettre les données dans le navigateur.

Si certains visionneurs doivent voir moins de lignes, gardez la requête sur un serveur. Deux montages le font, et tous deux gardent l'identifiant hors de la page : votre propre backend génère un jeton d'intégration de courte durée portant le filtre (la plateforme ajoute ce prédicat à chaque requête qu'elle autorise, avant toute jointure ou agrégation, de sorte que la page ne peut pas l'élargir), ou bien du code serveur de confiance interroge avec une clé API et renvoie des lignes que vous avez déjà restreintes. Choisir d'abord la voie rapide, puis la « restreindre » ensuite avec un filtre dans la page, produit un tableau de bord qui a l'air correct, ne lève aucune erreur, et livre toutes les lignes.

Voir Intégration pour comment un jeton est généré et présenté.

Authentification à deux facteurs​

E-mail + mot de passe est la seule entrée de Vibedasher qui repose sur un secret unique. Les connexions Google et LinkedIn portent le second facteur de leur fournisseur ; les clés API et le jeton CLI de vibedasher login ne sont pas des connexions. Le second facteur est donc opt-in, par utilisateur, sur la connexion par mot de passe uniquement : un code TOTP à six chiffres issu de n'importe quelle application d'authentification (Google Authenticator, 1Password, Authy…), activé depuis Paramètres → Authentification à deux facteurs en scannant un QR code et en confirmant un code. L'activation vous remet huit codes de récupération à usage unique, affichés une seule fois — un téléphone perdu se connecte avec l'un d'eux.

Ce que cela change à la connexion : après un mot de passe correct, le navigateur ne reçoit aucune session — il reçoit un défi de cinq minutes, à usage unique, que seule l'étape du code peut échanger. L'API ne voit jamais ce défi comme un identifiant : il porte une audience que l'API refuse d'emblée, il ne peut donc ni être rejoué contre api.*.vibedasher.com ni glissé dans le cookie de session. Cinq codes erronés verrouillent le défi pendant quinze minutes ; un code n'est valable qu'une fois.

Trois limites à connaître :

  • L'inscription est individuelle. Chaque membre l'active pour son propre compte ; ce n'est pas quelque chose qu'un administrateur enclenche pour tout le monde.
  • Les connexions OAuth ne sont jamais soumises au défi, volontairement — ajouter notre facteur par-dessus celui du fournisseur, c'est de la friction sans sécurité.
  • Le déblocage est une action d'administrateur. Un membre sans téléphone ni code de récupération s'adresse à un administrateur du compte, qui réinitialise son second facteur depuis la page du membre (l'action est journalisée). Un propriétaire qui est le seul administrateur, sans code de récupération, passe par le support.

Une configuration pratique​

Pour une équipe de dix personnes avec quelques jeux de données sensibles :

  1. Désignez comme admins les une ou deux personnes qui administrent le compte. Rappelez-vous qu'elles sont ingouvernables par les octrois — c'est le principe du rôle, et la raison de garder la liste courte.
  2. Tous ceux qui construisent sont des créateurs ; tous ceux qui ne font que lire sont des visionneurs. Le rôle visionneur est un refus ferme sur toute mutation, c'est donc une vraie frontière, pas une convention d'interface.
  3. Placez les tableaux de bord et jeux de données dans des dossiers qui correspondent à la façon dont vous voulez les partager — un dossier par audience, pas par sujet. Le dossier est l'octroi.
  4. Accordez à des groupes, pas à des personnes. L'octroi survit alors à l'arrivée ou au départ de quelqu'un.
  5. Ne créez pas de dossiers imbriqués en attendant que l'octroi du parent les atteigne. Accordez chacun d'eux.
  6. Si un tableau de bord doit aller à des personnes en dehors du compte, publiez-le et intégrez-le avec un jeton signé portant leurs filtres de ligne — ne leur donnez pas de compte.