mirror of
https://github.com/almet/notmyidea.git
synced 2025-05-01 13:02:23 +02:00
413 lines
No EOL
27 KiB
HTML
413 lines
No EOL
27 KiB
HTML
<!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 - 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="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>
|
||
|
||
</nav>
|
||
</div> <div class="wrap">
|
||
<div class="masthead">
|
||
<div class="container">
|
||
<h3 class="masthead-title">
|
||
<a href="https://blog.notmyidea.org/" title="Home">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 "principals" 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 "principals" d'un ACE</h2>
|
||
<blockquote>
|
||
Rappel, un "ACE" 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">'fxa:alexis'</span><span class="p">,</span> <span class="s1">'{}'</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">'fxa:natim'</span><span class="p">,</span>
|
||
<span class="s1">'{"/buckets/blog/groups/moderators"}'</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">'/buckets/blog'</span><span class="p">,</span>
|
||
<span class="s1">'bucket'</span><span class="p">,</span>
|
||
<span class="k">NULL</span><span class="p">,</span>
|
||
<span class="s1">'{"name": "blog"}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">'{}'</span><span class="p">,</span> <span class="s1">'{"fxa:alexis"}'</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">'/buckets/blog/groups/moderators'</span><span class="p">,</span>
|
||
<span class="s1">'group'</span><span class="p">,</span>
|
||
<span class="s1">'/buckets/blog'</span><span class="p">,</span>
|
||
<span class="s1">'{"name": "moderators", "members": ['</span><span class="n">fxa</span><span class="p">:</span><span class="n">natim</span><span class="s1">']}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">'{}'</span><span class="p">,</span> <span class="s1">'{}'</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">'/buckets/blog/collections/articles'</span><span class="p">,</span>
|
||
<span class="s1">'collection'</span><span class="p">,</span>
|
||
<span class="s1">'/buckets/blog'</span><span class="p">,</span>
|
||
<span class="s1">'{"name": "article"}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">'{"system.Everyone"}'</span><span class="p">,</span>
|
||
<span class="s1">'{"/buckets/blog/groups/moderators"}'</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">'/buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200'</span><span class="p">,</span>
|
||
<span class="s1">'record'</span><span class="p">,</span>
|
||
<span class="s1">'/buckets/blog/collections/articles'</span><span class="p">,</span>
|
||
<span class="s1">'{"name": "02f3f76f-7059-4ae4-888f-2ac9824e9200",</span>
|
||
<span class="s1"> "title": "Stocker les permissions", ...}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
|
||
<span class="s1">'{}'</span><span class="p">,</span> <span class="s1">'{}'</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 "principals" 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">'/buckets/blog:write'</span><span class="p">,</span>
|
||
<span class="s1">'/buckets/blog:read'</span><span class="p">,</span>
|
||
<span class="s1">'/buckets/blog/collections/article:write'</span><span class="p">,</span>
|
||
<span class="s1">'/buckets/blog/collections/article:read'</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">'fxa:natim'</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">'record'</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">'/buckets/blog/collections/article'</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">&&</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">&&</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">'fxa:natim'</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>
|
||
</div>
|
||
</body>
|
||
</html> |