Projet

Général

Profil

0002-matomo-manage-matomo-s-webservices-31778.patch

Nicolas Roche, 29 mars 2019 19:40

Télécharger (33,5 ko)

Voir les différences:

Subject: [PATCH 2/4] matomo: manage matomo's webservices (#31778)

 hobo/matomo/utils.py       | 279 ++++++++++++++++
 hobo/settings.py           |  17 +
 requirements.txt           |   1 +
 tests/test_matomo_utils.py | 630 +++++++++++++++++++++++++++++++++++++
 tox.ini                    |   1 +
 5 files changed, 928 insertions(+)
 create mode 100644 hobo/matomo/utils.py
 create mode 100644 tests/test_matomo_utils.py
hobo/matomo/utils.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015  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 re
18
import requests
19
import string
20

  
21
from lxml import etree
22
from random import *
23

  
24
from django.db import connection
25
from django.conf import settings
26
from django.core import exceptions
27
from django.utils.six.moves.urllib import parse as urlparse
28

  
29
from hobo.environment.models import Variable, Wcs, Combo, Fargo
30

  
31

  
32
def get_variable(name, default=''):
33
    """get hobo variables from DB
34
    set it to '' into DB if not already created
35
    """
36
    variable, created = Variable.objects.get_or_create(
37
        name=name,
38
        defaults={'auto': True, 'value': default})
39
    return variable
40

  
41
def get_variable_value(name, default=''):
42
    """get hobo variables's value from DB"""
43
    try:
44
        value = Variable.objects.get(name=name).value
45
    except exceptions.ObjectDoesNotExist:
46
        value = default
47
    return value
48

  
49
def get_tracking_js():
50
    """merge JS code from the 2 above variables"""
51
    tracking_js = get_variable_value('cnil_compliant_visits_tracking_js')
52
    tracking_js += get_variable_value('visits_tracking_js')
53
    return tracking_js
54

  
55
def put_tracking_js(tracking_js):
56
    """store JS code into only one of the 2 above variables"""
57
    variable1 = get_variable('cnil_compliant_visits_tracking_js')
58
    variable2 = get_variable('visits_tracking_js')
59
    if compute_cnil_acknowledgment_level(tracking_js) != 'error':
60
        variable1.value = tracking_js
61
        variable1.save()
62
        variable2.delete()
63
    else:
64
        variable1.delete()
65
        variable2.value = tracking_js
66
        variable2.save()
67

  
68
def get_tenant_name_and_public_urls():
69
    """get an alias for our matomo's id and urls to monitor"""
70
    tenant_name = None
71
    services = [x for x in Combo.objects.all() if x.template_name == 'portal-user']
72
    if services != [] and services[0] != '':
73
        tenant_name = urlparse.urlparse(services[0].base_url).netloc
74
    services += [x for x in Wcs.objects.all()]
75
    services += [x for x in Fargo.objects.all()]
76
    site_urls = [x.base_url for x in services if x.base_url != '']
77
    return tenant_name, site_urls
78

  
79
class MatomoException(Exception):
80
    """unexpected Matomo internal error"""
81

  
82
class MatomoError(MatomoException):
83
    """expected Matomo error responses"""
84

  
85
class MatomoWS(object):
86
    """api for matomo webservices"""
87

  
88
    def __init__(self):
89
        config = getattr(settings, 'MATOMO_SERVER', {})
90
        try:
91
            self.url_ws_base = config['URL']
92
            self.token_auth = config['TOKEN_AUTH']
93
            self.email_template = config['EMAIL_TEMPLATE']
94
        except KeyError as exc:
95
            raise MatomoError('no settings for matomo: %s' % str(exc))
96

  
97
    @staticmethod
98
    def parse_response(content):
99
        try:
100
            tree = etree.fromstring(content)
101
        except etree.XMLSyntaxError as exc:
102
            raise MatomoException('etree.XMLSyntaxError: %s' % str(exc))
103
        return tree
104

  
105
    @staticmethod
106
    def raise_on_error(tree):
107
        """handle matomo XML error messages"""
108
        tags = tree.xpath('/result/error')
109
        if tags != []:
110
            try:
111
                attr = tags[0].items()[0]
112
                if attr[0] == 'message':
113
                    raise MatomoError(attr[1])
114
            except IndexError:
115
                pass
116
            raise MatomoException('internal error')
117

  
118
    def call(self, data):
119
        data['module'] = 'API'
120
        data['token_auth'] = self.token_auth
121
        resp = requests.post(self.url_ws_base, data=data)
122
        tree = self.parse_response(resp.content)
123
        self.raise_on_error(tree)
124
        return tree
125

  
126
    def get_site_from_id(self, id_site):
127
        data = {'method': 'SitesManager.getSiteFromId',
128
                'idSite': id_site}
129
        tree = self.call(data)
130
        try:
131
            tag = tree.xpath('/result/row/idsite')[0]
132
        except IndexError:
133
            raise MatomoException('get_site_from_id fails')
134
        return tag.text
135

  
136
    def add_site(self, site_name, site_urls):
137
        data = {'method': 'SitesManager.addSite',
138
                'siteName': site_name}
139
        cpt = 0
140
        for url in site_urls:
141
            key = 'urls[%i]' % cpt
142
            data[key] = url
143
            cpt += 1
144
        tree = self.call(data)
145
        try:
146
            tag = tree.xpath('/result')[0]
147
        except IndexError:
148
            raise MatomoException('add_site fails')
149
        return tag.text
150

  
151
    def add_user(self, user_login, password, initial_id_site):
152
        data = {'method': 'UsersManager.addUser',
153
                'userLogin': user_login,
154
                'password': password,
155
                'email': self.email_template % user_login,
156
                'initialIdSite': initial_id_site}
157
        tree = self.call(data)
158
        success = True
159
        tags = tree.xpath('/result/success')
160
        if tags != []:
161
            try:
162
                attr = tags[0].items()[0]
163
                if attr[0] != 'message' or attr[1] != 'ok':
164
                    success = False
165
            except IndexError:
166
                success = False
167
        if not success:
168
            raise MatomoException('add_user fails')
169

  
170
    def get_javascript_tag(self, id_site):
171
        data = {'method': 'SitesManager.getJavascriptTag',
172
                'idSite': id_site}
173
        tree = self.call(data)
174
        try:
175
            tag = tree.xpath('/result')[0]
176
        except IndexError:
177
            raise MatomoException('get_javascript_tag fails')
178
        return tag.text
179

  
180
def is_site_already_added(matomo, id_site):
181
    # API does not allow to retrieve our site without using the matomo's id.
182
    try:
183
        id_site_resp = matomo.get_site_from_id(id_site)
184
    except MatomoError:
185
        return False
186
    return id_site_resp == id_site
187

  
188
def add_user_if_not_already_there(matomo, user_login, password, id_site):
189
    try:
190
        matomo.add_user(user_login, password, id_site)
191
    except MatomoError as exc:
192
        # not an error if user is already here
193
        if str(exc) != "Username '%s' already exists." % user_login:
194
            raise exc
195

  
196
def get_matomo_javascript_tag(matomo, id_site):
197
    """addapt JS return by Matomo and merge it whith previous JS code we have"""
198
    CNIL_JS = """
199
  /* disallow cookie's time extension */
200
  _paq.push([function() {
201
    var self = this;
202
    function getOriginalVisitorCookieTimeout() {
203
      var now = new Date(),
204
      nowTs = Math.round(now.getTime() / 1000),
205
      visitorInfo = self.getVisitorInfo();
206
      var createTs = parseInt(visitorInfo[2]);
207
      var cookieTimeout = 33696000; // 13 mois en secondes
208
      var originalTimeout = createTs + cookieTimeout - nowTs;
209
      return originalTimeout;
210
    }
211
    this.setVisitorCookieTimeout( getOriginalVisitorCookieTimeout() );
212
  }]);
213
"""
214
    matomo_tag = matomo.get_javascript_tag(id_site)
215
    enhanced_tag = matomo_tag.split('\n')
216

  
217
    # acording to publik-base-theme/templates/includes/tracking.html,
218
    # we need to remove <script> tags from matomo's output javascript.
219
    del enhanced_tag[1]
220
    del enhanced_tag[-3]
221

  
222
    # disallow cookie's time extension
223
    enhanced_tag.insert(2, CNIL_JS)
224

  
225
    enhanced_tag = '\n'.join(enhanced_tag)
226
    return enhanced_tag  + '\n'
227

  
228
def compute_cnil_acknowledgment_level(tracking_js):
229
    if tracking_js.find('google') != -1:
230
        # google reference found into javascript
231
        return 'error'
232
    if tracking_js.find('getOriginalVisitorCookieTimeout') == -1:
233
        # can't find cookie's life time extension prevention
234
        return 'warning'
235
    return 'success'
236

  
237
def auto_configure_matomo():
238
    """main function"""
239

  
240
    # retrieve matomo's variables
241
    id_site_var = get_variable('matomo_id_site')
242
    password_var = get_variable('matomo_password')
243
    error_message_var = get_variable('matomo_error')
244

  
245
    id_site = id_site_var.value
246
    password = password_var.value
247
    if password_var.value == '':
248
        characters = string.ascii_letters + string.punctuation  + string.digits
249
        password = "".join(choice(characters) for x in range(randint(8, 16)))
250

  
251
    try:
252
        tenant_name, site_urls = get_tenant_name_and_public_urls()
253
        if tenant_name is None:
254
            raise MatomoException("no portal-user url available for a matomo's id")
255

  
256
        matomo = MatomoWS()
257

  
258
        # create an account
259
        if not is_site_already_added(matomo, id_site):
260
            id_site = matomo.add_site(tenant_name, site_urls)
261
        add_user_if_not_already_there(matomo, tenant_name, password, id_site)
262

  
263
        # overwrite tracking_js (previous content is lost)
264
        tracking_js = get_matomo_javascript_tag(matomo, id_site)
265

  
266
    except MatomoException as exc:
267
        error_message_var.value = str(exc)
268
        error_message_var.save()
269
        return False
270

  
271
    # save matomo's variables
272
    id_site_var.value = id_site
273
    password_var.value = password
274
    error_message_var.value = ''
275
    id_site_var.save()
276
    password_var.save()
277
    error_message_var.save()
278
    put_tracking_js(tracking_js)
279
    return True
hobo/settings.py
202 202
        os.path.join(os.path.dirname(__file__), 'local_settings.py'))
203 203
if os.path.exists(local_settings_file):
204 204
    execfile(local_settings_file)
205

  
206
# MATOMO_SERVER: allow automatic configuration on a matomo server.
207
# This variable excepts:
208
# - the URL of the matomo server to connect
209
# - an authentication token for a matomo admin user
210
# - an email template for new emails associated with new matomo users
211
# The token is available on the matomo GUI into the personal parameters
212
# of the user.
213
#
214
# Example:
215
# MATOMO_SERVER = {
216
#     'URL': 'https://matomo.domain.org',
217
#     'TOKEN_AUTH': '0123456789abcdef0123456789abcdef',
218
#     'EMAIL_TEMPLATE': 'noreply+%s@domain.org'
219
# }
220

  
221
MATOMO_SERVER = {}
requirements.txt
2 2
-e git+http://repos.entrouvert.org/gadjo.git/#egg=gadjo
3 3
celery<4
4 4
django-mellon
5
lxml
5 6
prometheus_client
tests/test_matomo_utils.py
1
# -*- coding: utf-8 -*-
2

  
3
import mock
4
import pytest
5
from requests import Response
6

  
7
from django.test import override_settings
8

  
9
from hobo.environment.models import Variable, Wcs, Combo, Fargo
10
from hobo.matomo.utils import MatomoError, MatomoException, MatomoWS, \
11
       get_variable, get_variable_value, get_tracking_js, put_tracking_js, \
12
       get_tenant_name_and_public_urls, \
13
       is_site_already_added, add_user_if_not_already_there, \
14
       get_matomo_javascript_tag, compute_cnil_acknowledgment_level, \
15
       auto_configure_matomo
16

  
17
pytestmark = pytest.mark.django_db
18

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

  
23
GET_SITE_42_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
24
<result>
25
    <row>
26
        <idsite>42</idsite>
27
        <moretags>...</moretags>
28
    </row>
29
</result>
30
"""
31

  
32
GET_SITE_34_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
33
<result>
34
    <row>
35
        <idsite>34</idsite>
36
        <moretags>...</moretags>
37
    </row>
38
</result>
39
"""
40

  
41
GET_SITE_NO_ID_PROVIDED = """<?xml version="1.0" encoding="utf-8" ?>
42
<result>
43
        <error message="Please specify a value for 'idSite'." />
44
</result>
45
"""
46

  
47
GET_SITE_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
48
<result>
49
    <row>
50
        <not_idsite>there is no idsite tag</not_idsite>
51
        <moretags>...</moretags>
52
    </row>
53
</result>
54
"""
55

  
56
GET_SITE_NOT_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
57
<result>
58
    <error message="An unexpected website was found in the request: website id was set to '42' ." />
59
</result>
60
"""
61

  
62
ADD_SITE_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
63
<result>42</result>
64
"""
65

  
66
ADD_SITE_ERROR = """<?xml version="1.0" encoding="utf-8" ?>
67
<result>
68
        <error message="Please specify a value for 'siteName'." />
69
</result>
70
"""
71

  
72
ADD_SITE_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
73
<not_result>no result tag</not_result>
74
"""
75

  
76
ADD_USER_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
77
<result>
78
    <success message="ok" />
79
</result>
80
"""
81

  
82
USER_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
83
<result>
84
    <error message="Username 'hobo_dev_publik_love' already exists." />
85
</result>"""
86

  
87
MAIL_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
88
<result>
89
    <error message="User with email 'nestor@testor.org' already exists." />
90
</result>"""
91

  
92
BAD_CREDENTIAL = """<?xml version="1.0" encoding="utf-8" ?>
93
<result>
94
    <error message="You can\'t access this resource as it requires a \'superuser\' access." />
95
</result>"""
96

  
97
ADD_USER_BAD_RESPONSE_1 = """<?xml version="1.0" encoding="utf-8" ?>
98
<result>
99
    <success message="KO" />
100
</result>
101
"""
102

  
103
ADD_USER_BAD_RESPONSE_2 = """<?xml version="1.0" encoding="utf-8" ?>
104
<result>
105
    <success>no message attribute</success>
106
    <not_success>no success tag</not_success>
107
</result>
108
"""
109

  
110
JAVASCRIPT_TAG = """<?xml version="1.0" encoding="utf-8" ?>
111
<result>&lt;!-- Matomo --&gt;
112
&lt;script type=&quot;text/javascript&quot;&gt;
113
  var _paq = window._paq || [];
114
  /* tracker methods like &quot;setCustomDimension&quot; should be called before &quot;trackPageView&quot; */
115
  _paq.push(['trackPageView']);
116
  _paq.push(['enableLinkTracking']);
117
  (function() {
118
    var u=&quot;//matomo-test.entrouvert.org/&quot;;
119
    _paq.push(['setTrackerUrl', u+'piwik.php']);
120
    _paq.push(['setSiteId', '7']);
121
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
122
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
123
  })();
124
&lt;/script&gt;
125
&lt;!-- End Matomo Code --&gt;
126
</result>
127
"""
128

  
129
BAS_CREDENTIALS = """<?xml version="1.0" encoding="utf-8" ?>
130
<result>
131
        <error message="You can't access this resource as it requires 'view' access for the website id = 42." />
132
</result>
133
"""
134

  
135
JAVASCRIPT_TAG_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
136
<no_result_tag/>
137
"""
138

  
139
def test_get_variable():
140
    """hobo variables from"""
141

  
142
    # create the variable with '' value if not there
143
    id_site_var = get_variable('name1')
144
    assert id_site_var.value == ''
145

  
146
    # retrieve the variable if already there
147
    Variable.objects.create(name='name2', value='42')
148
    id_site_var = get_variable('name2')
149
    assert id_site_var.value == '42'
150

  
151
def test_get_variable_value():
152
    """hobo variables from DB"""
153

  
154
    # variable not there: return default value
155
    assert get_variable_value('name1') == ''
156
    assert get_variable_value('name2', default='42') == '42'
157

  
158
    # variable already there
159
    get_variable('name3', '42')
160
    assert get_variable_value('name3') == '42'
161

  
162
def test_get_tenant_name_and_public_urls():
163
    Combo.objects.create(base_url='https://combo.dev.publik.love',
164
                         template_name='portal-user')
165
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
166
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
167
    tenant_name, site_urls = get_tenant_name_and_public_urls()
168
    assert tenant_name == 'combo.dev.publik.love'
169
    assert site_urls[2] == 'https://fargo.dev.publik.love/'
170

  
171
def test_matomo_constructor():
172
    """build the matomo webservice object"""
173
    with override_settings(MATOMO_SERVER=CONFIG):
174
        matomo = MatomoWS()
175
        assert matomo.url_ws_base == 'https://matomo.test'
176
        assert matomo.token_auth == '1234'
177

  
178
    with override_settings(MATOMO_SERVER={}):
179
        try:
180
            matomo = MatomoWS()
181
        except MatomoError as exc:
182
            assert str(exc) == "no settings for matomo: 'URL'"
183
        else:
184
            assert False
185

  
186
def test_parse_response():
187
    """parser used by all matomo webservice calls"""
188
    with override_settings(MATOMO_SERVER=CONFIG):
189
        matomo = MatomoWS()
190

  
191
        # no error (expected format)
192
        content = """<?xml version="1.0" encoding="utf-8" ?><ok/>"""
193
        tree = matomo.parse_response(content)
194
        assert tree.tag == 'ok'
195

  
196
        # error (not XML format)
197
        content = """this is not XML"""
198
        try:
199
            tree = matomo.parse_response(content)
200
        except MatomoException as exc:
201
            assert str(exc).find("XMLSyntaxError: Start tag expected") != -1
202
        else:
203
            assert False
204

  
205
def test_get_error_message():
206
    """error handler used by all matomo webservice calls"""
207
    with override_settings(MATOMO_SERVER=CONFIG):
208
        matomo = MatomoWS()
209

  
210
        # no error (expected format)
211
        content = """<?xml version="1.0" encoding="utf-8" ?><ok/>"""
212
        tree = matomo.parse_response(content)
213
        matomo.raise_on_error(tree)
214
        assert tree.tag == 'ok'
215

  
216
        # error (expected format)
217
        content = """<?xml version="1.0" encoding="utf-8" ?>
218
<result>
219
    <error message="here is the error message" />
220
</result>
221
"""
222
        tree = matomo.parse_response(content)
223
        try:
224
            matomo.raise_on_error(tree)
225
        except MatomoError as exc:
226
            assert str(exc) == 'here is the error message'
227
        else:
228
            assert False
229

  
230
        # error (unexpected format)
231
        content = """<?xml version="1.0" encoding="utf-8" ?>
232
<result>
233
    <error>no 'message' attribute here</error>
234
</result>
235
"""
236
        tree = matomo.parse_response(content)
237
        try:
238
            matomo.raise_on_error(tree)
239
        except MatomoException as exc:
240
            assert str(exc) == 'internal error'
241
        else:
242
            assert False
243

  
244
@mock.patch('requests.post')
245
def test_get_site_from_id(mocked_post):
246
    """webservice to test if the site is already registered"""
247
    with override_settings(MATOMO_SERVER=CONFIG):
248
        matomo = MatomoWS()
249

  
250
        # site already here
251
        content = GET_SITE_42_ALREADY_THERE
252
        mocked_post.return_value.content = content
253
        assert matomo.get_site_from_id('42') == '42'
254

  
255
        # site not already here
256
        content = GET_SITE_NOT_ALREADY_THERE
257
        mocked_post.return_value.content = content
258
        try:
259
            matomo.get_site_from_id(42)
260
        except MatomoError as exc:
261
            assert str(exc).find('An unexpected website was found in ') != -1
262
        else:
263
            assert False
264

  
265
        # error on empty id
266
        content = GET_SITE_NO_ID_PROVIDED
267
        mocked_post.return_value.content = content
268
        try:
269
            matomo.get_site_from_id(42)
270
        except MatomoError as exc:
271
            assert str(exc) == "Please specify a value for 'idSite'."
272
        else:
273
            assert False
274

  
275
        # bad response (error on success response)
276
        content = GET_SITE_BAD_RESPONSE
277
        mocked_post.return_value.content = content
278
        try:
279
            matomo.get_site_from_id(42)
280
        except MatomoException as exc:
281
            assert str(exc) == 'get_site_from_id fails'
282
        else:
283
            assert False
284

  
285
@mock.patch('requests.post')
286
def test_is_site_already_added(mocked_post):
287
    """function to test if the site is already regisered"""
288
    with override_settings(MATOMO_SERVER=CONFIG):
289
        matomo = MatomoWS()
290

  
291
        # site already here
292
        content = GET_SITE_42_ALREADY_THERE
293
        mocked_post.return_value.content = content
294
        assert is_site_already_added(matomo, '42') is True
295

  
296
        # site not already here
297
        content = GET_SITE_NOT_ALREADY_THERE
298
        mocked_post.return_value.content = content
299
        assert is_site_already_added(matomo, '42') is False
300

  
301
        # empty id provided
302
        content = GET_SITE_NO_ID_PROVIDED
303
        mocked_post.return_value.content = content
304
        assert is_site_already_added(matomo, '42') is False
305

  
306
        # strange case
307
        content = GET_SITE_34_ALREADY_THERE
308
        mocked_post.return_value.content = content
309
        assert is_site_already_added(matomo, '42') is False
310

  
311
        # error
312
        content = GET_SITE_BAD_RESPONSE
313
        mocked_post.return_value.content = content
314
        try:
315
            is_site_already_added(matomo, '42')
316
        except MatomoException as exc:
317
            assert str(exc) == 'get_site_from_id fails'
318
        else:
319
            assert False
320

  
321
@mock.patch('requests.post')
322
def test_add_site(mocked_post):
323
    """webservice to add a new site"""
324
    urls = ['https://combo.dev.publik.love',
325
            'https://wcs.dev.publik.love']
326
    with override_settings(MATOMO_SERVER=CONFIG):
327
        matomo = MatomoWS()
328

  
329
        # success
330
        content = ADD_SITE_SUCCESS
331
        mocked_post.return_value.content = content
332
        site_id = matomo.add_site("hobo_dev_publik_love", urls)
333
        assert site_id == '42'
334

  
335
        # error
336
        content = ADD_SITE_ERROR
337
        mocked_post.return_value.content = content
338
        try:
339
            site_id = matomo.add_site("hobo_dev_publik_love", urls)
340
        except MatomoError as exc:
341
            assert str(exc) == "Please specify a value for 'siteName'."
342
        else:
343
            assert False
344

  
345
        # strange message
346
        content = ADD_SITE_BAD_RESPONSE
347
        mocked_post.return_value.content = content
348
        try:
349
            site_id = matomo.add_site("hobo_dev_publik_love", urls)
350
        except MatomoException as exc:
351
            assert str(exc) == 'add_site fails'
352
        else:
353
            assert False
354

  
355
@mock.patch('requests.post')
356
def test_add_user(mocked_post):
357
    """webservice to add new user"""
358
    with override_settings(MATOMO_SERVER=CONFIG):
359
        matomo = MatomoWS()
360

  
361
        # success
362
        content = ADD_USER_SUCCESS
363
        mocked_post.return_value.content = content
364
        matomo.add_user('nestor', 'xxx', '42')
365
        assert True
366

  
367
        # error (user already here)
368
        content = USER_ALREADY_THERE
369
        mocked_post.return_value.content = content
370
        try:
371
            matomo.add_user('hobo_dev_publik_love', 'xxx', '42')
372
        except MatomoError as exc:
373
            assert str(exc).find("Username 'hobo_dev_publik_love' already") != -1
374
        else:
375
            assert False
376

  
377
        # error (mail already registered)
378
        content = MAIL_ALREADY_THERE
379
        mocked_post.return_value.content = content
380
        try:
381
            matomo.add_user('nestor', 'xxx', '42')
382
        except MatomoError as exc:
383
            assert str(exc).find("User with email 'nestor@testor.org'") != -1
384
        else:
385
            assert False
386

  
387
        # error (bad credentials)
388
        content = BAD_CREDENTIAL
389
        mocked_post.return_value.content = content
390
        try:
391
            matomo.add_user('nestor', 'xxx', '42')
392
        except MatomoError as exc:
393
            assert str(exc).find("You can\'t access this resource") != -1
394
        else:
395
            assert False
396

  
397
        # bad success message (wrong attribute value)
398
        content = ADD_USER_BAD_RESPONSE_1
399
        mocked_post.return_value.content = content
400
        try:
401
            matomo.add_user('nestor', 'xxx', '42')
402
        except MatomoException as exc:
403
            assert str(exc) == 'add_user fails'
404
        else:
405
            assert False
406

  
407
       # bad success message (no message attribute)
408
        content = ADD_USER_BAD_RESPONSE_2
409
        mocked_post.return_value.content = content
410
        try:
411
            matomo.add_user('nestor', 'xxx', '42')
412
        except MatomoException as exc:
413
            assert str(exc) == 'add_user fails'
414
        else:
415
            assert False
416

  
417
@mock.patch('requests.post')
418
def test_add_user_if_not_already_there(mocked_post):
419
    """function to assert we have a user"""
420
    with override_settings(MATOMO_SERVER=CONFIG):
421
        matomo = MatomoWS()
422

  
423
        # success (add a new user)
424
        content = ADD_USER_SUCCESS
425
        mocked_post.return_value.content = content
426
        add_user_if_not_already_there(matomo, 'hobo_dev_publik_love', 'xxx', '42')
427
        assert True
428

  
429
        # success (user already here)
430
        content = USER_ALREADY_THERE
431
        mocked_post.return_value.content = content
432
        add_user_if_not_already_there(matomo, 'hobo_dev_publik_love', 'xxx', '42')
433
        assert True
434

  
435
        # error (bad credentials)
436
        content = BAD_CREDENTIAL
437
        mocked_post.return_value.content = content
438
        try:
439
            add_user_if_not_already_there(matomo, 'tenant_name', 'xxx', '42')
440
        except MatomoError:
441
            assert True
442
        else:
443
            assert False
444

  
445
@mock.patch('requests.post')
446
def test_get_javascript_tag(mocked_post):
447
    """webservice to get matomo JS tag"""
448
    with override_settings(MATOMO_SERVER=CONFIG):
449
        matomo = MatomoWS()
450

  
451
        # success
452
        content = JAVASCRIPT_TAG
453
        mocked_post.return_value.content = content
454
        javascript_tag = matomo.get_javascript_tag('42')
455
        assert javascript_tag.find('(function() {') != -1
456

  
457
        # error (bad credentials)
458
        content = BAD_CREDENTIAL
459
        mocked_post.return_value.content = content
460
        try:
461
            javascript_tag = matomo.get_javascript_tag('42')
462
        except MatomoError as exc:
463
            assert str(exc).find("You can't access this resource ") != -1
464
        else:
465
            assert False
466

  
467
       # bad response (no result tag)
468
        content = JAVASCRIPT_TAG_BAD_RESPONSE
469
        mocked_post.return_value.content = content
470
        try:
471
            javascript_tag = matomo.get_javascript_tag('42')
472
        except MatomoException as exc:
473
            assert str(exc) == 'get_javascript_tag fails'
474
        else:
475
            assert False
476

  
477
def test_compute_cnil_acknowledgment_level():
478
    """function use to inspect javascript content"""
479
    warning_content = JAVASCRIPT_TAG
480

  
481
    # can't find cookie's life time extension prevention
482
    assert compute_cnil_acknowledgment_level(warning_content) == 'warning'
483

  
484
    # ok
485
    success_content = warning_content + '\n...getOriginalVisitorCookieTimeout...'
486
    assert compute_cnil_acknowledgment_level(success_content) == 'success'
487

  
488
    # google reference found into javascript
489
    error_content = success_content + '\n...google...'
490
    assert compute_cnil_acknowledgment_level(error_content) == 'error'
491

  
492
def test_get_tracking_js():
493
    """read previous tracking JS from hobo variables"""
494
    var1 = get_variable('cnil_compliant_visits_tracking_js', 'content1')
495
    assert get_tracking_js() == 'content1'
496

  
497
    var1.delete()
498
    var1 = get_variable('cnil_compliant_visits_tracking_js', '')
499
    var2 = get_variable('visits_tracking_js', 'content2')
500
    assert get_tracking_js() == 'content2'
501

  
502
    var1.delete()
503
    var2.delete()
504
    get_variable('cnil_compliant_visits_tracking_js', 'content1')
505
    get_variable('visits_tracking_js', 'content2')
506
    assert get_tracking_js() == "content1content2"
507

  
508
def test_put_tracking_js():
509
    """write tracking js into hobo variables
510
    a banner will be displayed depending on the variable used
511
    """
512
    put_tracking_js('no gafa => no banner')
513
    value1 = get_variable_value('cnil_compliant_visits_tracking_js')
514
    value2 = get_variable_value('visits_tracking_js')
515
    assert value1 == 'no gafa => no banner'
516
    assert value2 == ''
517

  
518
    put_tracking_js('google => banner')
519
    value1 = get_variable_value('cnil_compliant_visits_tracking_js')
520
    value2 = get_variable_value('visits_tracking_js')
521
    assert value1 == ''
522
    assert value2 == 'google => banner'
523

  
524
@mock.patch('requests.post')
525
def test_get_matomo_javascript_tag(mocked_post):
526
    """function to get matomo JS tag"""
527
    with override_settings(MATOMO_SERVER=CONFIG):
528
        matomo = MatomoWS()
529

  
530
        # success
531
        content = JAVASCRIPT_TAG
532
        mocked_post.return_value.content = content
533
        javascript_tag = get_matomo_javascript_tag(matomo, '42')
534
        assert javascript_tag.find('(function() {') != -1
535
        assert javascript_tag.find('&lt;script') == -1
536
        assert javascript_tag.find('script&gt;') == -1
537
        assert compute_cnil_acknowledgment_level(javascript_tag) == 'success'
538

  
539
def auto_conf_mocked_post(url, **kwargs):
540
    contents = [GET_SITE_NO_ID_PROVIDED,
541
                ADD_SITE_SUCCESS,
542
                ADD_USER_SUCCESS,
543
                JAVASCRIPT_TAG]
544
    response = Response()
545
    response._content = contents[auto_conf_mocked_post.cpt]
546
    response.status_code = 200
547

  
548
    auto_conf_mocked_post.cpt += 1
549
    return response
550

  
551
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
552
def test_auto_configure_matomo(mocked_post):
553
    get_variable('matomo_id_site', '42')
554
    get_variable('matomo_password', '567')
555
    get_variable('tracking_js_var', 'js_code')
556
    get_variable('matomo_error', '')
557

  
558
    Combo.objects.create(base_url='https://combo.dev.publik.love',
559
                         template_name='portal-user')
560
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
561
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
562

  
563
    auto_conf_mocked_post.cpt = 0
564
    with override_settings(MATOMO_SERVER=CONFIG):
565
        assert auto_configure_matomo() is True
566

  
567
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
568
def test_auto_configure_matomo_no_url(mocked_post):
569
    get_variable('matomo_id_site', '42')
570
    get_variable('matomo_password', '567')
571
    get_variable('tracking_js_var', 'js_code')
572
    get_variable('matomo_error', '')
573

  
574
    # no Wc url so as to raise
575
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
576
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
577

  
578
    auto_conf_mocked_post.cpt = 0
579
    with override_settings(MATOMO_SERVER=CONFIG):
580
        assert auto_configure_matomo() is False
581
    message = Variable.objects.get(name='matomo_error').value
582
    assert message == "no portal-user url available for a matomo's id"
583

  
584
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
585
def test_auto_configure_matomo_no_password(mocked_post):
586
    get_variable('matomo_id_site', '42')
587

  
588
    # force to generate new password
589
    get_variable('matomo_password', '')
590
    get_variable('tracking_js_var', 'js_code')
591
    get_variable('matomo_error', '')
592

  
593
    Combo.objects.create(base_url='https://combo.dev.publik.love',
594
                         template_name='portal-user')
595
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
596
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
597

  
598
    auto_conf_mocked_post.cpt = 0
599
    with override_settings(MATOMO_SERVER=CONFIG):
600
        assert auto_configure_matomo() is True
601

  
602
def auto_conf_mocked_post_error(url, **kwargs):
603
    contents = [GET_SITE_NO_ID_PROVIDED,
604
                ADD_SITE_SUCCESS,
605
                ADD_USER_SUCCESS,
606
                JAVASCRIPT_TAG_BAD_RESPONSE]
607
    response = Response()
608
    response._content = contents[auto_conf_mocked_post.cpt]
609
    response.status_code = 200
610

  
611
    auto_conf_mocked_post.cpt += 1
612
    return response
613

  
614
@mock.patch('requests.post', side_effect=auto_conf_mocked_post_error)
615
def test_auto_configure_matomo_error(mocked_post):
616
    get_variable('matomo_id_site', '42')
617
    get_variable('matomo_password', '567')
618
    get_variable('tracking_js_var', 'js_code')
619
    get_variable('matomo_error', '')
620

  
621
    Combo.objects.create(base_url='https://combo.dev.publik.love',
622
                         template_name='portal-user')
623
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
624
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
625

  
626
    auto_conf_mocked_post.cpt = 0
627
    with override_settings(MATOMO_SERVER=CONFIG):
628
        assert auto_configure_matomo() is False
629
    message = Variable.objects.get(name='matomo_error').value
630
    assert message == 'get_javascript_tag fails'
tox.ini
52 52
	multitenant: systemd-python
53 53
	http://git.entrouvert.org/debian/django-tenant-schemas.git/snapshot/django-tenant-schemas-master.tar.gz
54 54
	httmock
55
	lxml
55 56
	requests
56 57
commands =
57 58
	./getlasso.sh
58
-