Projet

Général

Profil

0001-wcs-add-support-for-linking-displaying-card-file-fie.patch

Frédéric Péters, 08 novembre 2021 19:06

Télécharger (11,8 ko)

Voir les différences:

Subject: [PATCH] wcs: add support for linking/displaying card file fields
 (#51994)

 combo/apps/wcs/models.py                      |  2 +-
 .../templates/combo/wcs/card-field-value.html |  8 ++++-
 combo/apps/wcs/templates/combo/wcs/card.html  |  2 +-
 combo/apps/wcs/templatetags/wcs.py            | 19 ++++++++++++
 combo/apps/wcs/urls.py                        |  7 ++++-
 combo/apps/wcs/views.py                       | 22 +++++++++++--
 combo/utils/misc.py                           | 17 ++++++----
 combo/utils/requests_wrapper.py               | 17 ++--------
 tests/test_wcs.py                             | 31 +++++++++++++++++--
 9 files changed, 95 insertions(+), 30 deletions(-)
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
-