lingo: encrypt invoice id in URLs (#12669)
This commit is contained in:
parent
2a3f362245
commit
af34561673
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue