Conflit avec la solution RGPD de Quantcast "choice" et ember.js

Bonjour,

Il s’agit d’un bug assez spécifique, mais son impact pourrait dépasser largement ce cas précis, et j’ai donc une question à ce sujet.

(Par ailleurs, je m’excuse pour mon anglais ; je suis français et donc loin d’être un locuteur natif…)

Mais d’abord, laissez-moi expliquer le contexte.
J’utilise Discourse depuis un certain temps pour un forum français sur le Raspberry Pi (forum.raspberry-pi.fr). Ce forum utilise un système de gestion publicitaire (themoneytizer). Comme vous le savez probablement, l’Europe nous oblige à mettre en œuvre le RGPD pour protéger la vie privée des utilisateurs. Le principal acteur (du moins en France) pour le consentement RGPD est Quantcast et sa solution « Choice ».

J’ai donc utilisé la solution de Quantcast pendant un certain temps sans aucun problème, jusqu’à récemment où j’ai remarqué que le bouton « Accepter tout » ne fonctionnait plus correctement. Au clic, rien ne se passe, et en regardant dans la console de développement, j’obtiens cette erreur : « Uncaught TypeError: can’t define property “status”: Function is not extensible ».

Voici ce qui se passe (du moins à ma connaissance) :
Croyez-moi quand je dis que cela m’a pris un loooooong temps pour trouver la source du problème. Apparemment, ember.js (avec lequel je ne suis pas très familier) étend certains objets JavaScript natifs, tels que Array, String et Function. Et pour une raison quelconque, cela semble également empêcher l’extension de ces objets d’une certaine manière (je n’ai pas encore totalement compris cette partie).

La solution de Quantcast, de son côté, tente probablement sur la fonction FunctionAcceptAll (ce qui explique que le bug ne se produit que lors du clic sur le bouton « Accepter tout » et le bouton « Refuser tout ») d’étendre un objet, je présume un tableau, dont le comportement normal a été modifié par ember.js.

Après de nombreuses recherches pour comprendre ce bug, j’ai également découvert qu’il est possible de modifier le comportement d’Ember.js pour qu’il n’étende pas les prototypes JavaScript, comme expliqué sur cette page : https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/.

J’ai effectué quelques tests, et le bug disparaît si j’ajoute la ligne window.EmberENV.EXTEND_PROTOTYPES = {String: true, Array: false}; dans le fichier _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, juste après la ligne window.EmberENV.FORCE_JQUERY = true;.

Pour ceux d’entre vous qui voudraient essayer, vous pouvez consulter la page /tst/index.html sur le forum (il vous faudra peut-être une adresse IP européenne pour que le script se lance, je n’en ai aucune idée).
Maintenant, je pense que vous avez toutes les informations que je peux vous fournir.

Voici donc ma question.
Même si ce bug est assez spécifique, le RGPD est de plus en plus présent en Europe en ce moment, et les choses ne vont pas devenir plus faciles.
Quantcast occupe une position quasi monopolistique, du moins pour les acteurs qui ne peuvent pas se permettre de payer des centaines de dollars pour mettre en œuvre le RGPD. Ce bug empêche toute utilisation de Quantcast, et donc de la publicité sur Discourse en Europe, ce qui me semble être un gros problème.
De plus, même si j’ai trouvé ce bug uniquement avec Quantcast, ce type de bug pourrait en fait se produire pour de nombreux scripts tiers que nous devons intégrer pour les publicités ou autres, sur lesquels nous n’avons aucun contrôle et qui reposent sur le comportement « normal » de JavaScript pour les objets Array, String et Function.

Je ne connais pas assez le code de Discourse, alors je vous demande : les propriétés ajoutées par ember.js sur les objets Array, String et Function (voir le lien ci-dessus) sont-elles utilisées par Discourse ou non ? Si non, ne devrions-nous pas envisager de désactiver les extensions de prototypes d’ember.js afin d’éviter des effets secondaires comme celui-ci ?

J’espère que quelqu’un pourra me donner des informations à ce sujet,
merci

Cela n’est pas pris en charge pour le moment ; nous nous appuyons sur les extensions. Peut-être que dans une version future, cela ne sera plus nécessaire. @eviltrout pourra fournir plus de contexte.

Je ne suis pas sûr de savoir quoi faire ici, mais je pense que nous devrions prévoir une solution de contournement. Je suis surpris que cela rompe le RGPD ; peut-être faudrait-il ouvrir un ticket auprès de Quantcast pour en discuter et faire un lien ici ?

Comment ajoutez-vous les scripts nécessaires pour Quantcast ? Les ajoutez-vous via une balise script avec un src (externe) comme ceci :

<script src="foo"></script>

Ou les ajoutez-vous en ligne comme ceci ?

<script>
  alert("Bonjour le monde !");
</script>

Salut,
merci pour vos réponses.

@sam J’ai ouvert un ticket auprès de Quantcast le jour où j’ai créé ce sujet, et ils m’ont répondu aujourd’hui. Apparemment, ils l’examinent actuellement ; espérons qu’ils trouveront une solution. Je pense que c’est une correction assez simple pour eux, car le bouton « Accepter la sélection » fonctionne bien. Espérons qu’ils considèrent que Discourse et Ember.js sont suffisamment utilisés pour mériter une correction dédiée.

@Johani En réalité, je n’ajoute pas moi-même les scripts de Quantcast ; themoneytizer fournit un script qui s’en charge pour moi (ainsi que la recherche d’événements TCF2, etc.). Vous pouvez consulter ce script ici si vous le souhaitez.

Ce script semble ajouter un nouvel élément avant le premier script de la page. Je ne suis pas certain à 100 %, mais j’ai très clairement vu ce script exact dans certains exemples de documentation de Quantcast, donc je suppose qu’il s’agit d’un script assez couramment utilisé pour l’intégration de Quantcast Choice.

Merci encore pour votre temps.

Je ne vois pas comment nous pourrions supprimer les prototypes de tableau en toute sécurité dans un proche avenir. Ils se sont révélés inoffensifs au fil des ans et apportent beaucoup de commodité.

Peut-être pourriez-vous contacter Quantcast pour obtenir une version des scripts qui ne repose pas sur les prototypes de tableau ?

Salut à tous,
J’ai quelques nouvelles informations pour vous et j’ai trouvé une solution à ce problème.

Aujourd’hui, j’ai creusé à nouveau ce bug et je pense avoir trouvé plus d’informations.

En examinant le fichier cmp2ui-fr.js de Quantcast, j’ai pu identifier où le bug se produit, c’est dans cette fonction (nous n’avons que la version minifiée) :

function(t){for(var n in t){t[n].status=e;}}

Comme vous pouvez le voir, cette fonction utilise une boucle for..in, et la variable t est un tableau. Nous l’avons déjà expliqué : Ember.js étend le tableau natif de JavaScript. Il semble que l’une des modifications soit l’ajout d’une entrée _super.

L’entrée _super pointe vers une fonction ROOT(), qui semble faire référence à _utils.ROOT dans Ember.js (cela peut sembler familier à certains d’entre vous, mais pas à moi ^^). Cette fonction ROOT() n’est pas extensible.

Apparemment, cette propriété _super est considérée comme énumérable, ce qui signifie que lors de l’utilisation d’une boucle for..in sur un tableau, l’entrée _super est traitée comme une entrée « normale » (contrairement, par exemple, à values, bind, valueOf, c’est-à-dire pratiquement toutes les fonctions d’un objet tableau).

Je pense que c’est un bug d’Ember.js et non un comportement souhaitable.

J’ai donc pu rendre le bug très reproductible. Pour cela, créez simplement un tableau d’objets comme ceci :

var objs = [{'key':'val'},{'key':'val'}];

Ensuite, créez une fonction en mode strict, qui itère sur un tableau et définit une nouvelle propriété pour chaque entrée :

var tst_func = function (objs){'use strict';for(var i in objs){objs[i].newproperty = true }};

Enfin, appelez simplement cette fonction sur le tableau d’objets :

tst_func(objs);

Vous devriez obtenir une erreur : Uncaught TypeError: can't define property "newproperty": Function is not extensible.

En y réfléchissant, je pense que le bug est très probablement déjà présent dans la nature et n’est pas spécifiquement lié à Quantcast. Fondamentalement, quiconque essaie d’utiliser for..in s’expose à un comportement incohérent ou à un bug critique.

Pour moi, ce n’est vraiment pas à Discourse ou à Quantcast de résoudre cela, mais clairement à Ember.js. Cependant, cela ne change pas le fait que nous devons trouver une solution tant que le bug n’est pas corrigé dans Ember.js ^^.

La bonne nouvelle, c’est que je pense avoir trouvé un moyen de le corriger.

Une méthode consiste à ajouter une ligne if (!objs.hasOwnProperty(i)) {continue}; dans chaque boucle for..in (probablement aussi for each, for of, etc.). Cela ne modifie pas le comportement étrange de _super dans les tableaux, mais empêche localement d’y accéder. Cela signifie évidemment que cela ne fonctionne pas pour tout script externe que nous ne contrôlons pas, comme mon cas d’utilisation particulier avec Quantcast.

Une autre méthode, que je pense être la voie à suivre, consiste à modifier le prototype du tableau en JavaScript (nous remplaçons donc en quelque sorte la propre surcharge d’Ember.js ^^) pour rendre _super non énumérable. Pour cela, nous devons exécuter cette ligne JS APRÈS que Ember.js ait été chargé et évalué :

//Rendre _super non énumérable pour éviter le bug entre Ember.js et for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

La deuxième méthode permet toujours l’utilisation directe de _super, comme prévu, tout en empêchant son apparition dans les boucles. Cependant, je ne peux pas garantir que ce comportement étrange n’est pas utilisé par une fonction interne d’Ember.js ou un plugin externe.

En examinant le fichier _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, j’ai remarqué certaines lignes spécifiques à Discourse, comme :

var ALIASES = {
    "ember-addons/ember-computed-decorators":
      "discourse-common/utils/decorators",
    "discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
    "preload-store": "discourse/lib/preload-store",
    "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures",
  };
  var ALIAS_PREPEND = {
    fixtures: "discourse/tests/",
    helpers: "discourse/tests/",
  };

Peut-être pourrions-nous ajouter notre ligne Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); ici ?

Pour l’instant, j’ai corrigé le bug en ajoutant les lignes suivantes :
//Rendre _super non énumérable pour éviter le bug entre Ember.js et for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});

Avant d’appeler le script choice.js de Quantcast.

Je pense que quelqu’un pourrait corriger le bug localement en créant un fichier ‘fix_ember.js’ contenant ces deux lignes, en le servant statiquement via un proxy inverse (par exemple Nginx), puis en ajoutant une ligne <script src="/fix_emmber.js"></script> dans le pied de page du thème via la personnalisation. Écrire le script directement au lieu de créer un lien ne fonctionne pas à cause de l’extraction des scripts dans Discourse (voir Custom javascript in <head> disappear).

J’espère que ce sujet aidera d’autres personnes. J’ouvrirai un ticket auprès d’Ember.js demain pour vérifier s’il s’agit d’un bug ou d’un comportement très étrange mais délibéré.

Je vous tiendrai informés pour voir si nous devons inclure une correction dans Discourse.

PS : Un grand merci à Angus Croll de Extending JavaScript Natives – JavaScript, JavaScript…, son article m’a beaucoup aidé !

Un grand merci à @OsaAjani d’avoir identifié ce problème. Nous rencontrons exactement le même souci avec Quantcast Choice sur notre forum Discourse et sommes bloqués sur leur version 12 (qui était la dernière à ne pas générer d’erreur).

La dernière version est la 23, et nous aimerions vivement pouvoir l’utiliser.

Nous attendons avec impatience de savoir comment résoudre ce problème dès que possible.

Bonjour @Terrapop, je suis ravi si ce sujet peut vous aider. Pour l’instant, je n’ai absolument aucun retour sur le problème que j’ai ouvert sur le dépôt d’ember.js ([Bug] Property "_super" is enumerable on Array, creating conflicts with external libs (ex : Quantcast Choice RGPD) · Issue #19289 · emberjs/ember.js · GitHub). Ni aucun retour de la part de Quantcast.

La bonne nouvelle, c’est que j’utilise la correction décrite dans mon message précédent : créer un fichier fix_ember.js contenant le code :

Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

Et ajouter une ligne :

<script defer src="/fix_emmber.js"></script>

Dans le pied de page du thème, en utilisant les options de personnalisation du panneau d’administration.

Cette solution résout le problème, et pour l’instant, je n’ai constaté aucun effet secondaire. Vous pouvez donc utiliser cette correction en toute confiance et passer à la version 23 de Quantcast.

Nous avons ajouté la ligne avant le chargement de choice.js. Cela semble également fonctionner correctement.

Pour l’autre solution : nous n’avons pas de proxy inverse devant notre Discourse, donc nous ne savons pas comment créer correctement le fichier fix_ember.js. Est-ce même possible dans Docker ? Une suggestion ?

Eh bien, si vous pouvez le charger avant choice.js, c’est encore mieux !

Oui, ça marche. On ne peut pas assez vous remercier : on s’est arraché les cheveux pendant des jours à ce sujet, sans parvenir à trouver le problème. Nous avons échangé 20 e-mails avec Quantcast, mais ils n’ont pas non plus réussi à localiser le problème.

Eh bien, si vous avez un bon canal de communication avec Quantcast, vous pourriez peut-être leur indiquer ce post afin qu’ils puissent le corriger. J’ai essayé, mais comme je ne suis pas réellement leur client, leur support ne m’a pas vraiment donné de réponses.

Je le ferai. Bonne remarque.