Projet

Général

Profil

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

Nicolas Roche, 08 avril 2019 20:57

Télécharger (37,1 ko)

Voir les différences:

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

 hobo/matomo/utils.py       | 299 ++++++++++++++++
 hobo/settings.py           |  17 +
 requirements.txt           |   1 +
 tests/test_matomo_utils.py | 696 +++++++++++++++++++++++++++++++++++++
 tox.ini                    |   1 +
 5 files changed, 1014 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 hashlib
18
import re
19
import requests
20
import string
21

  
22
from lxml import etree
23
from random import *
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
JAVASCRIPT_PLACE_HOLDER = "/* Put your JavaScript code here */"
33
CNIL_JS = """
34
  // disallow cookie's time extension
35
  _paq.push([function() {
36
    var self = this;
37
    function getOriginalVisitorCookieTimeout() {
38
      var now = new Date(),
39
      nowTs = Math.round(now.getTime() / 1000),
40
      visitorInfo = self.getVisitorInfo();
41
      var createTs = parseInt(visitorInfo[2]);
42
      var cookieTimeout = 33696000; // 13 months in seconds
43
      var originalTimeout = createTs + cookieTimeout - nowTs;
44
      return originalTimeout;
45
    }
46
    this.setVisitorCookieTimeout( getOriginalVisitorCookieTimeout() );
47
  }]);
48
"""
49

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

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

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

  
73
def put_tracking_js(tracking_js):
74
    """store JS code into only one of the 2 above variables"""
75
    variable1 = get_variable('cnil_compliant_visits_tracking_js')
76
    variable2 = get_variable('visits_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

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

  
97
class MatomoException(Exception):
98
    """unexpected Matomo internal error"""
99

  
100
class MatomoError(MatomoException):
101
    """expected Matomo error responses"""
102

  
103
class MatomoWS(object):
104
    """api for matomo webservices"""
105

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  
270
    enhanced_tag = '\n'.join(lines)
271
    return enhanced_tag
272

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

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

  
288
    # update matomo account
289
    matomo = MatomoWS()
290
    id_site = upgrade_site(matomo, tenant_name, site_urls)
291
    logme_url = upgrade_user(matomo, tenant_name, id_site)
292
    tracking_js = upgrade_javascript_tag(matomo, id_site)
293

  
294
    # save matomo's variables
295
    logme_url_var = get_variable('matomo_logme_url')
296
    logme_url_var.value = logme_url
297
    logme_url_var.save()
298
    put_tracking_js(tracking_js)
299
    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
    a banner will be displayed depending on the variable used
609
    """
610
    put_tracking_js('no gafa => no banner')
611
    value1 = get_variable_value('cnil_compliant_visits_tracking_js')
612
    value2 = get_variable_value('visits_tracking_js')
613
    assert value1 == 'no gafa => no banner'
614
    assert value2 == ''
615

  
616
    put_tracking_js('google => banner')
617
    value1 = get_variable_value('cnil_compliant_visits_tracking_js')
618
    value2 = get_variable_value('visits_tracking_js')
619
    assert value1 == ''
620
    assert value2 == 'google => banner'
621

  
622
@mock.patch('requests.post')
623
def test_upgrade_javascript_tag(mocked_post):
624
    """function to get matomo JS tag"""
625
    with override_settings(MATOMO_SERVER=CONFIG):
626
        matomo = MatomoWS()
627

  
628
        # success
629
        content = JAVASCRIPT_TAG
630
        mocked_post.return_value.content = content
631
        javascript_tag = upgrade_javascript_tag(matomo, '42')
632
        assert javascript_tag.find('(function() {') != -1
633
        assert javascript_tag.find('&lt;script') == -1
634
        assert javascript_tag.find('script&gt;') == -1
635
        assert javascript_tag == ENHANCED_JAVASCRIPT_TAG
636
        assert compute_cnil_acknowledgment_level(javascript_tag) == 'success'
637

  
638
@mock.patch('requests.post')
639
def test_auto_configure_matomo(mocked_post):
640
    tracking_js_var = get_variable('visits_tracking_js', 'js_code')
641
    logme_url_var = get_variable('matomo_logme_url', '')
642

  
643
    Combo.objects.create(base_url='https://combo.dev.publik.love',
644
                         template_name='portal-user')
645
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
646
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
647

  
648
    with override_settings(MATOMO_SERVER=CONFIG):
649
        contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS,
650
                    DEL_UNKNOWN_USER, MATOMO_SUCCESS,
651
                    JAVASCRIPT_TAG]
652
        mocked_post.side_effect = requests_post_mocked_replies(contents)
653
        assert auto_configure_matomo() is True
654
        logme_url_var = get_variable('matomo_logme_url')
655
        assert logme_url_var.value != ''
656
        tracking_js_var = get_variable('visits_tracking_js')
657
        assert tracking_js_var.value == ''
658
        tracking_js2_var = get_variable('cnil_compliant_visits_tracking_js')
659
        assert tracking_js2_var.value != ''
660

  
661
@mock.patch('requests.post')
662
def test_auto_configure_matomo_no_url(mocked_post):
663
    # no Wc url so as to raise
664
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
665
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
666

  
667
    with override_settings(MATOMO_SERVER=CONFIG):
668
        try:
669
            auto_configure_matomo()
670
        except MatomoException as exc:
671
            assert str(exc) == "no portal-user's url available"
672
        else:
673
            assert False
674

  
675
@mock.patch('requests.post')
676
def test_auto_configure_matomo_error(mocked_post):
677
    tracking_js_var = get_variable('visits_tracking_js', 'js_code')
678

  
679
    Combo.objects.create(base_url='https://combo.dev.publik.love',
680
                         template_name='portal-user')
681
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
682
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
683

  
684
    with override_settings(MATOMO_SERVER=CONFIG):
685
        contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS,
686
                    DEL_UNKNOWN_USER, MATOMO_SUCCESS,
687
                    JAVASCRIPT_TAG_BAD_RESPONSE]
688
        mocked_post.side_effect = requests_post_mocked_replies(contents)
689
        try:
690
            auto_configure_matomo()
691
        except MatomoException as exc:
692
            assert str(exc) == "get_javascript_tag fails"
693
        else:
694
            assert False
695
        tracking_js_var = get_variable('visits_tracking_js')
696
        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
-