Merge pull request #11 from frgmt/hotfix/EncryptedIntegerField

Fix #10
This commit is contained in:
fragment 2022-05-07 19:38:05 +09:00 committed by GitHub
commit 935453a6d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 5 deletions

View file

@ -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):

View file

@ -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

View file

@ -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",