0001-utils-use-pycrytodomex-replace-Crypto-with-Cryptodom.patch
combo/apps/lingo/models.py | ||
---|---|---|
30 | 30 |
from django.conf import settings |
31 | 31 |
from django.db import models |
32 | 32 |
from django.forms import models as model_forms, Select |
33 | 33 |
from django.utils.translation import ugettext_lazy as _ |
34 | 34 |
from django.utils import timezone, dateparse, six |
35 | 35 |
from django.core.mail import EmailMultiAlternatives |
36 | 36 |
from django.urls import reverse |
37 | 37 |
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError |
38 |
from django.utils.encoding import python_2_unicode_compatible |
|
38 |
from django.utils.encoding import force_bytes, python_2_unicode_compatible
|
|
39 | 39 |
from django.utils.formats import localize |
40 | 40 |
from django.utils.http import urlencode |
41 | 41 |
from django.utils.six.moves.urllib import parse as urlparse |
42 | 42 |
from django.utils.timezone import make_aware, utc |
43 | 43 | |
44 | 44 |
from django.contrib.auth.models import User |
45 | 45 |
from django.template.loader import render_to_string |
46 | 46 | |
... | ... | |
454 | 454 |
'autobilling': _('Autobilling has been set for this invoice.'), |
455 | 455 |
'past-due-date': _('Due date is over.'), |
456 | 456 |
} |
457 | 457 |
return settings.LINGO_NO_ONLINE_PAYMENT_REASONS.get(self.no_online_payment_reason, |
458 | 458 |
reasons.get(self.no_online_payment_reason)) |
459 | 459 | |
460 | 460 |
@property |
461 | 461 |
def crypto_id(self): |
462 |
return aes_hex_encrypt(settings.SECRET_KEY, str(self.id))
|
|
462 |
return aes_hex_encrypt(settings.SECRET_KEY, force_bytes(str(self.id)))
|
|
463 | 463 | |
464 | 464 | |
465 | 465 |
class Transaction(models.Model): |
466 | 466 |
regie = models.ForeignKey(Regie, on_delete=models.CASCADE, null=True) |
467 | 467 |
items = models.ManyToManyField(BasketItem, blank=True) |
468 | 468 |
remote_items = models.CharField(max_length=512) |
469 | 469 |
to_be_paid_remote_items = models.CharField(max_length=512, null=True) |
470 | 470 |
start_date = models.DateTimeField(auto_now_add=True) |
combo/utils/crypto.py | ||
---|---|---|
11 | 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | 12 |
# GNU Affero General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import binascii |
18 | 18 | |
19 |
from Crypto.Cipher import AES |
|
20 |
from Crypto.Protocol.KDF import PBKDF2 |
|
21 |
from Crypto import Random |
|
19 |
from Cryptodome.Cipher import AES
|
|
20 |
from Cryptodome.Protocol.KDF import PBKDF2
|
|
21 |
from Cryptodome import Random
|
|
22 | 22 | |
23 | 23 |
from django.utils import six |
24 | 24 |
from django.utils.encoding import force_text |
25 | 25 | |
26 | 26 | |
27 | 27 |
class DecryptionError(Exception): |
28 | 28 |
pass |
29 | 29 |
setup.py | ||
---|---|---|
159 | 159 |
'XStatic_OpenSans', |
160 | 160 |
'XStatic_roboto-fontface>=0.5.0.0', |
161 | 161 |
'eopayment>=1.43', |
162 | 162 |
'python-dateutil', |
163 | 163 |
'djangorestframework>=3.3, <3.8', |
164 | 164 |
'django-ratelimit<3', |
165 | 165 |
'sorl-thumbnail', |
166 | 166 |
'Pillow', |
167 |
'pycryptodomex', |
|
167 | 168 |
'pyproj', |
168 | 169 |
'pywebpush', |
169 | 170 |
'pygal', |
170 | 171 |
'lxml', |
171 | 172 |
], |
172 | 173 |
zip_safe=False, |
173 | 174 |
cmdclass={ |
174 | 175 |
'build': build, |
tests/test_lingo_remote_regie.py | ||
---|---|---|
8 | 8 |
from requests.exceptions import ConnectionError |
9 | 9 | |
10 | 10 |
from django.apps import apps |
11 | 11 |
from django.test.client import RequestFactory |
12 | 12 |
from django.test import override_settings |
13 | 13 |
from django.urls import reverse |
14 | 14 |
from django.conf import settings |
15 | 15 |
from django.core.management import call_command |
16 |
from django.utils.encoding import force_text |
|
16 |
from django.utils.encoding import force_bytes, force_text
|
|
17 | 17 |
from django.utils.six.moves.urllib import parse as urlparse |
18 | 18 |
from django.utils.timezone import timedelta, now |
19 | 19 |
from django.contrib.auth.models import User |
20 | 20 | |
21 | 21 |
from combo.utils import check_query, aes_hex_encrypt |
22 | 22 |
from combo.data.models import Page |
23 | 23 |
from combo.apps.lingo.models import (Regie, ActiveItems, ItemsHistory, SelfDeclaredInvoicePayment, |
24 | 24 |
Transaction, BasketItem, PaymentBackend) |
... | ... | |
215 | 215 |
content = cell.render(context) |
216 | 216 |
assert content.strip() == '' |
217 | 217 | |
218 | 218 | |
219 | 219 |
@mock.patch('combo.apps.lingo.models.Regie.pay_invoice') |
220 | 220 |
@mock.patch('combo.apps.lingo.models.requests.get') |
221 | 221 |
def test_anonymous_successful_item_payment(mock_get, mock_pay_invoice, app, remote_regie): |
222 | 222 |
assert remote_regie.is_remote() == True |
223 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, 'F201601')
|
|
223 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
|
224 | 224 |
# invoice with amount_paid |
225 | 225 |
invoices = copy.deepcopy(INVOICES) |
226 | 226 |
invoices[0]['amount'] = '100.00' |
227 | 227 |
invoices[0]['amount_paid'] = '23.45' |
228 | 228 |
mock_json = mock.Mock() |
229 | 229 |
mock_json.json.return_value = {'err': 0, 'data': invoices[0]} |
230 | 230 |
mock_get.return_value = mock_json |
231 | 231 |
mock_pay_invoice.return_value = mock.Mock(status_code=200) |
... | ... | |
289 | 289 |
assert b_item.amount == Decimal(INVOICES[0]['amount']) |
290 | 290 |
assert b_item in trans.items.all() |
291 | 291 | |
292 | 292 |
assert resp.status_code == 200 |
293 | 293 | |
294 | 294 |
@mock.patch('combo.apps.lingo.models.requests.get') |
295 | 295 |
def test_anonymous_item_payment_email_error(mock_get, app, remote_regie): |
296 | 296 |
assert remote_regie.is_remote() == True |
297 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, 'F201601')
|
|
297 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
|
298 | 298 |
mock_json = mock.Mock() |
299 | 299 |
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]} |
300 | 300 |
mock_get.return_value = mock_json |
301 | 301 |
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id)) |
302 | 302 |
form = resp.form |
303 | 303 |
resp = form.submit() |
304 | 304 | |
305 | 305 |
assert resp.status_code == 302 |
... | ... | |
358 | 358 | |
359 | 359 |
@mock.patch('combo.apps.lingo.models.Regie.pay_invoice') |
360 | 360 |
@mock.patch('combo.apps.lingo.models.requests.get') |
361 | 361 |
@mock.patch('combo.apps.lingo.models.requests.post') |
362 | 362 |
def test_remote_item_payment_failure(mock_post, mock_get, mock_pay_invoice, app, remote_regie): |
363 | 363 |
page = Page(title='xxx', slug='active-remote-invoices-page', template_name='standard') |
364 | 364 |
page.save() |
365 | 365 |
assert remote_regie.is_remote() |
366 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, 'F201601')
|
|
366 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
|
367 | 367 |
mock_json = mock.Mock() |
368 | 368 |
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]} |
369 | 369 |
mock_get.return_value = mock_json |
370 | 370 |
mock_pay_invoice.return_value = mock.Mock(status_code=200) |
371 | 371 |
resp = app.get('/lingo/item/%s/%s/?page=%s' % (remote_regie.id, encrypt_id, page.pk)) |
372 | 372 |
form = resp.form |
373 | 373 | |
374 | 374 |
assert 'email' in form.fields |
... | ... | |
427 | 427 | |
428 | 428 | |
429 | 429 |
@mock.patch('combo.apps.lingo.models.Regie.pay_invoice') |
430 | 430 |
@mock.patch('combo.apps.lingo.models.requests.get') |
431 | 431 |
def test_remote_invoice_successfull_payment_redirect(mock_get, mock_pay_invoice, app, remote_regie): |
432 | 432 |
page = Page(title='xxx', slug='active-remote-invoices-page', template_name='standard') |
433 | 433 |
page.save() |
434 | 434 |
assert remote_regie.is_remote() |
435 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, 'F201601')
|
|
435 |
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
|
436 | 436 |
mock_json = mock.Mock() |
437 | 437 |
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]} |
438 | 438 |
mock_get.return_value = mock_json |
439 | 439 |
mock_pay_invoice.return_value = mock.Mock(status_code=200) |
440 | 440 |
resp = app.get('/lingo/item/%s/%s/?page=%s' % (remote_regie.id, encrypt_id, page.pk)) |
441 | 441 |
form = resp.form |
442 | 442 |
assert form['next_url'].value == '/active-remote-invoices-page/' |
443 | 443 |
form['email'] = 'test@example.net' |
tests/test_utils.py | ||
---|---|---|
2 | 2 | |
3 | 3 |
from combo.utils import (aes_hex_decrypt, aes_hex_encrypt, get_templated_url, |
4 | 4 |
TemplateError) |
5 | 5 |
from django.conf import settings |
6 | 6 |
from django.test import override_settings |
7 | 7 |
from django.template import Context, RequestContext |
8 | 8 |
from django.test.client import RequestFactory |
9 | 9 |
from django.contrib.auth.models import AnonymousUser |
10 |
from django.utils.encoding import force_bytes |
|
10 | 11 | |
11 | 12 | |
12 | 13 |
class MockUser(object): |
13 | 14 |
email = 'foo=3@example.net' |
14 | 15 |
is_authenticated = True |
15 | 16 | |
16 | 17 |
def __init__(self, samlized=True): |
17 | 18 |
self.samlized = samlized |
... | ... | |
20 | 21 |
if self.samlized: |
21 | 22 |
return 'r2&d2' |
22 | 23 |
return None |
23 | 24 | |
24 | 25 | |
25 | 26 |
def test_crypto_url(): |
26 | 27 |
invoice_id = '12-1234' |
27 | 28 |
key = settings.SECRET_KEY |
28 |
assert aes_hex_decrypt(key, aes_hex_encrypt(key, invoice_id)) == invoice_id
|
|
29 |
assert aes_hex_decrypt(key, aes_hex_encrypt(key, force_bytes(invoice_id))) == invoice_id
|
|
29 | 30 | |
30 | 31 | |
31 | 32 |
def test_templated_url(): |
32 | 33 |
assert get_templated_url('foobar') == 'foobar' |
33 | 34 |
assert get_templated_url('foo[]bar') == 'foo[]bar' |
34 | 35 |
assert get_templated_url('foo[bar') == 'foo[bar' |
35 | 36 |
assert get_templated_url('foo]bar') == 'foo]bar' |
36 | 37 |
assert get_templated_url('foo]bar[') == 'foo]bar[' |
37 |
- |