blog.notmyidea.org/content/code/2015-05-01-cliquet-permissions.rst
Alexis Métaireau 9df3b183b6 Enhanced UI & UX; Added New ISBN Plugin.
- Added the ability to display book cover for the category "Lectures" if ISBN cover is available.
- Moved author's name into a small tag for better hierarchy and readability.
- Implemented a feature to indicate link sizes depending on the number of articles associated with a given tag.
- Implemented a mini footer element displaying an RSS feed icon.
- Improved category display using description dictionary.
- Added a new plugin "isbn_downloader" to fetch ISBN information when needed.
- Included the count of articles for each category.
- Implemented changes for better layout and readability of tags and categories.
- Adjusted the layout of the webpage, improving the overall look of the page.
- Included "requests" in the requirements.txt for supplanting dependencies required by the new plugin and/or features.
2023-09-29 18:30:09 +02:00

551 lines
24 KiB
ReStructuredText
Executable file
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.

Service de nuages : La gestion des permissions
##############################################
:lang: fr
:summary: Démystification du vocabulaire des permissions et proposition d'implémentation pour Kinto
*Cet article est repris depuis le blog « Service de Nuages » de mon équipe à Mozilla*
Dans le cadre de la création d'un service de stockage de données personnelles
(Kinto), la gestion des permissions est un des gros challenges : qui doit avoir
accès à quoi, et comment le définir ?
**tl;dr: Quelques retours sur le vocabulaire des systèmes de permission et sur nos idées pour l'implementation des permissions dans un stockage générique.**
La problématique
================
La problématique est simple : des données sont stockées en ligne, et il
faut un moyen de pouvoir les partager avec d'autres personnes.
En regardant les cas d'utilisations, on se rend compte qu'on a plusieurs types
d'utilisateurs :
- les utilisateurs "finaux" (vous) ;
- les applications qui interagissent en leurs noms.
Tous les intervenants n'ont donc pas les mêmes droits : certains doivent
pouvoir lire, d'autres écrire, d'autres encore créer de nouveaux
enregistrements, et le contrôle doit pouvoir s'effectuer de manière fine : il
doit être possible de lire un enregistrement mais pas un autre, par exemple.
Nous sommes partis du constat que les solutions disponibles n'apportaient pas
une réponse satisfaisante à ces besoins.
Un problème de vocabulaire
==========================
Le principal problème rencontré lors des réflexions fût le vocabulaire.
Voici ci-dessous une explication des différents termes.
Le concept de « principal »
---------------------------
Un *principal*, en sécurité informatique, est une entité qui peut être
authentifiée par un système informatique. [#]_ En Français il s'agit
du « commettant », l'acteur qui commet l'action (oui, le terme est conceptuel !)
Il peut s'agir aussi bien d'un individu, d'un ordinateur, d'un
service ou d'un groupe regroupant l'une de ces entités, ce qui
est plus large que le classique « *user id* ».
Les permissions sont alors associées à ces *principals*.
Par exemple, un utilisateur est identifié de manière unique lors de la
connexion par le système d'authentification dont le rôle est de
définir une liste de *principals* pour l'utilisateur se connectant.
.. [#] Pour en savoir plus sur les *principals* :
https://en.wikipedia.org/wiki/Principal_%28computer_security%29
La différence entre rôle et groupe
----------------------------------
De but en blanc, il n'est pas évident de définir précisément la
différence entre ces deux concepts qui permettent d'associer
des permissions à un groupe de *principals*. [#]_
La différence est principalement sémantique. Mais on peut y voir une
différence dans la « direction » de la relation entre les deux concepts.
- Un rôle est une liste de permissions que l'on associe à un *principal*.
- Un groupe est une liste de *principals* que l'on peut associer à une permission.
.. [#] Plus d'informations :
http://stackoverflow.com/questions/7770728/group-vs-role-any-real-difference
La différence entre permission, ACL, ACE
----------------------------------------
.. epigraph::
Une ACL est une liste dAccess Control Entry (ACE) ou entrée de contrôle d'accès
donnant ou supprimant des droits d'accès à une personne ou un groupe.
-- https://fr.wikipedia.org/wiki/Access_Control_List
Je dirais même plus, dans notre cas, « à un *principal* ». Par exemple:
.. code-block:: text
create_record: alexis,remy,tarek
Cet ACE donne la permission ``create`` sur l'objet ``record`` aux
utilisateurs Tarek, Rémy et Alexis.
La délégation de permissions
============================
Imaginez l'exemple suivant, où un utilisateur stocke deux types de données en
ligne :
- des contacts ;
- une liste de tâches à faire qu'il peut associer à ses contacts.
L'utilisateur a tous les droits sur ses données.
Cependant il utilise deux applications qui doivent elles avoir un accès restreint :
- une application de gestion des contacts à qui il souhaite déléguer
la gestion intégrale de ses contacts : ``contacts:write`` ;
- une application de gestion des tâches à qui il souhaite déléguer la
gestion des tâches : ``contacts:read tasks:write``
Il souhaite que son application de contacts ne puisse pas accéder à
ses tâches et que son application de tâches ne puisse pas modifier ses
contacts existants, juste éventuellement en créer de nouveaux.
Il lui faut donc un moyen de déléguer certains de ses droits à un tiers
(l'application).
C'est précisément le rôle des `scopes OAuth2
<http://tools.ietf.org/html/rfc6749#page-23>`_.
Lors de la connexion d'un utilisateur, une fenêtre lui demande quels
accès il veut donner à l'application qui va agir en son nom.
Le service aura ensuite accès à ces *scopes* en regardant le jeton
d'authentification utilisé. Cette information doit être
considérée comme une entrée utilisateur (c'est à dire qu'on ne peut
pas lui faire confiance). Il s'agit de ce que l'utilisateur souhaite.
Or, il est également possible que l'utilisateur n'ait pas accès aux données
qu'il demande. Le service doit donc utiliser deux niveaux de permissions :
celles de l'utilisateur, et celles qui ont été déléguées à l'application.
Espace de noms
==============
Dans notre implémentation initiale de *Kinto* (notre service de stockage en
construction), l'espace de nom était implicite : les données stockées étaient
nécessairement celles de l'utilisateur connecté.
Les données d'un utilisateur étaient donc cloisonnées et impossible à partager.
L'utilisation d'espaces de noms est une manière simple de gérer le partage des données.
Nous avons choisi de reprendre le modèle de GitHub et de Bitbucket, qui
utilisent les « organisations » comme espaces de noms.
Dans notre cas, il est possible de créer des "buckets", qui correspondent à ces
espaces de noms. Un bucket est un conteneur de collections et de groupes
utilisateurs.
Les ACLs sur ces collections peuvent être attribuées à certains groupes du
*bucket* ainsi qu'à d'autres *principals* directement.
Notre proposition d'API
=======================
Les objets manipulés
--------------------
Pour mettre en place la gestion des permissions, nous avons identifié les
objets suivants :
+-----------------+---------------------------------------------------------+
| Objet | Description |
+=================+=========================================================+
| **bucket** | On peut les voir comme des espaces de noms. Ils |
| | permettent d'avoir différentes collections portant |
| | le même nom mais stockées dans différents *buckets* de |
| | manière à ce que les données soient distinctes. |
+-----------------+---------------------------------------------------------+
| **collection** | Une liste d'enregistrements. |
+-----------------+---------------------------------------------------------+
| **record** | Un enregistrement d'une collection. |
+-----------------+---------------------------------------------------------+
| **group** | Un groupe de commetants (« *principals* »). |
+-----------------+---------------------------------------------------------+
Pour la définition des ACLs, il y a une hiérarchie et les objets « héritent » des
ACLs de leur parents :
.. code-block:: text
+---------------+
| Bucket |
+---------------+
+----->+ - id +<---+
| | - permissions | |
| +---------------+ |
| |
| |
| |
| |
| |
+---+-----------+ +------+---------+
| Collection | | Group |
+---------------+ +----------------+
| - id | | - id |
| - permissions | | - members |
+------+--------+ | - permissions |
^ +----------------+
|
|
+------+---------+
| Record |
+----------------+
| - id |
| - data |
| - permissions |
+----------------+
Les permissions
---------------
Pour chacun de ces objets nous avons identifié les permissions suivantes :
+------------+-----------------------------------------+
| Permission | Description |
+============+=========================================+
| **read** | La permission de lire le contenu de |
| | l'objet et de ses sous-objets. |
+------------+-----------------------------------------+
| **write** | La permission de modifier et |
| | d'administrer un objet et ses sous- |
| | objets. La permission *write* permet la |
| | lecture, modification et suppression |
| | d'un objet ainsi que la gestion de ses |
| | permissions. |
+------------+-----------------------------------------+
| **create** | La permission de créer le sous-objet |
| | spécifié. Par exemple: |
| | ``collections:create`` |
+------------+-----------------------------------------+
À chaque permission spécifiée sur un objet est associée une liste de
*principals*.
Dans le cas de la permission ``create`` on est obligé de spécifier
l'objet enfant en question car un objet peut avoir plusieurs types
d'enfants. Par exemple : ``collections:create``, ``groups:create``.
Nous n'avons pour l'instant pas de permission pour `delete` et `update`,
puisque nous n'avons pas trouvé de cas d'utilisation qui les nécessitent.
Quiconque avec le droit d'écriture peut donc supprimer un enregistrement.
Les permissions d'un objet sont héritées de son parent. Par exemple,
un enregistrement créé dans une collection accessible à tout le monde
en lecture sera lui aussi accessible à tout le monde.
Par conséquent, les permissions sont cumulées. Autrement dit, il n'est pas
possible qu'un objet ait des permissions plus restrictives que son parent.
Voici la liste exhaustive des permissions :
+----------------+------------------------+-----------------------------------+
| Objet | Permissions associées | Commentaire |
+================+========================+===================================+
| Configuration | `buckets:create` | Les *principals* ayant le droit |
| (.ini) | | de créer un bucket sont définis |
| | | dans la configuration du serveur. |
| | | (*ex. utilisateurs authentifiés*) |
+----------------+------------------------+-----------------------------------+
| ``bucket`` | `write` | C'est en quelque sorte le droit |
| | | d'administration du *bucket*. |
| +------------------------+-----------------------------------+
| | `read` | C'est le droit de lire le contenu |
| | | de tous les objets du *bucket*. |
| +------------------------+-----------------------------------+
| | `collections:create` | Permission de créer des |
| | | collections dans le *bucket*. |
| +------------------------+-----------------------------------+
| | `groups:create` | Permission de créer des groupes |
| | | dans le *bucket*. |
+----------------+------------------------+-----------------------------------+
| ``collection`` | `write` | Permission d'administrer tous les |
| | | objets de la collection. |
| +------------------------+-----------------------------------+
| | `read` | Permission de consulter tous les |
| | | objets de la collection. |
| +------------------------+-----------------------------------+
| | `records:create` | Permission de créer des nouveaux |
| | | enregistrement dans la collection.|
+----------------+------------------------+-----------------------------------+
| ``record`` | `write` | Permission de modifier ou de |
| | | partager l'enregistrement. |
| +------------------------+-----------------------------------+
| | `read` | Permission de consulter |
| | | l'enregistrement. |
+----------------+------------------------+-----------------------------------+
| ``group`` | `write` | Permission d'administrer le |
| | | groupe |
| +------------------------+-----------------------------------+
| | `read` | Permission de consulter les |
| | | membres du groupe. |
+----------------+------------------------+-----------------------------------+
Les « *principals* »
--------------------
Les acteurs se connectant au service de stockage peuvent s'authentifier.
Ils reçoivent alors une liste de *principals* :
- ``Everyone``: le *principal* donné à tous les acteurs (authentifiés ou pas) ;
- ``Authenticated``: le *principal* donné à tous les acteurs authentifiés ;
- un *principal* identifiant l'acteur, par exemple ``fxa:32aa95a474c984d41d395e2d0b614aa2``
Afin d'éviter les collisions d'identifiants, le *principal* de l'acteur dépend
de son type d'authentification (``system``, ``basic``, ``ipaddr``, ``hawk``,
``fxa``) et de son identifiant (unique par acteur).
En fonction du *bucket* sur lequel se passe l'action, les groupes dont
fait partie l'utilisateur sont également ajoutés à sa liste de
``principals``. ``group:moderators`` par exemple.
Ainsi, si Bob se connecte avec *Firefox Accounts* sur le *bucket*
``servicedenuages_blog`` dans lequel il fait partie du groupe
``moderators``, il aura la liste de *principals* suivante :
``Everyone, Authenticated, fxa:32aa95a474c984d41d395e2d0b614aa2, group:moderators``
Il est donc possible d'assigner une permission à Bob en utilisant l'un de
ces quatre *principals*.
.. note::
Le *principal* ``<userid>`` dépend du *back-end* d'authentification (e.g.
``github:leplatrem``).
Quelques exemples
-----------------
**Blog**
+-------------------------+-------------+---------------------------------+
| Objet | Permissions | Principals |
+=========================+=============+=================================+
| ``bucket:blog`` | ``write`` | ``fxa:<blog owner id>`` |
+-------------------------+-------------+---------------------------------+
| ``collection:articles`` | ``write`` | ``group:moderators`` |
| +-------------+---------------------------------+
| | ``read`` | ``Everyone`` |
+-------------------------+-------------+---------------------------------+
| ``record:569e28r98889`` | ``write`` | ``fxa:<co-author id>`` |
+-------------------------+-------------+---------------------------------+
**Wiki**
+-------------------------+-------------+---------------------------------+
| Object | Permissions | Principals |
+=========================+=============+=================================+
| ``bucket:wiki`` | ``write`` | ``fxa:<wiki administrator id>`` |
+-------------------------+-------------+---------------------------------+
| ``collection:articles`` | ``write`` | ``Authenticated`` |
| +-------------+---------------------------------+
| | ``read`` | ``Everyone`` |
+-------------------------+-------------+---------------------------------+
**Sondages**
+-------------------------+-----------------------+--------------------------+
| Objet | Permissions | Principals |
+=========================+=======================+==========================+
| ``bucket:poll`` | ``write`` | ``fxa:<admin id>`` |
| +-----------------------+--------------------------+
| | ``collection:create`` | ``Authenticated`` |
+-------------------------+-----------------------+--------------------------+
| ``collection:<poll id>``| ``write`` | ``fxa:<poll author id>`` |
| +-----------------------+--------------------------+
| | ``record:create`` | ``Everyone`` |
+-------------------------+-----------------------+--------------------------+
**Cartes colaboratives**
+-------------------------+-----------------------+--------------------------+
| Objet | Permissions | Principals |
+=========================+=======================+==========================+
| ``bucket:maps`` | ``write`` | ``fxa:<admin id>`` |
| +-----------------------+--------------------------+
| | ``collection:create`` | ``Authenticated`` |
+-------------------------+-----------------------+--------------------------+
| ``collection:<map id>`` | ``write`` | ``fxa:<map author id>`` |
| +-----------------------+--------------------------+
| | ``read`` | ``Everyone`` |
+-------------------------+-----------------------+--------------------------+
| ``record:<record id>`` | ``write`` | ``fxa:<maintainer id>`` |
| | | (*ex. event staff member |
| | | maintaining venues*) |
+-------------------------+-----------------------+--------------------------+
**Plateformes**
Bien sûr, il y a plusieurs façons de modéliser les cas d'utilisation typiques.
Par exemple, on peut imaginer une plateforme de wikis (à la wikia.com), où les
wikis sont privés par défaut et certaines pages peuvent être rendues publiques :
+-------------------------+-----------------------+-----------------------------+
| Objet | Permissions | Principals |
+=========================+=======================+=============================+
| ``bucket:freewiki`` | ``write`` |``fxa:<administrator id>`` |
| +-----------------------+-----------------------------+
| | ``collection:create`` | ``Authenticated`` |
| +-----------------------+-----------------------------+
| | ``group:create`` | ``Authenticated`` |
+-------------------------+-----------------------+-----------------------------+
| ``collection:<wiki id>``| ``write`` | ``fxa:<wiki owner id>``, |
| | | ``group:<editors id>`` |
| +-----------------------+-----------------------------+
| | ``read`` | ``group:<readers id>`` |
+-------------------------+-----------------------+-----------------------------+
| ``record:<page id>`` | ``read`` | ``Everyone`` |
+-------------------------+-----------------------+-----------------------------+
L'API HTTP
----------
Lors de la création d'un objet, l'utilisateur se voit
attribué la permission ``write`` sur l'objet :
.. code-block:: http
PUT /v1/buckets/servicedenuages_blog HTTP/1.1
Authorization: Bearer 0b9c2625dc21ef05f6ad4ddf47c5f203837aa32ca42fced54c2625dc21efac32
Accept: application/json
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"id": "servicedenuages_blog",
"permissions": {
"write": ["fxa:49d02d55ad10973b7b9d0dc9eba7fdf0"]
}
}
Il est possible d'ajouter des permissions à l'aide de ``PATCH`` :
.. code-block:: http
PATCH /v1/buckets/servicedenuages_blog/collections/articles HTTP/1.1
Authorization: Bearer 0b9c2625dc21ef05f6ad4ddf47c5f203837aa32ca42fced54c2625dc21efac32
Accept: application/json
{
"permissions": {
"read": ["+system.Everyone"]
}
}
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"id": "servicedenuages_blog",
"permissions": {
"write": ["fxa:49d02d55ad10973b7b9d0dc9eba7fdf0"],
"read": ["system.Everyone"]
}
}
Pour le ``PATCH`` nous utilisons la syntaxe préfixée par un ``+`` ou
par un ``-`` pour ajouter ou enlever des *principals* sur un ACL.
Il est également possible de faire un ``PUT`` pour réinitialiser les ACLs,
sachant que le ``PUT`` va ajouter l'utilisateur courant à la
liste automatiquement mais qu'il pourra se retirer avec un ``PATCH``.
Ajouter l'utilisateur courant permet d'éviter les situations où plus
personne n'a accès aux données.
.. note::
La permission ``create`` est valable pour ``POST`` mais aussi pour ``PUT``
lorsque l'enregistrement n'existe pas.
Le cas spécifique des données utilisateurs
------------------------------------------
Une des fonctionnalités actuelles de *Kinto* est de pouvoir gérer des
collections d'enregistrements par utilisateur.
Sous *\*nix* il est possible, pour une
application, de sauvegarder la configuration de l'utilisateur courant
dans son dossier personnel sans se soucier de l'emplacement sur
le disque en utilisant ``~/``.
Dans notre cas si une application souhaite sauvegarder les contacts d'un
utilisateur, elle peut utiliser le raccourci ``~`` pour faire référence au
*bucket* **personnel** de l'utilisateur : ``/buckets/~/collections/contacts``
Cette URL retournera le code ``HTTP 307`` vers le *bucket* de l'utilisateur courant :
.. code-block:: http
POST /v1/buckets/~/collections/contacts/records HTTP/1.1
{
"name": "Rémy",
"emails": ["remy@example.com"],
"phones": ["+330820800800"]
}
HTTP/1.1 307 Temporary Redirect
Location: /v1/buckets/fxa:49d02d55ad10973b7b9d0dc9eba7fdf0/collections/contacts/records
Ainsi il est tout à fait possible à Alice de partager ses contacts
avec Bob. Il lui suffit pour cela de donner la permission ``read`` à
Bob sur sa collection et de donner l'URL complète
``/v1/buckets/fxa:49d02d55ad10973b7b9d0dc9eba7fdf0/collections/contacts/records``
à Bob.
La délégation des permissions
-----------------------------
Dans le cas de *Kinto*, nous avons défini un format pour restreindre les
permissions via les scopes OAuth2:
``storage:<bucket_id>:<collection_id>:<permissions_list>``.
Ainsi, si on reprend l'exemple précédent de la liste de tâches, il est possible pour
Bob de créer un token OAuth spécifique avec les *scopes* suivants :
``profile storage:todolist:tasks:write storage:~:contacts:read+records:create``
Donc, bien que Bob a la permission ``write`` sur ses contacts,
l'application utilisant ce token pourra uniquement lire les contacts
existants et en ajouter de nouveaux.
Une partie de la complexité est donc de réussir à présenter ces *scopes* de
manière lisible à l'utilisateur, afin qu'il choisisse quelles permissions
donner aux applications qui agissent en son nom.
Voilà où nous en sommes de notre réflexion !
Si vous avez des choses à ajouter, des points de désaccord ou autres
réflexions, n'hésitez pas à nous interrompre pendant qu'il est encore temps !