blog.notmyidea.org/service-de-nuages-stocker-et-interroger-les-permissions-avec-kinto-fr.html
2019-11-20 13:56:59 +01:00

432 lines
No EOL
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<title>Service de nuages : Stocker et interroger les permissions avec Kinto - Alexis - Carnets en ligne</title>
<meta charset="utf-8" />
<link href="https://blog.notmyidea.org/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="Alexis - Carnets en ligne Full Atom Feed" />
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/poole.css"/>
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/syntax.css"/>
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/lanyon.css"/>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=PT+Serif:400,400italic,700%7CPT+Sans:400">
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/styles.css"/>
<style>
h1 {
font-family: "Avant Garde", Avantgarde, "Century Gothic", CenturyGothic, "AppleGothic", sans-serif;
padding: 80px 50px;
text-align: center;
text-transform: uppercase;
text-rendering: optimizeLegibility;
color: #202020;
letter-spacing: .1em;
text-shadow:
-1px -1px 1px #111,
2px 2px 1px #eaeaea;
}
#main {
text-align: justify;
text-justify: inter-word;
}
#main h1 {
padding: 10px;
}
.post-headline {
padding: 15px;
text-align: center;
}
</style>
</head>
<body>
<!-- Target for toggling the sidebar `.sidebar-checkbox` is for regular
styles, `#sidebar-checkbox` for behavior. -->
<input type="checkbox" class="sidebar-checkbox" id="sidebar-checkbox">
<!-- Toggleable sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-item">
<div class="profile">
<img src="https://blog.notmyidea.org/theme/img/profile.png"/>
</div>
</div>
<nav class="sidebar-nav">
<a class="sidebar-nav-item" href="/">Articles</a>
<a class="sidebar-nav-item" href="https://www.vieuxsinge.com">Brasserie du Vieux Singe</a>
<a class="sidebar-nav-item" href="http://blog.notmyidea.org/pages/about.html">A propos</a>
<a class="sidebar-nav-item" href="https://twitter.com/ametaireau">Messages courts</a>
<a class="sidebar-nav-item" href="https://github.com/almet">Code</a>
</nav>
</div> <div class="wrap">
<div class="masthead">
<div class="container">
<h3 class="masthead-title">
<a href="https://blog.notmyidea.org/" title="Home">Alexis - Carnets en ligne</a>
</h3>
</div>
</div>
<div class="container content">
<div id="main" class="posts">
<h1 class="post-title">Service de nuages : Stocker et interroger les permissions avec Kinto</h1>
<span class="post-date">
26 mai 2015, dans <a class="no-color" href="category/technologie.html">Technologie</a>
</span>
<img id="illustration" class="illustration-Technologie" src="" />
<div class="post article">
<h1>🌟</h1>
<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>
</div>
</div>
</div>
<label for="sidebar-checkbox" class="sidebar-toggle"></label>
<script>
(function(document) {
var i = 0;
// snip empty header rows since markdown can't
var rows = document.querySelectorAll('tr');
for(i=0; i<rows.length; i++) {
var ths = rows[i].querySelectorAll('th');
var rowlen = rows[i].children.length;
if (ths.length > 0 && ths.length === rowlen) {
rows[i].remove();
}
}
})(document);
</script>
<script>
/* Lanyon & Poole are Copyright (c) 2014 Mark Otto. Adapted to Pelican 20141223 and extended a bit by @thomaswilley */
(function(document) {
var toggle = document.querySelector('.sidebar-toggle');
var sidebar = document.querySelector('#sidebar');
var checkbox = document.querySelector('#sidebar-checkbox');
document.addEventListener('click', function(e) {
var target = e.target;
if(!checkbox.checked ||
sidebar.contains(target) ||
(target === checkbox || target === toggle)) return;
checkbox.checked = false;
}, false);
})(document);
</script>
<!-- Piwik -->
<script type="text/javascript">
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//tracker.notmyidea.org/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', 3]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="//tracker.notmyidea.org/piwik.php?idsite=3" style="border:0;" alt="" /></p></noscript>
<!-- End Piwik Code -->
</div>
</body>
</html>