Projet

Général

Profil

0001-utils-accept-Django-syntax-in-templated_url-19261.patch

Thomas Noël, 07 octobre 2017 19:31

Télécharger (17,1 ko)

Voir les différences:

Subject: [PATCH] utils: accept Django syntax in templated_url (#19261)

 combo/data/models.py | 12 ++++---
 combo/utils.py       | 25 +++++++++++---
 tests/test_cells.py  | 93 ++++++++++++++++++++++++++++++++++++++++++++++++----
 tests/test_utils.py  | 45 ++++++++++++++++++++++---
 4 files changed, 156 insertions(+), 19 deletions(-)
combo/data/models.py
871 871
            log_errors = data_url_dict.get('log_errors', self.log_errors)
872 872
            try:
873 873
                url = utils.get_templated_url(data_url_dict['url'], context)
874
            except utils.UnknownTemplateVariableError:
874
            except utils.TemplateError as e:
875 875
                logger = logging.getLogger(__name__)
876
                logger.warning('unknown variable in template URL (%s)', self.url)
876
                logger.warning('error in templated URL (%s): %s', data_url_dict['url'], e)
877 877
                continue
878 878
            extra_context[data_key + '_url'] = url
879
            if not url:
880
                logger = logging.getLogger(__name__)
881
                logger.warning('templated URL (%s) is empty', data_url_dict['url'])
882
                continue
879 883
            try:
880 884
                json_response = utils.requests.get(url,
881 885
                        headers={'Accept': 'application/json'},
......
960 964

  
961 965
        try:
962 966
            url = utils.get_templated_url(self.actions[action]['url'], context)
963
        except utils.UnknownTemplateVariableError:
964
            logger.warning('unknown variable in URL (%s)', self.actions[action]['url'])
967
        except utils.TemplateError as e:
968
            logger.warning('error in templated URL (%s): %s', self.actions[action]['url'], e)
965 969
            raise PostException(error_message)
966 970

  
967 971
        json_response = utils.requests.post(url,
combo/utils.py
35 35

  
36 36
from django.conf import settings
37 37
from django.core.cache import cache
38
from django.template import Context
38
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist
39 39
from django.utils.html import strip_tags
40 40
from django.utils.http import urlencode, quote
41 41

  
......
174 174

  
175 175
requests = Requests()
176 176

  
177
class UnknownTemplateVariableError(KeyError):
178
    pass
177

  
178
class TemplateError(Exception):
179
    def __init__(self, msg, params=()):
180
        self.msg = msg
181
        self.params = params
182

  
183
    def __str__(self):
184
        return self.msg % self.params
185

  
179 186

  
180 187
def get_templated_url(url, context=None):
188
    if '{{' not in url and '{%' not in url and '[' not in url:
189
        return url
181 190
    template_vars = Context()
182 191
    if context:
183 192
        template_vars.update(context)
......
189 198
            if hasattr(user, 'saml_identifiers') and user.saml_identifiers.exists():
190 199
                template_vars['user_nameid'] = quote(user.saml_identifiers.first().name_id)
191 200
    template_vars.update(settings.TEMPLATE_VARS)
201
    if '{{' in url or '{%' in url:  # Django template
202
        try:
203
            return Template(url).render(template_vars)
204
        except VariableDoesNotExist as e:
205
            raise TemplateError(e.msg, e.params)
206
        except TemplateSyntaxError:
207
            raise TemplateError('syntax error')
208
    # ezt-like template
192 209
    def repl(matchobj):
193 210
        varname = matchobj.group(0)[1:-1]
194 211
        if varname == '[':
195 212
            return '['
196 213
        if varname not in template_vars:
197
            raise UnknownTemplateVariableError(varname)
214
            raise TemplateError('unknown variable %s', varname)
198 215
        return unicode(template_vars[varname])
199 216
    return re.sub(r'(\[.+?\])', repl, url)
200 217

  
tests/test_cells.py
134 134

  
135 135
    cell = JsonCell()
136 136
    cell.page = page
137
    cell.url = 'http://example.net/'
137 138
    cell.title = 'Example Site'
138 139
    cell.order = 0
139 140
    cell.save()
......
158 159
        requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200)
159 160
        context = cell.get_cell_extra_context({})
160 161
        assert context['json'] == data
161
        assert context['json_url'] == ''
162
        assert context['json_url'] == 'http://example.net/'
162 163
        assert context['json_status'] == 200
163 164
        assert 'json_error' not in context
164 165

  
165 166
        requests_get.return_value = mock.Mock(status_code=204)  # 204 : No Content
166 167
        context = cell.get_cell_extra_context({})
167 168
        assert context['json'] is None
168
        assert context['json_url'] == ''
169
        assert context['json_url'] == 'http://example.net/'
169 170
        assert context['json_status'] == 204
170 171
        assert 'json_error' not in context
171 172

  
172
        cell.url = 'http://test2'
173 173
        requests_get.return_value = mock.Mock(content='not found', status_code=404,
174 174
                                             headers={})
175 175
        context = cell.get_cell_extra_context({})
176 176
        assert context['json'] is None
177
        assert context['json_url'] == 'http://test2'
177
        assert context['json_url'] == 'http://example.net/'
178 178
        assert context['json_status'] == 404
179 179
        assert 'json_error' not in context
180 180

  
......
182 182
                                              headers={'content-type': 'application/json'})
183 183
        context = cell.get_cell_extra_context({})
184 184
        assert context['json'] is None
185
        assert context['json_url'] == 'http://test2'
185
        assert context['json_url'] == 'http://example.net/'
186 186
        assert context['json_status'] == 404
187 187
        assert context['json_error'] == data
188 188

  
......
192 192
        context = cell.get_cell_extra_context({})
193 193
        assert context['json'] is None
194 194
        assert context['json_status'] == -1
195
        assert context['json_url'] == 'http://test2'
195
        assert context['json_url'] == 'http://example.net/'
196 196
        assert context['json_error'] == 'boom'
197 197
        assert isinstance(context['json_exception'], requests.ConnectionError)
198 198

  
199
        cell.url = ''  # no URL -> no request, no data, no status
200
        requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200)
201
        context = cell.get_cell_extra_context({})
202
        assert context['json'] is None
203
        assert context['json_url'] == ''
204
        assert 'json_status' not in context
205
        assert 'json_error' not in context
206

  
199 207
    with pytest.raises(NothingInCacheException):
200 208
        cell.url = 'http://test3'
201 209
        cell.render({})
......
494 502
            assert context['plop_url'] == 'http://bar'
495 503
            assert context['plop_status'] == 200
496 504
            assert 'plop_error' not in context
505

  
506
    # additional-data url depends on others results, with Django-syntax URL
507
    with override_settings(JSON_CELL_TYPES={
508
            'test-config-json-cell-2': {
509
                'name': 'Foobar',
510
                'url': 'http://foo',
511
                'additional-data': [
512
                    {'key': 'plop', 'url': 'http://{{json.data}}', 'log_errors': False, 'timeout': 42},
513
                    {'key': 'plop2', 'url': '{% if plop %}http://{{json.data}}/{{plop.data}}{% endif %}', 'log_errors': False,
514
                     'timeout': 10},
515
                ]
516
            }},
517
            TEMPLATE_DIRS=['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]):
518
        cell = ConfigJsonCell()
519
        cell.key = 'test-config-json-cell-2'
520
        cell.page = page
521
        cell.title = 'Example Site'
522
        cell.order = 0
523
        cell.save()
524

  
525
        data = {'data': 'bar'}
526

  
527
        with mock.patch('combo.utils.requests.get') as requests_get:
528
            requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200)
529
            url = reverse('combo-public-ajax-page-cell',
530
                kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()})
531
            resp = app.get(url)
532
            assert resp.body.strip() == '/var1=bar/var2=bar/'
533
            assert len(requests_get.mock_calls) == 3
534
            assert requests_get.mock_calls[0][1][0] == 'http://foo'
535
            assert requests_get.mock_calls[0][-1]['log_errors'] == True
536
            assert requests_get.mock_calls[0][-1]['timeout'] == 28
537
            assert requests_get.mock_calls[1][1][0] == 'http://bar'
538
            assert requests_get.mock_calls[1][-1]['log_errors'] == False
539
            assert requests_get.mock_calls[1][-1]['timeout'] == 42
540
            assert requests_get.mock_calls[2][1][0] == 'http://bar/bar'
541
            assert requests_get.mock_calls[2][-1]['log_errors'] == False
542
            assert requests_get.mock_calls[2][-1]['timeout'] == 10
543
            context = cell.get_cell_extra_context({})
544
            assert context['json'] == data
545
            assert context['json_url'] == 'http://foo'
546
            assert context['json_status'] == 200
547
            assert context['plop'] == data
548
            assert context['plop_url'] == 'http://bar'
549
            assert context['plop_status'] == 200
550
            assert context['plop2'] == data
551
            assert context['plop2_url'] == 'http://bar/bar'
552
            assert context['plop2_status'] == 200
553

  
554
        with mock.patch('combo.utils.requests.get') as requests_get:
555
            requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=404,
556
                                                  headers={'content-type': 'application/json'})
557
            url = reverse('combo-public-ajax-page-cell',
558
                kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()})
559
            resp = app.get(url)
560
            assert resp.body.strip() == '/var1=/var2=/'
561
            # can not create plop and plop2 url: only one request for "json"
562
            assert len(requests_get.mock_calls) == 2
563
            assert requests_get.mock_calls[0][1][0] == 'http://foo'
564
            context = cell.get_cell_extra_context({})
565
            assert context['json'] == None
566
            assert context['json_url'] == 'http://foo'
567
            assert context['json_status'] == 404
568
            assert context['json_error'] == data
569
            assert context['plop'] == None
570
            assert context['plop_url'] == 'http://'
571
            assert context['plop_status'] == 404
572
            assert context['plop_error'] == data
573
            # plop2 url is empty, no request: None value, no status
574
            assert context['plop2'] == None
575
            assert context['plop2_url'] == ''
576
            assert 'plop2_status' not in context
577
            assert 'plop2_error' not in context
tests/test_utils.py
1 1
import pytest
2 2

  
3 3
from combo.utils import (aes_hex_decrypt, aes_hex_encrypt, get_templated_url,
4
                         UnknownTemplateVariableError)
4
                         TemplateError)
5 5
from django.conf import settings
6 6
from django.test import override_settings
7 7
from django.template import Context
......
44 44
    assert get_templated_url('foobar[[]]') == 'foobar[]'
45 45
    assert get_templated_url('foobar[[]test]') == 'foobar[test]'
46 46

  
47
    with pytest.raises(UnknownTemplateVariableError):
47
    assert get_templated_url('{{ test_url }}') == ''
48
    with pytest.raises(TemplateError, match='unknown variable test_url'):
48 49
        get_templated_url('[test_url]')
50

  
49 51
    with override_settings(TEMPLATE_VARS={'test_url': 'http://www.example.net'}):
52
        assert get_templated_url('{{ test_url }}') == 'http://www.example.net'
50 53
        assert get_templated_url('[test_url]') == 'http://www.example.net'
54
        assert get_templated_url('{{ test_url }}/hello') == 'http://www.example.net/hello'
51 55
        assert get_templated_url('[test_url]/hello') == 'http://www.example.net/hello'
52 56

  
53 57
    # contexts without users
......
55 59
    request.user = None
56 60
    for context in (None, Context({}), Context({'request': None}),
57 61
                    Context({'request': request})):
62
        assert get_templated_url('NameID={{ user_nameid }}', context=context) == 'NameID='
63
        assert get_templated_url('email={{ user_email }}', context=context) == 'email='
58 64
        if context is None:
59
            with pytest.raises(UnknownTemplateVariableError) as e:
65
            with pytest.raises(TemplateError, match='unknown variable user_nameid'):
60 66
                get_templated_url('NameID=[user_nameid]', context=context)
61
            with pytest.raises(UnknownTemplateVariableError) as e:
67
            with pytest.raises(TemplateError, match='unknown variable user_email'):
62 68
                get_templated_url('email=[user_email]', context=context)
63 69
        else:
64 70
            assert get_templated_url('NameID=[user_nameid]', context=context) == 'NameID='
65 71
            assert get_templated_url('email=[user_email]', context=context) == 'email='
66
        with pytest.raises(UnknownTemplateVariableError) as e:
72
        with pytest.raises(TemplateError, match='unknown variable bar'):
67 73
            get_templated_url('foo=[bar]', context=context)
68 74
        if context:
69 75
            context['foobar'] = 'barfoo'
76
            assert get_templated_url('{{foobar}}', context=context) == 'barfoo'
70 77
            assert get_templated_url('[foobar]', context=context) == 'barfoo'
71 78

  
72 79
    # contexts with users
73 80
    request = RequestFactory().get('/')
74 81
    request.user = MockUser(samlized=False)
75 82
    context = Context({'request': request})
83
    assert get_templated_url('email={{ user_email }}', context=context) == \
84
            'email=foo%3D3%40example.net'
76 85
    assert get_templated_url('email=[user_email]', context=context) == \
77 86
            'email=foo%3D3%40example.net'
78 87
    request.user = MockUser(samlized=True)
88
    assert get_templated_url('email={{user_email}}&NameID={{user_nameid}}', context=context) == \
89
            'email=foo%3D3%40example.net&NameID=r2%26d2'
79 90
    assert get_templated_url('email=[user_email]&NameID=[user_nameid]', context=context) == \
80 91
            'email=foo%3D3%40example.net&NameID=r2%26d2'
81 92

  
......
84 95
    request.user = MockUser(samlized=True)
85 96
    context = Context({'request': request})
86 97
    context['foobar'] = 'barfoo'
98
    assert get_templated_url('{{ foobar }}/email={{ user_email }}&NameID={{ user_nameid }}',
99
            context=context) == 'barfoo/email=foo%3D3%40example.net&NameID=r2%26d2'
87 100
    assert get_templated_url('[foobar]/email=[user_email]&NameID=[user_nameid]',
88 101
            context=context) == 'barfoo/email=foo%3D3%40example.net&NameID=r2%26d2'
89 102
    with override_settings(TEMPLATE_VARS={'test_url': 'http://www.example.net'}):
90 103
        request = RequestFactory().get('/')
91 104
        request.user = MockUser(samlized=True)
92 105
        context = Context({'foobar': 'barfoo', 'request': request})
106
        assert get_templated_url('{{test_url}}/{{foobar}}/?NameID={{user_nameid}}&email={{user_email}}',
107
                                 context=context) == \
108
                'http://www.example.net/barfoo/?NameID=r2%26d2&email=foo%3D3%40example.net'
93 109
        assert get_templated_url('[test_url]/[foobar]/?NameID=[user_nameid]&email=[user_email]',
94 110
                                 context=context) == \
95 111
                'http://www.example.net/barfoo/?NameID=r2%26d2&email=foo%3D3%40example.net'
......
101 117
    context.update({'foo': 'bar'})
102 118
    ctx = Context()
103 119
    ctx.update(context)
120
    assert get_templated_url('{{ foo }}', context=ctx) == 'bar'
104 121
    assert get_templated_url('[foo]', context=ctx) == 'bar'
122

  
123
    # accept django syntax
124
    assert get_templated_url('{% if "foo" %}ok{% endif %}') == 'ok'
125
    assert get_templated_url('{% if foo %}{{ foo }}{% endif %}') == ''
126
    assert get_templated_url('{% if foo %}{{ foo }}{% endif %}', context={'foo': 'ok'}) == 'ok'
127
    assert get_templated_url('{{ bar|default:"ok" }}') == 'ok'
128
    assert get_templated_url('{{ foo|default:"ko" }}', context={'foo': 'ok'}) == 'ok'
129
    assert get_templated_url('{nodjango}') == '{nodjango}'
130
    assert get_templated_url('{{') == '{{'
131
    assert get_templated_url('{%{{ ok }}{%') == '{%{%'
132
    assert get_templated_url('{{ foo.bar }}', context={'foo': {'bar': 'ok'}}) == 'ok'
133
    assert get_templated_url('{{ foo.bar }}') == ''
134
    assert get_templated_url('{{ foo.0 }}{{ foo.1 }}{{ foo.2 }}', context={'foo': ['ok', 'doc']}) == 'okdoc'
135
    assert get_templated_url('{{ foo.0.bar }}{{ foo.0.zoo }}', context={'foo': [{'bar': 'ok'}, 'ko']}) == 'ok'
136
    # catch django syntax errors in TemplateError
137
    for template in ('{% foobar %}', '{% if "coucou" %}', '{{}}', '{{ if "x" }}', '{{ _private }}'):
138
        with pytest.raises(TemplateError, match='syntax error'):
139
            assert get_templated_url(template, context=ctx) == 'bar'
105
-