Projet

Général

Profil

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

Nicolas Roche, 03 avril 2019 20:16

Télécharger (21,9 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   | 114 ++++++++++++++
 hobo/matomo/urls.py                           |  26 ++++
 hobo/matomo/views.py                          | 125 ++++++++++++++++
 hobo/settings.py                              |   1 +
 hobo/templates/hobo/home.html                 |   1 +
 hobo/urls.py                                  |   3 +
 tests/test_matomo_views.py                    | 141 ++++++++++++++++++
 12 files changed, 508 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
  {% if not enabled %}
29
  {% blocktrans %}
30
  <p>
31
    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>).
32
  </p>
33
  <p>
34
    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.
35
  </p>
36
  The Matomo tool requires little setting to be exempt from legal consent:
37
  {% endblocktrans %}
38
  <ul>
39
    <li>{% trans 'No life time extension on cookies' %}
40
      <a href=https://www.cnil.fr/sites/default/files/typo/document/Configuration_piwik.pdf>
41
	...
42
      </a>
43
    </li>
44
    <li>{% trans 'Anonymize the IP address' %}
45
      {% if matomo_ws_available %}
46
      <a href={{matomo_url}}/index.php?module=PrivacyManager&action=privacySettings>
47
	...
48
      </a>
49
      {% endif %}
50
    </li>
51

  
52
    <li>{% trans 'Offer a solution against the deposit of cookies' %}
53
      {% if matomo_ws_available %}
54
      <a href={{matomo_url}}/index.php?module=PrivacyManager&action=usersOptOut>
55
	...
56
      </a>
57
      {% endif %}
58
    </li>
59
  </ul>
60
  {% else %}
61
  {% if mode == 'manual' %}
62
  {% trans "Manual mode" %}
63
  {% elif mode == 'auto' %}
64
  {% trans "Automatic mode" %}
65
  {% endif %}
66
  {% endif %}
67
</div>
68

  
69
{% if not enabled %}
70
<p>
71
  {% if error != '' %}
72
  <div class="errornotice">
73
    {{ error }}
74
  </div>
75
  {% endif %}
76
  {% trans "Support is currently disabled." %}
77
</p>
78
<p>
79
  <a class="button" rel="popup" href="{% url 'matomo-enable-manual' %}">{% trans 'Manual' %}</a>
80
  {% if matomo_ws_available %}
81
  <a class="button" rel="popup" href="{% url 'matomo-enable-auto' %}">{% trans 'Automatic' %}</a>
82
  {% endif %}
83
</p>
84
{% else %}
85

  
86
{% if tracking_js != '' %}
87
{% if cnil_ack_level == 'success' %}
88
<div class="successnotice">
89
  {% trans "Excellent respect of user rights" %}
90
</div>
91
{% elif cnil_ack_level == 'warning' %}
92
<div class="warningnotice">
93
  {% trans "Good respect of user rights" %}
94
</div>
95
{% elif cnil_ack_level == 'error' %}
96
<div class="errornotice">
97
  {% trans "No respect of user rights" %}
98
</div>
99
{% endif %}
100
{% endif %}
101

  
102
{% if mode == 'manual' %}
103
<form method="post">
104
  {% csrf_token %}
105
  {{ form.as_p }}
106

  
107
  <div class="buttons">
108
    <button class="submit-button">{% trans "Save" %}</button>
109
  </div>
110
</form>
111
{% endif %}
112

  
113
{% endif %}
114
{% 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
    MatomoError, MatomoWS, \
26
    get_tenant_name_and_public_urls, compute_cnil_acknowledgment_level, \
27
    auto_configure_matomo
28

  
29

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

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

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

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

  
55
        try:
56
            matomo = MatomoWS()
57
        except MatomoError:
58
            context['matomo_ws_available'] = False
59
        else:
60
            context['matomo_ws_available'] = True
61
            context['matomo_url'] = matomo.url_ws_base
62

  
63
        # automatic mode is using variables on DB
64
        if context['mode'] == 'auto':
65
            tenant_name, unused = get_tenant_name_and_public_urls()
66
            password = get_variable('matomo_password').value
67
            context['matomo_user_login'] = tenant_name
68
            context['matomo_password'] = hashlib.md5(password).hexdigest()
69
            context['id_site'] = get_variable('matomo_id_site').value
70
        return context
71

  
72
home = HomeView.as_view()
73

  
74

  
75
class EnableManualView(FormView):
76
    form_class = EnableForm
77
    template_name = 'hobo/matomo_enable_manual.html'
78
    success_url = reverse_lazy('matomo-home')
79

  
80
    def form_valid(self, form):
81
        variable = get_variable('matomo_enable')
82
        variable.value = 'true'
83
        variable.save()
84
        variable = get_variable('matomo_mode')
85
        variable.value = 'manual'
86
        variable.save()
87
        return super(EnableManualView, self).form_valid(form)
88

  
89
enable_manual = EnableManualView.as_view()
90

  
91

  
92
class EnableAutoView(FormView):
93
    form_class = EnableForm
94
    template_name = 'hobo/matomo_enable_auto.html'
95
    success_url = reverse_lazy('matomo-home')
96

  
97
    def form_valid(self, form):
98
        variable = get_variable('matomo_mode')
99
        variable.value = 'auto'
100
        variable.save()
101
        if auto_configure_matomo():
102
            variable = get_variable('matomo_enable')
103
            variable.value = 'true'
104
            variable.save()
105
        return super(EnableAutoView, self).form_valid(form)
106

  
107
enable_auto = EnableAutoView.as_view()
108

  
109

  
110
class DisableView(FormView):
111
    form_class = EnableForm
112
    template_name = 'hobo/matomo_disable.html'
113
    success_url = reverse_lazy('matomo-home')
114

  
115
    def form_valid(self, form):
116
        put_tracking_js('')
117
        for key, val in [('matomo_enable', 'false'),
118
                         ('matomo_mode', ''),
119
                         ('matomo_error', '')]:
120
            variable = get_variable(key)
121
            variable.value = val
122
            variable.save()
123
        return super(DisableView, self).form_valid(form)
124

  
125
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' %}">{% trans 'User tracking' %}</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
def test_available_options(admin_user):
111
    """check available buttons (manual/automatic configurations)"""
112
    with override_settings(MATOMO_SERVER=CONFIG):
113
        app = login(TestApp(application))
114
        resp = app.get('/matomo/', status=200)
115
        assert str(resp).find('href="/matomo/enable-manual"') != -1
116
        assert str(resp).find('href="/matomo/enable-auto"') != -1
117

  
118
    # without configuration: no automatic configuration available
119
    app = login(TestApp(application))
120
    resp = app.get('/matomo/', status=200)
121
    assert str(resp).find('href="/matomo/enable-manual"') != -1
122
    assert str(resp).find('href="/matomo/enable-auto"') == -1
123

  
124
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
125
def test_enable_auto(mocked_post, admin_user):
126
    """automatic scenario"""
127
    Combo.objects.create(base_url='https://combo.dev.publik.love',
128
                         template_name='portal-user')
129
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
130
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
131

  
132
    auto_conf_mocked_post.cpt = 0
133
    with override_settings(MATOMO_SERVER=CONFIG):
134
        app = login(TestApp(application))
135
        resp1 = app.get('/matomo/enable-auto', status=200)
136
        resp2 = resp1.form.submit()
137

  
138
        # call utils.py::auto_configure_matomo()
139
        resp3 = resp2.follow()
140
        print resp3.body
141
        assert resp3.body.find('Excellent respect of user rights') != -1
0
-