Projet

Général

Profil

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

Nicolas Roche, 04 avril 2019 10:34

Télécharger (35,2 ko)

Voir les différences:

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

 hobo/matomo/utils.py       | 292 ++++++++++++++++
 hobo/settings.py           |  17 +
 requirements.txt           |   1 +
 tests/test_matomo_utils.py | 662 +++++++++++++++++++++++++++++++++++++
 tox.ini                    |   1 +
 5 files changed, 973 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 = 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 months in seconds
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
    lines = 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
    regex = re.compile('</?script.*>')
220
    count = len(lines)
221
    while count > 0:
222
        count -= 1
223
        if regex.match(lines[count]):
224
            del lines[count]
225

  
226
    # and we also need to addapt matomo HTML comments to JS
227
    regex = re.compile('<!-- (.*) -->')
228
    for count, line in enumerate(lines):
229
        lines[count] = regex.sub('// \\1', line)
230

  
231
    # disallow cookie's time extension
232
    regex = re.compile(r'\s*var _paq = window._paq \|\| \[\];')
233
    for count, line in enumerate(lines):
234
        if regex.match(line):
235
            lines.insert(count+1, CNIL_JS)
236
            break
237

  
238
    enhanced_tag = '\n'.join(lines)
239
    return enhanced_tag
240

  
241
def compute_cnil_acknowledgment_level(tracking_js):
242
    if tracking_js.find('google') != -1:
243
        # google reference found into javascript
244
        return 'error'
245
    if tracking_js.find('getOriginalVisitorCookieTimeout') == -1:
246
        # can't find cookie's life time extension prevention
247
        return 'warning'
248
    return 'success'
249

  
250
def auto_configure_matomo():
251
    """main function"""
252

  
253
    # retrieve matomo's variables
254
    id_site_var = get_variable('matomo_id_site')
255
    password_var = get_variable('matomo_password')
256
    error_message_var = get_variable('matomo_error')
257

  
258
    id_site = id_site_var.value
259
    password = password_var.value
260
    if password_var.value == '':
261
        characters = string.ascii_letters + string.punctuation  + string.digits
262
        password = "".join(choice(characters) for x in range(randint(8, 16)))
263

  
264
    try:
265
        tenant_name, site_urls = get_tenant_name_and_public_urls()
266
        if tenant_name is None:
267
            raise MatomoException("no portal-user url available for a matomo's id")
268

  
269
        matomo = MatomoWS()
270

  
271
        # create an account
272
        if not is_site_already_added(matomo, id_site):
273
            id_site = matomo.add_site(tenant_name, site_urls)
274
        add_user_if_not_already_there(matomo, tenant_name, password, id_site)
275

  
276
        # overwrite tracking_js (previous content is lost)
277
        tracking_js = get_matomo_javascript_tag(matomo, id_site)
278

  
279
    except MatomoException as exc:
280
        error_message_var.value = str(exc)
281
        error_message_var.save()
282
        return False
283

  
284
    # save matomo's variables
285
    id_site_var.value = id_site
286
    password_var.value = password
287
    error_message_var.value = ''
288
    id_site_var.save()
289
    password_var.save()
290
    error_message_var.save()
291
    put_tracking_js(tracking_js)
292
    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 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
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
BAS_CREDENTIALS = """<?xml version="1.0" encoding="utf-8" ?>
161
<result>
162
        <error message="You can't access this resource as it requires 'view' access for the website id = 42." />
163
</result>
164
"""
165

  
166
JAVASCRIPT_TAG_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
167
<no_result_tag/>
168
"""
169

  
170
def test_get_variable():
171
    """hobo variables from"""
172

  
173
    # create the variable with '' value if not there
174
    id_site_var = get_variable('name1')
175
    assert id_site_var.value == ''
176

  
177
    # retrieve the variable if already there
178
    Variable.objects.create(name='name2', value='42')
179
    id_site_var = get_variable('name2')
180
    assert id_site_var.value == '42'
181

  
182
def test_get_variable_value():
183
    """hobo variables from DB"""
184

  
185
    # variable not there: return default value
186
    assert get_variable_value('name1') == ''
187
    assert get_variable_value('name2', default='42') == '42'
188

  
189
    # variable already there
190
    get_variable('name3', '42')
191
    assert get_variable_value('name3') == '42'
192

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

  
202
def test_matomo_constructor():
203
    """build the matomo webservice object"""
204
    with override_settings(MATOMO_SERVER=CONFIG):
205
        matomo = MatomoWS()
206
        assert matomo.url_ws_base == 'https://matomo.test'
207
        assert matomo.token_auth == '1234'
208

  
209
    with override_settings(MATOMO_SERVER={}):
210
        try:
211
            matomo = MatomoWS()
212
        except MatomoError as exc:
213
            assert str(exc) == "no settings for matomo: 'URL'"
214
        else:
215
            assert False
216

  
217
def test_parse_response():
218
    """parser used by all matomo webservice calls"""
219
    with override_settings(MATOMO_SERVER=CONFIG):
220
        matomo = MatomoWS()
221

  
222
        # no error (expected format)
223
        content = """<?xml version="1.0" encoding="utf-8" ?><ok/>"""
224
        tree = matomo.parse_response(content)
225
        assert tree.tag == 'ok'
226

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

  
236
def test_get_error_message():
237
    """error handler used by all matomo webservice calls"""
238
    with override_settings(MATOMO_SERVER=CONFIG):
239
        matomo = MatomoWS()
240

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

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

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

  
275
@mock.patch('requests.post')
276
def test_get_site_from_id(mocked_post):
277
    """webservice to test if the site is already registered"""
278
    with override_settings(MATOMO_SERVER=CONFIG):
279
        matomo = MatomoWS()
280

  
281
        # site already here
282
        content = GET_SITE_42_ALREADY_THERE
283
        mocked_post.return_value.content = content
284
        assert matomo.get_site_from_id('42') == '42'
285

  
286
        # site not already here
287
        content = GET_SITE_NOT_ALREADY_THERE
288
        mocked_post.return_value.content = content
289
        try:
290
            matomo.get_site_from_id(42)
291
        except MatomoError as exc:
292
            assert str(exc).find('An unexpected website was found in ') != -1
293
        else:
294
            assert False
295

  
296
        # error on empty id
297
        content = GET_SITE_NO_ID_PROVIDED
298
        mocked_post.return_value.content = content
299
        try:
300
            matomo.get_site_from_id(42)
301
        except MatomoError as exc:
302
            assert str(exc) == "Please specify a value for 'idSite'."
303
        else:
304
            assert False
305

  
306
        # bad response (error on success response)
307
        content = GET_SITE_BAD_RESPONSE
308
        mocked_post.return_value.content = content
309
        try:
310
            matomo.get_site_from_id(42)
311
        except MatomoException as exc:
312
            assert str(exc) == 'get_site_from_id fails'
313
        else:
314
            assert False
315

  
316
@mock.patch('requests.post')
317
def test_is_site_already_added(mocked_post):
318
    """function to test if the site is already regisered"""
319
    with override_settings(MATOMO_SERVER=CONFIG):
320
        matomo = MatomoWS()
321

  
322
        # site already here
323
        content = GET_SITE_42_ALREADY_THERE
324
        mocked_post.return_value.content = content
325
        assert is_site_already_added(matomo, '42') is True
326

  
327
        # site not already here
328
        content = GET_SITE_NOT_ALREADY_THERE
329
        mocked_post.return_value.content = content
330
        assert is_site_already_added(matomo, '42') is False
331

  
332
        # empty id provided
333
        content = GET_SITE_NO_ID_PROVIDED
334
        mocked_post.return_value.content = content
335
        assert is_site_already_added(matomo, '42') is False
336

  
337
        # strange case
338
        content = GET_SITE_34_ALREADY_THERE
339
        mocked_post.return_value.content = content
340
        assert is_site_already_added(matomo, '42') is False
341

  
342
        # error
343
        content = GET_SITE_BAD_RESPONSE
344
        mocked_post.return_value.content = content
345
        try:
346
            is_site_already_added(matomo, '42')
347
        except MatomoException as exc:
348
            assert str(exc) == 'get_site_from_id fails'
349
        else:
350
            assert False
351

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

  
360
        # success
361
        content = ADD_SITE_SUCCESS
362
        mocked_post.return_value.content = content
363
        site_id = matomo.add_site("hobo_dev_publik_love", urls)
364
        assert site_id == '42'
365

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

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

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

  
392
        # success
393
        content = ADD_USER_SUCCESS
394
        mocked_post.return_value.content = content
395
        matomo.add_user('nestor', 'xxx', '42')
396
        assert True
397

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

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

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

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

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

  
448
@mock.patch('requests.post')
449
def test_add_user_if_not_already_there(mocked_post):
450
    """function to assert we have a user"""
451
    with override_settings(MATOMO_SERVER=CONFIG):
452
        matomo = MatomoWS()
453

  
454
        # success (add a new user)
455
        content = ADD_USER_SUCCESS
456
        mocked_post.return_value.content = content
457
        add_user_if_not_already_there(matomo, 'hobo_dev_publik_love', 'xxx', '42')
458
        assert True
459

  
460
        # success (user already here)
461
        content = USER_ALREADY_THERE
462
        mocked_post.return_value.content = content
463
        add_user_if_not_already_there(matomo, 'hobo_dev_publik_love', 'xxx', '42')
464
        assert True
465

  
466
        # error (bad credentials)
467
        content = BAD_CREDENTIAL
468
        mocked_post.return_value.content = content
469
        try:
470
            add_user_if_not_already_there(matomo, 'tenant_name', 'xxx', '42')
471
        except MatomoError:
472
            assert True
473
        else:
474
            assert False
475

  
476
@mock.patch('requests.post')
477
def test_get_javascript_tag(mocked_post):
478
    """webservice to get matomo JS tag"""
479
    with override_settings(MATOMO_SERVER=CONFIG):
480
        matomo = MatomoWS()
481

  
482
        # success
483
        content = JAVASCRIPT_TAG
484
        mocked_post.return_value.content = content
485
        javascript_tag = matomo.get_javascript_tag('42')
486
        assert javascript_tag.find('(function() {') != -1
487

  
488
        # error (bad credentials)
489
        content = BAD_CREDENTIAL
490
        mocked_post.return_value.content = content
491
        try:
492
            javascript_tag = matomo.get_javascript_tag('42')
493
        except MatomoError as exc:
494
            assert str(exc).find("You can't access this resource ") != -1
495
        else:
496
            assert False
497

  
498
       # bad response (no result tag)
499
        content = JAVASCRIPT_TAG_BAD_RESPONSE
500
        mocked_post.return_value.content = content
501
        try:
502
            javascript_tag = matomo.get_javascript_tag('42')
503
        except MatomoException as exc:
504
            assert str(exc) == 'get_javascript_tag fails'
505
        else:
506
            assert False
507

  
508
def test_compute_cnil_acknowledgment_level():
509
    """function use to inspect javascript content"""
510
    warning_content = JAVASCRIPT_TAG
511

  
512
    # can't find cookie's life time extension prevention
513
    assert compute_cnil_acknowledgment_level(warning_content) == 'warning'
514

  
515
    # ok
516
    success_content = warning_content + '\n...getOriginalVisitorCookieTimeout...'
517
    assert compute_cnil_acknowledgment_level(success_content) == 'success'
518

  
519
    # google reference found into javascript
520
    error_content = success_content + '\n...google...'
521
    assert compute_cnil_acknowledgment_level(error_content) == 'error'
522

  
523
def test_get_tracking_js():
524
    """read previous tracking JS from hobo variables"""
525
    var1 = get_variable('cnil_compliant_visits_tracking_js', 'content1')
526
    assert get_tracking_js() == 'content1'
527

  
528
    var1.delete()
529
    var1 = get_variable('cnil_compliant_visits_tracking_js', '')
530
    var2 = get_variable('visits_tracking_js', 'content2')
531
    assert get_tracking_js() == 'content2'
532

  
533
    var1.delete()
534
    var2.delete()
535
    get_variable('cnil_compliant_visits_tracking_js', 'content1')
536
    get_variable('visits_tracking_js', 'content2')
537
    assert get_tracking_js() == "content1content2"
538

  
539
def test_put_tracking_js():
540
    """write tracking js into hobo variables
541
    a banner will be displayed depending on the variable used
542
    """
543
    put_tracking_js('no gafa => no banner')
544
    value1 = get_variable_value('cnil_compliant_visits_tracking_js')
545
    value2 = get_variable_value('visits_tracking_js')
546
    assert value1 == 'no gafa => no banner'
547
    assert value2 == ''
548

  
549
    put_tracking_js('google => banner')
550
    value1 = get_variable_value('cnil_compliant_visits_tracking_js')
551
    value2 = get_variable_value('visits_tracking_js')
552
    assert value1 == ''
553
    assert value2 == 'google => banner'
554

  
555
@mock.patch('requests.post')
556
def test_get_matomo_javascript_tag(mocked_post):
557
    """function to get matomo JS tag"""
558
    with override_settings(MATOMO_SERVER=CONFIG):
559
        matomo = MatomoWS()
560

  
561
        # success
562
        content = JAVASCRIPT_TAG
563
        mocked_post.return_value.content = content
564
        javascript_tag = get_matomo_javascript_tag(matomo, '42')
565
        assert javascript_tag.find('(function() {') != -1
566
        assert javascript_tag.find('&lt;script') == -1
567
        assert javascript_tag.find('script&gt;') == -1
568
        assert javascript_tag == ENHANCED_JAVASCRIPT_TAG
569
        assert compute_cnil_acknowledgment_level(javascript_tag) == 'success'
570

  
571
def auto_conf_mocked_post(url, **kwargs):
572
    contents = [GET_SITE_NO_ID_PROVIDED,
573
                ADD_SITE_SUCCESS,
574
                ADD_USER_SUCCESS,
575
                JAVASCRIPT_TAG]
576
    response = Response()
577
    response._content = contents[auto_conf_mocked_post.cpt]
578
    response.status_code = 200
579

  
580
    auto_conf_mocked_post.cpt += 1
581
    return response
582

  
583
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
584
def test_auto_configure_matomo(mocked_post):
585
    get_variable('matomo_id_site', '42')
586
    get_variable('matomo_password', '567')
587
    get_variable('tracking_js_var', 'js_code')
588
    get_variable('matomo_error', '')
589

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

  
595
    auto_conf_mocked_post.cpt = 0
596
    with override_settings(MATOMO_SERVER=CONFIG):
597
        assert auto_configure_matomo() is True
598

  
599
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
600
def test_auto_configure_matomo_no_url(mocked_post):
601
    get_variable('matomo_id_site', '42')
602
    get_variable('matomo_password', '567')
603
    get_variable('tracking_js_var', 'js_code')
604
    get_variable('matomo_error', '')
605

  
606
    # no Wc url so as to raise
607
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
608
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
609

  
610
    auto_conf_mocked_post.cpt = 0
611
    with override_settings(MATOMO_SERVER=CONFIG):
612
        assert auto_configure_matomo() is False
613
    message = Variable.objects.get(name='matomo_error').value
614
    assert message == "no portal-user url available for a matomo's id"
615

  
616
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
617
def test_auto_configure_matomo_no_password(mocked_post):
618
    get_variable('matomo_id_site', '42')
619

  
620
    # force to generate new password
621
    get_variable('matomo_password', '')
622
    get_variable('tracking_js_var', 'js_code')
623
    get_variable('matomo_error', '')
624

  
625
    Combo.objects.create(base_url='https://combo.dev.publik.love',
626
                         template_name='portal-user')
627
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
628
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
629

  
630
    auto_conf_mocked_post.cpt = 0
631
    with override_settings(MATOMO_SERVER=CONFIG):
632
        assert auto_configure_matomo() is True
633

  
634
def auto_conf_mocked_post_error(url, **kwargs):
635
    contents = [GET_SITE_NO_ID_PROVIDED,
636
                ADD_SITE_SUCCESS,
637
                ADD_USER_SUCCESS,
638
                JAVASCRIPT_TAG_BAD_RESPONSE]
639
    response = Response()
640
    response._content = contents[auto_conf_mocked_post.cpt]
641
    response.status_code = 200
642

  
643
    auto_conf_mocked_post.cpt += 1
644
    return response
645

  
646
@mock.patch('requests.post', side_effect=auto_conf_mocked_post_error)
647
def test_auto_configure_matomo_error(mocked_post):
648
    get_variable('matomo_id_site', '42')
649
    get_variable('matomo_password', '567')
650
    get_variable('tracking_js_var', 'js_code')
651
    get_variable('matomo_error', '')
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
    auto_conf_mocked_post.cpt = 0
659
    with override_settings(MATOMO_SERVER=CONFIG):
660
        assert auto_configure_matomo() is False
661
    message = Variable.objects.get(name='matomo_error').value
662
    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
-