urn:ietf:params:jmap:principals) : un annuaire de Principals (les personnes, équipes, ressources et lieux de l’organisation) et un modèle de droits uniforme que chaque type de collection réutilise.
Objets et méthodes
Le modèle de droits
Chaque collection partageable (Mailbox, Calendar, TaskList, AddressBook, FileNode, Label) porte la même carteshareWith : identifiant de principal vers objet de droits (mayRead, mayWrite, mayAdmin, plus des droits de domaine comme mayReadFreeBusy). Les droits sont :
- Écrits par le propriétaire sur l’objet collection ; chaque changement de droit produit une
ShareNotificationpour le bénéficiaire et un changement enregistré pour la resynchronisation. - Exercés par l’indirection
shared:: un bénéficiaire adresse les données du propriétaire avec l’identifiant de compte accordé dans le slotaccountIdJMAP standard ; l’objetaccountsde la session liste ce qui est joignable. Les capacités se résolvent sur l’appelant authentifié, la résidence des données sur le propriétaire : l’usurpation n’est pas représentable dans la forme de la requête. - Fermés par défaut. Le dispatch des comptes partagés refuse par défaut ; une erreur de recherche de droits refuse plutôt que d’accorder.
Sémantique d’annuaire à connaître
- Les types de principals sont honnêtes. Le principal d’un compte d’équipe partagé est
type: "other"(non connectable), jamaisindividual: les sélecteurs de personnes peuvent se fier au type. - Les bénéficiaires doivent être adossés à un humain. Un droit accordé à un principal adossé à un compte non connectable est rejeté à l’écriture sur tous les domaines.
- Détruire un principal purge ses droits. Un destroy de
Principal/setretire les clés du bénéficiaire de chaque carteshareWith(pas d’entrées d’ACL pendantes) et avertit ou s’interrompt s’il laisserait un drive partagé sans gardien éligible. - L’effacement laisse des pierres tombales. Le principal d’une personne effacée est nettoyé de ses données personnelles mais conservé comme ancre de graphe, et un principal en pierre tombale est refusé comme nouveau bénéficiaire (conformité).
Principal/getAvailability(le libre/occupé) est documenté sur la page agenda.
ShareNotification/query renvoie un ordre total (du plus récent au plus ancien). Les règles de rigueur sont identiques à la surface courrier.
D’une organisation à l’autre : la fédération
Tout ce qui précède partage des données à l’intérieur d’une organisation. Partager avec une organisation différente est une surface distincte,urn:oximail:params:jmap:federation, et ce n’est volontairement pas le même mécanisme : un grant fédéré n’est ni une entrée shareWith, ni une ligne d’accès au compte. En v0.30.0, ce qu’il porte, c’est la disponibilité (le libre/occupé) : le grant élargit qui peut demander, jamais ce qui est visible.
L’objet de capacité annonce le vocabulaire au lieu de laisser les clients le découvrir par des refus :
Le bénéficiaire est une organisation, pas une personne
supportedGranteeKinds vaut ["realm"], et ce n’est pas une question de calendrier. L’identité qu’une requête fédérée prouve aujourd’hui est le nœud émetteur, pas lequel de ses principals a agi. Un grant nommant une seule personne à l’autre bout ne serait donc pas réellement plus étroit qu’un grant nommant son organisation : il en aurait seulement l’air dans une interface. La liste existe pour que, le jour où une couche d’identité par principal arrivera, "principal" soit un ajout que le client peut détecter, et non un changement de comportement à deviner.
granteeValue doit être un realm avec lequel vous avez réellement été appairé. L’appairage lui-même est une cérémonie d’opérateur bilatérale et hors bande : ce n’est pas une méthode JMAP, et c’est précisément pourquoi FederationPeer existe. Sans cette liste, un client n’avait que deux options, mauvaises toutes les deux : un champ libre où presque toute saisie est rejetée, ou un sélecteur que rien ne remplit.
FederationPeer est borné à l’organisation et lisible par tout compte authentifié de celle-ci, et pas seulement par les administrateurs : un appairage est une relation de l’organisation, sans vue par sujet. L’objet porte realm (la valeur à envoyer en granteeValue), jurisdiction, state et pairedAt. Il n’expose volontairement ni la clé épinglée, ni l’ancre d’appairage, ni l’époque de clé, ni la cause de suspension : ce sont des écritures d’opérateur, et une clé publique sur chaque écran de consentement serait de la surface de sécurité achetée sans aucune fonctionnalité en échange.
FederationPeer n’a pas de méthode changes, et son state n’est pas un compteur de journal de changements : c’est une empreinte de la liste servie. L’appairage et la suspension se font dans des cérémonies d’opérateur, hors de toute requête JMAP, donc rien n’écrit de ligne de journal pour elles ; un état de journal serait ici une chaîne qui ne bouge jamais alors que les données dessous changent.Le consentement ne se délègue pas
- Seul le sujet peut créer ou modifier un grant, en agissant sur son propre compte. Un administrateur peut révoquer, car la révocation retire un accès et un opérateur qui doit couper une fuite n’a pas à attendre le sujet, mais jamais accorder à la place de quelqu’un. Un grant qu’un administrateur peut créer pour vous n’est pas un consentement. Le contrôle est plus strict qu’un test de rôle : une requête qui porte un accès partagé, c’est quelqu’un qui agit pour un autre, quel que soit son niveau.
- Chaque écriture est une seule transaction : la ligne de grant, la
ShareNotificationqui prévient le sujet et la ligne d’audit qui nomme l’auteur de l’écriture valident ensemble. L’audit enregistre qui a réellement écrit, pas le compte que la requête visait : la révocation d’un administrateur n’est donc jamais attribuée au sujet. - Un
granteeKindnon pris en charge est refusé bruyamment."principal"s’analyse comme une requête bien formée et aurait l’air accepté ; la ligne créée serait un grant de realm portant le nom d’une personne.
granteeKind, granteeValue et expiresAt sont réglables. rights en particulier ne l’est pas : un client capable d’élargir son propre enregistrement de consentement au-delà de ce que le résolveur honore serait une permission déguisée en réglage. expiresAt: null signifie que le sujet a explicitement choisi « sans expiration », et jurisdiction est gelée à la création : le consentement a été donné à une contrepartie dans un lieu donné, et un champ modifiable laisserait le lieu changer sans nouveau consentement.
Pourquoi un grant peut cesser d’agir, et comment le sujet l’apprend
FederationGrant.state répond directement à « pourquoi cette organisation ne me voit-elle plus ? » : active, unpaired, expired, dormancy_exhausted, dormant, lapsed ou reanchored. Le stockage le dérive du prédicat même qu’applique la lecture d’autorisation : il ne peut donc pas s’écarter de la décision. FederationPeer.state (active, dormant, lapsed) emploie les mêmes mots pour les mêmes défaillances, et seul active accepte un grant, là encore dérivé du prédicat sur lequel l’écriture refuse : un realm que le sélecteur propose est un realm que l’écriture accepte.
Deux conséquences valent la peine d’être prises en compte dès la conception :
- La révocation est unilatérale, immédiate, et ne vaut que vers l’avant. Un pair ne lit ni n’écrit jamais vos grants ; il apprend une révocation en se voyant refusé. C’est ce qui garde la révocation hors du terrain de la cohérence distribuée, et c’est aussi pourquoi
revocationIsForwardOnlyest annoncé comme donnée : révoquer retire le chemin de lecture pour la suite et ne défait rien de ce qui a déjà été divulgué. - Le journal des divulgations est l’autre moitié de cette phrase.
FederationDisclosureest ce qui rend « révoquer ne défait rien de déjà divulgué » vérifiable au lieu de simplement rassurant : il survit au grant, et il n’existe aucunFederationDisclosure/set, pas même limité au destroy. C’est la rétention qui borne le journal ; le sujet n’en efface rien. Il enregistre les divulgations, jamais les tentatives : un pair qui sonde cinq cents adresses ne crée aucune ligne au sujet des quatre cent quatre-vingt-dix-neuf personnes qui n’ont jamais consenti. Chaque ligne nomme le realm du pair, la couche d’identité vérifiée, la juridiction sous laquelle le grant se tenait à cet instant, et la date.
Les filtres et les tris sur les deux méthodes
query sont refusés (unsupportedFilter / unsupportedSort) plutôt qu’ignorés en silence. Un client qui filtrerait les pairs sur state: "active" et recevrait discrètement aussi les dormants proposerait à son utilisateur un bénéficiaire que l’écriture rejette ensuite.