mirror of
https://github.com/almet/notmyidea.git
synced 2025-04-30 12:32:38 +02:00
455 lines
No EOL
46 KiB
XML
455 lines
No EOL
46 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis - Carnets en ligne</title><link href="https://blog.notmyidea.org/" rel="alternate"></link><link href="https://blog.notmyidea.org/feeds/all-fr.atom.xml" rel="self"></link><id>https://blog.notmyidea.org/</id><updated>2015-07-07T00:00:00+02:00</updated><entry><title>Service de nuages : Perspectives pour l'été</title><link href="https://blog.notmyidea.org/service-de-nuages-perspectives-pour-lete-fr.html" rel="alternate"></link><published>2015-07-07T00:00:00+02:00</published><updated>2015-07-07T00:00:00+02:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2015-07-07:/service-de-nuages-perspectives-pour-lete-fr.html</id><summary type="html"><p class="first last">Le travail en cours et les fonctionnalités à venir pour les prochains mois.</p>
|
||
</summary><content type="html"><p><em>Cet article est repris depuis le blog « Service de Nuages » de mon équipe à Mozilla</em></p>
|
||
<p>Mozilla a pour coutume d'organiser régulièrement des semaines de travail où tous les employés
|
||
sont réunis physiquement. Pour cette dernière édition, nous avons pu retrouver
|
||
nos collègues du monde entier à <a class="reference external" href="http://www.openstreetmap.org/node/268148288#map=4/50.12/-122.95">Whistler, en Colombie Britannique au Canada</a> !</p>
|
||
<img alt="«All Hands» talk about Lego, by &#64;davidcrob - CC0" class="align-center" src="{filename}/images/whistler-talks.jpg" />
|
||
<p>Ce fût l'occasion pour notre équipe de se retrouver, et surtout de partager notre
|
||
vision et nos idées dans le domaine du stockage, afin de collecter des cas d'utilisation pour
|
||
notre solution <a class="reference external" href="https://kinto.readthedocs.org">Kinto</a>.</p>
|
||
<p>Dans cet article, nous passons en revue les pistes que nous avons pour
|
||
les prochains mois.</p>
|
||
<div class="section" id="ateliers-et-promotion">
|
||
<h2>Ateliers et promotion</h2>
|
||
<p>Nicolas a présenté <a class="reference external" href="https://github.com/mozilla-services/kinto.js">Kinto.js</a> dans un atelier dédié, avec comme support de
|
||
présentation le <a class="reference external" href="http://kintojs.readthedocs.org/en/latest/tutorial/">tutorial d'introduction</a>.</p>
|
||
<p>L'application résultante, pourtant toute simple, permet d'appréhender les
|
||
concepts de synchronisation de Kinto. Le tout sans installation prélable,
|
||
puisque Rémy a mis en place un <a class="reference external" href="https://kinto.dev.mozaws.net/v1/">serveur de dev effacé tous les jours</a>.</p>
|
||
<p>Nous avions mis un point d'honneur à faire du Vanilla.JS, déjà pour éviter les
|
||
combats de clochers autour des frameworks, mais aussi pour mettre en évidence qu'avec
|
||
HTML5 et ES6, on n'était plus aussi démunis qu'il y a quelques années.</p>
|
||
<p>Ce petit atelier nous a permis de nous rendre compte qu'on avait encore de
|
||
grosses lacunes en terme de documentation, surtout en ce qui concerne
|
||
l'éco-système et la vision globale des projets (Kinto, Kinto.js, Cliquet, ...).
|
||
Nous allons donc faire de notre mieux pour combler ce manque.</p>
|
||
<img alt="Kinto.js workshop - CC0" class="align-center" src="{filename}/images/whistler-workshop.jpg" />
|
||
</div>
|
||
<div class="section" id="mozilla-payments">
|
||
<h2>Mozilla Payments</h2>
|
||
<p>Comme <a class="reference external" href="http://www.servicedenuages.fr/la-gestion-des-permissions">décrit précédemment</a>, nous avons mis en place un système de permissions pour répondre aux besoins de suivi des paiements et abonnements.</p>
|
||
<p>Pour ce projet, Kinto sera utilisé depuis une application Django, via un client Python.</p>
|
||
<p>Maintenant que les développements ont été livrés, il faut transformer l'essai, réussir l'intégration, l'hébergement et la montée en puissance. La solution doit être livrée à la fin de l'année.</p>
|
||
<div class="section" id="a-venir">
|
||
<h3>À venir</h3>
|
||
<p>Nous aimerions en profiter pour implémenter une fonctionnalité qui nous tient à coeur : la construction de la liste des enregistrements accessibles en lecture sur une collection partagée.</p>
|
||
<img alt="Whistler Alta Lake - CC0" class="align-center" src="{filename}/images/whistler-lake.jpg" />
|
||
</div>
|
||
</div>
|
||
<div class="section" id="firefox-os-et-stockage">
|
||
<h2>Firefox OS et stockage</h2>
|
||
<p>Nous avons eu beaucoup d'échanges avec l'équipe de Firefox OS, avec qui nous avions
|
||
déjà eu l'occasion de collaborer, pour le <a class="reference external" href="https://github.com/mozilla-services/msisdn-gateway">serveur d'identification BrowserID par SMS</a> et pour <a class="reference external" href="https://github.com/mozilla-services/loop-server">Firefox Hello</a>.</p>
|
||
<div class="section" id="in-app-sync">
|
||
<h3>In-App sync</h3>
|
||
<p>Kinto, la solution simple promue pour la synchronisation de données dans les applications
|
||
Firefox OS ? La classe ! C'est ce qu'on avait en tête depuis longtemps, déjà à
|
||
l'époque avec <a class="reference external" href="http://daybed.readthedocs.org/">Daybed</a>. Voici donc une belle opportunité à saisir !</p>
|
||
<p>Il va falloir expliciter les limitations et hypothèses simplificatrices de notre
|
||
solution, surtout en termes de gestion de la concurrence. Nous sommes persuadés
|
||
que ça colle avec la plupart des besoins, mais il ne faudrait pas décevoir :)</p>
|
||
<p>Le fait que <a class="reference external" href="https://github.com/daleharvey">Dale</a>, un des auteurs de <a class="reference external" href="http://pouchdb.com/">PouchDB</a> et <a class="reference external" href="https://github.com/michielbdejong">Michiel de Jong</a>, un des auteurs de <a class="reference external" href="http://remotestorage.io/">Remote Storage</a>, nous aient encouragés sur nos premiers pas nous a bien motivé !</p>
|
||
</div>
|
||
<div class="section" id="cut-the-rope">
|
||
<h3>Cut the Rope</h3>
|
||
<p>Kinto devrait être mis à profit pour synchroniser les paramètres et les scores
|
||
du <a class="reference external" href="http://mozilla.cuttherope.net/">jeu</a>. Un premier exercice et une première vitrine sympas !</p>
|
||
</div>
|
||
<div class="section" id="syncto">
|
||
<h3>« SyncTo »</h3>
|
||
<p><a class="reference external" href="https://docs.services.mozilla.com/storage/apis-1.5.html">Firefox Sync</a> est la solution qui permet de synchroniser les données de Firefox (favoris, extensions, historique, complétion des formulaires, mots de passe, ...) entre plusieurs périphériques, de manière chiffrée.</p>
|
||
<p>L'implémentation du client en JavaScript est relativement complexe et date un peu maintenant.
|
||
Le code existant n'est pas vraiment portable dans <em>Firefox OS</em> et les tentatives de réécriture
|
||
n'ont pas abouti.</p>
|
||
<p>Nous souhaitons implémenter un pont entre <em>Kinto</em> et <em>Firefox Sync</em>, de manière
|
||
à pouvoir utiliser le client <em>Kinto.js</em>, plus simple et plus moderne, pour récupérer
|
||
les contenus et les stocker dans IndexedDB. Le delta à implémenter côté serveur est faible car nous nous étions
|
||
inspirés du protocole déjà éprouvé de Sync. Côté client, il s'agira surtout de
|
||
câbler l'authentification BrowserId et la Crypto.</p>
|
||
<p>Alexis a sauté sur l'occasion pour commencer l'écriture d'<a class="reference external" href="https://github.com/mozilla-services/syncclient">un client python pour Firefox Sync</a>, qui servira de brique de base pour l'écriture du service.</p>
|
||
</div>
|
||
<div class="section" id="cloud-storage">
|
||
<h3>Cloud Storage</h3>
|
||
<p>Eden Chuang et Sean Lee ont présenté les avancées sur l'intégration de services de stockages
|
||
distants (<em>DropBox, Baidu Yun</em>) dans <em>Firefox OS</em>. Actuellement, leur preuve de
|
||
concept repose sur <a class="reference external" href="https://fr.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</a>.</p>
|
||
<p>Nous avons évidemment en tête d'introduire la notion de fichiers attachés dans
|
||
<em>Kinto</em>, en implémentant la specification
|
||
<a class="reference external" href="https://tools.ietf.org/html/draft-dejong-remotestorage-05">*Remote Storage*</a>,
|
||
mais pour l'instant les cas d'utilisations ne se sont pas encore présentés officiellement.</p>
|
||
</div>
|
||
<div class="section" id="id2">
|
||
<h3>À venir</h3>
|
||
<p>Nous serons probablement amenés à introduire la gestion de la concurrence dans
|
||
le client JS, en complément de ce qui a été fait sur le serveur, pour permettre
|
||
les écritures simultanées et synchronisation en tâche de fond.</p>
|
||
<p>Nous sommes par ailleurs perpétuellement preneurs de vos retours — et bien
|
||
entendu de vos contributions — tant sur le code <a class="reference external" href="https://github.com/mozilla-services/kinto/">serveur</a>
|
||
que <a class="reference external" href="https://github.com/mozilla-services/kinto.js/">client</a> !</p>
|
||
<img alt="Firefox OS Cloud Storage Presentation - CC0" class="align-center" src="{filename}/images/whistler-cloud-storage.jpg" />
|
||
</div>
|
||
</div>
|
||
<div class="section" id="contenus-applicatifs-de-firefox">
|
||
<h2>Contenus applicatifs de Firefox</h2>
|
||
<p>Aujourd'hui Firefox a un cycle de release de six semaines. Un des objectifs
|
||
consiste à désolidariser certains contenus applicatifs de ces cycles
|
||
relativement longs (ex. <em>règles de securité, dictionnaires, traductions, ...</em>) <a class="footnote-reference" href="#id4" id="id3">[1]</a>.</p>
|
||
<p>Il s'agit de données JSON et binaire qui doivent être versionnées et synchronisées par
|
||
les navigateurs (<em>lecture seule</em>).</p>
|
||
<p>Il y a plusieurs outils officiels qui existent pour gérer ça (<em>Balrog</em>, <em>Shavar</em>, ...),
|
||
et pour l'instant, aucun choix n'a été fait. Mais lors des conversations avec
|
||
l'équipe en charge du projet, ce fût vraiment motivant de voir que même pour
|
||
ce genre de besoins internes, <em>Kinto</em> est tout aussi pertinent !</p>
|
||
<table class="docutils footnote" frame="void" id="id4" rules="none">
|
||
<colgroup><col class="label" /><col /></colgroup>
|
||
<tbody valign="top">
|
||
<tr><td class="label"><a class="fn-backref" href="#id3">[1]</a></td><td>La bonne nouvelle c'est que toutes les fonctionnalités <em>third-party</em> qui ont
|
||
été intégrées récemment vont redevenir des <em>add-ons</em> \o/.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<img alt="Landscape - CC0" class="align-center" src="{filename}/images/whistler-landscape.jpg" />
|
||
</div>
|
||
<div class="section" id="awesome-bar">
|
||
<h2>Awesome bar</h2>
|
||
<p>L'équipe <em>Firefox Labs</em>, le laboratoire qui élève des pandas roux en éprouvette,
|
||
serait vraiment intéressé par notre solution, notamment pour abreuver en données
|
||
un prototype pour améliorer <em>Awesome bar</em>, qui fusionnerait URL, historique et recherche.</p>
|
||
<p>Nous ne pouvons pas en dire beaucoup plus pour l'instant, mais les fonctionnalités
|
||
de collections d'enregistrements partagées entre utilisateurs de <em>Kinto</em>
|
||
correspondent parfaitement à ce qui est envisagé pour le futur du navigateur :)</p>
|
||
<div class="section" id="id5">
|
||
<h3>À venir</h3>
|
||
<p>Nous serons donc probablement amenés, avant de la fin de l'année, à introduire des
|
||
fonctionnalités d'indexation et de recherche <em>full-text</em> (comprendre <em>ElasticSearch</em>).
|
||
Cela rejoint nos plans précédents, puisque c'est quelque chose que nous avions dans
|
||
<em>Daybed</em>, et qui figurait sur notre feuille de route !</p>
|
||
<img alt="Firefox Labs Meeting - CC0" class="align-center" src="{filename}/images/whistler-labs.jpg" />
|
||
</div>
|
||
</div>
|
||
<div class="section" id="browser-html">
|
||
<h2>Browser.html</h2>
|
||
<p>L'équipe <em>Recherche</em> explore les notions de plateforme, et travaille notamment
|
||
sur l'implémentation d'un navigateur en JS/HTML avec <em>React</em>:
|
||
<a class="reference external" href="https://github.com/mozilla/browser.html">browser.html</a></p>
|
||
<p><em>Kinto</em> correspond parfaitement aux attentes
|
||
de l'équipe pour synchroniser les données associées à un utilisateur.</p>
|
||
<p>Il pourrait s'agir de données de navigation (comme Sync), mais aussi de collections
|
||
d'enregistrements diverses, comme par exemple les préférences du navigateur
|
||
ou un équivalent à <em>Alexa.com Top 500</em> pour fournir la complétion d'URL sans
|
||
interroger le moteur de recherche.</p>
|
||
<p>L'exercice pourrait être poussé jusqu'à la synchronisation d'états <em>React</em>
|
||
entre périphériques (par exemple pour les onglets).</p>
|
||
<div class="section" id="id7">
|
||
<h3>À venir</h3>
|
||
<p>Si <em>browser.html</em> doit stocker des données de navigation, il faudra ajouter
|
||
des fonctionnalités de chiffrement sur le client JS. Ça tombe bien, c'est un
|
||
sujet passionant, et <a class="reference external" href="http://www.w3.org/TR/WebCryptoAPI/">il y a plusieurs standards</a> !</p>
|
||
<p>Pour éviter d'interroger le serveur à intervalle régulier afin de synchroniser les
|
||
changements, l'introduction des <a class="reference external" href="https://w3c.github.io/push-api/">*push notifications*</a> semble assez naturelle.
|
||
Il s'agirait alors de la dernière pierre qui manque à l'édifice pour obtenir
|
||
un «<em>Mobile/Web backend as a service</em>» complet.</p>
|
||
<img alt="Roadmap - CC0" class="align-center" src="{filename}/images/whistler-roadmap.jpg" />
|
||
</div>
|
||
</div>
|
||
<div class="section" id="conclusion">
|
||
<h2>Conclusion</h2>
|
||
<p>Nous sommes dans une situation idéale, puisque ce que nous avions imaginé
|
||
sur <a class="reference external" href="https://github.com/mozilla-services/kinto/wiki/Roadmap">notre feuille de route</a> correspond à ce qui nous est demandé par les
|
||
différentes équipes.</p>
|
||
<p>L'enjeu consiste maintenant à se coordonner avec tout le monde, ne pas décevoir,
|
||
tenir la charge, continuer à améliorer et à faire la promotion du produit, se concentrer
|
||
sur les prochaines étapes et embarquer quelques contributeurs à nos cotés pour
|
||
construire une solution libre, générique, simple et auto-hébergeable pour le stockage
|
||
de données sur le Web :)</p>
|
||
<img alt="Friday Night Party - CC0" class="align-center" src="{filename}/images/whistler-top-roof.jpg" />
|
||
</div>
|
||
</content></entry><entry><title>Service de nuages : Stocker et interroger les permissions avec Kinto</title><link href="https://blog.notmyidea.org/service-de-nuages-stocker-et-interroger-les-permissions-avec-kinto-fr.html" rel="alternate"></link><published>2015-05-26T00:00:00+02:00</published><updated>2015-05-26T00:00:00+02:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2015-05-26:/service-de-nuages-stocker-et-interroger-les-permissions-avec-kinto-fr.html</id><summary type="html"><p class="first last">Comment faire pour stocker et interroger la base de données au sujet des permissions avec Kinto ?</p>
|
||
</summary><content type="html"><p><em>Cet article est repris depuis le blog « Service de Nuages » de mon équipe à Mozilla</em></p>
|
||
<p><strong>tl;dr: On a maintenant un super système de permission mais comment faire pour stocker et interroger ces permissions de manière efficace ?</strong></p>
|
||
<div class="section" id="la-problematique">
|
||
<h2>La problématique</h2>
|
||
<p>Maintenant que nous avons défini un modèle de gestion des permissions
|
||
sur les objets qui nous satisfait, le problème est de stocker ces
|
||
permissions de manière efficace afin de pouvoir autoriser ou interdire
|
||
l'accès à un objet pour la personne qui fait la requête.</p>
|
||
<p>Chaque requête sur notre API va générer une ou plusieurs demandes
|
||
d'accès, il faut donc que la réponse soit très rapide sous peine
|
||
d'impacter la vélocité du service.</p>
|
||
</div>
|
||
<div class="section" id="obtenir-la-liste-des-principals-d-un-utilisateur">
|
||
<h2>Obtenir la liste des &quot;principals&quot; d'un utilisateur</h2>
|
||
<p>Les <em>principals</em> de l'utilisateur correspondent à son <tt class="docutils literal">user_id</tt>
|
||
ainsi qu'à la liste des identifiants des groupes dans lesquels il a
|
||
été ajouté.</p>
|
||
<p>Pour éviter de recalculer les <em>principals</em> de l'utilisateur à chaque
|
||
requête, le mieux reste de maintenir une liste des <em>principals</em> par
|
||
utilisateur.</p>
|
||
<p>Ainsi lorsqu'on ajoute un utilisateur à un groupe, il faut bien penser
|
||
à ajouter le groupe à la liste des <em>principals</em> de l'utilisateur.</p>
|
||
<p>Ça se complexifie lorsqu'on ajoute un groupe à un groupe.</p>
|
||
<p>Dans un premier temps interdire l'ajout d'un groupe à un groupe est
|
||
une limitation qu'on est prêts à accepter pour simplifier le
|
||
modèle.</p>
|
||
<p>L'avantage de maintenir la liste des <em>principals</em> d'un utilisateur
|
||
lors de la modification de cette liste c'est qu'elle est déjà
|
||
construite lors des lectures, qui sont dans notre cas plus fréquentes
|
||
que les écritures.</p>
|
||
<p>Cela nécessite de donner un identifiant unique aux groupes pour tous
|
||
les <em>buckets</em>.</p>
|
||
<p>Nous proposons de de les nommer avec leur URI:
|
||
<tt class="docutils literal">/buckets/blog/groups/moderators</tt></p>
|
||
</div>
|
||
<div class="section" id="obtenir-la-liste-des-principals-d-un-ace">
|
||
<h2>Obtenir la liste des &quot;principals&quot; d'un ACE</h2>
|
||
<blockquote>
|
||
Rappel, un &quot;ACE&quot; est un <em>Access Control Entry</em>, un des éléments
|
||
d'une ACL (e.g. <em>modifier un enregistrement</em>).</blockquote>
|
||
<p>Avec le <a class="reference external" href="{filename}/2015.05.cliquet-permissions.rst">système de permissions choisi</a>, les permissions d'un
|
||
objet héritent de celle de l'objet parent.</p>
|
||
<p>Par exemple, avoir le droit d'écriture sur un <em>bucket</em> permet la
|
||
création des permissions et la modification de tous ses records.</p>
|
||
<p>Ce qui veut dire que pour obtenir la liste complète des <em>principals</em>
|
||
ayant une permission sur un objet, il faut regarder à plusieurs
|
||
endroits.</p>
|
||
<p>Rémy a <a class="reference external" href="https://gist.github.com/Natim/77c8f61c1d42e476cef8#file-permission-py-L9-L52">décrit dans un gist la liste d'héritage de chaque permission</a>.</p>
|
||
<p>Prenons l'exemple de l'ajout d'un record dans une collection.</p>
|
||
<p>Le droit <tt class="docutils literal">records:create</tt> est obtenu si l'on a l'un des droits suivants:</p>
|
||
<ul class="simple">
|
||
<li><tt class="docutils literal">bucket:write</tt></li>
|
||
<li><tt class="docutils literal">collection:write</tt></li>
|
||
<li><tt class="docutils literal">records:create</tt></li>
|
||
</ul>
|
||
<p>Notre première idée était de stocker les permissions sur chaque objet
|
||
et de maintenir la liste exhaustive des permissions lors d'une
|
||
modification d'ACL. Cependant cela nécessitait de construire cette
|
||
liste lors de l'ajout d'un objet et de mettre à jour tout l'arbre lors
|
||
de sa suppression. (<em>Je vous laisse imaginer le nombre d'opérations
|
||
nécessaires pour ajouter un administrateur sur un *bucket</em> contenant
|
||
1000 collections avec 100000 records chacune.*)</p>
|
||
<p>La solution que nous avons désormais adoptée consiste à stocker les
|
||
<em>principals</em> de chaque <em>ACE</em> (<em>qui</em> a le droit de faire telle action
|
||
sur l'objet), et de faire l'union des <em>ACE</em> hérités, afin de les
|
||
croiser avec les <em>principals</em> de l'utilisateur :</p>
|
||
<blockquote>
|
||
(ACE(object, permission) ∪ inherited_ACE) ∩ PRINCIPALS(user)</blockquote>
|
||
<p>Par exemple l'ACE: <tt class="docutils literal">/buckets/blog/collections/article:records:create</tt> hérite de
|
||
l'ACE <tt class="docutils literal">/buckets/blog/collections/article:write</tt> et de <tt class="docutils literal">/buckets/blog:write</tt> :</p>
|
||
<blockquote>
|
||
(ACE(/buckets/blog/collections/article:records:create) ∪ ACE(/buckets/blog/collections/article:write) ∪ ACE(/buckets/blog:write)) ∩ PRINCIPALS('fxa:alexis')</blockquote>
|
||
</div>
|
||
<div class="section" id="recuperer-les-donnees-de-l-utilisateur">
|
||
<h2>Récupérer les données de l'utilisateur</h2>
|
||
<p>La situation se corse lorsqu'on souhaite limiter la liste des
|
||
<em>records</em> d'une collection à ceux accessibles pour l'utilisateur, car
|
||
on doit faire cette intersection pour tous les <em>records</em>.</p>
|
||
<p>Une première solution est de regarder si l'utilisateur est mentionné
|
||
dans les <em>ACL*s du *bucket</em> ou de la <em>collection</em>:</p>
|
||
<p>Ensuite, si ce n'est pas le cas, alors on filtre les <em>records</em> pour
|
||
lesquels les <em>principals</em> correspondent à ceux de l'utilisateur.</p>
|
||
<div class="highlight"><pre><span></span><span class="n">principals</span> <span class="o">=</span> <span class="n">get_user_principals</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
|
||
<span class="n">can_read_all</span> <span class="o">=</span> <span class="n">has_read_perms</span><span class="p">(</span><span class="n">bucket_id</span><span class="p">,</span> <span class="n">collection_id</span><span class="p">,</span>
|
||
<span class="n">principals</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">can_read_all</span><span class="p">:</span>
|
||
<span class="n">records</span> <span class="o">=</span> <span class="n">get_all_records</span><span class="p">(</span><span class="n">bucket_id</span><span class="p">,</span> <span class="n">collection_id</span><span class="p">,</span>
|
||
<span class="n">filters</span><span class="o">=</span><span class="p">[</span><span class="o">...</span><span class="p">])</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">records</span> <span class="o">=</span> <span class="n">filter_read_records</span><span class="p">(</span><span class="n">bucket_id</span><span class="p">,</span> <span class="n">collection_id</span><span class="p">,</span>
|
||
<span class="n">principals</span><span class="o">=</span><span class="n">principals</span><span class="p">,</span>
|
||
<span class="n">filters</span><span class="o">=</span><span class="p">[</span><span class="o">...</span><span class="p">])</span>
|
||
</pre></div>
|
||
<p>Il faudra faire quelque chose de similaire pour la suppression
|
||
multiple, lorsqu'un utilisateur souhaitera supprimer des
|
||
enregistrements sur lesquels il a les droits de lecture mais pas
|
||
d'écriture.</p>
|
||
</div>
|
||
<div class="section" id="le-modele-de-donnees">
|
||
<h2>Le modèle de données</h2>
|
||
<p>Pour avoir une idée des requêtes dans un backend SQL, voyons un peu ce
|
||
que donnerait le modèle de données.</p>
|
||
<div class="section" id="le-format-des-id">
|
||
<h3>Le format des ID</h3>
|
||
<p>Utiliser des URI comme identifiant des objets présente de nombreux
|
||
avantages (lisibilité, unicité, cohérence avec les URLs)</p>
|
||
<ul class="simple">
|
||
<li>bucket: <tt class="docutils literal">/buckets/blog</tt></li>
|
||
<li>groupe: <tt class="docutils literal">/buckets/blog/group/moderators</tt></li>
|
||
<li>collection: <tt class="docutils literal">/buckets/blog/collections/articles</tt></li>
|
||
<li>record: <tt class="docutils literal"><span class="pre">/buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200</span></tt></li>
|
||
</ul>
|
||
</div>
|
||
<div class="section" id="les-tables">
|
||
<h3>Les tables</h3>
|
||
<p>Pour le stockage des principals et des permissions:</p>
|
||
<div class="highlight"><pre><span></span><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="k">user</span><span class="p">(</span><span class="n">id</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="n">principals</span> <span class="nb">TEXT</span><span class="p">[]);</span>
|
||
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">perms</span><span class="p">(</span><span class="n">ace</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="n">principals</span> <span class="nb">TEXT</span><span class="p">[]);</span>
|
||
</pre></div>
|
||
<p>La table <em>perms</em> va associer des <em>principals</em> à chaque <em>ACE</em>
|
||
(e.g.``/buckets/blog:write``).</p>
|
||
<p>Pour le stockage des données:</p>
|
||
<div class="highlight"><pre><span></span><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="k">object</span><span class="p">(</span><span class="n">id</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="k">type</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="n">parent_id</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="k">data</span> <span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="n">write_principals</span> <span class="nb">TEXT</span><span class="p">[],</span> <span class="n">read_principals</span> <span class="nb">TEXT</span><span class="p">[]);</span>
|
||
</pre></div>
|
||
<p>La colonne <em>parent_id</em> permet de savoir à qui appartient l'objet
|
||
(e.g. groupe d'un <em>bucket</em>, collection d'un <em>bucket</em>, <em>record</em> d'une
|
||
collection, ...).</p>
|
||
</div>
|
||
<div class="section" id="exemple-d-utilisateur">
|
||
<h3>Exemple d'utilisateur</h3>
|
||
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">principals</span><span class="p">)</span>
|
||
<span class="k">VALUES</span> <span class="p">(</span><span class="s1">&#39;fxa:alexis&#39;</span><span class="p">,</span> <span class="s1">&#39;{}&#39;</span><span class="p">);</span>
|
||
|
||
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">principals</span><span class="p">)</span>
|
||
<span class="k">VALUES</span> <span class="p">(</span><span class="s1">&#39;fxa:natim&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;/buckets/blog/groups/moderators&quot;}&#39;</span><span class="p">);</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="section" id="exemple-d-objets">
|
||
<h3>Exemple d'objets</h3>
|
||
<div class="section" id="bucket">
|
||
<h4>Bucket</h4>
|
||
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
|
||
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
|
||
<span class="k">VALUES</span> <span class="p">(</span>
|
||
<span class="s1">&#39;/buckets/blog&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;bucket&#39;</span><span class="p">,</span>
|
||
<span class="k">NULL</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;name&quot;: &quot;blog&quot;}&#39;</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">&#39;{}&#39;</span><span class="p">,</span> <span class="s1">&#39;{&quot;fxa:alexis&quot;}&#39;</span><span class="p">);</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="section" id="group">
|
||
<h4>Group</h4>
|
||
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
|
||
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
|
||
<span class="k">VALUES</span> <span class="p">(</span>
|
||
<span class="s1">&#39;/buckets/blog/groups/moderators&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;group&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;/buckets/blog&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;name&quot;: &quot;moderators&quot;, &quot;members&quot;: [&#39;</span><span class="n">fxa</span><span class="p">:</span><span class="n">natim</span><span class="s1">&#39;]}&#39;</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">&#39;{}&#39;</span><span class="p">,</span> <span class="s1">&#39;{}&#39;</span><span class="p">);</span>
|
||
</pre></div>
|
||
<p>Ce groupe peut être gére par <tt class="docutils literal">fxa:alexis</tt> puisqu'il a la permission
|
||
<tt class="docutils literal">write</tt> dans le <em>bucket</em> parent.</p>
|
||
</div>
|
||
<div class="section" id="collection">
|
||
<h4>Collection</h4>
|
||
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
|
||
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
|
||
<span class="k">VALUES</span> <span class="p">(</span>
|
||
<span class="s1">&#39;/buckets/blog/collections/articles&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;collection&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;/buckets/blog&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;name&quot;: &quot;article&quot;}&#39;</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;system.Everyone&quot;}&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;/buckets/blog/groups/moderators&quot;}&#39;</span><span class="p">);</span>
|
||
</pre></div>
|
||
<p>Cette collection d'articles peut être lue par tout le monde,
|
||
et gérée par les membres du groupe <tt class="docutils literal">moderators</tt>, ainsi que
|
||
<tt class="docutils literal">fxa:alexis</tt>, via le <em>bucket</em>.</p>
|
||
</div>
|
||
<div class="section" id="records">
|
||
<h4>Records</h4>
|
||
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
|
||
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
|
||
<span class="k">VALUES</span> <span class="p">(</span>
|
||
<span class="s1">&#39;/buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;record&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;/buckets/blog/collections/articles&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;{&quot;name&quot;: &quot;02f3f76f-7059-4ae4-888f-2ac9824e9200&quot;,</span>
|
||
<span class="s1"> &quot;title&quot;: &quot;Stocker les permissions&quot;, ...}&#39;</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">&#39;{}&#39;</span><span class="p">,</span> <span class="s1">&#39;{}&#39;</span><span class="p">);</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="interroger-les-permissions">
|
||
<h3>Interroger les permissions</h3>
|
||
<div class="section" id="id1">
|
||
<h4>Obtenir la liste des &quot;principals&quot; d'un ACE</h4>
|
||
<p>Comme vu plus haut, pour vérifier une permission, on fait l'union des
|
||
<em>principals</em> requis par les objets hérités, et on teste leur
|
||
intersection avec ceux de l'utilisateur:</p>
|
||
<div class="highlight"><pre><span></span><span class="k">WITH</span> <span class="n">required_principals</span> <span class="k">AS</span> <span class="p">(</span>
|
||
<span class="k">SELECT</span> <span class="k">unnest</span><span class="p">(</span><span class="n">principals</span><span class="p">)</span> <span class="k">AS</span> <span class="n">p</span>
|
||
<span class="k">FROM</span> <span class="n">perms</span>
|
||
<span class="k">WHERE</span> <span class="n">ace</span> <span class="k">IN</span> <span class="p">(</span>
|
||
<span class="s1">&#39;/buckets/blog:write&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;/buckets/blog:read&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;/buckets/blog/collections/article:write&#39;</span><span class="p">,</span>
|
||
<span class="s1">&#39;/buckets/blog/collections/article:read&#39;</span><span class="p">)</span>
|
||
<span class="p">),</span>
|
||
<span class="n">user_principals</span> <span class="k">AS</span> <span class="p">(</span>
|
||
<span class="k">SELECT</span> <span class="k">unnest</span><span class="p">(</span><span class="n">principals</span><span class="p">)</span>
|
||
<span class="k">FROM</span> <span class="k">user</span>
|
||
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="s1">&#39;fxa:natim&#39;</span>
|
||
<span class="p">)</span>
|
||
<span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
|
||
<span class="k">FROM</span> <span class="n">user_principals</span> <span class="n">a</span>
|
||
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">required_principals</span> <span class="n">b</span>
|
||
<span class="k">ON</span> <span class="n">a</span><span class="p">.</span><span class="n">p</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="n">p</span><span class="p">;</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="section" id="filtrer-les-objets-en-fonction-des-permissions">
|
||
<h4>Filtrer les objets en fonction des permissions</h4>
|
||
<p>Pour filtrer les objets, on fait une simple intersection de liste
|
||
(<em>merci PostgreSQL</em>):</p>
|
||
<div class="highlight"><pre><span></span><span class="k">SELECT</span> <span class="k">data</span>
|
||
<span class="k">FROM</span> <span class="k">object</span> <span class="n">o</span><span class="p">,</span> <span class="k">user</span> <span class="n">u</span>
|
||
<span class="k">WHERE</span> <span class="n">o</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">&#39;record&#39;</span>
|
||
<span class="k">AND</span> <span class="n">o</span><span class="p">.</span><span class="n">parent_id</span> <span class="o">=</span> <span class="s1">&#39;/buckets/blog/collections/article&#39;</span>
|
||
<span class="k">AND</span> <span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">read_principals</span> <span class="o">&amp;&amp;</span> <span class="n">u</span><span class="p">.</span><span class="n">principals</span> <span class="k">OR</span>
|
||
<span class="n">o</span><span class="p">.</span><span class="n">write_principals</span> <span class="o">&amp;&amp;</span> <span class="n">u</span><span class="p">.</span><span class="n">principals</span><span class="p">)</span>
|
||
<span class="k">AND</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="s1">&#39;fxa:natim&#39;</span><span class="p">;</span>
|
||
</pre></div>
|
||
<p>Les listes s'indexent bien, notamment grâce aux <a class="reference external" href="http://www.postgresql.org/docs/current/static/indexes-types.html">index GIN</a>.</p>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="avec-redis">
|
||
<h3>Avec Redis</h3>
|
||
<p><em>Redis</em> présente plusieurs avantages pour ce genre de
|
||
problématiques. Notamment, il gère les <em>set</em> nativement (listes de
|
||
valeurs uniques), ainsi que les opérations d'intersection et d'union.</p>
|
||
<p>Avec <em>Redis</em> on peut écrire l'obtention des <em>principals</em> pour un <em>ACE</em>
|
||
comme cela :</p>
|
||
<div class="highlight"><pre><span></span>SUNIONSTORE temp_perm:/buckets/blog/collections/articles:write permission:/buckets/blog:write permission:/buckets/blog/collections/articles:write
|
||
SINTER temp_perm:/buckets/blog/collections/articles:write principals:fxa:alexis
|
||
</pre></div>
|
||
<ul class="simple">
|
||
<li><tt class="docutils literal">SUNIONSTORE</tt> permet de créer un set contenant les éléments de
|
||
l'union de tous les set suivants. Dans notre cas on le nomme
|
||
<tt class="docutils literal"><span class="pre">temp_perm:/buckets/blog/collections/articles:write</span></tt> et il contient
|
||
l'union des sets d'ACLs suivants:
|
||
- <tt class="docutils literal"><span class="pre">permission:/buckets/blog:write</span></tt>
|
||
- <tt class="docutils literal"><span class="pre">permission:/buckets/blog/collections/articles:write</span></tt></li>
|
||
<li><tt class="docutils literal">SINTER</tt> retourne l'intersection de tous les sets passés en paramètres dans notre cas :
|
||
- <tt class="docutils literal"><span class="pre">temp_perm:/buckets/blog/collections/articles:write</span></tt>
|
||
- <tt class="docutils literal">principals:fxa:alexis</tt></li>
|
||
</ul>
|
||
<p>Plus d'informations sur :
|
||
- <a class="reference external" href="http://redis.io/commands/sinter">http://redis.io/commands/sinter</a>
|
||
- <a class="reference external" href="http://redis.io/commands/sunionstore">http://redis.io/commands/sunionstore</a></p>
|
||
<p>Si le set résultant de la commande <tt class="docutils literal">SINTER</tt> n'est pas vide, alors
|
||
l'utilisateur possède la permission.</p>
|
||
<p>On peut ensuite supprimer la clé temporaire <tt class="docutils literal">temp_perm</tt>.</p>
|
||
<p>En utilisant <tt class="docutils literal">MULTI</tt> on peut <a class="reference external" href="https://gist.github.com/Natim/77c8f61c1d42e476cef8#file-permission-py-L117-L124">même faire tout cela au sein d'une
|
||
transaction</a>
|
||
et garantir ainsi l'intégrité de la requête.</p>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="conclusion">
|
||
<h2>Conclusion</h2>
|
||
<p>La solution a l'air simple mais nous a demandé beaucoup de réflexion
|
||
en passant par plusieurs propositions.</p>
|
||
<p>L'idée finale est d'avoir :</p>
|
||
<ul class="simple">
|
||
<li>Un backend spécifique permettant de stocker les <em>principals</em> des
|
||
utilisateurs et des <em>ACE</em> (e.g. avec les sets Redis) ;</li>
|
||
<li>La liste des principals read et write sur la table des objets.</li>
|
||
</ul>
|
||
<p>C'est dommage d'avoir le concept de permissions à deux endroits, mais
|
||
cela permet de connaître rapidement la permission d'un utilisateur sur
|
||
un objet et également de pouvoir récupérer tous les objets d'une
|
||
collection pour un utilisateur si celui-ci n'a pas accès à tous les
|
||
records de la collection, ou toutes les collections du bucket.</p>
|
||
</div>
|
||
</content></entry></feed> |