diff --git a/.gitignore b/.gitignore index 438dfbcd..c65c2cc3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ re2o.png __pycache__/* static_files/* static/logo/* +media/* diff --git a/cotisations/payment.py b/cotisations/payment.py index b2e684d7..07cbe5dc 100644 --- a/cotisations/payment.py +++ b/cotisations/payment.py @@ -11,6 +11,8 @@ from django.utils.datastructures import MultiValueDictKeyError from django.http import HttpResponse, HttpResponseBadRequest from collections import OrderedDict + +from preferences.models import AssoOption from .models import Facture from .payment_utils.comnpay import Payment as ComnpayPayment @@ -36,6 +38,7 @@ def refuse_payment(request): @csrf_exempt def ipn(request): + option, _created = AssoOption.objects.get_or_create() p = ComnpayPayment() order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', ) try: @@ -43,7 +46,7 @@ def ipn(request): except MultiValueDictKeyError: return HttpResponseBadRequest("HTTP/1.1 400 Bad Request") - if not p.validSec(data, "DEMO"): + if not p.validSec(data, option.payment_pass): return HttpResponseBadRequest("HTTP/1.1 400 Bad Request") result = True if (request.POST['result'] == 'OK') else False @@ -51,7 +54,7 @@ def ipn(request): idTransaction = request.POST['idTransaction'] # On vérifie que le paiement nous est destiné - if not idTpe == "DEMO": + if not idTpe == option.payment_id: return HttpResponseBadRequest("HTTP/1.1 400 Bad Request") try: @@ -78,10 +81,14 @@ def ipn(request): def comnpay(facture, request): host = request.get_host() + option, _created = AssoOption.objects.get_or_create() p = ComnpayPayment( - "DEMO", - "DEMO", - 'https://' + host + reverse('cotisations:accept_payment', kwargs={'factureid':facture.id}), + str(option.payment_id), + str(option.payment_pass), + 'https://' + host + reverse( + 'cotisations:accept_payment', + kwargs={'factureid':facture.id} + ), 'https://' + host + reverse('cotisations:refuse_payment'), 'https://' + host + reverse('cotisations:ipn'), "", @@ -90,7 +97,11 @@ def comnpay(facture, request): r = { 'action' : 'https://secure.homologation.comnpay.com', 'method' : 'POST', - 'content' : p.buildSecretHTML("Rechargement du solde", facture.prix(), idTransaction=str(facture.id)), + 'content' : p.buildSecretHTML( + "Rechargement du solde", + facture.prix(), + idTransaction=str(facture.id) + ), 'amount' : facture.prix, } return r diff --git a/preferences/aes_field.py b/preferences/aes_field.py new file mode 100644 index 00000000..76712a81 --- /dev/null +++ b/preferences/aes_field.py @@ -0,0 +1,53 @@ +import string +import binascii +from random import choice +from Crypto.Cipher import AES + +from django.db import models +from django.conf import settings + +EOD = '`%EofD%`' # This should be something that will not occur in strings + + +def genstring(length=16, chars=string.printable): + return ''.join([choice(chars) for i in range(length)]) + + +def encrypt(key, s): + obj = AES.new(key) + datalength = len(s) + len(EOD) + if datalength < 16: + saltlength = 16 - datalength + else: + saltlength = 16 - datalength % 16 + ss = ''.join([s, EOD, genstring(saltlength)]) + return obj.encrypt(ss) + + +def decrypt(key, s): + obj = AES.new(key) + ss = obj.decrypt(s) + return ss.split(bytes(EOD, 'utf-8'))[0] + + +class AESEncryptedField(models.CharField): + def save_form_data(self, instance, data): + setattr(instance, self.name, + binascii.b2a_base64(encrypt(settings.AES_KEY, data))) + + def value_from_object(self, obj): + return decrypt(settings.AES_KEY, + binascii.a2b_base64(getattr(obj, self.attname))).decode('utf-8') + + def to_python(self, value): + if value is None: + return None + return decrypt(settings.AES_KEY, + binascii.a2b_base64(value)).decode('utf-8') + + def from_db_value(self, value, expression, connection, *args): + + if value is None: + return value + return decrypt(settings.AES_KEY, + binascii.a2b_base64(value)).decode('utf-8') diff --git a/preferences/migrations/0039_auto_20180115_0003.py b/preferences/migrations/0039_auto_20180115_0003.py new file mode 100644 index 00000000..da1b6631 --- /dev/null +++ b/preferences/migrations/0039_auto_20180115_0003.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-01-14 23:03 +from __future__ import unicode_literals + +from django.db import migrations, models +import preferences.aes_field + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0038_auto_20180114_2209'), + ] + + operations = [ + migrations.AddField( + model_name='assooption', + name='payment_id', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='assooption', + name='payment_pass', + field=preferences.aes_field.AESEncryptedField(max_length=255, null=True), + ), + ] diff --git a/preferences/migrations/0040_auto_20180115_0010.py b/preferences/migrations/0040_auto_20180115_0010.py new file mode 100644 index 00000000..407cb831 --- /dev/null +++ b/preferences/migrations/0040_auto_20180115_0010.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-01-14 23:10 +from __future__ import unicode_literals + +from django.db import migrations, models +import preferences.aes_field + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0039_auto_20180115_0003'), + ] + + operations = [ + migrations.AlterField( + model_name='assooption', + name='payment_id', + field=models.CharField(default='', max_length=255), + ), + migrations.AlterField( + model_name='assooption', + name='payment_pass', + field=preferences.aes_field.AESEncryptedField(default='', max_length=255), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 133d6f82..d4ef9645 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -28,6 +28,8 @@ from __future__ import unicode_literals from django.db import models import cotisations.models +from .aes_field import AESEncryptedField + class OptionalUser(models.Model): """Options pour l'user : obligation ou nom du telephone, @@ -471,6 +473,16 @@ class AssoOption(models.Model): choices=PAYMENT, default='NONE', ) + payment_id = models.CharField( + max_length=255, + default='', + ) + payment_pass = AESEncryptedField( + max_length=255, + default='', + ) + + class Meta: permissions = ( ("view_assooption", "Peut voir les options de l'asso"), diff --git a/re2o/settings_local.example.py b/re2o/settings_local.example.py index 107fd18f..26c1317d 100644 --- a/re2o/settings_local.example.py +++ b/re2o/settings_local.example.py @@ -26,6 +26,10 @@ SECRET_KEY = 'SUPER_SECRET_KEY' DB_PASSWORD = 'SUPER_SECRET_DB' +# AES key for secret key encryption +AES_KEY = 'WHAT_A_WONDERFULL_KEY' + + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False