New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Hugo Internal vs GDPR #4616

Closed
onedrawingperday opened this Issue Apr 14, 2018 · 61 comments

Comments

Projects
None yet
8 participants
@onedrawingperday
Contributor

onedrawingperday commented Apr 14, 2018

Following the discussion at the forum: https://discourse.gohugo.io/t/hugo-vs-the-general-data-protection-regulations-gdpr-in-eu-eea/11526/12

I will focus on the Google Analytics internal template in this first post. But feel free to add suggestions about other templates such as Disqus, Twitter etc

Google Analytics

Notes

  1. The following suggestions are for Analytics.js that is used in the current internal template

  2. For Google Tag Manager things are more complicated and the user needs to set various settings directly in the Google Analytics dashboard. I'm not using Google Tag Manager myself and I cannot help with it.

  3. I am not some expert these suggestions are my own research and feel free to build on them to help @bep build the internal templates that will make Hugo offer GDPR compliant websites out of the box.


The general idea I am proposing is to make the Hugo GA internal template not collect any personally identifiable information so that it falls outside the scope of the GDPR. With such settings a Hugo site admin will not need user opt-in for Google Analytics cookies because no such cookies will be installed on a user's device. However as you may have guessed this will severely limit GA reporting. Namely no returning visitors etc. Also the owner of a GA property should have the User ID, Google Data Sharing and Advertising features disabled on their dashboard.

Note
Enabling any of the above features or using the default Google Analytics code as is in the Hugo internal template requires user opt-in on the frontend. The tricky part is that you will need to have Google Analytics disabled until the user agrees to it. I am not going to cover this scenario here.

The key settings that need to change in the internal template are:
Anonymize IP
The GDPR treats IPs as personally identifiable information so the following should be enabled in the tracking code:
ga('set', 'anonymizeIp', true); reference

Disable Cookies and Use Session Storage to Store the Client ID
Session Storage is for the duration of a user's visit on a website. Even the Client ID is treated as Personally Identifiable Information in some quarters. There is no consensus about this currently. See a blog post about this here

var GA_SESSION_STORAGE_KEY = 'ga:clientId';
if (window.sessionStorage) {
  ga('create', 'UA-XXXXX-Y', {
    'storage': 'none',
    'clientId': sessionStorage.getItem(GA_SESSION_STORAGE_KEY)
  });
  ga(function(tracker) {
    sessionStorage.setItem(GA_SESSION_STORAGE_KEY, tracker.get('clientId'));
  });
}
else {
  ga('create', 'UA-XXXXX-Y', 'auto');
}
ga('send', 'pageview');

The above is a modified version of Google's sample code for using Local Storage to store the Client ID.

However Local Storage is persistent until cleared by the user and as such a Hugo site admin would still need user opt-in as Local Storage is treated the same as cookies.

There are caveats with the above setup below is the disclaimer by Google in the above page I linked to:

Note: unlike cookies, localStorage is bound by the same-origin policy. If parts of your site are on different subdomains, or if some pages use http and others pages use https, you cannot use localStorage to track users between those pages. For this reason, cookies continues to be the officially recommended way to store the Client ID.

Basically what the GDPR does is to limit Data Collection to the bare minimum. A setup as the one I propose above would make the Hugo GA internal template GDPR compliant but at the expense of GA reporting.

It's up to the community to decide how to go about this.

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 14, 2018

YouTube

Google is offering video embedding under the domain youtube-nocookie.com so that cookies are not set on a user's device before he/she presses the play button.

Hugo's current internal YouTube template needs to change to the following:

t.addInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
  <iframe src="//www.youtube-nocookie.com/embed/{{ .Get "id" }}?{{ with .Get "autoplay" }}{{ if eq . "true" }}autoplay=1{{ end }}{{ end }}"
  {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0" title="YouTube Video"></iframe>
</div>{{ else }}
<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
  <iframe src="//www.youtube-nocookie.com/embed/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0" title="YouTube Video"></iframe>
 </div>
{{ end }}`)

Basically the change is from www.youtube.com to www.youtube-nocookie.com

@bep bep added this to the v0.39 milestone Apr 14, 2018

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 14, 2018

A clarification about the YouTube-no cookie above.

YouTube offers this "privacy enhanced" option since 2009 and it will not set cookies until a user clicks to play a video, or so it claims.

I discovered that there’s been some controversy about it in the past.

Unless Google begins offering a better privacy embed solution it’s all we've got.

@bep bep modified the milestones: v0.39, v0.40 Apr 15, 2018

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 16, 2018

Modified Google Analytics. snippet. Version 2.

The snippet I posted above checks whether Session Storage is available in the client's browser and if it's not it creates the Google Analytics cookies the old way.

That's not allowed under the GDPR without opt-in. So I have modified the snippet to use Session Storage only. Of course this will only work on browsers that support Session Storage see: https://caniuse.com/#search=sessionstorage

But then again I don't think Hugo should support non modern browsers.

Here is the final GDPR compliant snippet tested and working.

    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)
    [0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

var GA_SESSION_STORAGE_KEY = 'ga:clientId';

  ga('create', 'UA-XXXXX-Y', {
    'storage': 'none',
    'clientId': sessionStorage.getItem(GA_SESSION_STORAGE_KEY)
  });
  ga(function(tracker) {
    sessionStorage.setItem(GA_SESSION_STORAGE_KEY, tracker.get('clientId'));
  });
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');

Of course Google might come up with a better GDPR compliant offering by the 25th of May but this is the best solution I've come up with and it's compliant because it doesn't set persistent cookies or Local Storage on a visitor's device.

@it-gro

This comment has been minimized.

it-gro commented Apr 16, 2018

This is about Disqus.com and the internal disqus template
hugo/tpl/tplimpl/template_embedded.go

I'm not a layer - but browsing to
https://disqus.com/admin/
I see user comments (of the site I'm the admin) including the users nickname, the email address the user used for registration and the IP-address.
Question is, if this is GDPR relevant? Processing personal data.
https://help.disqus.com/moderation/moderating-101

Anyway - here's a proposal - Please comment.

{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
<script>
    var disqus_config = function () {
    {{with .GetParam "disqus_identifier" }}this.page.identifier = '{{ . }}';{{end}}
    {{with .GetParam "disqus_title" }}this.page.title = '{{ . }}';{{end}}
    {{with .GetParam "disqus_url" }}this.page.url = '{{ . | html  }}';{{end}}
    };
    function disqusAgree(){
      localStorage.setItem("agreed_to_disqus_thread", "YES");
      localStorage.setItem("agreed_to_disqus_thread_date", (new Date()).toLocaleString() );
      location.reload();
    };
    (function() {
        if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
            document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
            return;
        }
      {{- if ne ($.Param "disqusSkipAgree") true }}
        if ((localStorage.getItem("agreed_to_disqus_thread") != "YES") ) {
          document.getElementById('disqus_thread').innerHTML = '{{ (default `Show comments powered by [disqus.com](https://disqus.com)` (i18n `disqusTxtAgree`) ) | markdownify }} <button id="agree-to-disqus" type="button" onclick="disqusAgree()">{{default `Show me` (i18n `disqusBtnAgree`)}}</button>';
          return;
        }
      {{- end }}
        var d = document, s = d.createElement('script'); s.async = true;
        s.src = '//' + {{ .Site.DisqusShortname }} + '.disqus.com/embed.js';
        s.setAttribute('data-timestamp', +new Date());
        (d.head || d.body).appendChild(s);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
{{end}}

The part's I added:

      {{- if ne ($.Param "disqusSkipAgree") true }}
        if ((localStorage.getItem("agreed_to_disqus_thread") != "YES") ) {
          document.getElementById('disqus_thread').innerHTML = '{{ (default `Show comments powered by [disqus.com](https://disqus.com)` (i18n `disqusTxtAgree`) ) | markdownify }} <button id="agree-to-disqus" type="button" onclick="disqusAgree()">{{default `Show me` (i18n `disqusBtnAgree`)}}</button>';
          return;
        }
      {{- end }}

By setting

[params]
  disqusSkipAgree = true

The new behaviour is turned off.

If not set or <> true
A visitor first has to press a button in order to see the comments.
The browsers local storage is used to remember his "agreement".

I think the text should be changeable.
=>
Text besides the button:

(default `Show comments powered by [disqus.com](https://disqus.com)` (i18n `disqusTxtAgree`) ) | markdownify

The button itself

default `Show me` (i18n `disqusBtnAgree`)

This way (if i18n is accessible inside a internal template?) users can modify the text:

i18n/en.yaml

- id: "disqusTxtAgree"
  translation: "Show comments powered by [disqus.com](https://disqus.com). Agree to the [Terms](/terms)"

- id: "disqusBtnAgree"
  translation: "Yes, I Agree"

(/terms would be the reference to the site terms, needed by GDPR)

    function disqusAgree(){
      localStorage.setItem("agreed_to_disqus_thread", "YES");
      localStorage.setItem("agreed_to_disqus_thread_date", (new Date()).toLocaleString() );
      location.reload();
    };

This is the function called by clicking the button. It stores a YES and a readable time stamp.
If once pressed, the button will not show again (until the local storage is cleared).

What do you think?

Edit: See here an example

it-gro pushed a commit to it-gro/hugo-theme-w3css-basic that referenced this issue Apr 16, 2018

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 17, 2018

@it-gro The tricky part in seeking user consent as lawful basis under the GDPR is that we have to avoid making consent to processing a precondition of a service. See this guidance from the UK Information Commissioner Office (ICO): https://ico.org.uk/for-organisations/guide-to-the-general-data-protection-regulation-gdpr/lawful-basis-for-processing/consent/

I don't know if it's technically possible but I sincerely think that it would be best to show the Disqus Comments as plain text under each article and then move the Consent you developed at the bottom if a user wants to comment using Disqus.

@brunoamaral

This comment has been minimized.

Contributor

brunoamaral commented Apr 17, 2018

@onedrawingperday I'm using tag manager on a number of sites. What information have you gathered and how can I help?

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 17, 2018

@brunoamaral I don’t use Google Tag Manager for Google Analytics but see this post for all the tweaks that someone suggests: https://www.humix.be/en/blog/configure-google-analytics-for-gdpr/

Under the GDPR whenever an identifier is stored in a user’s device in the form of persistent cookies or local storage or the recording of an IP address etc a website is required by the new regulation to offer user opt-in. This means that tracking needs to be disabled by default and enabled only if a user agrees to it.

In my above proposal I have tried to make Google Analytics fall outside the scope of the GDPR by not storing anything persistent on a user’s device and moving the GA client ID to session storage.

If you can come up with something similar for Google Tag Manager (that is now the default Analytics tracking code), YouTube or any other of Hugo’s internal templates for third party services that would help a lot.

@brunoamaral

This comment has been minimized.

Contributor

brunoamaral commented Apr 17, 2018

Alright, then just to sum up what I know and share with others:

The default install of google tag manager (GTM) will only fire events to Google Analytics or one of it's other tracking apps. It does not store information, and if the Google Analytics implementation is done in the way you describe above, the admin of the site can rest assured.

The problem comes up when you use google tagmanager itself to fire the Google Analytics tag, in this way:
image

That {{Universal Analytics ID}} is a variable that sets options for the GA script. Here is the implementation I use at brunoamaral.eu:

image

I'm using the same anonymizeIp option you suggested, but so far have not found an equivalent to the other options you set.

In short, if the admin is using GTM as a one-stop-shop for events and setup of the Universal Analytics Code, the compliance with GDPR falls on his shoulders. He needs to configure it on the GTM workspace. The default code for GTM does not break GDRP, as far as I know.

I will keep looking into this and report back if I find anything relevant.

@gedw99

This comment has been minimized.

gedw99 commented Apr 18, 2018

This is really useful.
My partner is going to a conference on the new directives this week.

If there is a hugo sample template repo being setup to show how Hugo should be done I will be happy to contribute all the aspects we learn and code that is relevant.

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 18, 2018

@gedw99 You can view the source of Hugo's current internal templates for third party services & other things at: https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/template_embedded.go

@gedw99

This comment has been minimized.

gedw99 commented Apr 18, 2018

@onedrawingperday
i never knew hugo has a default template that it uses. So if you dont give it any theme to use is uses this ?

Wont we break everyone if we change this though ?

@brunoamaral

This comment has been minimized.

Contributor

brunoamaral commented Apr 18, 2018

@gedw99 these internal templates need to explicit on the theme in order to be used.

@onedrawingperday I have been doing some research and am wondering about the approach of using LocalStorage. It's still identifiable information, and may still be in the scope of GDPR. Question is if we should wait for the user to agree to cookies before activating google analytics or not.

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 18, 2018

@gedw99
These are the Hugo internal templates for: Disqus, Google Analytics, Open Graph, Schema, Twitter Cards,
See: https://gohugo.io/templates/internal/#the-internal-templates

There are also internal shortcodes for YouTube, Vimeo, Instagram, Twitter, Speacker Deck
See: https://gohugo.io/content-management/shortcodes/#use-hugo-s-built-in-shortcodes

Wont we break everyone if we change this though ?

@bep asked me to open this issue to make the Hugo internal templates GDPR compliant. When the regulation is enforced come May the 25th, Hugo site admins will have to seek user consent as legal basis for the data collection of the 3rd party services that are enabled by these internal Hugo templates. Otherwise they will be breaking the new regulation and might face heavy fines. However this does not apply only to EU and EEA based websites but also to every web platform that has a European audience.

It's up to the Hugo community to decide what they want to do. Make Hugo GDPR compliant and break some eggs or leave things as they stand now and leave Hugo users fend for themselves.

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 18, 2018

@brunoamaral
Local Storage is persistent Session Storage is not. In the GA snippet I posted above I have moved everything into Session Storage, so that once the browser tab is shut there will be no GA identifier in a user's device. Also the IP address of a visitor will be masked and no personal information is transmitted.

I also think that it would be prudent to have a notice asking users to agree to a website's privacy policy. See the new GDPR notice of https://www.nike.com

Maybe Google will start offering a GDPR compliant tracking code for Google Analytics (but I very much doubt it).

@bep

This comment has been minimized.

Member

bep commented Apr 20, 2018

Just want to chime and say that I have not forgotten about this, just have been ... kind of busy on other fronts.

@it-gro

This comment has been minimized.

it-gro commented Apr 20, 2018

@onedrawingperday
avoid making consent to processing a precondition of a service.
It is my understanding that the the "service" is the static page build using hugo.
Not agreeing to the hugo site "discus terms" does not stop this service (showing pages and posts on the site) for the visitor. But it affects the ability to see or make any comments (using discus.com) which is not a service of the hugo site (but from discus.com).
GDPR compliant agreement for the site is needed since the site-admin can see / process visitors personal data on the discus.com admin site (see discourse.gohugo.io).

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 20, 2018

@it-gro
Of course the GDPR is open to interpretation at this stage. It's up to you to decide what you want to do.

In my humble opinion comments are an essential part of a blog. Users shouldn't have to accept Disqus tracking to view them.

You will need to inform them with a notice. So that if they do not wish to have the Disqus tracking on their devices comments are disabled.

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 20, 2018

Ah! I accidentally closed the issue and reopened... pressed the wrong button on the phone. Apologies.

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented Apr 20, 2018

@jhabdas
Thanks for the heads up. I've made a few tests but I cannot see this script being loaded on my end with the youtube-nookie iframe.

Can you please tell me what you see on your end? Where is the script loaded? In the parent DOM or within the iframe? (checked both and the console and I couldn't find it). Also does it load before or after video playback?

Thanks

@TotallyInformation

This comment has been minimized.

TotallyInformation commented Apr 20, 2018

I have received an update from Google regarding GDPR compliance and the use of GA. I can post here if required.

They have added granular data retention controls and user deletion. They have also updated their user consent policy.

@it-gro

This comment has been minimized.

it-gro commented Apr 20, 2018

@onedrawingperday

You will need to inform them with a notice. So that if they do not wish to have the Disqus tracking on their devices comments are disabled.

Which may be done via this:

i18n/en.yaml

- id: "disqusTxtAgree"
  translation: "Show comments powered by [disqus.com](https://disqus.com). Agree to the [Terms](/terms)"

(see Example)

@gedw99

This comment has been minimized.

gedw99 commented Apr 20, 2018

@bep bep removed this from the v0.40 milestone Apr 20, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit to bep/hugo that referenced this issue May 21, 2018

bep added a commit that referenced this issue May 21, 2018

Add YouTube shortcode simple mode
Adapted from the work of @onedrawingperday.

See #4616

bep added a commit that referenced this issue May 21, 2018

bep added a commit that referenced this issue May 22, 2018

tpl: Add another class and an id to youtube_simple
To provide some more styling options.

See #4616

@bep bep changed the title from Hugo Internal templates GDPR compliant to Improve Hugo Internal vs GDPR May 25, 2018

@bep

This comment has been minimized.

Member

bep commented May 25, 2018

I will close this for now. There are still work to do here, but we need to continue that in a shorter thread... Thanks to all for the input!

@rwieruch

This comment has been minimized.

rwieruch commented May 26, 2018

@bep I think lots of people where following this thread for GDPR. Where can people follow this issue now? Really looking forward to the anonymized IP address for GA 👍

@onedrawingperday

This comment has been minimized.

Contributor

onedrawingperday commented May 26, 2018

@rwieruch The new Hugo Privacy Config has various options for GA.
It includes the anonymized IP option and then some more: see here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment