0001-utils-accept-Django-syntax-in-templated_url-19261.patch
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 |
- |