Projet

Général

Profil

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

Nicolas Roche, 15 avril 2019 14:22

Télécharger (37,8 ko)

Voir les différences:

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

 hobo/matomo/utils.py       | 302 ++++++++++++++++
 hobo/settings.py           |  17 +
 requirements.txt           |   1 +
 tests/test_matomo_utils.py | 706 +++++++++++++++++++++++++++++++++++++
 tox.ini                    |   1 +
 5 files changed, 1027 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) 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
import re
19
import requests
20
import string
21

  
22
from lxml import etree
23
from random import choice, randint
24

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

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

  
32
CNIL_JS = """
33
  // disallow cookie's time extension
34
  _paq.push([function() {
35
    var self = this;
36
    function getOriginalVisitorCookieTimeout() {
37
      var now = new Date(),
38
      nowTs = Math.round(now.getTime() / 1000),
39
      visitorInfo = self.getVisitorInfo();
40
      var createTs = parseInt(visitorInfo[2]);
41
      var cookieTimeout = 33696000; // 13 months in seconds
42
      var originalTimeout = createTs + cookieTimeout - nowTs;
43
      return originalTimeout;
44
    }
45
    this.setVisitorCookieTimeout( getOriginalVisitorCookieTimeout() );
46
  }]);
47
"""
48

  
49
def get_variable(name, default=''):
50
    """get hobo variables from DB
51
    set it to '' into DB if not already created
52
    """
53
    variable, created = Variable.objects.get_or_create(
54
        name=name,
55
        defaults={'auto': True, 'value': default})
56
    return variable
57

  
58
def get_variable_value(name, default=''):
59
    """get hobo variables's value from DB"""
60
    try:
61
        value = Variable.objects.get(name=name).value
62
    except exceptions.ObjectDoesNotExist:
63
        value = default
64
    return value
65

  
66
def get_tracking_js():
67
    """merge JS code from the 2 above variables"""
68
    tracking_js = get_variable_value('cnil_compliant_visits_tracking_js')
69
    tracking_js += get_variable_value('visits_tracking_js')
70
    return tracking_js
71

  
72
def put_tracking_js(tracking_js):
73
    """store JS code into only one of the 2 above variables"""
74
    variable1 = get_variable('cnil_compliant_visits_tracking_js')
75
    variable2 = get_variable('visits_tracking_js')
76
    if tracking_js != '':
77
        if compute_cnil_acknowledgment_level(tracking_js) != 'error':
78
            variable1.value = tracking_js
79
            variable1.save()
80
            variable2.delete()
81
        else:
82
            variable1.delete()
83
            variable2.value = tracking_js
84
            variable2.save()
85
    else:
86
        variable1.delete()
87
        variable2.delete()
88

  
89
def get_tenant_name_and_public_urls():
90
    """get an alias for our matomo's id and urls to monitor"""
91
    tenant_name = None
92
    services = [x for x in Combo.objects.all() if x.template_name == 'portal-user']
93
    if services != [] and services[0] != '':
94
        tenant_name = urlparse.urlparse(services[0].base_url).netloc
95
    services += [x for x in Wcs.objects.all()]
96
    services += [x for x in Fargo.objects.all()]
97
    site_urls = [x.base_url for x in services if x.base_url != '']
98
    return tenant_name, site_urls
99

  
100
class MatomoException(Exception):
101
    """unexpected Matomo internal error"""
102

  
103
class MatomoError(MatomoException):
104
    """expected Matomo error responses"""
105

  
106
class MatomoWS(object):
107
    """api for matomo webservices"""
108

  
109
    def __init__(self):
110
        config = settings.MATOMO_SERVER
111
        try:
112
            self.url_ws_base = config['URL']
113
            self.token_auth = config['TOKEN_AUTH']
114
            self.email_template = config['EMAIL_TEMPLATE']
115
        except KeyError as exc:
116
            raise MatomoError('no settings for matomo: %s' % str(exc))
117

  
118
    @staticmethod
119
    def parse_response(content):
120
        try:
121
            tree = etree.fromstring(content)
122
        except etree.XMLSyntaxError as exc:
123
            raise MatomoException('etree.XMLSyntaxError: %s' % str(exc))
124
        return tree
125

  
126
    @staticmethod
127
    def raise_on_error(tree):
128
        """handle matomo XML error messages"""
129
        tags = tree.xpath('/result/error')
130
        if tags != []:
131
            try:
132
                attr = tags[0].items()[0]
133
                if attr[0] == 'message':
134
                    raise MatomoError(attr[1])
135
            except IndexError:
136
                pass
137
            raise MatomoException('internal error')
138

  
139
    @staticmethod
140
    def assert_success(tree, message='matomo'):
141
        """handle generic 'ok' responses"""
142
        success = True
143
        tags = tree.xpath('/result/success')
144
        if tags != []:
145
            try:
146
                attr = tags[0].items()[0]
147
                if attr[0] != 'message' or attr[1] != 'ok':
148
                    success = False
149
            except IndexError:
150
                success = False
151
        if not success:
152
            raise MatomoException(message + ' fails')
153
        return success
154

  
155
    def call(self, data):
156
        data['module'] = 'API'
157
        data['token_auth'] = self.token_auth
158
        resp = requests.post(self.url_ws_base, data=data)
159
        tree = self.parse_response(resp.content)
160
        self.raise_on_error(tree)
161
        return tree
162

  
163
    def get_site_id_from_site_url(self, url):
164
        data = {'method': 'SitesManager.getSitesIdFromSiteUrl',
165
                'url': url}
166
        tree = self.call(data)
167
        try:
168
            if tree.xpath('/result[not(*)]')[0].text is None:
169
                raise MatomoError('url not found')
170
        except IndexError:
171
            pass
172
        try:
173
            tag = tree.xpath('/result/row/idsite')[0]
174
        except IndexError:
175
            raise MatomoException('get_site_id_from_site_url fails')
176
        return tag.text
177

  
178
    def add_site(self, site_name, site_urls):
179
        data = {'method': 'SitesManager.addSite',
180
                'siteName': site_name}
181
        cpt = 0
182
        for url in site_urls:
183
            key = 'urls[%i]' % cpt
184
            data[key] = url
185
            cpt += 1
186
        tree = self.call(data)
187
        try:
188
            tag = tree.xpath('/result')[0]
189
        except IndexError:
190
            raise MatomoException('add_site fails')
191
        return tag.text
192

  
193
    def add_user(self, user_login, password, initial_id_site):
194
        data = {'method': 'UsersManager.addUser',
195
                'userLogin': user_login,
196
                'password': password,
197
                'email': self.email_template % user_login,
198
                'initialIdSite': initial_id_site}
199
        tree = self.call(data)
200
        return self.assert_success(tree, 'add_user')
201

  
202
    def del_user(self, user_login):
203
        data = {'method': 'UsersManager.deleteUser',
204
                'userLogin': user_login}
205
        tree = self.call(data)
206
        return self.assert_success(tree, 'del_user')
207

  
208
    def get_javascript_tag(self, id_site):
209
        data = {'method': 'SitesManager.getJavascriptTag',
210
                'idSite': id_site}
211
        tree = self.call(data)
212
        try:
213
            tag = tree.xpath('/result')[0]
214
        except IndexError:
215
            raise MatomoException('get_javascript_tag fails')
216
        return tag.text
217

  
218
def upgrade_site(matomo, tenant_name, site_urls):
219
    try:
220
        # tenant name match because it is the basename of one of registered urls
221
        id_site = matomo.get_site_id_from_site_url(tenant_name)
222
    except MatomoError as exc:
223
        if str(exc) == 'url not found':
224
            id_site = matomo.add_site(tenant_name, site_urls)
225
        else:
226
            raise exc
227
    return id_site
228

  
229
def upgrade_user(matomo, user_login, id_site):
230
    # API is not obvious to change password (need the previous one)
231
    try:
232
        matomo.del_user(user_login)
233
    except MatomoError:
234
        pass
235

  
236
    # generate a password and add a new user
237
    characters = string.ascii_letters + string.punctuation + string.digits
238
    password = "".join(choice(characters) for x in range(randint(8, 16)))
239
    matomo.add_user(user_login, password, id_site)
240

  
241
    # build the user's login url
242
    password_md5 = hashlib.md5(password).hexdigest()
243
    logme_url = '%s/index.php?module=Login&action=logme&login=%s&password=%s' % (
244
        matomo.url_ws_base, user_login, password_md5)
245
    return logme_url
246

  
247
def upgrade_javascript_tag(matomo, id_site):
248
    """addapt JS return by Matomo and merge it whith previous JS code we have"""
249
    matomo_tag = matomo.get_javascript_tag(id_site)
250
    lines = matomo_tag.split('\n')
251

  
252
    # acording to publik-base-theme/templates/includes/tracking.html,
253
    # we need to remove <script> tags from matomo's output javascript,
254
    regex = re.compile('</?script.*>')
255
    count = len(lines)
256
    while count > 0:
257
        count -= 1
258
        if regex.match(lines[count]):
259
            del lines[count]
260

  
261
    # and we also need to addapt matomo HTML comments to JS
262
    regex = re.compile('<!-- (.*) -->')
263
    for count, line in enumerate(lines):
264
        lines[count] = regex.sub('// \\1', line)
265

  
266
    # disallow cookie's time extension
267
    regex = re.compile(r'\s*var _paq = window._paq \|\| \[\];')
268
    for count, line in enumerate(lines):
269
        if regex.match(line):
270
            lines.insert(count+1, CNIL_JS)
271
            break
272

  
273
    enhanced_tag = '\n'.join(lines)
274
    return enhanced_tag
275

  
276
def compute_cnil_acknowledgment_level(tracking_js):
277
    if tracking_js.find('google') != -1:
278
        # google reference found into javascript
279
        return 'error'
280
    if tracking_js.find('getOriginalVisitorCookieTimeout') == -1:
281
        # can't find cookie's life time extension prevention
282
        return 'warning'
283
    return 'success'
284

  
285
def auto_configure_matomo():
286
    """main function"""
287
    tenant_name, site_urls = get_tenant_name_and_public_urls()
288
    if tenant_name is None:
289
        raise MatomoException("no portal-user's url available")
290

  
291
    # update matomo account
292
    matomo = MatomoWS()
293
    id_site = upgrade_site(matomo, tenant_name, site_urls)
294
    logme_url = upgrade_user(matomo, tenant_name, id_site)
295
    tracking_js = upgrade_javascript_tag(matomo, id_site)
296

  
297
    # save matomo's variables
298
    logme_url_var = get_variable('matomo_logme_url')
299
    logme_url_var.value = logme_url
300
    logme_url_var.save()
301
    put_tracking_js(tracking_js)
302
    return True
hobo/settings.py
198 198

  
199 199
MELLON_USERNAME_TEMPLATE = '{attributes[name_id_content]}'
200 200

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

  
216
MATOMO_SERVER = {}
217

  
201 218
local_settings_file = os.environ.get('HOBO_SETTINGS_FILE',
202 219
        os.path.join(os.path.dirname(__file__), 'local_settings.py'))
203 220
if os.path.exists(local_settings_file):
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 \
11
     get_variable, get_variable_value, get_tracking_js, put_tracking_js, \
12
     get_tenant_name_and_public_urls, MatomoError, MatomoException, MatomoWS, \
13
     upgrade_site, upgrade_user, upgrade_javascript_tag, \
14
     compute_cnil_acknowledgment_level, auto_configure_matomo
15

  
16
pytestmark = pytest.mark.django_db
17

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

  
22
MATOMO_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
23
<result>
24
    <success message="ok" />
25
</result>
26
"""
27

  
28
MATOMO_ERROR = """<?xml version="1.0" encoding="utf-8" ?>
29
<result>
30
    <error message="here is the error message" />
31
</result>
32
"""
33

  
34
MATOMO_BAD_RESPONSE_1 = """<?xml version="1.0" encoding="utf-8" ?>
35
<result>
36
    <success message="KO" />
37
</result>
38
"""
39

  
40
MATOMO_BAD_RESPONSE_2 = """<?xml version="1.0" encoding="utf-8" ?>
41
<result>
42
    <success>no message attribute</success>
43
    <not_success>no success tag</not_success>
44
</result>
45
"""
46

  
47
GET_SITE_42_FROM_URL = """<?xml version="1.0" encoding="utf-8" ?>
48
<result>
49
    <row>
50
        <idsite>42</idsite>
51
        <moretags>...</moretags>
52
    </row>
53
</result>
54
"""
55

  
56
GET_NO_SITE_FROM_URL = """<?xml version="1.0" encoding="utf-8" ?>
57
<result />
58
"""
59

  
60
GET_SITE_BAD_QUERY = """<?xml version="1.0" encoding="utf-8" ?>
61
<result>
62
        <error message="Please specify a value for 'url'." />
63
</result>
64
"""
65

  
66
GET_SITE_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
67
<result>
68
    <row>
69
        <not_idsite>there is no idsite tag</not_idsite>
70
        <moretags>...</moretags>
71
    </row>
72
</result>
73
"""
74

  
75
ADD_SITE_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
76
<result>42</result>
77
"""
78

  
79
ADD_SITE_ERROR = """<?xml version="1.0" encoding="utf-8" ?>
80
<result>
81
        <error message="Please specify a value for 'siteName'." />
82
</result>
83
"""
84

  
85
ADD_SITE_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
86
<not_result>no result tag</not_result>
87
"""
88

  
89
USER_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
90
<result>
91
    <error message="Username 'hobo.dev.publik.love' already exists." />
92
</result>"""
93

  
94
MAIL_ALREADY_THERE = """<?xml version="1.0" encoding="utf-8" ?>
95
<result>
96
    <error message="User with email 'hobo.dev.publik.love@testor.org' already exists." />
97
</result>"""
98

  
99
BAD_CREDENTIAL = """<?xml version="1.0" encoding="utf-8" ?>
100
<result>
101
    <error message="You can\'t access this resource as it requires a \'superuser\' access." />
102
</result>"""
103

  
104
DEL_UNKNOWN_USER = """<?xml version="1.0" encoding="utf-8" ?>
105
<result>
106
        <error message="User 'hobo.dev.publik.love' doesn't exist." />
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
ENHANCED_JAVASCRIPT_TAG = """// Matomo
130
  var _paq = window._paq || [];
131

  
132
  // disallow cookie's time extension
133
  _paq.push([function() {
134
    var self = this;
135
    function getOriginalVisitorCookieTimeout() {
136
      var now = new Date(),
137
      nowTs = Math.round(now.getTime() / 1000),
138
      visitorInfo = self.getVisitorInfo();
139
      var createTs = parseInt(visitorInfo[2]);
140
      var cookieTimeout = 33696000; // 13 months in seconds
141
      var originalTimeout = createTs + cookieTimeout - nowTs;
142
      return originalTimeout;
143
    }
144
    this.setVisitorCookieTimeout( getOriginalVisitorCookieTimeout() );
145
  }]);
146

  
147
  /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
148
  _paq.push(['trackPageView']);
149
  _paq.push(['enableLinkTracking']);
150
  (function() {
151
    var u="//matomo-test.entrouvert.org/";
152
    _paq.push(['setTrackerUrl', u+'piwik.php']);
153
    _paq.push(['setSiteId', '7']);
154
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
155
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
156
  })();
157
// End Matomo Code
158
"""
159

  
160
JAVASCRIPT_TAG_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
161
<no_result_tag/>
162
"""
163

  
164
def requests_post_mocked_replies(contents):
165
    """buid an iterator for mock's side_effect parameter"""
166
    responses = []
167
    for content in contents:
168
        response = Response()
169
        response._content = content
170
        response.status_code = 200
171
        responses.append(response)
172
    return responses
173

  
174
def test_get_variable():
175
    """hobo variables from"""
176

  
177
    # create the variable with '' value if not there
178
    id_site_var = get_variable('name1')
179
    assert id_site_var.value == ''
180

  
181
    # retrieve the variable if already there
182
    Variable.objects.create(name='name2', value='42')
183
    id_site_var = get_variable('name2')
184
    assert id_site_var.value == '42'
185

  
186
def test_get_variable_value():
187
    """hobo variables from DB"""
188

  
189
    # variable not there: return default value
190
    assert get_variable_value('name1') == ''
191
    assert get_variable_value('name2', default='42') == '42'
192

  
193
    # variable already there
194
    get_variable('name3', '42')
195
    assert get_variable_value('name3') == '42'
196

  
197
def test_get_tenant_name_and_public_urls():
198
    Combo.objects.create(base_url='https://combo.dev.publik.love',
199
                         template_name='portal-user')
200
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
201
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
202
    tenant_name, site_urls = get_tenant_name_and_public_urls()
203
    assert tenant_name == 'combo.dev.publik.love'
204
    assert site_urls[2] == 'https://fargo.dev.publik.love/'
205

  
206
def test_matomo_constructor():
207
    """build the matomo webservice object"""
208
    with override_settings(MATOMO_SERVER=CONFIG):
209
        matomo = MatomoWS()
210
        assert matomo.url_ws_base == 'https://matomo.test'
211
        assert matomo.token_auth == '1234'
212

  
213
    with override_settings(MATOMO_SERVER={}):
214
        try:
215
            matomo = MatomoWS()
216
        except MatomoError as exc:
217
            assert str(exc) == "no settings for matomo: 'URL'"
218
        else:
219
            assert False
220

  
221
def test_parse_response():
222
    """parser used by all matomo webservice calls"""
223
    with override_settings(MATOMO_SERVER=CONFIG):
224
        matomo = MatomoWS()
225

  
226
        # no error (expected format)
227
        content = """<?xml version="1.0" encoding="utf-8" ?><ok/>"""
228
        tree = matomo.parse_response(content)
229
        assert tree.tag == 'ok'
230

  
231
        # error (not XML format)
232
        content = """this is not XML"""
233
        try:
234
            tree = matomo.parse_response(content)
235
        except MatomoException as exc:
236
            assert str(exc).find("XMLSyntaxError: Start tag expected") != -1
237
        else:
238
            assert False
239

  
240
def test_parse_error_message():
241
    """error handler used by all matomo webservice calls"""
242
    with override_settings(MATOMO_SERVER=CONFIG):
243
        matomo = MatomoWS()
244

  
245
        # no error (expected format)
246
        content = """<?xml version="1.0" encoding="utf-8" ?><ok/>"""
247
        tree = matomo.parse_response(content)
248
        matomo.raise_on_error(tree)
249
        assert tree.tag == 'ok'
250

  
251
        # error (expected format)
252
        content = """<?xml version="1.0" encoding="utf-8" ?>
253
<result>
254
    <error message="here is the error message" />
255
</result>
256
"""
257
        tree = matomo.parse_response(content)
258
        try:
259
            matomo.raise_on_error(tree)
260
        except MatomoError as exc:
261
            assert str(exc) == 'here is the error message'
262
        else:
263
            assert False
264

  
265
        # error (unexpected format)
266
        content = """<?xml version="1.0" encoding="utf-8" ?>
267
<result>
268
    <error>no 'message' attribute here</error>
269
</result>
270
"""
271
        tree = matomo.parse_response(content)
272
        try:
273
            matomo.raise_on_error(tree)
274
        except MatomoException as exc:
275
            assert str(exc) == 'internal error'
276
        else:
277
            assert False
278

  
279
@mock.patch('requests.post')
280
def test_assert_success(mocked_post):
281
    """webservice to add new user"""
282
    with override_settings(MATOMO_SERVER=CONFIG):
283
        matomo = MatomoWS()
284

  
285
        # success
286
        tree = matomo.parse_response(MATOMO_SUCCESS)
287
        matomo.raise_on_error(tree)
288
        assert matomo.assert_success(tree, 'me') is True
289

  
290
        # error (KO instead of ok)
291
        tree = matomo.parse_response(MATOMO_BAD_RESPONSE_1)
292
        matomo.raise_on_error(tree)
293
        try:
294
            matomo.assert_success(tree, 'me')
295
        except MatomoException as exc:
296
            assert str(exc).find('me fails') != -1
297
        else:
298
            assert False
299

  
300
        # error (no message attribute)
301
        tree = matomo.parse_response(MATOMO_BAD_RESPONSE_2)
302
        matomo.raise_on_error(tree)
303
        try:
304
            matomo.assert_success(tree, 'me')
305
        except MatomoException as exc:
306
            assert str(exc).find('me fails') != -1
307
        else:
308
            assert False
309

  
310
@mock.patch('requests.post')
311
def test_get_site_from_site_url(mocked_post):
312
    """webservice to test if the site is already registered"""
313
    with override_settings(MATOMO_SERVER=CONFIG):
314
        matomo = MatomoWS()
315

  
316
        # site already here
317
        content = GET_SITE_42_FROM_URL
318
        mocked_post.return_value.content = content
319
        assert matomo.get_site_id_from_site_url('combo.dev.publik.love') == '42'
320

  
321
        # no such url
322
        content = GET_NO_SITE_FROM_URL
323
        mocked_post.return_value.content = content
324
        try:
325
            matomo.get_site_id_from_site_url('combo.dev.publik.love')
326
        except MatomoError as exc:
327
            assert str(exc).find('url not found') != -1
328
        else:
329
            assert False
330

  
331
        # error on empty id
332
        content = GET_SITE_BAD_QUERY
333
        mocked_post.return_value.content = content
334
        try:
335
            matomo.get_site_id_from_site_url('combo.dev.publik.love')
336
        except MatomoError as exc:
337
            assert str(exc) == "Please specify a value for 'url'."
338
        else:
339
            assert False
340

  
341
        # bad response (error on success response)
342
        content = GET_SITE_BAD_RESPONSE
343
        mocked_post.return_value.content = content
344
        try:
345
            matomo.get_site_id_from_site_url('combo.dev.publik.love')
346
        except MatomoException as exc:
347
            assert str(exc) == 'get_site_id_from_site_url fails'
348
        else:
349
            assert False
350

  
351
@mock.patch('requests.post')
352
def test_add_site(mocked_post):
353
    """webservice to add a new site"""
354
    urls = ['https://combo.dev.publik.love',
355
            'https://wcs.dev.publik.love']
356
    with override_settings(MATOMO_SERVER=CONFIG):
357
        matomo = MatomoWS()
358

  
359
        # success
360
        content = ADD_SITE_SUCCESS
361
        mocked_post.return_value.content = content
362
        site_id = matomo.add_site("hobo.dev.publik.love", urls)
363
        assert site_id == '42'
364

  
365
        # error
366
        content = ADD_SITE_ERROR
367
        mocked_post.return_value.content = content
368
        try:
369
            site_id = matomo.add_site("hobo.dev.publik.love", urls)
370
        except MatomoError as exc:
371
            assert str(exc) == "Please specify a value for 'siteName'."
372
        else:
373
            assert False
374

  
375
        # strange message
376
        content = ADD_SITE_BAD_RESPONSE
377
        mocked_post.return_value.content = content
378
        try:
379
            site_id = matomo.add_site("hobo.dev.publik.love", urls)
380
        except MatomoException as exc:
381
            assert str(exc) == 'add_site fails'
382
        else:
383
            assert False
384

  
385
@mock.patch('requests.post')
386
def test_add_user(mocked_post):
387
    """webservice to add new user"""
388
    with override_settings(MATOMO_SERVER=CONFIG):
389
        matomo = MatomoWS()
390

  
391
        # success
392
        content = MATOMO_SUCCESS
393
        mocked_post.return_value.content = content
394
        matomo.add_user('hobo.dev.publik.love', 'xxx', '42')
395
        assert True
396

  
397
        # error (user already here)
398
        content = USER_ALREADY_THERE
399
        mocked_post.return_value.content = content
400
        try:
401
            matomo.add_user('hobo.dev.publik.love', 'xxx', '42')
402
        except MatomoError as exc:
403
            assert str(exc).find("Username 'hobo.dev.publik.love' already") != -1
404
        else:
405
            assert False
406

  
407
        # error (mail already registered)
408
        content = MAIL_ALREADY_THERE
409
        mocked_post.return_value.content = content
410
        try:
411
            matomo.add_user('hobo.dev.publik.love', 'xxx', '42')
412
        except MatomoError as exc:
413
            assert str(exc).find("email 'hobo.dev.publik.love@testor.org'") != -1
414
        else:
415
            assert False
416

  
417
        # error (bad credentials)
418
        content = BAD_CREDENTIAL
419
        mocked_post.return_value.content = content
420
        try:
421
            matomo.add_user('hobo.dev.publik.love', 'xxx', '42')
422
        except MatomoError as exc:
423
            assert str(exc).find("You can\'t access this resource") != -1
424
        else:
425
            assert False
426

  
427
        # bad success message (wrong attribute value)
428
        content = MATOMO_BAD_RESPONSE_1
429
        mocked_post.return_value.content = content
430
        try:
431
            matomo.add_user('hobo.dev.publik.love', 'xxx', '42')
432
        except MatomoException as exc:
433
            assert str(exc) == 'add_user fails'
434
        else:
435
            assert False
436

  
437
       # bad success message (no message attribute)
438
        content = MATOMO_BAD_RESPONSE_2
439
        mocked_post.return_value.content = content
440
        try:
441
            matomo.add_user('hobo.dev.publik.love', 'xxx', '42')
442
        except MatomoException as exc:
443
            assert str(exc) == 'add_user fails'
444
        else:
445
            assert False
446

  
447
@mock.patch('requests.post')
448
def test_del_user(mocked_post):
449
    """webservice to add new user"""
450
    with override_settings(MATOMO_SERVER=CONFIG):
451
        matomo = MatomoWS()
452

  
453
        # success
454
        content = MATOMO_SUCCESS
455
        mocked_post.return_value.content = content
456
        matomo.del_user('hobo.dev.publik.love')
457
        assert True
458

  
459
        # error (unknown user)
460
        content = DEL_UNKNOWN_USER
461
        mocked_post.return_value.content = content
462
        try:
463
            matomo.del_user('hobo.dev.publik.love')
464
        except MatomoError as exc:
465
            assert str(exc).find("User 'hobo.dev.publik.love' doesn't exist.") != -1
466
        else:
467
            assert False
468

  
469
@mock.patch('requests.post')
470
def test_get_javascript_tag(mocked_post):
471
    """webservice to get matomo JS tag"""
472
    with override_settings(MATOMO_SERVER=CONFIG):
473
        matomo = MatomoWS()
474

  
475
        # success
476
        content = JAVASCRIPT_TAG
477
        mocked_post.return_value.content = content
478
        javascript_tag = matomo.get_javascript_tag('42')
479
        assert javascript_tag.find('(function() {') != -1
480

  
481
        # error (bad credentials)
482
        content = BAD_CREDENTIAL
483
        mocked_post.return_value.content = content
484
        try:
485
            javascript_tag = matomo.get_javascript_tag('42')
486
        except MatomoError as exc:
487
            assert str(exc).find("You can't access this resource ") != -1
488
        else:
489
            assert False
490

  
491
       # bad response (no result tag)
492
        content = JAVASCRIPT_TAG_BAD_RESPONSE
493
        mocked_post.return_value.content = content
494
        try:
495
            javascript_tag = matomo.get_javascript_tag('42')
496
        except MatomoException as exc:
497
            assert str(exc) == 'get_javascript_tag fails'
498
        else:
499
            assert False
500

  
501
@mock.patch('requests.post')
502
def test_upgrade_site(mocked_post):
503
    """function to test if the site is already regisered"""
504
    urls = ['https://combo.dev.publik.love',
505
            'https://wcs.dev.publik.love']
506
    with override_settings(MATOMO_SERVER=CONFIG):
507
        matomo = MatomoWS()
508

  
509
        # site not already here
510
        contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS]
511
        mocked_post.side_effect = requests_post_mocked_replies(contents)
512
        site_id = upgrade_site(matomo, "hobo.dev.publik.love", urls)
513
        assert site_id == '42'
514

  
515
        # site already here
516
        contents = [GET_SITE_42_FROM_URL]
517
        mocked_post.side_effect = requests_post_mocked_replies(contents)
518
        site_id = upgrade_site(matomo, "hobo.dev.publik.love", urls)
519
        assert site_id == '42'
520

  
521
        # error while adding new site
522
        contents = [GET_NO_SITE_FROM_URL, MATOMO_ERROR]
523
        mocked_post.side_effect = requests_post_mocked_replies(contents)
524
        try:
525
            upgrade_site(matomo, "hobo.dev.publik.love", urls)
526
        except MatomoException as exc:
527
            assert True
528
        else:
529
            assert False
530

  
531
        # error while looking for site already there
532
        contents = [MATOMO_ERROR]
533
        mocked_post.side_effect = requests_post_mocked_replies(contents)
534
        try:
535
            upgrade_site(matomo, "hobo.dev.publik.love", urls)
536
        except MatomoException as exc:
537
            assert str(exc) == 'here is the error message'
538
        else:
539
            assert False
540

  
541
@mock.patch('requests.post')
542
def test_upgrade_user(mocked_post):
543
    """function to assert we have a user"""
544
    with override_settings(MATOMO_SERVER=CONFIG):
545
        matomo = MatomoWS()
546

  
547
        # success (add a new user)
548
        contents = [DEL_UNKNOWN_USER, MATOMO_SUCCESS]
549
        mocked_post.side_effect = requests_post_mocked_replies(contents)
550
        logme_url = upgrade_user(matomo, 'hobo.dev.publik.love', '42')
551
        assert logme_url.find('action=logme&login=hobo.dev.publik.love') != -1
552

  
553
        # success (user already here)
554
        contents = [MATOMO_SUCCESS, MATOMO_SUCCESS]
555
        mocked_post.side_effect = requests_post_mocked_replies(contents)
556
        logme_url = upgrade_user(matomo, 'hobo.dev.publik.love', '42')
557
        assert logme_url.find('action=logme&login=hobo.dev.publik.love') != -1
558

  
559
        # recover on error (del user fails)
560
        contents = [MATOMO_ERROR, MATOMO_SUCCESS]
561
        mocked_post.side_effect = requests_post_mocked_replies(contents)
562
        logme_url = upgrade_user(matomo, 'hobo.dev.publik.love', '42')
563
        assert logme_url.find('action=logme&login=hobo.dev.publik.love') != -1
564

  
565
        # error (add user fails)
566
        contents = [MATOMO_SUCCESS, MATOMO_ERROR]
567
        mocked_post.side_effect = requests_post_mocked_replies(contents)
568
        try:
569
            upgrade_user(matomo, 'hobo.dev.publik.love', '42')
570
        except MatomoError:
571
            assert True
572
        else:
573
            assert False
574

  
575
def test_compute_cnil_acknowledgment_level():
576
    """function use to inspect javascript content"""
577
    warning_content = JAVASCRIPT_TAG
578

  
579
    # can't find cookie's life time extension prevention
580
    assert compute_cnil_acknowledgment_level(warning_content) == 'warning'
581

  
582
    # ok
583
    success_content = warning_content + '\n...getOriginalVisitorCookieTimeout...'
584
    assert compute_cnil_acknowledgment_level(success_content) == 'success'
585

  
586
    # google reference found into javascript
587
    error_content = success_content + '\n...google...'
588
    assert compute_cnil_acknowledgment_level(error_content) == 'error'
589

  
590
def test_get_tracking_js():
591
    """read previous tracking JS from hobo variables"""
592
    var1 = get_variable('cnil_compliant_visits_tracking_js', 'content1')
593
    assert get_tracking_js() == 'content1'
594

  
595
    var1.delete()
596
    var1 = get_variable('cnil_compliant_visits_tracking_js', '')
597
    var2 = get_variable('visits_tracking_js', 'content2')
598
    assert get_tracking_js() == 'content2'
599

  
600
    var1.delete()
601
    var2.delete()
602
    get_variable('cnil_compliant_visits_tracking_js', 'content1')
603
    get_variable('visits_tracking_js', 'content2')
604
    assert get_tracking_js() == "content1content2"
605

  
606
def test_put_tracking_js():
607
    """write tracking js into hobo variables:
608
    - visits_tracking_js: a banner will be displayed (javascript may be not removed)
609
    - cnil_compliant_visits_tracking_js: javascript is dislayed normally
610
    """
611
    # JS is stored into 'cnil_compliant_visits_tracking_js'
612
    put_tracking_js('/* no gafa => no banner */')
613
    value1 = get_variable_value('cnil_compliant_visits_tracking_js', 'undefined')
614
    value2 = get_variable_value('visits_tracking_js', 'undefined')
615
    assert value1 == '/* no gafa => no banner */'
616
    assert value2 == 'undefined'
617

  
618
    # JS is stord into 'visits_tracking_js'
619
    put_tracking_js('/* google => banner */')
620
    value1 = get_variable_value('cnil_compliant_visits_tracking_js', 'undefined')
621
    value2 = get_variable_value('visits_tracking_js', 'undefined')
622
    assert value1 == 'undefined'
623
    assert value2 == '/* google => banner */'
624

  
625
    # test we remove variables when no more used
626
    put_tracking_js('')
627
    value1 = get_variable_value('cnil_compliant_visits_tracking_js', 'undefined')
628
    value2 = get_variable_value('visits_tracking_js', 'undefined')
629
    assert value1 == 'undefined'
630
    assert value2 == 'undefined' 
631

  
632
@mock.patch('requests.post')
633
def test_upgrade_javascript_tag(mocked_post):
634
    """function to get matomo JS tag"""
635
    with override_settings(MATOMO_SERVER=CONFIG):
636
        matomo = MatomoWS()
637

  
638
        # success
639
        content = JAVASCRIPT_TAG
640
        mocked_post.return_value.content = content
641
        javascript_tag = upgrade_javascript_tag(matomo, '42')
642
        assert javascript_tag.find('(function() {') != -1
643
        assert javascript_tag.find('&lt;script') == -1
644
        assert javascript_tag.find('script&gt;') == -1
645
        assert javascript_tag == ENHANCED_JAVASCRIPT_TAG
646
        assert compute_cnil_acknowledgment_level(javascript_tag) == 'success'
647

  
648
@mock.patch('requests.post')
649
def test_auto_configure_matomo(mocked_post):
650
    tracking_js_var = get_variable('visits_tracking_js', 'js_code')
651
    logme_url_var = get_variable('matomo_logme_url', '')
652

  
653
    Combo.objects.create(base_url='https://combo.dev.publik.love',
654
                         template_name='portal-user')
655
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
656
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
657

  
658
    with override_settings(MATOMO_SERVER=CONFIG):
659
        contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS,
660
                    DEL_UNKNOWN_USER, MATOMO_SUCCESS,
661
                    JAVASCRIPT_TAG]
662
        mocked_post.side_effect = requests_post_mocked_replies(contents)
663
        assert auto_configure_matomo() is True
664
        logme_url_var = get_variable('matomo_logme_url')
665
        assert logme_url_var.value != ''
666
        tracking_js_var = get_variable('visits_tracking_js')
667
        assert tracking_js_var.value == ''
668
        tracking_js2_var = get_variable('cnil_compliant_visits_tracking_js')
669
        assert tracking_js2_var.value != ''
670

  
671
@mock.patch('requests.post')
672
def test_auto_configure_matomo_no_url(mocked_post):
673
    # no Wc url so as to raise
674
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
675
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
676

  
677
    with override_settings(MATOMO_SERVER=CONFIG):
678
        try:
679
            auto_configure_matomo()
680
        except MatomoException as exc:
681
            assert str(exc) == "no portal-user's url available"
682
        else:
683
            assert False
684

  
685
@mock.patch('requests.post')
686
def test_auto_configure_matomo_error(mocked_post):
687
    tracking_js_var = get_variable('visits_tracking_js', 'js_code')
688

  
689
    Combo.objects.create(base_url='https://combo.dev.publik.love',
690
                         template_name='portal-user')
691
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
692
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
693

  
694
    with override_settings(MATOMO_SERVER=CONFIG):
695
        contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS,
696
                    DEL_UNKNOWN_USER, MATOMO_SUCCESS,
697
                    JAVASCRIPT_TAG_BAD_RESPONSE]
698
        mocked_post.side_effect = requests_post_mocked_replies(contents)
699
        try:
700
            auto_configure_matomo()
701
        except MatomoException as exc:
702
            assert str(exc) == "get_javascript_tag fails"
703
        else:
704
            assert False
705
        tracking_js_var = get_variable('visits_tracking_js')
706
        assert tracking_js_var.value == 'js_code'
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
-