Projet

Général

Profil

0003-matomo-views-form-manual-and-automatic-configuration.patch

Nicolas Roche, 29 mars 2019 19:40

Télécharger (21 ko)

Voir les différences:

Subject: [PATCH 3/4] matomo: views form manual and automatic configuration
 (#31778)

 hobo/matomo/__init__.py                       |   0
 hobo/matomo/forms.py                          |  37 +++++
 .../matomo/templates/hobo/matomo_disable.html |  20 +++
 .../templates/hobo/matomo_enable_auto.html    |  20 +++
 .../templates/hobo/matomo_enable_manual.html  |  20 +++
 hobo/matomo/templates/hobo/matomo_home.html   | 117 ++++++++++++++++
 hobo/matomo/urls.py                           |  26 ++++
 hobo/matomo/views.py                          | 117 ++++++++++++++++
 hobo/settings.py                              |   1 +
 hobo/templates/hobo/home.html                 |   1 +
 hobo/urls.py                                  |   3 +
 tests/test_matomo_views.py                    | 127 ++++++++++++++++++
 12 files changed, 489 insertions(+)
 create mode 100644 hobo/matomo/__init__.py
 create mode 100644 hobo/matomo/forms.py
 create mode 100644 hobo/matomo/templates/hobo/matomo_disable.html
 create mode 100644 hobo/matomo/templates/hobo/matomo_enable_auto.html
 create mode 100644 hobo/matomo/templates/hobo/matomo_enable_manual.html
 create mode 100644 hobo/matomo/templates/hobo/matomo_home.html
 create mode 100644 hobo/matomo/urls.py
 create mode 100644 hobo/matomo/views.py
 create mode 100644 tests/test_matomo_views.py
hobo/matomo/forms.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015-2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django import forms
18
from django.utils.translation import ugettext_lazy as _
19

  
20

  
21
class SettingsForm(forms.Form):
22
    """
23
    According to publik-base-theme/templates/includes/tracking.html,
24
    the 2 tracking_js variables are merged into the unique below field.
25
    If JS code added is compliant to CNIL policy, we store it into
26
    'cnil_compliant_visits_tracking_js' else into 'visits_tracking_js'.
27
    The goal is to display a banner advertising users about intrusive JS.
28
    """
29
    tracking_js = forms.CharField(
30
        label=_('Tracking Javascript'),
31
        help_text=_('See <a href="https://developer.matomo.org/guides/tracking-javascript-guide">Matomo partners site</a> for getting your Javascript\'s tracking code.'),
32
        required=False,
33
        widget=forms.Textarea(attrs={'size': 1024}))
34

  
35

  
36
class EnableForm(forms.Form):
37
    pass
hobo/matomo/templates/hobo/matomo_disable.html
1
{% extends "hobo/matomo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans "User tracking" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
{% csrf_token %}
11
<p>
12
{% trans "Are you sure you want to disable user tracking support?" %}
13
{{ form.as_p }}
14
<div class="buttons">
15
  <button class="submit-button">{% trans "Disable" %}</button>
16
  <a class="cancel" href="{% url 'matomo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

  
20
{% endblock %}
hobo/matomo/templates/hobo/matomo_enable_auto.html
1
{% extends "hobo/matomo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans "User tracking" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
{% csrf_token %}
11
<p>
12
{% trans "Are you sure you want to enable automatic user tracking support?" %}
13
{{ form.as_p }}
14
<div class="buttons">
15
<button class="submit-button">{% trans "Enable" %}</button>
16
<a class="cancel" href="{% url 'matomo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

  
20
{% endblock %}
hobo/matomo/templates/hobo/matomo_enable_manual.html
1
{% extends "hobo/matomo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans "User tracking" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
{% csrf_token %}
11
<p>
12
{% trans "Are you sure you want to enable manual user tracking support?" %}
13
{{ form.as_p }}
14
<div class="buttons">
15
<button class="submit-button">{% trans "Enable" %}</button>
16
<a class="cancel" href="{% url 'matomo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

  
20
{% endblock %}
hobo/matomo/templates/hobo/matomo_home.html
1
{% extends "hobo/base.html" %}
2
{% load i18n %}
3

  
4
{% block breadcrumb %}
5
{{ block.super }}
6
<a href="{% url 'matomo-home' %}">Matomo</a>
7
{% endblock %}
8

  
9
{% block appbar %}
10
  <h2>{% trans "User tracking" %}</h2>
11
  {% if enabled %}
12
  <span class="actions">
13
    <a rel="popup" href="{% url 'matomo-disable' %}">
14
    {% trans 'Disable' %}
15
    </a>
16
    {% if mode == 'auto' %}
17
    <a href={{matomo_url}}/index.php?module=Login&action=logme&login={{matomo_user_login}}&password={{matomo_password}}>
18
      {% trans "Open visits tracking dashboard" %}
19
    </a>
20
    {% endif %}
21
  </span>
22
  {% endif %}
23
{% endblock %}
24

  
25
{% block content %}
26

  
27
<div class="infonotice">
28
  {% blocktrans %}
29
  <p>
30
  Matomo is the tracker solution recommended by the National Commission for Data Protection and Liberties (<a href=https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience>CNIL-France</a>).
31
  </p>
32
  <p>
33
    The audience measurement tools are deployed to obtain information about visitor navigation. They help to understand where users come from on a site and reconstruct their browsing activity. These tools use technologies that permit to trace users on your site and associate a \"referrer\" or campaign with a unique identifier.
34
  </p>
35
  The Matomo tool requires little setting to be exempt from legal consent:
36
  {% endblocktrans %}
37
  <ul>
38
    <li>{% trans 'No life time extension on cookies' %}
39
      {% if enabled and mode != 'auto' %}
40
      <a href=https://www.cnil.fr/sites/default/files/typo/document/Configuration_piwik.pdf>
41
	...
42
      </a>
43
      {% endif %}
44
    </li>
45
    <li>{% trans 'Anonymize the IP address' %}
46
      {% if enabled and mode == 'auto' %}
47
      <a href={{matomo_url}}/index.php?module=PrivacyManager&action=privacySettings>
48
	...
49
      </a>
50
      {% endif %}
51
    </li>
52

  
53
    <li>{% trans 'Offer a solution against the deposit of cookies' %}
54
      {% if enabled and mode == 'auto' %}
55
      <a href={{matomo_url}}/index.php?module=PrivacyManager&action=usersOptOut>
56
	...
57
      </a>
58
      {% endif %}
59
    </li>
60
  </ul>
61
</div>
62

  
63
{% if not enabled %}
64
<p>
65
  {% if error != '' %}
66
  <div class="errornotice">
67
    {{ error }}
68
  </div>
69
  {% endif %}
70
 {% trans "Support is currently disabled." %}
71
</p>
72
<p>
73
  <a class="button" rel="popup" href="{% url 'matomo-enable-manual' %}">{% trans 'Manual' %}</a>
74
  <a class="button" rel="popup" href="{% url 'matomo-enable-auto' %}">{% trans 'Automatic' %}</a>
75
</p>
76
{% else %}
77
  {% if tracking_js != '' %}
78
    {% if cnil_ack_level == 'success' %}
79
<div class="successnotice">
80
  {% trans "Excellent respect of user rights" %}
81
</div>
82
    {% elif cnil_ack_level == 'warning' %}
83
<div class="warningnotice">
84
  {% trans "Good respect of user rights" %}
85
</div>
86
    {% elif cnil_ack_level == 'error' %}
87
<div class="errornotice">
88
  {% trans "No respect of user rights" %}
89
</div>
90
    {% endif %}
91
  {% endif %}
92

  
93
  {% if mode == 'manual' %}
94
<h3>
95
    {% trans "Manual mode" %}
96
</h3>
97

  
98
<form method="post">
99
  {% csrf_token %}
100
  {{ form.as_p }}
101

  
102
<div class="buttons">
103
  <button class="submit-button">{% trans "Save" %}</button>
104
</div>
105
</form>
106
  {% endif %}
107

  
108
  {% if mode == 'auto' %}
109
<p>
110
  <h3>
111
    {% trans "Automatic mode" %}
112
  </h3>
113
</p>
114
  {% endif %}
115

  
116
{% endif %}
117
{% endblock %}
hobo/matomo/urls.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015-2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.conf.urls import url
18

  
19
from . import views
20

  
21
urlpatterns = [
22
    url(r'^$', views.home, name='matomo-home'),
23
    url(r'^enable-manual$', views.enable_manual, name='matomo-enable-manual'),
24
    url(r'^enable-auto$', views.enable_auto, name='matomo-enable-auto'),
25
    url(r'^disable$', views.disable, name='matomo-disable'),
26
]
hobo/matomo/views.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015-2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import hashlib
18

  
19
from django.core.urlresolvers import reverse_lazy
20
from django.conf import settings
21
from django.views.generic import RedirectView, FormView
22

  
23
from .forms import SettingsForm, EnableForm
24
from .utils import get_variable, get_tracking_js, put_tracking_js, \
25
    get_tenant_name_and_public_urls, compute_cnil_acknowledgment_level, \
26
    auto_configure_matomo
27

  
28

  
29
class HomeView(FormView):
30
    template_name = 'hobo/matomo_home.html'
31
    form_class = SettingsForm
32
    success_url = reverse_lazy('matomo-home')
33

  
34
    def get_initial(self):
35
        initial = super(HomeView, self).get_initial()
36
        initial['tracking_js'] = get_tracking_js()
37
        return initial
38

  
39
    def form_valid(self, form):
40
        if get_variable('matomo_mode').json == 'manual':
41
            tracking_js = form.cleaned_data['tracking_js']
42
            put_tracking_js(tracking_js)
43
        return super(HomeView, self).form_valid(form)
44

  
45
    def get_context_data(self, **kwargs):
46
        context = super(HomeView, self).get_context_data(**kwargs)
47
        context['enabled'] = bool(get_variable('matomo_enable').json)
48
        context['mode'] = get_variable('matomo_mode').json
49
        context['error'] = get_variable('matomo_error').value
50
        tracking_js = get_tracking_js()
51
        context['cnil_ack_level'] = compute_cnil_acknowledgment_level(tracking_js)
52
        context['tracking_js'] = tracking_js
53

  
54
        if context['mode'] == 'auto':
55
            config = getattr(settings, 'MATOMO_SERVER', {})
56
            tenant_name, unused = get_tenant_name_and_public_urls()
57
            password = get_variable('matomo_password').value
58
            context['matomo_url'] = config['URL']
59
            context['matomo_user_login'] = tenant_name
60
            context['matomo_password'] = hashlib.md5(password).hexdigest()
61
            context['id_site'] = get_variable('matomo_id_site').value
62
        return context
63

  
64
home = HomeView.as_view()
65

  
66

  
67
class EnableManualView(FormView):
68
    form_class = EnableForm
69
    template_name = 'hobo/matomo_enable_manual.html'
70
    success_url = reverse_lazy('matomo-home')
71

  
72
    def form_valid(self, form):
73
        variable = get_variable('matomo_enable')
74
        variable.value = 'true'
75
        variable.save()
76
        variable = get_variable('matomo_mode')
77
        variable.value = 'manual'
78
        variable.save()
79
        return super(EnableManualView, self).form_valid(form)
80

  
81
enable_manual = EnableManualView.as_view()
82

  
83

  
84
class EnableAutoView(FormView):
85
    form_class = EnableForm
86
    template_name = 'hobo/matomo_enable_auto.html'
87
    success_url = reverse_lazy('matomo-home')
88

  
89
    def form_valid(self, form):
90
        variable = get_variable('matomo_mode')
91
        variable.value = 'auto'
92
        variable.save()
93
        if auto_configure_matomo():
94
            variable = get_variable('matomo_enable')
95
            variable.value = 'true'
96
            variable.save()
97
        return super(EnableAutoView, self).form_valid(form)
98

  
99
enable_auto = EnableAutoView.as_view()
100

  
101

  
102
class DisableView(FormView):
103
    form_class = EnableForm
104
    template_name = 'hobo/matomo_disable.html'
105
    success_url = reverse_lazy('matomo-home')
106

  
107
    def form_valid(self, form):
108
        put_tracking_js('')
109
        variable = get_variable('matomo_enable')
110
        variable.value = 'false'
111
        variable.save()
112
        variable = get_variable('matomo_mode')
113
        variable.value = ''
114
        variable.save()
115
        return super(DisableView, self).form_valid(form)
116

  
117
disable = DisableView.as_view()
hobo/settings.py
41 41
    'gadjo',
42 42
    'hobo.environment',
43 43
    'hobo.franceconnect',
44
    'hobo.matomo',
44 45
    'hobo.profile',
45 46
    'hobo.theme',
46 47
    'hobo.emails',
hobo/templates/hobo/home.html
10 10
    <li><a href="{% url 'theme-home' %}">{% trans 'Theme' %}</a></li>
11 11
    <li><a href="{% url 'emails-home' %}">{% trans 'Emails' %}</a></li>
12 12
    <li><a href="{% url 'franceconnect-home' %}">FranceConnect</a></li>
13
    <li><a href="{% url 'matomo-home' %}">Matomo</a></li>
13 14
    <li><a href="{% url 'environment-home' %}">{% trans 'Services' %}</a></li>
14 15
    <li><a href="{% url 'environment-variables' %}">{% trans 'Variables' %}</a></li>
15 16
  </ul>
hobo/urls.py
8 8
from .urls_utils import decorated_includes
9 9
from .environment.urls import urlpatterns as environment_urls
10 10
from .franceconnect.urls import urlpatterns as franceconnect_urls
11
from .matomo.urls import urlpatterns as matomo_urls
11 12
from .profile.urls import urlpatterns as profile_urls
12 13
from .theme.urls import urlpatterns as theme_urls
13 14
from .emails.urls import urlpatterns as emails_urls
......
20 21
                                             include(profile_urls))),
21 22
    url(r'^franceconnect/',
22 23
        decorated_includes(admin_required, include(franceconnect_urls))),
24
    url(r'^matomo/',
25
        decorated_includes(admin_required, include(matomo_urls))),
23 26
    url(r'^theme/', decorated_includes(admin_required,
24 27
                                             include(theme_urls))),
25 28
    url(r'^emails/', decorated_includes(admin_required, include(emails_urls))),
tests/test_matomo_views.py
1
# -*- coding: utf-8 -*-
2

  
3
import mock
4
import pytest
5
from requests import Response
6
from webtest import TestApp
7

  
8
from django.conf import settings
9
from django.contrib.auth.models import User
10
from django.test import override_settings
11

  
12
from hobo.environment.models import Variable, Wcs, Combo, Fargo
13
from hobo.wsgi import application
14

  
15
pytestmark = pytest.mark.django_db
16

  
17
CONFIG = {'URL': 'https://matomo.test',
18
          'TOKEN_AUTH': '1234',
19
          'EMAIL_TEMPLATE': 'noreply+%s@entrouvert.test'}
20

  
21
GET_SITE_NOT_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
22
<result>
23
    <error message="An unexpected website was found in the request: website id was set to '42' ." />
24
</result>
25
"""
26

  
27
ADD_SITE_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
28
<result>42</result>
29
"""
30

  
31
ADD_USER_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
32
<result>
33
    <success message="ok" />
34
</result>
35
"""
36

  
37
JAVASCRIPT_TAG = """<?xml version="1.0" encoding="utf-8" ?>
38
<result>&lt;!-- Matomo --&gt;
39
&lt;script type=&quot;text/javascript&quot;&gt;
40
  var _paq = window._paq || [];
41
  /* tracker methods like &quot;setCustomDimension&quot; should be called before &quot;trackPageView&quot; */
42
  _paq.push(['trackPageView']);
43
  _paq.push(['enableLinkTracking']);
44
  (function() {
45
    var u=&quot;//matomo-test.entrouvert.org/&quot;;
46
    _paq.push(['setTrackerUrl', u+'piwik.php']);
47
    _paq.push(['setSiteId', '7']);
48
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
49
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
50
  })();
51
&lt;/script&gt;
52
&lt;!-- End Matomo Code --&gt;
53
</result>
54
"""
55

  
56
@pytest.fixture
57
def admin_user():
58
    try:
59
        user = User.objects.get(username='admin')
60
    except User.DoesNotExist:
61
        user = User.objects.create_superuser('admin', email=None, password='admin')
62
    return user
63

  
64
def login(app, username='admin', password='admin'):
65
    login_page = app.get('/login/')
66
    login_form = login_page.forms[0]
67
    login_form['username'] = username
68
    login_form['password'] = password
69
    resp = login_form.submit()
70
    assert resp.status_int == 302
71
    return app
72

  
73
def test_unlogged_access():
74
    # connect while not being logged in
75
    app = TestApp(application)
76
    resp = app.get('/matomo/', status=302)
77
    assert resp.location.endswith('/login/?next=/matomo/')
78

  
79
def test_access(admin_user):
80
    app = login(TestApp(application))
81
    assert app.get('/matomo/', status=200)
82

  
83
def test_disable(admin_user):
84
    app = login(TestApp(application))
85
    resp1 = app.get('/matomo/disable', status=200)
86
    resp2 = resp1.form.submit()
87
    assert resp2.location.endswith('/matomo/')
88

  
89
def test_enable_manual(admin_user):
90
    """scenario where user manually paste a javascript code"""
91
    app = login(TestApp(application))
92
    resp1 = app.get('/matomo/enable-manual', status=200)
93
    resp2 = resp1.form.submit().follow()
94
    resp2.form['tracking_js'] = '...js_code...'
95
    resp3 = resp2.form.submit().follow()
96
    assert resp3.body.find('Good respect of user rights') != -1
97

  
98
def auto_conf_mocked_post(url, **kwargs):
99
    contents = [GET_SITE_NOT_ALREADY_THERE,
100
                ADD_SITE_SUCCESS,
101
                ADD_USER_SUCCESS,
102
                JAVASCRIPT_TAG]
103
    response = Response()
104
    response._content = contents[auto_conf_mocked_post.cpt]
105
    response.status_code = 200
106

  
107
    auto_conf_mocked_post.cpt += 1
108
    return response
109

  
110
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
111
def test_enable_auto(mocked_post, admin_user):
112
    """automatic scenario"""
113
    Combo.objects.create(base_url='https://combo.dev.publik.love',
114
                         template_name='portal-user')
115
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
116
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
117

  
118
    auto_conf_mocked_post.cpt = 0
119
    with override_settings(MATOMO_SERVER=CONFIG):
120
        app = login(TestApp(application))
121
        resp1 = app.get('/matomo/enable-auto', status=200)
122
        resp2 = resp1.form.submit()
123

  
124
        # call utils.py::auto_configure_matomo()
125
        resp3 = resp2.follow()
126
        print resp3.body
127
        assert resp3.body.find('Excellent respect of user rights') != -1
0
-