mirror of
https://github.com/jazzband/django-fernet-encrypted-fields.git
synced 2026-03-16 22:40:27 +00:00
commit
935453a6d5
3 changed files with 106 additions and 5 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import base64
|
||||
from django.conf import settings
|
||||
from cryptography.fernet import Fernet, MultiFernet
|
||||
|
||||
from cryptography.fernet import Fernet, MultiFernet, InvalidToken
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from django.conf import settings
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
from django.db.backends.base.operations import BaseDatabaseOperations
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
|
|
@ -46,6 +49,7 @@ class EncryptedFieldMixin(object):
|
|||
return "TextField"
|
||||
|
||||
def get_prep_value(self, value):
|
||||
value = super().get_prep_value(value)
|
||||
if value:
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
|
|
@ -67,7 +71,12 @@ class EncryptedFieldMixin(object):
|
|||
or hasattr(self, "_already_decrypted")
|
||||
):
|
||||
return value
|
||||
value = self.f.decrypt(bytes(value, "utf-8")).decode("utf-8")
|
||||
try:
|
||||
value = self.f.decrypt(bytes(value, "utf-8")).decode("utf-8")
|
||||
except InvalidToken:
|
||||
pass
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
return super(EncryptedFieldMixin, self).to_python(value)
|
||||
|
||||
def clean(self, value, model_instance):
|
||||
|
|
@ -94,7 +103,40 @@ class EncryptedDateTimeField(EncryptedFieldMixin, models.DateTimeField):
|
|||
|
||||
|
||||
class EncryptedIntegerField(EncryptedFieldMixin, models.IntegerField):
|
||||
pass
|
||||
@cached_property
|
||||
def validators(self):
|
||||
# These validators can't be added at field initialization time since
|
||||
# they're based on values retrieved from `connection`.
|
||||
validators_ = [*self.default_validators, *self._validators]
|
||||
internal_type = models.IntegerField().get_internal_type()
|
||||
min_value, max_value = BaseDatabaseOperations.integer_field_ranges[internal_type]
|
||||
if min_value is not None and not any(
|
||||
(
|
||||
isinstance(validator, validators.MinValueValidator)
|
||||
and (
|
||||
validator.limit_value()
|
||||
if callable(validator.limit_value)
|
||||
else validator.limit_value
|
||||
)
|
||||
>= min_value
|
||||
)
|
||||
for validator in validators_
|
||||
):
|
||||
validators_.append(validators.MinValueValidator(min_value))
|
||||
if max_value is not None and not any(
|
||||
(
|
||||
isinstance(validator, validators.MaxValueValidator)
|
||||
and (
|
||||
validator.limit_value()
|
||||
if callable(validator.limit_value)
|
||||
else validator.limit_value
|
||||
)
|
||||
<= max_value
|
||||
)
|
||||
for validator in validators_
|
||||
):
|
||||
validators_.append(validators.MaxValueValidator(max_value))
|
||||
return validators_
|
||||
|
||||
|
||||
class EncryptedDateField(EncryptedFieldMixin, models.DateField):
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import re
|
|||
from django.db import connection
|
||||
from django.test import TestCase, override_settings
|
||||
from django.utils import timezone
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from .models import TestModel
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ class FieldTest(TestCase):
|
|||
|
||||
model = TestModel()
|
||||
model.char = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("char", model.id)
|
||||
|
|
@ -37,6 +39,7 @@ class FieldTest(TestCase):
|
|||
|
||||
model = TestModel()
|
||||
model.text = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("text", model.id)
|
||||
|
|
@ -52,6 +55,7 @@ class FieldTest(TestCase):
|
|||
|
||||
model = TestModel()
|
||||
model.datetime = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("datetime", model.id)
|
||||
|
|
@ -62,11 +66,19 @@ class FieldTest(TestCase):
|
|||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.datetime, plaintext)
|
||||
|
||||
plaintext = "text"
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model.datetime = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
def test_integer_field_encrypted(self):
|
||||
plaintext = 42
|
||||
|
||||
model = TestModel()
|
||||
model.integer = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("integer", model.id)
|
||||
|
|
@ -77,11 +89,27 @@ class FieldTest(TestCase):
|
|||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.integer, plaintext)
|
||||
|
||||
# "IntegerField": (-2147483648, 2147483647)
|
||||
plaintext = 2147483648
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model.integer = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
plaintext = "text"
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
model.integer = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
def test_date_field_encrypted(self):
|
||||
plaintext = timezone.now().date()
|
||||
|
||||
model = TestModel()
|
||||
model.date = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("date", model.id)
|
||||
|
|
@ -90,11 +118,19 @@ class FieldTest(TestCase):
|
|||
self.assertNotEqual(ciphertext, plaintext.isoformat())
|
||||
self.assertEqual(fresh_model.date, plaintext)
|
||||
|
||||
plaintext = "text"
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model.date = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
def test_float_field_encrypted(self):
|
||||
plaintext = 42.44
|
||||
|
||||
model = TestModel()
|
||||
model.floating = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("floating", model.id)
|
||||
|
|
@ -105,11 +141,19 @@ class FieldTest(TestCase):
|
|||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.floating, plaintext)
|
||||
|
||||
plaintext = "text"
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
model.floating = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
def test_email_field_encrypted(self):
|
||||
plaintext = "test@gmail.com"
|
||||
|
||||
model = TestModel()
|
||||
model.email = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("email", model.id)
|
||||
|
|
@ -120,11 +164,19 @@ class FieldTest(TestCase):
|
|||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.email, plaintext)
|
||||
|
||||
plaintext = "text"
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model.email = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
def test_boolean_field_encrypted(self):
|
||||
plaintext = True
|
||||
|
||||
model = TestModel()
|
||||
model.boolean = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value("boolean", model.id)
|
||||
|
|
@ -140,6 +192,13 @@ class FieldTest(TestCase):
|
|||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.boolean, plaintext)
|
||||
|
||||
plaintext = "text"
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model.boolean = plaintext
|
||||
model.full_clean()
|
||||
model.save()
|
||||
|
||||
|
||||
class RotatedSaltTestCase(TestCase):
|
||||
@classmethod
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -11,7 +11,7 @@ setup(
|
|||
author="fragment.co.jp",
|
||||
author_email="info@fragment.co.jp",
|
||||
packages=["encrypted_fields"],
|
||||
version="0.1.1",
|
||||
version="0.1.2",
|
||||
install_requires=[
|
||||
"Django>=2.2",
|
||||
"cryptography>=35.0.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue