Projet

Général

Profil

0001-lingo-start-unauthenticated-user-support-in-API-3687.patch

Emmanuel Cazenave, 17 décembre 2019 10:50

Télécharger (8,54 ko)

Voir les différences:

Subject: [PATCH] lingo: start unauthenticated user support in API (#36875)

 combo/apps/lingo/urls.py    |  2 +-
 combo/apps/lingo/views.py   | 38 ++++++++++++++++++++----------
 tests/test_lingo_payment.py | 47 +++++++++++++++++++++++++++++++++----
 3 files changed, 70 insertions(+), 17 deletions(-)
combo/apps/lingo/urls.py
74 74
        ItemDownloadView.as_view(), name='download-item-pdf'),
75 75
    url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/$',
76 76
        ItemView.as_view(), name='view-item'),
77
    url(r'^lingo/item/(?P<item_id>\d+)/pay$',
77
    url(r'^lingo/item/(?P<signature>\w+)/pay$',
78 78
        BasketItemPayView.as_view(), name='basket-item-pay-view'),
79 79
    url(r'^lingo/self-invoice/(?P<cell_id>\w+)/$', SelfInvoiceView.as_view(),
80 80
        name='lingo-self-invoice'),
combo/apps/lingo/views.py
39 39
import eopayment
40 40

  
41 41
from combo.data.models import Page
42
from combo.utils import check_request_signature, aes_hex_decrypt, DecryptionError
42
from combo.utils import check_request_signature, aes_hex_decrypt, aes_hex_encrypt, DecryptionError
43 43
from combo.profile.utils import get_user_from_name_id
44 44

  
45 45
from .models import (Regie, BasketItem, Transaction, TransactionOperation,
......
150 150
            elif request.GET.get('email'):
151 151
                user = User.objects.get(email=request.GET.get('email'))
152 152
            else:
153
                raise Exception('no user specified')
153
                user = None
154 154
        except User.DoesNotExist:
155 155
            raise Exception('unknown user')
156 156

  
......
192 192
                    'Bad format for capture date, it should be yyyy-mm-dd.')
193 193

  
194 194
        item.save()
195
        item.regie.compute_extra_fees(user=item.user)
195
        if user:
196
            item.regie.compute_extra_fees(user=item.user)
196 197

  
197
        payment_url = reverse('basket-item-pay-view', kwargs={'item_id': item.id})
198
        payment_url = reverse(
199
            'basket-item-pay-view',
200
            kwargs={
201
                'signature': aes_hex_encrypt(settings.SECRET_KEY, str(item.id))
202
            })
198 203
        return JsonResponse({'result': 'success', 'id': str(item.id),
199 204
                             'payment_url': request.build_absolute_uri(payment_url)})
200 205

  
......
321 326

  
322 327
class PayMixin(object):
323 328
    @atomic
324
    def handle_payment(self, request, regie, items, remote_items, next_url='/', email=''):
329
    def handle_payment(
330
            self, request, regie, items, remote_items, next_url='/', email='', firstname='',
331
            lastname=''):
325 332
        if remote_items:
326 333
            total_amount = sum([x.amount for x in remote_items])
327 334
        else:
......
344 351
            lastname = user.last_name
345 352
        else:
346 353
            transaction.user = None
347
            firstname = ''
348
            lastname = ''
349 354

  
350 355
        transaction.save()
351 356
        transaction.regie = regie
......
436 441
class BasketItemPayView(PayMixin, View):
437 442
    def get(self, request, *args, **kwargs):
438 443
        next_url = request.GET.get('next_url') or '/'
439
        if not (request.user and request.user.is_authenticated):
440
            return HttpResponseForbidden(_('No item payment allowed for anonymous users.'))
444
        email = request.GET.get('email', '')
445
        firstname = request.GET.get('firstname', '')
446
        lastname = request.GET.get('lastname', '')
447

  
448
        signature = kwargs.get('signature')
449
        try:
450
            item_id = aes_hex_decrypt(settings.SECRET_KEY, signature)
451
        except DecryptionError:
452
            return HttpResponseForbidden(_('Invalid payment request.'))
441 453

  
442
        item = BasketItem.objects.get(pk=kwargs['item_id'])
454
        item = BasketItem.objects.get(pk=item_id)
443 455
        regie = item.regie
444 456
        if regie.extra_fees_ws_url:
445 457
            return HttpResponseForbidden(_('No item payment allowed as extra fees set.'))
446 458

  
447
        if item.user != request.user:
459
        if item.user and item.user != request.user:
448 460
            return HttpResponseForbidden(_('Wrong item: payment not allowed.'))
449 461

  
450
        return self.handle_payment(request, regie, [item], [], next_url)
462
        return self.handle_payment(
463
            request, regie, [item], [], next_url, email, firstname, lastname
464
        )
451 465

  
452 466

  
453 467
class PaymentException(Exception):
tests/test_lingo_payment.py
22 22
from combo.apps.lingo.models import (
23 23
    Regie, BasketItem, Transaction, TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell,
24 24
    PaymentBackend)
25
from combo.utils import sign_url
25
from combo.utils import aes_hex_decrypt, sign_url
26 26

  
27 27
from .test_manager import login
28 28

  
......
297 297
    assert resp.status_code == 200
298 298
    response = json.loads(resp.text)
299 299
    assert response['result'] == 'success'
300
    assert response['payment_url'].endswith('/lingo/item/%s/pay' % item.id)
300
    payment_url = urlparse.urlparse(response['payment_url'])
301
    assert payment_url.path.startswith('/lingo/item/')
302
    assert payment_url.path.endswith('/pay')
301 303
    assert BasketItem.objects.filter(amount=Decimal('22.23')).exists()
302 304
    assert BasketItem.objects.filter(amount=Decimal('22.23'))[0].regie_id == other_regie.id
303 305

  
......
374 376
    assert 'Can not add a basket item to a remote regie.' in resp.text
375 377

  
376 378

  
379
def test_unauthenticated_user(app, regie):
380
    url = reverse('api-add-basket-item')
381
    data = {'amount': 10, 'display_name': 'test item'}
382
    url = sign_url(url, settings.LINGO_API_SIGN_KEY)
383
    resp = app.post_json(url, params=data)
384
    assert resp.status_code == 200
385
    payment_url = resp.json['payment_url']
386

  
387
    item = BasketItem.objects.first()
388
    assert item.user is None
389
    assert item.amount == Decimal('10.00')
390
    path = urlparse.urlparse(payment_url).path
391
    start = '/lingo/item/'
392
    end = '/pay'
393
    assert path.startswith(start)
394
    assert path.endswith(end)
395
    signature = path.replace(start, '').replace(end, '')
396
    assert aes_hex_decrypt(settings.SECRET_KEY, signature) == str(item.id)
397

  
398
    resp = app.get(
399
        payment_url,
400
        params={
401
            'next_url': 'http://example.net/form/id/',
402
            'email': 'foo@localhost',
403
            'firstname': 'foo',
404
            'lastname': 'bar'
405
        }
406
    )
407
    assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
408
    qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
409
    assert qs['amount'] == ['10.00']
410
    assert qs['email'] == ['foo@localhost']
411

  
412
    # bad signature
413
    payment_url = '/lingo/item/xxxxx/pay'
414
    resp = app.get(payment_url, status=403)
415
    assert 'Invalid payment request.' in resp.text
416

  
377 417
def test_cant_pay_if_different_capture_date(app, basket_page, regie, user):
378 418
    capture1 = (timezone.now() + timedelta(days=1)).date()
379 419
    capture2 = (timezone.now() + timedelta(days=2)).date()
......
417 457
    assert BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=True).exists()
418 458
    payment_url = resp.json['payment_url']
419 459
    resp = app.get(payment_url, status=403)
420
    assert 'No item payment allowed for anonymous users.' in resp.text
460
    assert 'Wrong item: payment not allowed.' in resp.text
421 461

  
422 462
    login(app, username='john.doe', password='john.doe')
423 463
    resp = app.get(payment_url, status=403)
......
440 480
    assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
441 481
    qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
442 482
    assert qs['amount'] == ['12.00']
443

  
444 483
    # simulate successful payment response from dummy backend
445 484
    data = {'transaction_id': qs['transaction_id'][0], 'ok': True,
446 485
            'amount': qs['amount'][0], 'signed': True}
447
-