Adds fields for Purchase

tz
Laurent 1 year ago
parent bc983e9561
commit 43c06c13da
  1. 2
      .gitignore
  2. 17
      api/serializers.py
  3. 6
      api/views.py
  4. 51
      shared/cryptography.py
  5. 2
      tournaments/admin.py
  6. 22
      tournaments/migrations/0084_remove_purchase_id_alter_purchase_identifier.py
  7. 18
      tournaments/migrations/0085_rename_identifier_purchase_id.py
  8. 5
      tournaments/models/purchase.py
  9. 11
      tournaments/models/tournament.py
  10. 20
      tournaments/utils/cryptography.py

2
.gitignore vendored

@ -6,7 +6,7 @@ padelclub_backend/settings_local.py
myenv/ myenv/
tournaments/config_local.py shared/config_local.py
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

@ -11,6 +11,21 @@ from django.core.mail import EmailMessage
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from api.tokens import account_activation_token from api.tokens import account_activation_token
from shared.cryptography import encryption_util
class EncryptedUserField(serializers.Field):
def to_representation(self, value):
# Encrypt data when sending it out
return encryption_util.encrypt_aes_gcm(str(value.id))
def to_internal_value(self, data):
# Decrypt data when receiving it
decrypted_user_id = encryption_util.decrypt_aes_gcm(data)
user = CustomUser.objects.get(id=decrypted_user_id)
if decrypted_user_id is None:
raise serializers.ValidationError("Invalid encrypted data")
return user
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
@ -152,6 +167,8 @@ class PlayerRegistrationSerializer(serializers.ModelSerializer):
# ['id', 'team_registration_id', 'first_name', 'last_name', 'licence_id', 'rank', 'has_paid'] # ['id', 'team_registration_id', 'first_name', 'last_name', 'licence_id', 'rank', 'has_paid']
class PurchaseSerializer(serializers.ModelSerializer): class PurchaseSerializer(serializers.ModelSerializer):
user = EncryptedUserField()
class Meta: class Meta:
model = Purchase model = Purchase
fields = '__all__' fields = '__all__'

@ -108,9 +108,9 @@ class PurchaseViewSet(viewsets.ModelViewSet):
return [] return []
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
identifier = request.data.get('identifier') id = request.data.get('id')
if Purchase.objects.filter(identifier=identifier).exists(): if Purchase.objects.filter(id=id).exists():
return Response({"detail": "This transaction identifier is already registered."}, status=status.HTTP_208_ALREADY_REPORTED) return Response({"detail": "This transaction id is already registered."}, status=status.HTTP_208_ALREADY_REPORTED)
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)

@ -0,0 +1,51 @@
from Crypto.Cipher import AES
import base64
import os
from .config_local import CRYPTO_KEY
class EncryptionUtil:
def __init__(self, key):
# In a real application, store this key securely (e.g., environment variables)
self.crypto_key = key
def encrypt_aes_gcm(self, plaintext):
# Decode the base64 encoded key
key = base64.b64decode(self.crypto_key)
# Generate a random 12-byte nonce
nonce = os.urandom(12)
# Create the cipher object
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
# Encrypt the plaintext
ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode('utf-8'))
# Combine nonce, ciphertext, and tag
encrypted_data = nonce + ciphertext + tag
# Encode the result in base64
encrypted_base64 = base64.b64encode(encrypted_data).decode('utf-8')
return encrypted_base64
def decrypt_aes_gcm(self, encrypted_base64):
# Decode the base64 encoded data and key
encrypted_data = base64.b64decode(encrypted_base64)
key = base64.b64decode(self.crypto_key)
# Extract the nonce, tag, and ciphertext from the combined encrypted data
nonce = encrypted_data[:12] # AES GCM nonce is 12 bytes
tag = encrypted_data[-16:] # AES GCM tag is 16 bytes
ciphertext = encrypted_data[12:-16] # Ciphertext is everything in between
# Create the cipher object and decrypt the data
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
decrypted_data = cipher.decrypt_and_verify(ciphertext, tag)
# Convert decrypted bytes to string (assuming UTF-8 encoding)
decrypted_text = decrypted_data.decode('utf-8')
return decrypted_text
encryption_util = EncryptionUtil(CRYPTO_KEY)

@ -74,7 +74,7 @@ class ClubAdmin(admin.ModelAdmin):
search_fields = ('name', 'acronym') search_fields = ('name', 'acronym')
class PurchaseAdmin(admin.ModelAdmin): class PurchaseAdmin(admin.ModelAdmin):
list_display = ['user', 'identifier', 'product_id', 'quantity', 'purchase_date', 'revocation_date', 'expiration_date'] list_display = ['id', 'user', 'product_id', 'quantity', 'purchase_date', 'revocation_date', 'expiration_date']
list_filter = ['user'] list_filter = ['user']
class CourtAdmin(admin.ModelAdmin): class CourtAdmin(admin.ModelAdmin):

@ -0,0 +1,22 @@
# Generated by Django 5.1 on 2024-09-18 08:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0083_purchase_expiration_date'),
]
operations = [
migrations.RemoveField(
model_name='purchase',
name='id',
),
migrations.AlterField(
model_name='purchase',
name='identifier',
field=models.BigIntegerField(primary_key=True, serialize=False, unique=True),
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-09-18 08:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0084_remove_purchase_id_alter_purchase_identifier'),
]
operations = [
migrations.RenameField(
model_name='purchase',
old_name='identifier',
new_name='id',
),
]

@ -3,9 +3,8 @@ import uuid
from . import CustomUser from . import CustomUser
class Purchase(models.Model): class Purchase(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.BigIntegerField(primary_key=True, unique=True, editable=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
identifier = models.BigIntegerField(unique=True)
purchase_date = models.DateTimeField() purchase_date = models.DateTimeField()
product_id = models.CharField(max_length=100) product_id = models.CharField(max_length=100)
quantity = models.IntegerField(null=True, blank=True) quantity = models.IntegerField(null=True, blank=True)
@ -13,4 +12,4 @@ class Purchase(models.Model):
expiration_date = models.DateTimeField(null=True, blank=True) expiration_date = models.DateTimeField(null=True, blank=True)
def __str__(self): def __str__(self):
return f"{self.identifier} > {self.product_id} - {self.purchase_date} - {self.user.username}" return f"{self.id} > {self.product_id} - {self.purchase_date} - {self.user.username}"

@ -7,8 +7,7 @@ from . import Event, TournamentPayment, FederalMatchCategory, FederalCategory, F
import uuid import uuid
from django.utils import timezone, formats from django.utils import timezone, formats
from datetime import datetime, timedelta from datetime import datetime, timedelta
from .. import config_local from shared.cryptography import encryption_util
from ..utils.cryptography import decrypt_aes_gcm
from ..utils.extensions import plural_format from ..utils.extensions import plural_format
class TeamSortingType(models.IntegerChoices): class TeamSortingType(models.IntegerChoices):
@ -67,8 +66,8 @@ class Tournament(models.Model):
return self.display_name() return self.display_name()
def is_canceled(self): def is_canceled(self):
if self.local_id and config_local.CRYPTO_KEY: if self.local_id:
decrypted = decrypt_aes_gcm(self.local_id, config_local.CRYPTO_KEY) decrypted = encryption_util.decrypt_aes_gcm(self.local_id)
value = int(decrypted[18]) value = int(decrypted[18])
if 0 <= value <= 4: if 0 <= value <= 4:
return True return True
@ -78,8 +77,8 @@ class Tournament(models.Model):
return False return False
def payment(self): def payment(self):
if self.global_id and config_local.CRYPTO_KEY: if self.global_id:
decrypted = decrypt_aes_gcm(self.global_id, config_local.CRYPTO_KEY) decrypted = encryption_util.decrypt_aes_gcm(self.global_id)
value = int(decrypted[18]) value = int(decrypted[18])
return TournamentPayment(value) return TournamentPayment(value)
else: else:

@ -1,20 +0,0 @@
from Crypto.Cipher import AES
import base64
def decrypt_aes_gcm(encrypted_base64, key_base64):
# Decode the base64 encoded data and key
encrypted_data = base64.b64decode(encrypted_base64)
key = base64.b64decode(key_base64)
# Extract the nonce, tag, and ciphertext from the combined encrypted data
nonce = encrypted_data[:12] # AES GCM nonce is 12 bytes
tag = encrypted_data[-16:] # AES GCM tag is 16 bytes
ciphertext = encrypted_data[12:-16] # Ciphertext is everything in between
# Create the cipher object and decrypt the data
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
decrypted_data = cipher.decrypt_and_verify(ciphertext, tag)
# Convert decrypted bytes to string (assuming UTF-8 encoding)
decrypted_text = decrypted_data.decode('utf-8')
return decrypted_text
Loading…
Cancel
Save