lingo: encrypt invoice id in URLs (#12669)

This commit is contained in:
Jean-Baptiste Jaillet 2016-07-20 18:20:41 +02:00 committed by Frédéric Péters
parent 2a3f362245
commit af34561673
6 changed files with 66 additions and 8 deletions

View File

@ -41,7 +41,7 @@ from ckeditor.fields import RichTextField
from combo.data.models import CellBase
from combo.data.library import register_cell_class
from combo.utils import NothingInCacheException, sign_url
from combo.utils import NothingInCacheException, sign_url, aes_hex_encrypt
EXPIRED = 9999
@ -269,6 +269,10 @@ class RemoteItem(object):
return settings.LINGO_NO_ONLINE_PAYMENT_REASONS.get(self.no_online_payment_reason,
reasons.get(self.no_online_payment_reason))
@property
def crypto_id(self):
return aes_hex_encrypt(settings.SECRET_KEY, self.id)
class Transaction(models.Model):
regie = models.ForeignKey(Regie, null=True)

View File

@ -26,11 +26,11 @@
{% endblocktrans %}
</td>
<td>
<a href="{% url 'view-item' regie_id=item.regie.pk item_id=item.id %}" rel="popup" class="icon-view">{% trans "View" %}
<a href="{% url 'view-item' regie_id=item.regie.pk item_crypto_id=item.crypto_id %}" rel="popup" class="icon-view">{% trans "View" %}
{% if item.online_payment and item.amount >= item.regie.payment_min_amount %}{% trans "and pay" %}{% endif %}
</a>
{% if item.has_pdf %}
<br/><a href="{% url 'download-item-pdf' regie_id=item.regie.pk item_id=item.id %}" class="icon-pdf"
<br/><a href="{% url 'download-item-pdf' regie_id=item.regie.pk item_crypto_id=item.crypto_id %}" class="icon-pdf"
>{% trans "Download" %}</a>
{% endif %}
</td>

View File

@ -47,8 +47,8 @@ urlpatterns = patterns('',
url(r'^lingo/return/(?P<regie_pk>\w+)/$', ReturnView.as_view(), name='lingo-return'),
url(r'^manage/lingo/', decorated_includes(manager_required,
include(lingo_manager_urls))),
url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_id>[\w,-]+)/pdf$',
url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/pdf$',
ItemDownloadView.as_view(), name='download-item-pdf'),
url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_id>[\w,-]+)/$',
url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/$',
ItemView.as_view(), name='view-item'),
)

View File

@ -37,7 +37,7 @@ from django.utils.encoding import smart_text
import eopayment
from combo.utils import check_query
from combo.utils import check_query, aes_hex_decrypt
try:
from mellon.models import UserSAMLIdentifier
@ -387,7 +387,8 @@ class ItemDownloadView(View):
def get(self, request, *args, **kwargs):
regie = Regie.objects.get(pk=kwargs['regie_id'])
try:
data = regie.download_item(request, kwargs['item_id'])
item_id = aes_hex_decrypt(settings.SECRET_KEY, kwargs['item_crypto_id'])
data = regie.download_item(request, item_id)
except PermissionDenied:
return HttpResponseForbidden()
@ -409,7 +410,10 @@ class ItemView(TemplateView):
def get_context_data(self, **kwargs):
regie = Regie.objects.get(pk=kwargs['regie_id'])
item = regie.get_item(self.request, kwargs['item_id'])
item_id = aes_hex_decrypt(settings.SECRET_KEY, kwargs['item_crypto_id'])
item = regie.get_item(self.request, item_id)
if not item:
raise Http404(_('No item was found.'))
return {'item': item, 'regie': regie}

View File

@ -18,6 +18,12 @@ import datetime
import base64
import hmac
import hashlib
import binascii
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto import Random
from HTMLParser import HTMLParser
import logging
import random
@ -29,6 +35,42 @@ from django.core.cache import cache
from django.utils.html import strip_tags
from django.utils.http import urlencode, quote
class DecryptionError(Exception):
pass
def aes_hex_encrypt(key, data):
'''Generate an AES key from any key material using PBKDF2, and encrypt data using CFB mode. A
new IV is generated each time, the IV is also used as salt for PBKDF2.
'''
iv = Random.get_random_bytes(2) * 8
aes_key = PBKDF2(key, iv)
aes = AES.new(aes_key, AES.MODE_CFB, iv)
crypted = aes.encrypt(data)
return '%s%s' % (binascii.hexlify(iv[:2]), binascii.hexlify(crypted))
def aes_hex_decrypt(key, payload, raise_on_error=True):
'''Decrypt data encrypted with aes_base64_encrypt'''
try:
iv, crypted = payload[:4], payload[4:]
except (ValueError, TypeError):
if raise_on_error:
raise DecryptionError('bad payload')
return None
try:
iv = binascii.unhexlify(iv) * 8
crypted = binascii.unhexlify(crypted)
except TypeError:
if raise_on_error:
raise DecryptionError('incorrect hexadecimal encoding')
return None
aes_key = PBKDF2(key, iv)
aes = AES.new(aes_key, AES.MODE_CFB, iv)
return aes.decrypt(crypted)
class NothingInCacheException(Exception):
pass

8
tests/test_utils.py Normal file
View File

@ -0,0 +1,8 @@
from combo.utils import aes_hex_decrypt, aes_hex_encrypt
from django.conf import settings
def test_crypto_url():
invoice_id = '12-1234'
key = settings.SECRET_KEY
assert aes_hex_decrypt(key, aes_hex_encrypt(key, invoice_id)) == invoice_id