0001-wcs-add-support-for-linking-displaying-card-file-fie.patch
combo/apps/wcs/models.py | ||
---|---|---|
996 | 996 |
if not card_id: |
997 | 997 |
return extra_context |
998 | 998 |
card_slug = self.carddef_reference.split(':')[1] |
999 |
api_url = '/api/cards/%s/%s/' % (card_slug, card_id) |
|
999 |
api_url = '/api/cards/%s/%s/?include-files-content=off' % (card_slug, card_id)
|
|
1000 | 1000 | |
1001 | 1001 |
wcs_site = get_wcs_services().get(self.wcs_site) |
1002 | 1002 |
combo/apps/wcs/templates/combo/wcs/card-field-value.html | ||
---|---|---|
1 |
{% spaceless %} |
|
1 |
{% load combo wcs %}{% spaceless %}
|
|
2 | 2 |
{% if field.type == "text" and mode != 'inline' and value %} |
3 | 3 |
<div class="value">{{ field|format_text:value }}</div> |
4 | 4 |
{% else %} |
... | ... | |
7 | 7 |
{{ value|date }} |
8 | 8 |
{% elif field.type == "bool" and value is not None %} |
9 | 9 |
{{ value|yesno }} |
10 |
{% elif field.type == 'file' and value %} |
|
11 |
{% if value.content_type|startswith:"image/" %} |
|
12 |
<img alt="" src="{% make_public_url url=value.url %}"> |
|
13 |
{% else %} |
|
14 |
<a href="{% make_public_url url=value.url %}" download="{{ value.filename }}">{{ value.filename }}</a> |
|
15 |
{% endif %} |
|
10 | 16 |
{% else %} |
11 | 17 |
{{ value|default:"" }} |
12 | 18 |
{% endif %} |
combo/apps/wcs/templates/combo/wcs/card.html | ||
---|---|---|
35 | 35 | |
36 | 36 |
{% else %} |
37 | 37 |
{% for field in schema.fields %} |
38 |
{% if 'varname' in field and field.varname and field.type != 'file' %}
|
|
38 |
{% if 'varname' in field and field.varname %} |
|
39 | 39 |
{% with card.fields|get:field.varname as value %} |
40 | 40 |
<div class="card--auto-field"> |
41 | 41 |
<span class="label">{{ field.label }}</span> |
combo/apps/wcs/templatetags/wcs.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django import template |
18 |
from django.conf import settings |
|
19 |
from django.urls import reverse |
|
20 |
from django.utils.encoding import force_bytes |
|
18 | 21 |
from django.utils.html import escape |
19 | 22 |
from django.utils.safestring import mark_safe |
20 | 23 | |
24 |
from combo.utils import aes_hex_encrypt |
|
25 | ||
21 | 26 |
register = template.Library() |
22 | 27 | |
23 | 28 | |
... | ... | |
71 | 76 |
if field.get('pre'): |
72 | 77 |
return mark_safe('<pre>%s</pre>' % escape(value)) |
73 | 78 |
return mark_safe('<p>' + '\n'.join([(escape(x) or '</p><p>') for x in value.splitlines()]) + '</p>') |
79 | ||
80 | ||
81 |
@register.simple_tag(takes_context=True) |
|
82 |
def make_public_url(context, url): |
|
83 |
if not context.request.session.session_key: |
|
84 |
context.request.session.cycle_key() |
|
85 |
session_key = context.request.session.session_key |
|
86 |
return reverse( |
|
87 |
'wcs-redirect-crypto-url', |
|
88 |
kwargs={ |
|
89 |
'session_key': session_key, |
|
90 |
'crypto_url': aes_hex_encrypt(settings.SECRET_KEY, force_bytes(url)), |
|
91 |
}, |
|
92 |
) |
combo/apps/wcs/urls.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
from django.conf.urls import url |
18 | 18 | |
19 |
from .views import TrackingCodeView, tracking_code_search |
|
19 |
from .views import TrackingCodeView, redirect_crypto_url, tracking_code_search
|
|
20 | 20 | |
21 | 21 |
urlpatterns = [ |
22 | 22 |
url(r'^tracking-code/$', TrackingCodeView.as_view(), name='wcs-tracking-code'), |
23 | 23 |
url(r'^api/search/tracking-code/$', tracking_code_search, name='wcs-tracking-code-search'), |
24 |
url( |
|
25 |
r'^api/wcs/file/(?P<session_key>\w+)/(?P<crypto_url>[\w,-]+)/$', |
|
26 |
redirect_crypto_url, |
|
27 |
name='wcs-redirect-crypto-url', |
|
28 |
), |
|
24 | 29 |
] |
combo/apps/wcs/views.py | ||
---|---|---|
21 | 21 |
from django.conf import settings |
22 | 22 |
from django.contrib import messages |
23 | 23 |
from django.core.exceptions import DisallowedRedirect, PermissionDenied |
24 |
from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse |
|
24 |
from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect, JsonResponse
|
|
25 | 25 |
from django.utils.http import urlquote |
26 | 26 |
from django.utils.translation import ugettext_lazy as _ |
27 | 27 |
from django.views.decorators.csrf import csrf_exempt |
28 | 28 |
from django.views.generic import View |
29 | 29 | |
30 |
from combo.utils import requests
|
|
31 |
from combo.utils.misc import is_url_from_known_service |
|
30 |
from combo.utils import DecryptionError, aes_hex_decrypt, requests, sign_url
|
|
31 |
from combo.utils.misc import get_known_service_for_url, is_url_from_known_service
|
|
32 | 32 | |
33 | 33 |
from .models import TrackingCodeInputCell |
34 | 34 |
from .utils import get_wcs_services |
... | ... | |
133 | 133 |
} |
134 | 134 |
) |
135 | 135 |
return JsonResponse(response) |
136 | ||
137 | ||
138 |
def redirect_crypto_url(request, session_key, crypto_url): |
|
139 |
if session_key != request.session.session_key: |
|
140 |
return HttpResponseForbidden() |
|
141 |
try: |
|
142 |
real_url = aes_hex_decrypt(settings.SECRET_KEY, crypto_url) |
|
143 |
except DecryptionError: |
|
144 |
return HttpResponseForbidden('invalid crypto url') |
|
145 | ||
146 |
service = get_known_service_for_url(real_url) |
|
147 |
if '?' not in real_url: |
|
148 |
real_url += '?' |
|
149 |
real_url += '&orig=%s' % service['orig'] |
|
150 |
redirect_url = sign_url(real_url, service['secret']) |
|
151 |
return HttpResponseRedirect(redirect_url) |
combo/utils/misc.py | ||
---|---|---|
41 | 41 |
return flat_context |
42 | 42 | |
43 | 43 | |
44 |
def get_known_service_for_url(url): |
|
45 |
netloc = urllib.parse.urlparse(url).netloc |
|
46 |
for services in settings.KNOWN_SERVICES.values(): |
|
47 |
for service in services.values(): |
|
48 |
remote_url = service.get('url') |
|
49 |
if urllib.parse.urlparse(remote_url).netloc == netloc: |
|
50 |
return service |
|
51 |
return None |
|
52 | ||
53 | ||
44 | 54 |
def is_url_from_known_service(url): |
45 | 55 |
netloc = urllib.parse.urlparse(url).netloc |
46 | 56 |
if not netloc: |
47 | 57 |
return True |
48 |
for service_id in settings.KNOWN_SERVICES or {}: |
|
49 |
for service_key in settings.KNOWN_SERVICES[service_id]: |
|
50 |
service = settings.KNOWN_SERVICES[service_id][service_key] |
|
51 |
if urllib.parse.urlparse(service.get('url')).netloc == netloc: |
|
52 |
return True |
|
53 |
return False |
|
58 |
return bool(get_known_service_for_url(url)) |
combo/utils/requests_wrapper.py | ||
---|---|---|
27 | 27 |
from requests import Session as RequestsSession |
28 | 28 |
from requests.auth import AuthBase |
29 | 29 | |
30 |
from .misc import get_known_service_for_url |
|
30 | 31 |
from .signature import sign_url |
31 | 32 | |
32 | 33 | |
... | ... | |
59 | 60 |
self.cookies.clear() |
60 | 61 | |
61 | 62 |
if remote_service == 'auto': |
62 |
remote_service = None |
|
63 |
scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(url) |
|
64 |
for services in settings.KNOWN_SERVICES.values(): |
|
65 |
for service in services.values(): |
|
66 |
remote_url = service.get('url') |
|
67 |
remote_scheme, remote_netloc, dummy, dummy, dummy, dummy = urllib.parse.urlparse( |
|
68 |
remote_url |
|
69 |
) |
|
70 |
if remote_scheme == scheme and remote_netloc == netloc: |
|
71 |
remote_service = service |
|
72 |
break |
|
73 |
else: |
|
74 |
continue |
|
75 |
break |
|
63 |
remote_service = get_known_service_for_url(url) |
|
76 | 64 |
if remote_service: |
77 | 65 |
# only keeps the path (URI) in url parameter, scheme and netloc are |
78 | 66 |
# in remote_service |
67 |
scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(url) |
|
79 | 68 |
url = urllib.parse.urlunparse(('', '', path, params, query, fragment)) |
80 | 69 |
else: |
81 | 70 |
logging.warning('service not found in settings.KNOWN_SERVICES for %s', url) |
tests/test_wcs.py | ||
---|---|---|
2 | 2 |
import json |
3 | 3 |
import re |
4 | 4 |
import urllib.parse |
5 |
from importlib import import_module |
|
5 | 6 |
from unittest import mock |
6 | 7 | |
7 | 8 |
import pytest |
... | ... | |
195 | 196 |
'fielda': 'a', |
196 | 197 |
'fieldb': True, |
197 | 198 |
'fieldc': '2020-09-28', |
198 |
'fieldd': {'filename': 'file.pdf', 'url': 'http://some-url.com/download?f=42'},
|
|
199 |
'fieldd': {'filename': 'file.pdf', 'url': 'http://127.0.0.1:8999/download?f=42'},
|
|
199 | 200 |
'fielde': 'lorem<strong>ipsum\n\nhello world', |
200 | 201 |
'fieldf': 'lorem<strong>ipsum\n\nhello world', |
201 | 202 |
'related': 'Foo Bar', |
... | ... | |
279 | 280 |
def context(): |
280 | 281 |
ctx = {'request': RequestFactory().get('/')} |
281 | 282 |
ctx['request'].user = None |
282 |
ctx['request'].session = {} |
|
283 |
session_engine = import_module(settings.SESSION_ENGINE) |
|
284 |
ctx['request'].session = session_engine.SessionStore() |
|
283 | 285 |
return ctx |
284 | 286 | |
285 | 287 | |
... | ... | |
1843 | 1845 |
assert PyQuery(result).find('span.label:contains("Related") + span').text() == 'Foo Bar' |
1844 | 1846 |
assert 'related_raw' not in result |
1845 | 1847 |
assert 'related_structured' not in result |
1846 |
assert 'Field D' not in result
|
|
1848 |
assert PyQuery(result).find('span.label:contains("Field D") + span a').text() == 'file.pdf'
|
|
1847 | 1849 | |
1848 | 1850 |
context.pop('title') |
1849 | 1851 |
cell.title_type = 'manual' |
... | ... | |
2561 | 2563 |
request.user = AnonymousUser() |
2562 | 2564 |
hits = search_site(request, 'form') |
2563 | 2565 |
assert len(hits) == 2 |
2566 | ||
2567 | ||
2568 |
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send) |
|
2569 |
def test_card_file_redirection(mock_send, app): |
|
2570 |
page = Page(title='One', slug='one', template_name='standard') |
|
2571 |
page.save() |
|
2572 |
cell = WcsCardInfosCell(page=page, placeholder='content', order=0) |
|
2573 |
cell.carddef_reference = 'default:card_model_1' |
|
2574 |
cell.card_id = '11' |
|
2575 |
cell.save() |
|
2576 |
resp = app.get('/one/') |
|
2577 |
ajax_cell_url = PyQuery(resp.text).find('[data-ajax-cell-url]').attr['data-ajax-cell-url'] |
|
2578 |
resp = app.get(ajax_cell_url) |
|
2579 |
file_url = PyQuery(resp.text).find('[download]').attr['href'] |
|
2580 |
resp = app.get(file_url) |
|
2581 |
assert 'download?f=42' in resp.location |
|
2582 |
assert '&signature=' in resp.location |
|
2583 | ||
2584 |
# invalid crypto |
|
2585 |
resp = app.get(file_url[:-2] + 'X/', status=403) |
|
2586 | ||
2587 |
# invalid session key |
|
2588 |
resp = app.get(file_url.replace('file/', 'file/X'), status=403) |
|
2564 |
- |