MozFR

ES6 en détails : les générateurs

Suite de la traduction, qui continue la série d’articles de Jason Orendorff. L’article original se trouve ici.

Merci aux traducteurs et relecteurs :) Marine, Mentalo, Benjamin, Ilphrin et Goofy !


ES6 en détails est une série d’articles décrivant les nouvelles fonctionnalités ajoutées au langage de programmation JavaScript avec la sixième édition du standard ECMAScript (ES6 en abrégé).

Je suis vraiment excité par l’article d’aujourd’hui. Aujourd’hui, nous allons parler de la fonctionnalité la plus magique d’ES6.

Mais qu’entends-je par « magique » ? Pour les débutants, cette fonctionnalité est si différente de ce qui existait déjà en JavaScript que cela peut sembler un peu flou à première vue. D’une certaine façon, elle peut complètement renverser le comportement du langage ! Si ce n’est pas de la magie, je ne sais pas ce que c’est.

Mais ce n’est pas tout : cette fonctionnalité peut repousser les frontières de « l’enfer des callbacks » aux limites du surnaturel.

En fais-je suffisamment assez ? Allons-y et jugez-en par vous-même.

Introduction aux générateurs ES6

Que sont les générateurs?

Commençons par en observer un :

function* quips(nom) {
  yield "bonjour " + nom + " !";
  yield "j'espère que vous appréciez ces billets";
  if (nom.startsWith("X")) {
    yield "Tiens, votre nom commence par un X, " + nom;
  }
  yield "À la prochaine !";
}

Ceci est un morceau de code pour simuler un chat qui parle, probablement un type d’application crucial sur Internet aujourd’hui. (Essayez-la, cliquez sur le lien et jouez avec le chat. Quand vous serez perdu, revenez ici pour quelques explications.)

Cela ressemble à une fonction, n’est-ce pas ? C’est ce qu’on appelle une fonction génératrice (ou générateur) et ça possède beaucoup de liens avec les fonctions. Dès le premier coup d’œil, on peut toutefois observer deux différences :

  • Les fonctions classiques commencent par function. Les fonctions génératrices commencent par function*.
  • Dans une fonction génératrice, yield est un mot-clé, avec une syntaxe similaire à return. La différence est que, tandis qu’une fonction (même un générateur) ne peut utiliser return qu’une seule fois, un générateur peut utiliser yield plusieurs fois. L’expression yield suspend l’exécution du générateur, qui peut donc être reprise plus tard.

Voici donc la principale différence entre une fonction classique et une fonction génératrice. Les fonctions normales ne peuvent pas être mises en pause. Les générateurs peuvent être interrompus puis repris.

Ce que font les générateurs

Que se passe-t-il lorsqu’on appelle la fonction génératrice quips() ?

> var iter = quips("jorendorff");
  [object Generator]
> iter.next()
  { value: "bonjour jorendorff!", done: false }
> iter.next()
  { value: "j'espère que vous appréciez ces billets", done: false }
> iter.next()
  { value: "À la prochaine !", done: false }
> iter.next()
  { value: undefined, done: true }

Vous êtes sans doute familier des fonctions classiques et de leur comportement. Lorsqu’elles sont appelées, elles démarrent immédiatement et ne s’arrêtent que lorsqu’elles rencontrent le mot-clé return ou throw. Élémentaire pour tout programmeur en JavaScript.

Un appel à un générateur ressemble à un appel à une fonction classique : quips("jorendorff"). Cependant, quand on appelle un générateur, il ne démarre pas immédiatement. En fait, il renvoie un objet générateur en pause (nommé iter dans l’exemple ci-dessus). On peut considérer cet objet générateur comme un appel de fonction, gelé dans le temps. En particulier, il est mis en pause tout au début de la fonction génératrice, juste avant de démarrer la première ligne de code.

À chaque appel de la méthode .next() de l’objet générateur, l’appel de la fonction se remet en route jusqu’au yield suivant.

C’est pour cette raison qu’à chaque fois que nous avons appelé iter.next() dans l’exemple ci-dessus nous avons obtenu une valeur différente (sous la forme d’une chaîne de caractères).

Lors du dernier appel à iter.next(), nous avons finalement atteint la fin de la fonction génératrice, le champ .done vaut donc true. Atteindre la fin d’une fonction revient à renvoyer undefined, c’est pour cela que la propriété .value du résultat vaut undefined.

C’est sans doute le bon moment pour revenir à la page de démo du chat parlant et manipuler le code pour de bon. Essayez par exemple d’ajouter un yield dans une boucle, que se passe-t-il ?

D’un point de vue technique, chaque fois qu’un générateur utilise yield, le cadre de sa pile (stack frame) — qui contient les variables locales, les arguments, les valeurs temporaires ainsi que la position actuelle de l’exécution dans le générateur — est ôté de la pile. Cependant, le générateur conserve une référence vers ce cadre afin que le prochain appel à .next() puisse réutiliser ce cadre et continuer l’exécution du code.

Il est important de préciser que les générateurs ne sont pas des threads. Dans les langages utilisant des threads, on peut rencontrer plusieurs parties de code qui fonctionnent en même temps, entraînant habituellement des accès concurrents, un certain indéterminisme et de meilleures performances.

Les générateurs n’agissent pas du tout de cette façon. Quand un générateur fonctionne, il se situe dans le même thread que son appel. L’ordre des exécutions est séquentiel et déterministe, il n’y a pas d’accès concurrents. Contrairement aux systèmes utilisant des threads, un générateur est uniquement suspendu aux endroits correspondants aux yield contenus dans son code.

OK, nous savons maintenant ce que sont les générateurs. Nous avons vu un générateur fonctionner, s’arrêter puis reprendre. Arrive maintenant la grande question : en quoi cette chose bizarre pourrait-elle nous être utile ?

Les générateurs sont des itérateurs

La semaine dernière nous avons vu que les itérateurs ES6 ne sont pas seulement une classe native. Ils représentent une extension du langage dans son ensemble. Vous pouvez créer vos propres itérateurs, il suffit d’implémenter deux méthodes : [Symbol.iterator()] et next().

Malgré tout, implémenter une interface demande toujours un minimum de travail. Voyons à quoi ressemble réellement l’implémentation d’un itérateur. Par exemple, créons un itérateur basique qui compte d’un nombre à un autre, comme le ferait une bonne vieille boucle for(;;) en C.

// Cela devrait sonner 3 fois
for (var value of range(0, 3)) {
  alert("Ding ! à l'étage numéro " + value);
}

Voici une solution qui utilise une classe ES6 (si cette syntaxe vous semble un peu obscure, pas de panique, ce sera l’objet d’un prochain billet).

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    } else {
      return {done: true, value: undefined};
    }
  }
}

// On renvoie un nouvel itérateur qui compte de 'start' à 'stop'.
function range(start, stop) {
  return new RangeIterator(start, stop);
}

Essayez ce code.

Note : il est nécessaire d’utiliser Firefox Nightly ou Chrome avec une version supérieure ou égale à 42 pour que cet exemple fonctionne.

C’est pour cela qu’implémenter un itérateur se fait comme avec Java ou Swift, ce n’est pas si compliqué ! Mais ce n’est pas vraiment trivial non plus ! Est-ce que ce code contient des erreurs ? Pas facile à dire. Il ne ressemble pas du tout à la boucle for(;;) habituelle que l’on essaie de remplacer : le protocole des itérateurs nous oblige à démanteler la boucle.

À ce stade, vos sentiments concernant les itérateurs sont sans doute mitigés. Ils sont sûrement super à utiliser mais ils semblent difficiles à implémenter.

À tout hasard, n’auriez vous pas idée d’une toute nouvelle structure de contrôle du flux en JavaScript, fonctionnant étape par étape, et qui rendraient les itérateurs beaucoup plus simples à construire ? Puisque nous avons les générateurs, pourquoi ne pas les utiliser ici ?

Essayons :

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

Essayez ce code.

Les 4 lignes du générateur ci-dessus remplacent exactement les 23 lignes utilisées précédemment pour implémenter range(), y compris l’intégralité de la classe RangeIterator. Ceci est possible parce que les générateurs sont des itérateurs. Tous les générateurs possèdent une implémentation native de .next() et de [Symbol.iterator](). Il suffit simplement d’écrire le comportement de la boucle.

Implémenter des itérateurs sans disposer de générateurs, c’est équivalent à devoir écrire un courrier en utilisant uniquement la voie passive. Lorsqu’il est impossible d’exprimer directement ce qu’on a besoin, on se retrouve à formuler des phrases de façon alambiquée. RangeIterator était long et étrange car on l’utilisait pour décrire une boucle sans pouvoir utiliser une syntaxe de boucle. Les générateurs permettent de répondre à ce problème.

Comment peut-on utiliser les générateurs comme des itérateurs ?

  • En rendant n’importe quel objet itérable. Il suffit d’écrire une fonction génératrice qui parcourt this, et qui utilise yield sur chaque valeur rencontrée. La fonction obtenue peut être définie comme la méthode [Symbol.iterator] de l’objet.

  • En simplifiant les fonctions de construction de tableaux. Si votre fonction retourne un tableau de résultats à chaque appel, comme ceci :

     
    // Divise un tableau à une dimension 'icons'
    // en tableaux de taille fixe 'rowLength'
    function splitIntoRows(icons, rowLength) {
      var rows = [];
      for (var i = 0; i < icons.length; i += rowLength) {
        rows.push(icons.slice(i, i + rowLength));
      }
      return rows;
    }
    

    Les générateurs permettent de faire la même chose de manière plus concise :

    function* splitIntoRows(icons, rowLength) {
      for (var i = 0; i < icons.length; i += rowLength) {
        yield icons.slice(i, i + rowLength);
      }
    }
    

    La seule différence de comportement est le fait qu’au lieu de travailler sur tous les résultats à la fois et de renvoyer un tableau les contenant, ceci renvoie un itérateur et les valeurs de retour sont calculées l’une après l’autre, à la demande.

  • Pour des résultats de grande taille, cela peut également être utile. Il est impossible de construire un tableau infini. Mais vous pouvez renvoyer un générateur qui produit une séquence sans fin, chaque appelant pourra récupérer autant de valeurs que nécessaire à partir de ce générateur.

  • Pour « refactorer » des boucles complexes : vous avez une fonction énorme et moche ? Vous souhaitez la scinder en éléments plus simples ? Les générateurs sont des couteaux que vous pouvez ajouter à votre boîte à outils. Lorsque vous êtes face à une boucle complexe, vous pouvez sortir la partie qui produit les données dans une fonction génératrice indépendante. Puis modifier la boucle en utilisant for(var toto of monNouveauGenerateur(args)).

  • Pour créer des outils afin de manipuler les itérables. ES6 ne fournit pas de bibliothèque complète pour filtrer, mapper et bidouiller les ensembles de données itérables. Cependant, les générateurs sont très bien adaptés à la création de ces outils dont vous pourriez avoir besoin, en écrivant simplement quelques lignes de code. Par exemple, supposons que vous ayez besoin d’un équivalent de Array.prototype.filter() qui fonctionne sur les NodeList du DOM et pas uniquement sur les objets Array. Facile !

    function* filter(test, iterable) {
      for (var item of iterable) {
        if (test(item))
          yield item;
      }
    }
    

Alors, est-ce que les générateurs sont utiles ? Évidemment. Ils représentent une façon étonnament simple d’implémenter des itérateurs personnalisés et les itérateurs représentent le nouveau standard pour les données et les boucles dans ES6.

Mais ce n’est pas la seule chose que peuvent faire les générateurs. Ce n’est peut-être même pas la plus importante chose qu’ils peuvent faire !

Générateurs et code asynchrone

Voici du code JS que j’ai écrit il y a quelque temps :

          };
        })
      });
    });
  });
});

Cela vous rappelle peut-être quelques lignes de votre code. Les API asynchrones demandent généralement une fonction de rappel (callback), ce qui signifie qu’il faut écrire une fonction anonyme supplémentaire chaque fois qu’on fait quelque chose. Ainsi, si vous avez un code qui fait 3 choses, plutôt que 3 lignes de code, vous aurez 3 niveaux d’indentation dans votre code.

Voici un autre fragment de code JS que j’ai écrit :

}).on('close', function () {
  done(undefined, undefined);
}).on('error', function (error) {
  done(error);
});

Les API asynchrones possèdent certaines conventions concernant la gestion des erreurs plutôt que d’utiliser des exceptions. Différentes API possèdent différentes conventions. Dans la plupart des cas, les erreurs sont ignorées par défaut. Dans certains cas, même les cas de terminaisons normales sont ignorés par défaut.

Jusqu’à maintenant, ces problèmes ont été le prix à payer pour utiliser la programmation asynchrone. Nous avons fini par accepter que le code asynchrone n’est pas aussi propre et simple que la version synchrone correspondante.

Les générateurs nous font espérer que ce ne soit plus le cas.

Q.async() est un tentative expérimentale pour utiliser les générateurs avec des promesses afin de produire du code asynchrone qui ressemble à du code synchrone. Par exemple :

// Code synchrone pour créer une fonction qui fait du bruit
function makeNoise() {
  shake();
  rattle();
  roll();
}

// Code asynchrone pour faire du bruit.
// Renvoie un objet Promise qui est résolu
// quand nous avons fini de faire du bruit.
function makeNoise_async() {
  return Q.async(function* () {
    yield shake_async();
    yield rattle_async();
    yield roll_async();
  });
}

La principale différence est que, dans la version asynchrone, il faut ajouter le mot-clé yield à chaque appel d’une fonction asynchrone.

Ajouter un détail comme une condition if ou un bloc try-catch dans la version Q.async se fait exactement de la même façon que dans la version synchrone. Comparé à d’autres façons d’écrire du code asynchrone, cela ressemble beaucoup moins à l’apprentissage d’un nouveau langage.

Si vous êtes arrivé jusqu’ici, vous apprécierez sans doute l’article très détaillé de James Long sur ce sujet.

Les générateurs illustrent donc un nouveau modèle de programmation asynchrone qui semble mieux adapté au cerveau humain. Les travaux autour de ces concepts sont toujours en cours. Entre autres choses, une meilleure syntaxe devrait aider. Pour la version ES7, il existe une proposition pour construire des fonctions asynchrones basées à la fois sur les promesses (promises) et les générateurs, inspirées de fonctionnalités similaires existant en C#.

Quand puis-je commencer à utiliser cette fonctionnalité ?

Côté serveur, vous pouvez utiliser les générateurs ES6 dès aujourd’hui avec io.js (et avec Node en utilisant l’option —harmony)

Côté navigateur, seuls Firefox 27+ et Chrome 39+ supportent les générateurs ES6 pour le moment. Pour les utiliser à travers le web, vous devrez utiliser un compilateur tel que Babel ou Traceur pour traduire votre code ES6 en code ES5, plus largement compatible avec les anciens navigateurs.

Quelques remerciements à qui de droit : les générateurs ont d’abord été implémentés dans JS par Brendan Eich ; sa conception suivait de près les générateurs Python, inspirés par Icon. Ils sont apparus dans Firefox 2.0 dès 2006. Le chemin vers la standardisation a été chaotique et la syntaxe ainsi que le comportement ont évolué au cours de cette période. Les générateurs d’ES6 ont été implémentés dans Firefox et Chrome par la même personne, Andy Wingo, un professionnel du compilateur. Ce travail a été sponsorisé par Bloomberg.

yield;

Il y a encore à dire sur les générateurs. Nous n’avons pas évoqué les méthodes .throw() et return(), les arguments optionnels de .next() ou encore la syntaxe de l’expression yield*. Cependant, je pense que ça suffira pour ce billet, déjà suffisamment déconcertant. Comme le font les générateurs, il vaut mieux faire une pause et reprendre une autre fois.

La semaine prochaine, nous changerons d’allure. Nous avons abordé deux sujets assez conséquents, l’un après l’autre. Ne pourrions-nous pas parler d’une fonctionnalité ES6 qui ne changera pas votre vie ? Quelque chose de simple et de manifestement utile ? Quelque chose qui vous fera sourire ? ES6 possède aussi quelques fonctionnalités de ce genre.

À venir : une fonctionnalité qui va immédiatement trouver sa place dans le code que vous écrivez tous les jours. Rejoignez-nous la semaine prochaine pour une explication détaillée sur les patrons de chaînes (template strings) d’ES6 !

Stocker et interroger les permissions avec Kinto

tl;dr: On a maintenant un super système de permission mais comment faire pour stocker et interroger ces permissions de manière efficace ?

La problématique

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.

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.

Obtenir la liste des "principals" d'un utilisateur

Les principals de l'utilisateur correspondent à son user_id ainsi qu'à la liste des identifiants des groupes dans lesquels il a été ajouté.

Pour éviter de recalculer les principals de l'utilisateur à chaque requête, le mieux reste de maintenir une liste des principals par utilisateur.

Ainsi lorsqu'on ajoute un utilisateur à un groupe, il faut bien penser à ajouter le groupe à la liste des principals de l'utilisateur.

Ça se complexifie lorsqu'on ajoute un groupe à un groupe.

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.

L'avantage de maintenir la liste des principals 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.

Cela nécessite de donner un identifiant unique aux groupes pour tous les buckets.

Nous proposons de de les nommer avec leur URI: /buckets/blog/groups/moderators

Obtenir la liste des "principals" d'un ACE

Rappel, un "ACE" est un Access Control Entry, un des éléments d'une ACL (e.g. modifier un enregistrement).

Avec le système de permissions choisi, les permissions d'un objet héritent de celle de l'objet parent.

Par exemple, avoir le droit d'écriture sur un bucket permet la création des permissions et la modification de tous ses records.

Ce qui veut dire que pour obtenir la liste complète des principals ayant une permission sur un objet, il faut regarder à plusieurs endroits.

Rémy a décrit dans un gist la liste d'héritage de chaque permission.

Prenons l'exemple de l'ajout d'un record dans une collection.

Le droit records:create est obtenu si l'on a l'un des droits suivants:

  • bucket:write
  • collection:write
  • records:create

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. (Je vous laisse imaginer le nombre d'opérations nécessaires pour ajouter un administrateur sur un *bucket contenant 1000 collections avec 100000 records chacune.*)

La solution que nous avons désormais adoptée consiste à stocker les principals de chaque ACE (qui a le droit de faire telle action sur l'objet), et de faire l'union des ACE hérités, afin de les croiser avec les principals de l'utilisateur :

(ACE(object, permission) ∪ inherited_ACE) ∩ PRINCIPALS(user)

Par exemple l'ACE: /buckets/blog/collections/article:records:create hérite de l'ACE /buckets/blog/collections/article:write et de /buckets/blog:write :

(ACE(/buckets/blog/collections/article:records:create) ∪ ACE(/buckets/blog/collections/article:write) ∪ ACE(/buckets/blog:write)) ∩ PRINCIPALS('fxa:alexis')

Récupérer les données de l'utilisateur

La situation se corse lorsqu'on souhaite limiter la liste des records d'une collection à ceux accessibles pour l'utilisateur, car on doit faire cette intersection pour tous les records.

Une première solution est de regarder si l'utilisateur est mentionné dans les ACL*s du *bucket ou de la collection:

Ensuite, si ce n'est pas le cas, alors on filtre les records pour lesquels les principals correspondent à ceux de l'utilisateur.

principals = get_user_principals(user_id)
can_read_all = has_read_perms(bucket_id, collection_id,
                              principals)
if can_read_all:
    records = get_all_records(bucket_id, collection_id,
                              filters=[...])
else:
    records = filter_read_records(bucket_id, collection_id,
                                  principals=principals,
                                  filters=[...])

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.

Le modèle de données

Pour avoir une idée des requêtes dans un backend SQL, voyons un peu ce que donnerait le modèle de données.

Le format des ID

Utiliser des URI comme identifiant des objets présente de nombreux avantages (lisibilité, unicité, cohérence avec les URLs)

  • bucket: /buckets/blog
  • groupe: /buckets/blog/group/moderators
  • collection: /buckets/blog/collections/articles
  • record: /buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200

Les tables

Pour le stockage des principals et des permissions:

CREATE TABLE user(id TEXT, principals TEXT[]);
CREATE TABLE perms(ace TEXT, principals TEXT[]);

La table perms va associer des principals à chaque ACE (e.g.``/buckets/blog:write``).

Pour le stockage des données:

CREATE TABLE object(id TEXT, type TEXT, parent_id TEXT, data JSONB,
                    write_principals TEXT[], read_principals TEXT[]);

La colonne parent_id permet de savoir à qui appartient l'objet (e.g. groupe d'un bucket, collection d'un bucket, record d'une collection, ...).

Exemple d'utilisateur

INSERT INTO user (id, principals)
     VALUES ('fxa:alexis', '{}');

INSERT INTO user (id, principals)
     VALUES ('fxa:natim',
             '{"/buckets/blog/groups/moderators"}');

Exemple d'objets

Bucket

INSERT INTO object (id, type, parent_id, data,
                    read_principals, write_principals)
VALUES (
    '/buckets/blog',
    'bucket',
    NULL,
    '{"name": "blog"}'::JSONB,
    '{}', '{"fxa:alexis"}');

Group

INSERT INTO object (id, type, parent_id, data,
                    read_principals, write_principals)
VALUES (
    '/buckets/blog/groups/moderators',
    'group',
    '/buckets/blog',
    '{"name": "moderators", "members": ['fxa:natim']}'::JSONB,
    '{}', '{}');

Ce groupe peut être gére par fxa:alexis puisqu'il a la permission write dans le bucket parent.

Collection

INSERT INTO object (id, type, parent_id, data,
                    read_principals, write_principals)
VALUES (
    '/buckets/blog/collections/articles',
    'collection',
    '/buckets/blog',
    '{"name": "article"}'::JSONB,
    '{"system.Everyone"}',
    '{"/buckets/blog/groups/moderators"}');

Cette collection d'articles peut être lue par tout le monde, et gérée par les membres du groupe moderators, ainsi que fxa:alexis, via le bucket.

Records

INSERT INTO object (id, type, parent_id, data,
                    read_principals, write_principals)
VALUES (
    '/buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200',
    'record',
    '/buckets/blog/collections/articles',
    '{"name": "02f3f76f-7059-4ae4-888f-2ac9824e9200",
      "title": "Stocker les permissions", ...}'::JSONB,
    '{}', '{}');

Interroger les permissions

Obtenir la liste des "principals" d'un ACE

Comme vu plus haut, pour vérifier une permission, on fait l'union des principals requis par les objets hérités, et on teste leur intersection avec ceux de l'utilisateur:

WITH required_principals AS (
     SELECT unnest(principals) AS p
       FROM perms
      WHERE ace IN (
         '/buckets/blog:write',
         '/buckets/blog:read',
         '/buckets/blog/collections/article:write',
         '/buckets/blog/collections/article:read')
 ),
 user_principals AS (
     SELECT unnest(principals)
       FROM user
      WHERE id = 'fxa:natim'
 )
 SELECT COUNT(*)
   FROM user_principals a
  INNER JOIN required_principals b
     ON a.p = b.p;

Filtrer les objets en fonction des permissions

Pour filtrer les objets, on fait une simple intersection de liste (merci PostgreSQL):

SELECT data
  FROM object o, user u
 WHERE o.type = 'record'
   AND o.parent_id = '/buckets/blog/collections/article'
   AND (o.read_principals && u.principals OR
        o.write_principals && u.principals)
   AND u.id = 'fxa:natim';

Les listes s'indexent bien, notamment grâce aux index GIN.

Avec Redis

Redis présente plusieurs avantages pour ce genre de problématiques. Notamment, il gère les set nativement (listes de valeurs uniques), ainsi que les opérations d'intersection et d'union.

Avec Redis on peut écrire l'obtention des principals pour un ACE comme cela :

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
  • SUNIONSTORE permet de créer un set contenant les éléments de l'union de tous les set suivants. Dans notre cas on le nomme temp_perm:/buckets/blog/collections/articles:write et il contient l'union des sets d'ACLs suivants: - permission:/buckets/blog:write - permission:/buckets/blog/collections/articles:write
  • SINTER retourne l'intersection de tous les sets passés en paramètres dans notre cas : - temp_perm:/buckets/blog/collections/articles:write - principals:fxa:alexis

Plus d'informations sur : - http://redis.io/commands/sinter - http://redis.io/commands/sunionstore

Si le set résultant de la commande SINTER n'est pas vide, alors l'utilisateur possède la permission.

On peut ensuite supprimer la clé temporaire temp_perm.

En utilisant MULTI on peut même faire tout cela au sein d'une transaction et garantir ainsi l'intégrité de la requête.

Conclusion

La solution a l'air simple mais nous a demandé beaucoup de réflexion en passant par plusieurs propositions.

L'idée finale est d'avoir :

  • Un backend spécifique permettant de stocker les principals des utilisateurs et des ACE (e.g. avec les sets Redis) ;
  • La liste des principals read et write sur la table des objets.

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.

Mozilla recherche des fans d’Apple

Firefox pour iOSiOS vous connaissez ? Non pas Ios, l’île grecque des Cyclades, mais le système d’exploitation des iPhone et des iPad, les appareils mobiles d’Apple. Si vous possédez un de ces smartphones ou une de ces tablettes à la pomme, vous pouvez aider Mozilla et son assistance communautaire SUMO.

Comme l’indique Goofy, un des responsables de l’édition francophone, sur la liste de MozFr :

Je relaie cette demande récemment parue et que rappelle un billet récent du blog de SUMO :

Nous sommes toujours à la recherche de 2 localiseurs ayant un appareil iOS pour chacune des langues suivantes : japonais, français, italien et espagnol, pour nous aider à tester Firefox pour iOS. Si vous êtes intéressé, prenez contact avec Roland. [comme nous avons traduit]

Donc si vous avez un appareil sous iOS, il s’agit essentiellement de sans doute faire des copies d’écran avec interface en français pour illustrer les nouveaux articles de SUMO et puis de tester la validité des procédures expliquées sur les articles.

Vous pouvez contacter Roland à partir de https://mozillians.org/u/rolandtanglao/. Je peux faire l’intermédiaire si nécessaire.

À moins que vous ne lisiez que des organes de presse qui attendent les communiqués de presse, vous savez que Firefox pour iOS devait sortir avec Firefox 38.0.5 pour ordinateur (intégration de Pocket mais pas en français, mode de lecture et salon Hello pour partager l’onglet en cours ou la fenêtre), Firefox 38.0.5 pour Android et une campagne de promotion pour Firefox Developer Edition 40, le 2 juin, mais seules ces trois dernières versions seront lancées ce jour-là. Si vous voulez en savoir plus d’ici là, faites comme les journalistes et fouillez un peu le wiki de Mozilla, Bugzilla et les notes de version bêta et Aurora.

Blog du groupe comm communautaire pour Firefox OS

Le week-end dernier les contenus du groupe comm pour Firefox OS ont été migrés du blog communautaire général vers un blog spécialisé de MozFr pour Firefox OS.

Pour le contexte, je reprendrai l’intro que j’ai rédigée en réponse au message de Pascal sur la liste communautaire générale :


Depuis le début des préparatifs du groupe comm, nous cherchons à avoir notre propre blog et avons commencé à publier à la demande de Mozilla sur le blog général. Nous nous désespérons depuis octobre 2014 de ne pas avoir notre propre espace de communication cohérent.

Des essais avaient été menés pour avoir un blog WordPress mais les dev bénévoles d’alors n’avaient pu mener le projet à sa fin. Quand nous avions voulu reprendre le projet – avec des connaissances sur WordPress en interne pour ce CMS qui a les fonctions qu’il nous faut – il nous avais été pratiquement refusé pour des raisons de sécurité. Un blog Dotclear nous a été finalement proposé et le blog de test cité a été créé. Précisons que nous n’avons dessus que des droits limités.

Julien (qui a désormais les droits d’administrateur dessus) a accepté de le designer à l’image de Firefox OS avec les fonctions que nous avons demandées. Il a peu de temps et le blog n’a pas avancé malgré l’installation de plugins disponibles pour Dotclear et une recherche parmi les thèmes adaptés. Il est en vacances. Nous serions très heureux d’avoir une version qui ressemble un peu à quelque chose pour pouvoir mettre en avant notre propre objet de communication cohérent.


Concernant le refus de WordPress pour des raisons de sécurité, ce n’était pas un reproche aux admin de MozFr mais un état de fait à prendre en compte et qui rajoutait un obstacle sur notre chemin…

Migration

Nous en sommes venus à la conclusion qu’il fallait migrer le contenu se trouvant sur le blog général vers notre propre instance du Dotclear de MozFr avec un thème générique (nous avons retenu Ductile, l’ancien thème par défaut de Dotclear dans une version plus évoluée que celle du blog général, responsive aussi mais en HTML5)1.

Nous avons remplacé le site vitrine en espérant pouvoir créer un thème reprenant le concept des tuiles lui-même adapté en son temps du site de Mozilla.

Pascal a donc mis notre instance Dotclear à l’adresse https://firefoxos.mozfr.org (N. B. le S), migré les articles relatifs à Firefox OS (82 billets, 1 page et 85 commentaires) du blog général au blog Firefox OS et mis en place des redirections permanentes pour les articles déplacés afin de ne pas perdre le référencement et d’éviter les erreurs 404. Après quelques réglages, le blog semble OK2 avec son thème générique.

D’accord avec Pascal, nous avons laissé le dernier article sur Firefox OS en Afrique car il s’agit d’un billet qui s’adresse à la communauté et qu’il reprend des appels de membres de la communauté.

La communauté francophone qui blogue a aussi ses planètes : planete.mozfr.org et mozfr.org3. Le nouveau blog y est.

raccourcis en haut du blog Firefox OS frJ’ai fait quelques personnalisations à la marge. Pour les raccourcis en haut de page, j’ai créé une page Nos réseaux sociaux, comme nous n’avons pas encore mis en place de formulaire de contact ni décidé de la forme qu’il prendra, pour le « @ », et mis la page Assistance migrée sous le « ? ».

Le pied de page comporte désormais une description avec des liens :

Blog de la communauté francophone pour Firefox OS, le système d’exploitation mobile de Mozilla.
Apprenez-en davantage sur notre équipe de communication.

Licence

Lundi, nous avons confirmé que par défaut (donc sauf mentions explicites contraires), les textes seront en CC BY-SA 4.0 (Creative Commons – Attribution – partage dans les mêmes conditions 4.0 International). Nous avons écarté la restriction NC qui aurait interdit les réutilisations commerciales. Par contre, les « graphiques » (photos, dessins, images d’illustrations, logos, maquette, charte graphique, identité visuelle, vidéos, etc.) seront en « Tous droits réservés ». La complexité, l’entremêlement des droits et la non disponibilité de certains éléments du droit d’auteur sur les images nous font préférer de restreindre la licence libérale aux productions textuelles. Dans les deux cas, une mention spéciale pourra changer la donne pour un contenu précis. Nous faisons attention à mentionner nos sources et les crédits pour les illustrations.

La note de copyright renvoyée dans le code des pages est pour l’heure : Textes : CC BY-SA 4.0. Graphismes/images/photos/vidéos/visuels : Tous droits réservés.. Je n’ai pas trouvé de mention de pied de page qui me satisfasse.

Tout cela sera précisé dans de futures mentions légales et politique de données personnelles dont il faut reprendre l’étude abandonnée cette automne avec le blog sous WordPress.

Statistiques

Grâce à Anthony qui avait mis en place Piwik à l’automne, nous avons pu configurer le plugin ad hoc pour Dotclear4. Piwik avec un léger paramétrage permet de s’exonérer de la demande de consentement préalable au dépôt d’un cookie imposée par le droit européen et sa transposition que contrôle la CNIL en France.

Et maintenant ?

On peut dire que le blog est en bêta. Il reste encore pas mal à faire pour avoir un objet de communication en bonnet difforme. Comme signalé nous avons des pages de mentions légales, données personnelles, de crédit, etc. Si vous voulez participer même sans être juriste à l’élaboration d’instruments en accord avec les valeurs de Mozilla et de la communauté, rejoignez-nous.

Le gros truc va être le thème à l’image de Firefox OS et pensé pour répondre aux besoins d’un espace central de communication. Donc, si vous avez des compétences en Dotclear ou en webdesign (comme l’écrit désormais le dictionnaire), nous serions heureux et reconnaissants de bénéficier de votre aide.

Nous avons une liste publique sans approbation préalable. Y’a plein de gens sympa que seront heureux de vous lire, même juste pour un avis ponctuel, et d’ailleurs, en attendant, vous pouvez faire qu’observer.



Note 1 : Nous avions le choix entre les thèmes :

  • International que nous avions choisi avec une disposition magazine – démo – HTML5 mais pas responsive.
  • Berlin : thème par défaut de Dotclear 2.7 ; Démo : en HTML5 qui a l’air responsive.
  • Ductile : thème par défaut de Dotclear 2.4. La version installée est en HTML5 et responsive ; démo.
  • Ductile-mozfr basé sur une version de Ductile non HTML5 mais responsive. C’est le thème du blog général.

Pour être complet, ces thèmes sont installés mais ne répondent pas à nos exigences minimales :

  • Blowup : ancien thème de Dotclear – démo – en HTML5 mais pas responsive.
  • Blue Silence : l’ancien thème par défaut de Dotclear avant Blowup n’est ni HTML5 ni responsive.
  • Chestnut que nous avions choisi auparavant en disposition magazine mais qui n’est ni en HTML5 ni responsive et que nous avions donc abandonné ; démo.
  • Time Fliesdémo – HTML5 mais pas responsive.

Note 2 : Des « @ » semblent avoir disparus d’autres non.

Note 3 : aussi en HTTPS, ce qui pose un problème pour afficher les iframe (fichiers inclus) des sites eux restés en HTTP.

Note 4 : cf. Tutoriel : installer Piwik sous dotclear 2 par StandarTux.

© 2010-2014 Mozinet - Ce billet a été publié sur BlogZiNet.

ES6 en détails : les itérateurs et la boucle for-of

Suite de la traduction, qui continue la série d’articles de Jason Orendorff. L’article original se trouve ici.

Merci aux traducteurs et relecteurs :) Marine, Mentalo, Benjamin, Amarok, Lucas, Ilphrin et Goofy !


ES6 en détails est une série d’articles décrivant les nouvelles fonctionnalités ajoutées au langage de programmation JavaScript avec la sixième édition du standard ECMAScript (ES6 en abrégé).

Comment itérer sur les éléments d’un tableau ? Lorsque JavaScript est apparu, il y a vingt ans de ça, on aurait fait ainsi :

for (var index = 0; index < monTableau.length; index++) {
  console.log(monTableau[index]);
}

Depuis ES5, on peut utiliser la méthode native forEach :

monTableau.forEach(function (value) {
  console.log(value);
});

C’est un petit peu plus court, mais il y a un léger inconvénient : il n’est pas possible de sortir de cette boucle avec une instruction break ou de faire retourner la fonction englobante avec une instruction return.

Ce serait effectivement agréable s’il y avait une syntaxe de boucle for itérant directement sur les éléments du tableau.

Quid d’une boucle for-in ?

for (var index in monTableau) {    
  // ne faites pas ceci
  console.log(monTableau[index]);
}

C’est une mauvaise idée pour plusieurs raisons :

  1. Les valeurs assignées pour l’index dans ce code sont les chaînes de caractères "0", "1", "2", et ainsi de suite : ce ne sont pas réellement des nombres. Étant donné qu’on ne souhaite sûrement pas manipuler des chaînes de façon arithmétique ("2" + 1 == "21"), c’est, a minima, peu pratique ;
  2. Le corps de la boucle ne va pas seulement s’exécuter pour chaque élément du tableau, mais également pour toutes les propriétés non-natives que l’on pourrait avoir ajoutées. Par exemple, si votre tableau possède une propriété énumérable monTableau.nom, alors cette boucle va s’exécuter une fois de plus, avec index == "nom". Même les propriétés présentes sur la chaîne de prototypes du tableau peuvent être visitées ;
  3. Le plus étonnant dans tout ça est que, dans certaines circonstances, ce code va boucler sur les éléments du tableau dans un ordre arbitraire.

Pour faire court, for-in a été pensé pour travailler avec de bons vieux objets (type Object) dont les clés sont des chaînes de caractères. Pour les tableaux (type Array), ce n’est pas génial.

La puissante boucle for-of

Vous souvenez-vous de la semaine dernière, lorsque j’ai promis qu’ES6 ne casserait pas le code JavaScript que vous avez déjà écrit ? En fait, il existe des millions de sites Web qui dépendent du comportement de for-in — oui, même de son comportement sur les tableaux. Il n’a donc jamais été question de « réparer » for-in afin qu’elle soit plus utile pour manipuler avec des tableaux. Le seul moyen pour ES6 d’améliorer les choses était d’ajouter une nouvelle forme de syntaxe de boucle.

La voilà :

for (var valeur of monTableau) {
  console.log(valeur);
}

Hmm. Après toute cette publicité, ça ne semble pas si impressionnant, n’est-ce pas ? Pourtant, nous allons voir que for-of a plus d’un tour dans son sac. Pour l’instant, notez simplement que :

  1. C’est la syntaxe la plus concise et la plus directe pour boucler sur les éléments d’un tableau ;
  2. Celle-ci permet d’éviter les pièges de for-in ;
  3. Contrairement à forEach(), elle est compatible avec les instructions break, continue et return.

La boucle for-in est faite pour boucler sur les propriétés d’un objet.

La boucle for-of est faite pour boucler sur des données — comme les valeurs d’un tableau.

Mais ce n’est pas tout.

for-of fonctionne également avec les autres collections

for-of n’est pas restreint aux tableaux. Cela fonctionne également avec la plupart des objets qui agissent comme des tableaux, telles que les NodeList du DOM. Cela fonctionne également sur les chaînes de caractères, en considérant que la chaîne est une séquence de caractères Unicode :

for (var chr of "♂ ♀"){
  alert(chr);
}

Cela fonctionne également avec les objets Map et Set.

Oh, désolé si vous n’avez jamais entendu parler des objets Map et Set, ceux-ci sont des nouveautés apportées par ES6. Nous en parlerons plus tard dans un article dédié. Si vous avez déjà utilisé des Maps (dictionnaires) et Sets (ensembles) dans d’autres langages, vous ne serez pas très surpris.

Par exemple, un objet Set peut être utilisé pour éliminer des doublons :

// créons un ensemble à partir d'un tableau de mots
var motsUniques = new Set(mots);

Une fois que l’on a un ensemble (Set), que se passe-t-il si on veut itérer sur son contenu ? Facile :

for (var mot of motsUniques) {
  console.log(mot);
}

Une Map est légèrement différente : les données que celle-ci contient sont des paires de clés-valeurs, il faudra donc utiliser l’affectation « destructurée » (destructuring) pour séparer la clé et la valeur en deux variables distinctes :

for (var [clé, valeur] of annuaireMap) {
  console.log("Le numéro de téléphone de "+clé +" est " + valeur);
}

La décomposition est une autre fonctionnalité, également apparue avec ES6 qui fera un sujet idéal pour un prochain article que j’écrirai.

Maintenant, vous avez une idée du tableau : JS possède déjà un certain nombre de structures de données pour des collections, et il y en a encore plus à venir. for-of a été pensé pour être l’instruction de boucle ultime à utiliser avec toutes ces structures.

for-of ne fonctionne pas avec des objets tout simples, mais si vous voulez itérer sur les propriétés d’un objet, vous pouvez soit utiliser for-in (c’est sa raison d’être), soit la fonction native Object.keys() :

// affiche toutes les propriétés propres énumérables 
// d'un objet dans la console
for (var clé of Object.keys(monObjet)) {
  console.log(clé + ": " + monObjet[clé]);
}

Sous le capot

« Les bons artistes copient, les très bons artistes volent » — Pablo Picasso

Un thème récurrent d’ES6 est le fait que les nouvelles fonctionnalités ajoutées au langage ne viennent pas de nulle part. La plupart ont été essayées et se sont avérées utiles dans d’autres langages.

La boucle for-of, par exemple, rappelle des instructions de boucle similaires en C++, Java, C#, et Python. Comme celles-ci, elle fonctionne avec des structures de données différentes, fournies par le langage et sa bibliothèque standard. Mais c’est également un ajout au langage.

De même qu’avec les instructions for ou forEach dans ces autres langages, for-of fonctionne entièrement à l’aide d’appels de fonctions. Ce que les tableaux (objets Array), dictionnaires (objets Map), ensembles (objets Set) et les autres objets ont en commun est le fait qu’ils ont chacun une méthode iterator (NdT : pour « itérateur »).

Il existe une autre catégorie d’objets qui peuvent aussi avoir une méthode iterator : n’importe quel objet.

De la même manière que lorsqu’on ajoute une méthode monObjet.toString() à n’importe quel objet, JS sait comment convertir cet objet en une chaîne de caractères, on peut ajouter la méthode monObjetSymbol.iterator() à n’importe quel objet, et, soudainement, JS sait comment itérer sur cet objet.

Par exemple, imaginons que vous utilisiez jQuery, et que, bien que vous soyez très fan de .each(), vous aimeriez que les objets jQuery fonctionnent également avec for-of. Voici la manière de procéder :

// Comme les objets jQuery se comportent comme des tableaux,
// donnons-leur la même méthode iterator que celle de l'objet Array
jQuery.prototype[Symbol.iterator] =
Array.prototype[Symbol.iterator];

OK, je sais ce que vous êtes en train de vous dire. Cette syntaxe Symbol.iterator est bizarre. Que se passe-t-il ici ? C’est en lien avec le nom de la méthode. Le comité de standardisation aurait pu appeler cette méthode .iterator(), or dans ce cas, votre code aurait déjà pu comporter des objets ayant une méthode .iterator(), et cela aurait pu être une source de confusion. C’est pourquoi le standard préfère utiliser un symbole plutôt qu’une chaine de caractères comme nom de la méthode.

Les symboles sont apparus avec ES6, et nous vous expliquerons tout sur eux — vous l’avez deviné — dans un prochain article de blog. Pour l’instant, tout ce que vous avez besoin de savoir, c’est que le standard peut définir un symbole entièrement nouveau, comme Symbol.iterator, et qu’il est garanti que cela ne créera pas de conflit avec du code existant. Le compromis que cela implique est une syntaxe un peu étrange. Mais c’est un petit prix à payer pour cette nouvelle fonctionnalité versatile et la garantie d’une excellente rétro-compatibilité.

Un objet qui possède une méthode Symbol.iterator() est appelé un itérable. Dans les semaines qui viennent, nous verrons que le concept d’objets itérables est utilisé à travers tout le langage, pas seulement avec for-of mais également avec les constructeurs des objets Map et Set, avec les affectations déstructurées et avec le nouvel opérateur « spread » (NdT : ou « opérateur de décomposition »).

Les objets itérateurs

Il est probable que vous n’ayez jamais à implémenter un itérateur de A à Z. On verra pourquoi la semaine prochaine. Cependant, pour être tout à fait complet, regardons de près à quoi ressemble un objet itérable (si vous sautez cette section, vous ne manquerez que quelques détails techniques).

Une boucle for-of commence par appeler la méthode Symbol.iterator() de la collection utilisée. Ceci renvoie un nouvel itérateur qui peut être n’importe quel objet possédant une méthode .next() ; la boucle for-of appellera cette méthode de façon répétitive, une fois pour chaque itération. Par exemple, voici l’itérateur le plus simple auquel je peux penser :

var itérateurPleinDeZéros = {
   [Symbol.iterator]: function () {
    return this;
  },
  next: function () {
    return {done: false, value: 0};
  }
};

À chaque appel de la méthode .next(), le même résultat sera renvoyé et communiquera les informations suivantes à la boucle for-of :

  1. nous n’avons pas encore terminé les itérations ;
  2. la prochaine valeur est 0.

Cela signifie que la boucle for (valeur of itérateurPleinDeZéros) {} sera une boucle infinie. Évidemment, un itérateur utilisé dans du code ne sera pas aussi trivial.

Ce concept d’itérateur, avec les propriétés .done et .value, est légèrement différente de celui utilisé dans les autres langages. En Java, les itérateurs possèdent des méthodes séparées .hasNext() et .next(). En Python, ils possèdent une seule méthode .next() qui appelle StopIteration lorsqu’il n’y a plus de valeur. Fondamentalement, ces trois conceptions renvoient les mêmes informations.

Un itérateur peut également implémenter les méthodes optionnelles .return() et .throw(exc). La boucle for-of appelle la méthode .return() si la boucle est interrompue de façon prématurée, à cause d’une exception, d’une instruction break ou return.

Un itérateur peut également implémenter la méthode return() s’il est nécessaire de procéder à un nettoyage ou de libérer des ressources utilisées. La plupart des objets itérateurs n’auront pas à implémenter cette méthode. La méthode throw(exc) est un cas encore plus spécifique : les boucles for-of ne les appellent jamais, on en parlera la semaine prochaine.

Maintenant que nous connaissons tous les détails, nous pouvons nous intéresser à une boucle for-of simple et la ré-écrire avec les appels des méthodes sous-jacentes.

Commençons avec la boucle for-of :

for (VAR of ITERABLE) {
  INSTRUCTIONS
}

Voici un équivalent, un peu brut, utilisant les méthodes sous-jacentes ainsi que quelques variables temporaires :

var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!result.done) {
  VAR = result.value;
  INSTRUCTIONS
  $result = $iterator.next();
}

Ce code n’illustre pas l’utilisation de la méthode .return(). On pourrait l’ajouter mais je pense que cela ajouterait plus de confusion. Les boucles for-of sont simples à utiliser, malgré cela, il se passe beaucoup de choses en arrière-plan.

Quand puis-je commencer à utiliser cette fonctionnalité ?

Les boucles for-of sont supportées par les versions actuelles de Firefox. Elles sont supportés dans Chrome sous réserve d’activer l’option « Activer la fonctionnalité expérimentale JavaScript » accessible via l’URL chrome://flags. Cela fonctionne également dans le prochain navigateur Edge (nom de code « Spartan ») de Microsoft. En revanche, cette fonctionnalité n’est pas disponible dans les différentes versions d’Internet Explorer. Si vous souhaitez utiliser cette nouvelle syntaxe et que devez prendre en compte IE et Safari, vous pouvez utiliser un compilateur tel que Babel ou Traceur de Google pour traduire votre code ES6 en code ES5, plus largement compatible avec les anciens navigateurs.

Côté serveur, pas besoin d’installer un compilateur - vous pouvez utiliser les boucles for-of dès aujourd’hui avec io.js (et avec Node en utilisant l’option --harmony)

(Mise à jour : j’ai oublié de mentionner que for-of est désactivé par défaut dans Chrome. Merci à Oleg d’avoir signalé cet oubli dans les les commentaires.) (NdT : voir ce commentaire).

{done: true}

Whaou !

Bon, c’est tout pour aujourd’hui, mais nous n’en n’avons toujours pas fini avec la boucle for-of.

Il y a encore une nouvelle sorte d’objet apparue avec ES6 et qui fonctionne très bien avec les boucles for-of. Je n’en ai pas parlé car c’est le sujet de l’article de la semaine prochaine. Je pense que cette nouvelle fonctionnalité est la plus magique d’ES6. Si vous ne l’avez jamais utilisé dans des langages comme Python et C#, vous serez vraisemblablement déstabilisé au début. Ce sera la manière la plus simple d’écrire un itérateur, un atout pour le refactoring et cela pourrait changer la façon d’écrire du code asynchrone tant au niveau du navigateur que du serveur.

Rejoignez-nous donc la semaine prochaine pour une étude approfondie des générateurs dans ES6.

Ah, les articles « putaclic » dont Mozilla Firefox est la victime idéale en ce mois de mai 2015.

Il est de bon ton sur certains webzines de faire des articles « putaclic » sur Mozilla Firefox. En ce mois de mai 2015, c’est la fête du slip

Nous avons eu droit à l’arrivée du support de la technologie EME (uniquement pour MS-Windows Vista et supérieur, en 32 bits), dixit la page de wiki de la technologie en question. Parfois en oubliant de préciser que des versions sans EME sont disponibles pour les versions MS-Windows.

Currently, Adobe Primetime is only available in Microsoft Windows Vista and above when using 32-bit versions of Firefox. Mac OS X, Linux, Windows XP and 64-bit versions of Firefox are currently not supported.

Ce qui donne traduit :

Actuellement, Adobe Primetime est disponible uniquement dans Microsoft Windows Vista avec l’utilisation des versions 32 bits de Firefox. Les versions Mac OS X, Linux, Windows XP et 64 bits de Firefox ne sont actuellement pas pris en charge.

Vu le magnifique support d’Adobe Flash sous Linux, je pense que le code de l’EME ne sera jamais activé sous les distribution GNU/Linux, ce qui n’empechera pas certaines personnes de hurler au fork.

Je ne reviendrais pas sur l’affaire dite « Pocket », n’utilisant pas de services de ce type, et n’en voyant pas l’utilité au quotidien.

Maintenant, c’est au tour des tuiles sponsorisées que certains webzines annonce l’arrivée sans préciser que la fonctionnalité est désactivable en… 2 clics !

Fonctionnalité dont il est question depuis au moins… novembre 2014. Vous parlez d’une nouveauté. Mais six mois sur internet, c’est parfois le bout du monde :)

J’ai fait une minuscule vidéo pour montrer la complexité de désactivation des dites tuiles qui nécessitent une énorme manipulation tellement difficile à faire qu’il faut sortir de Polytechnique pour y arriver.

Bon, j’ai eu tendance à utiliser un langage assez fleuri, mais cela est lié à mon niveau d’exaspération envers une partie de la communauté du logiciel libre.

Je sais très bien ce qui va se passer quand la fonctionnalité des tuiles « sponsorisées » va arriver. On va avoir droit aux mêmes vierges effarouchées qui vont vouer aux gémonies le navigateur de la Fondation Mozilla, et appeller à un nouveau fork. Ce qui ne fera qu’amoindrir encore la force de frappe du navigateur.

Car il faut être réaliste, dans le monde du libre, il n’y a que deux moteurs : Gecko (Mozilla Firefox, Seamonkey et ses « forks » que sont IceWeasel de Debian GNU/Linux et GNU IceCat) et les frères jumeaux Webkit/Blink (Chromium, Midori, Rekonq, Web, QupZilla et Opera depuis sa version 15).

Un vrai choix après tout… Ou pas !

Mamie Fox sera à Geekopolis le week-end prochain

Bonjour à tous mes petits Fox,

Mon petit-fils le Fox m’avait dit que le mois de mai allait être très chargé et plus particulièrement les deux prochaines semaines. Mais comme je suis une super mamie, je vais découvrir plusieurs événements tous différents, dont mon petit-fils le Fox connait déjà l’ensemble des sujets sur le bout des doigts.

Cette semaine, je vais vous parler d’un événement appelé Geekopolis et la prochaine fois de deux événements où je serais aussi présente : l’Ubuntu Party et la Very Important Party.

Affiche de Geekopolis

Pour commencer, je vais accompagner mon petit-fils le Fox au festival des cultures de l’imaginaire appelé Geekopolis. Il s’agit de sa troisième édition. Elle se déroulera le 23 et 24 mai 2015 à la Paris Expo Porte de Versailles. Son thème principal est le coté obscur des univers Geek.

Mon petit-fils m’a expliqué que je vais y découvrir 5 quartiers qui représentent chacun un univers habillé autour du thème principal : Nautilus, Tokyo, Métropolis, Avalon et Teklab.

Les 5 quartiers de Geekopolis

Avalon : Médiéval Fantastique
L’univers Avalon va nous permettre de faire des jeux de plateaux et des jeux de rôles, voir et participer à l’escrime médiévale, participer aux différents ateliers…
Little Tokyo : Manga, Japon et Jeux vidéo
L’univers Little Tokyo va vous amener dans l’univers japonais et du manga. Ainsi, nous pourrons participer au karaoké et aux jeux musicaux, voir des concerts…
Metropolis : Science Fiction
Nous allons pouvoir nous entrainer au sabre laser, découvrir les véhicules de science-fiction, devenir aventurier galactique…
Nautilus : l’univers Steampunk (Jules Verne)
L’univers Nautilus nous amène dans un univers de fonds marins avec des défilés, des duels, le téléguide de poissons volants…
Teklab : Sciences et nouvelles technologies
L’univers Teklas nous amène dans la réalité augmentée et dans ce qui nous attend à l’avenir au niveau de la robotique du futur, des sciences et des nouvelles technologies…

Mon petit-fils m’a aussi expliqué qu’il ne s’agit pas d’une manifestation culturelle comme les autres où j’ai pu me rendre car c’est réellement « le festival dont vous êtes le héros ».

Cette phrase m’a surprise au départ car je n’ai pas l’habitude d’être une héroïne. Heureusement, mon petit-fils m’a expliqué qu’il s’agit d’un lieu avec de nombreuses activités, des ateliers, des conférences, des démonstrations et des spectacles. Je pourrai participer, jouer et être un personnage d’une série télé ou d’un jeu.

L’ensemble des informations est disponible sur la page d’actualité du site officiel Geekopolis.

Bien entendu, je vous attends sur notre stand Mozilla, avec les nombreux amis de mon petit Fox au village du Libre qui se trouve dans l’univers Teklab.

Pour couronner le tout, Geekopolis propose une bande annonce officielle que je vous conseille.

La bande annonce de Geekopolis sur YouTube (30 s)

Comme mon petit-fils le Fox était impatient de me faire découvrir les différents univers de Geekopolis, il m’a sorti de son placard quelques photos souvenirs de l’édition 2014 :

Tout d’abord, une harpe sans corde et elle fonctionne :

Geekopolis 2014 : harpe

Ensuite, un match de quitdiche – il s’agit d’un nouveau sport qui est mal connu chez nous, mais sur d’autres continents il est très répandu.

Geekopolis 2014 : match de quitdiche geekopolis_2014_match_quitdiche_2_perrot.jpg Geekopolis 2014 : match de quitdiche

Mais aussi une attaque de zombies :

Geekopolis 2014 : zombies Geekopolis 2014 : zombies Geekopolis 2014 : zombies

Et beaucoup d’autres choses que je garde en réserve.

Enfin, je vous donne rendez-vous après Geekopolis pour découvrir les autres événements qui se dérouleront le dernier week-end de mai.

En attendant, rendez-vous à Geekopolis, ça promet d’être fun !

Mamie Fox


@hellosct1

Précédent message de Mamie Fox : Mamie Fox revient des JDLL 2015

Crédit illustrations : nos 1, 2 et 3 Geekopolis. Tous droits réservés.

Photos nos 4 à 10 Christophe Perrot sous licence Creative Commons Attribution - pas d’utilisation commerciale - partage dans les mêmes conditions 2.0 générique (CC BY-NC-SA 2.0) sur Flickr

ES6 en détails : une introduction

Ce billet est une traduction de cet article, écrit par Jason Orendorff qui participe au développement du moteur JavaScript de Firefox. Ce billet sera le premier d’une série de traductions, chaque billet décrivant une nouvelle fonctionnalité apportée par ECMAScript 6.

Merci à Marine, Mentalo, Lucas et Benjamin pour la traduction :)


Bienvenue dans cette série hebdomadaire que sera « ES6 en détails » ! Nous explorerons ECMAScript 6 (ES6), la nouvelle édition, imminente, du langage JavaScript. ES6 contient de nouvelles et nombreuses fonctionnalités qui feront de JavaScript (JS) un langage plus puissant et expressif. Chaque semaine, nous détaillerons une de ces fonctionnalités. Avant de commencer, arrêtons-nous quelques minutes sur ce qu’est ES6 et ce qu’on peut en attendre.

Quel est le champ d’application d’ECMAScript ?

Le langage de programmation JavaScript a été standardisé par l’organisation de standardisation ECMA sous le nom d’ECMAScript (ES). Entre autres choses, ECMAScript définit :

ECMAScript ne définit rien qui concerne HTML ou CSS ni même les API Web telles que le DOM (Document Object Model). Ces éléments sont définis dans des standards distincts. ECMAScript couvre les aspects de JavaScript qui interviennent dans un navigateur ou dans un autre environnement (par exemple node.js).

Le nouveau standard

La semaine dernière (NdT : le 16 avril 2015), la version finale de la spécification de la sixième édition du langage ECMAScript, a été proposée à l’Assemblée Générale ECMA pour relecture. Qu’est-ce que cela signifie ?

Cela signifie que cet été, nous aurons un nouveau standard pour le cœur du langage de programmation Javascript.

C’est une grande nouvelle. Un nouveau standard pour le langage JavaScript ne se fait pas en un jour. La dernière édition, ES5, est sortie en 2009. Le comité de standardisation travaille sur ES6 depuis cette date.

ES6 est une évolution majeure du langage. Votre code JS continuera à fonctionner. ES6 a été conçu pour apporter un maximum de compatibilité avec le code existant. En fait, de nombreux navigateurs supportent d’ores et déjà les fonctionnalités d’ES6, et les implémentent continuellement. Votre code JS est déjà exécuté par les navigateurs qui implémentent les fonctionnalités d’ES6. Si vous n’avez pas constaté de problèmes de compatibilité à ce jour, vous n’en verrez probablement jamais.

Compter jusqu’à 6

Les éditions précédentes du standard d’ECMAScript furent numérotées 1, 2, 3, et 5.

Qu’est-il arrivé à la quatrième édition ? Elle était planifiée — et le travail avait bien avancé — mais elle a été stoppée car considérée comme trop ambitieuse (elle proposait, par exemple, un système avancé de typage statique optionnel, doté de types génériques et d’une inférence de types).

ES4 a été un sujet controversé. Quand le comité de standardisation a finalement décidé d’arrêter ce projet, ses membres ont décidé de publier une version plus modeste d’ES5, puis ont commencé à travailler sur d’autres fonctionnalités. Ce travail a été appelé « Harmony », et c’est pourquoi la spécification ES5 contient ces deux phrases :

« ECMAScript est un langage passionnant et son évolution n’est pas figée. Une amélioration technique considérable se poursuivra dans les futures éditions de la spécification. »

Cette déclaration peut être lue comme une promesse faite pour ECMAScript 6.

Les promesses tenues

ES5, la version du langage datant de 2009, a introduit Object.create(), Object.defineProperty(), les accesseurs et les mutateurs (aussi respectivement appelés getters et setters), le mode strict et les objets JSON. J’ai déjà utilisé toutes ces fonctionnalités, et j’aime ce qu’ES5 a apporté à ce langage. Cependant, force est de constater que ces fonctionnalités eurent assez peu d’impact sur la façon d’écrire du code JS. Selon moi, l’innovation majeure a probablement été les nouvelles méthodes sur les tableaux : .map(), .filter(), et ainsi de suite.

Eh bien ES6 est différent. Il est le fruit d’années de travail harmonieux. Et il regorge de nouvelles fonctionnalités pour le langage et sa bibliothèque. C’est, à ce jour, la mise à jour la plus importante de JS. Les nouvelles fonctionnalités vont des fonctions basiques, comme les fonctions fléchées ou l’interpolation de chaînes de caractères, à de nouveaux concepts qui vont vous en mettre plein la vue comme les proxies et les générateurs. ES6 va changer la manière dont vous développez en JS.

Le but de cette série est de vous montrer comment, en examinant les nouvelles fonctionnalités qu’ES6 apporte aux développeurs JavaScript.

Nous démarrerons par la classique « fonctionnalité manquante » que j’attends impatiemment en Javascript depuis une décennie.

Rendez-vous la semaine prochaine pour découvrir les itérateurs et la boucle for-of d’ES6.

Ce qui a étonné AmarOk en rejoignant le groupe comm pour Firefox OS et Mozilla

Nous nous étions promis de le faire plus souvent : donner la parole à un utilisateur de Firefox OS ou à un membre de l’écosystème de Firefox OS (développeur, développeur d’applications, communiquant, traducteur, membre de la communauté participant à la promotion, etc. bien sûr au féminin comme au masculin). Parmi ces Starfox, nous accueillons AmarOk qui a participé à sa première réunion du groupe de communication pour Firefox OS. Ce n’était cependant pas sa première contribution dans ce domaine. Nous lui avons demandé ce qui l’avait étonné dans le travail d’un groupe de comm et lors de son entrée dans l’univers Mozilla.

AmarOkPour me présenter vite fait, je contribue à Mozilla depuis maintenant un an et demi. Et cette expérience m’a permis de découvrir plein de choses. Au début, je cherchais à faire du code, mais le manque de temps (projets personnels, études, etc.) m’a poussé à voir d’autres choses pour me prendre moins de temps.

J’ai notamment pu faire un peu de traduction de MDN, réaliser quelques petits « patchs », et en fin d’année 2014 participer au lancement de Firefox OS en France (Leclerc de Nantes et de Vannes avec quelques autres Mozilliens). J’ai toujours suivi le projet, mais ma contribution a baissé durant ces derniers mois. Mes contributions les plus récentes pour MozFr sont le flyer pour la communauté, l’animation du stand Webmaker à la Maker Faire de Saint-Malo avec la Maison du Libre de Brest et un article paru sur Zeste de Savoir sur le Mozilla Science Lab justement intitulé : Le Mozilla Science Lab.

À mon arrivée dans la communauté, je cherchais à m’investir plus dans des projets autour de l’open source. Il faut avouer que Mozilla possède énormément de sites pour les nouveaux contributeurs (des conférences filmées comme l’apéro des petits nouveaux, What can I do for Mozilla, Bugs Ahoy!) ainsi que de nombreux outils (Transifex, Bugzilla, MDN, etc.). Mais la communauté apporte une aide très précieuse et on arrive très vite à prendre les outils en main. Il existe de très nombreux projets Mozilla. Il est donc facile de trouver un projet dans lequel on peut aider. Le plus dur est de choisir parmi les projets qui nous plaisent.

Bref, je suis la liste du groupe de communication depuis sa création et j’ai enfin eu l’occasion d’assister à une de leur réunion, surtout en tant que curieux. Et comme d’habitude, je regrette que ce type de réunion se fasse via Vidyo (et je sais que je ne suis pas le seul).

Mais c’est à priori mon seul défaut ressenti. La réunion a été fluide, rapidement traitée vu le nombre d’informations qu’il y avait à dire. Une réunion hebdomadaire est un rythme dur à tenir pour des bénévoles n’ayant pas forcément le temps. Je suis donc heureux de recevoir un message m’y invitant chaque semaine. Je suis aussi heureux de voir le projet Firefox OS se développer et la synchronisation qu’il peut y avoir entre les différentes communautés francophones (qui fut un des thèmes abordés lors de la réunion pour l’intégration de Firefox OS en Afrique).


Pour finir, nous lui avons demandé s’il avait une actualité à nous faire partager ?

Une petite actualité : le POC21. Je trouve l’intention admirable, et j’espère que ça donnera vie à de nouveaux projets en septembre et que ça fera connaître ce genre d’initiative à un plus large public.


AmarOk


Le groupe est conscient des difficultés pratiques et éthiques posées par l’utilisation d’outils de travail fermés, privés et centralisés. Nous sommes à l’écoute et en recherche de solutions meilleures mais ne voulons pas épuiser toute notre énergie dans cette quête et dans l’installation et la maintenance de nouveaux outils. Voyez cette réponse à propos de l’utilisation de Google Docs.

Débuter

MozFr

Participer

S’inscrire sur la liste communautaire générale

Groupe comm pour Firefox OS

Guide du débutant

Petites choses à faire idéales pour débuter avec l’aide bienveillante d’un mentor


Notre précédent Starfox : Philippe

Le précédent rapport d’étonnement : Ce qui a étonné Pyves en rejoignant le groupe comm pour Firefox OS et Mozilla

Crédit illustrations : Avatar par AmarOk. WTFPL.

Mise en forme et laïus : Mozinet

La gestion des permissions

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. [1] 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.

[1]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. [2]

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.
[2]Plus d'informations : http://stackoverflow.com/questions/7770728/group-vs-role-any-real-difference

La différence entre permission, ACL, ACE

Une ACL est une liste d’Access 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:

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.

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 :

           +---------------+
           | 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 (.ini) buckets:create Les principals ayant le droit 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 :

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 :

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 :

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 !