From 125427a239f1f845d0a3849b9707dce624593d45 Mon Sep 17 00:00:00 2001 From: Kamil Marczak Date: Sat, 22 May 2021 09:06:08 +0200 Subject: [PATCH] ADD custom encrypted fields Add crypto fields Add crypto fields --- .travis.yml | 1 + docs/index.html | Bin 209069 -> 210821 bytes docs/index.md | 28 +++- rest_framework_extensions/__init__.py | 2 +- rest_framework_extensions/fields.py | 126 +++++++++++++++++- tests_app/requirements.txt | 3 +- tests_app/tests/unit/fields/__init__.py | 0 tests_app/tests/unit/fields/tests.py | 166 ++++++++++++++++++++++++ 8 files changed, 322 insertions(+), 4 deletions(-) create mode 100644 tests_app/tests/unit/fields/__init__.py create mode 100644 tests_app/tests/unit/fields/tests.py diff --git a/.travis.yml b/.travis.yml index e10a9c0..065f0be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ python: - 3.8 install: + - python -m pip install -U pip - pip install tox tox-travis script: diff --git a/docs/index.html b/docs/index.html index 5f045d30ebd6e50662b709682be7478f1da87bcc..3eb6fd4f68e466617427500e4aee3798882d74f2 100644 GIT binary patch delta 1551 zcmb7E&ubGw6lPN?8j3`;dQkK=dT7BmEr^0_BiL3bf|XVe1tCm!U$RqYXV#h7#(0Sy zJlgB_+LI?iMeN-_!&~tW(7On}{gJgsDVkH}?fdb4@4fH6Ihc6)b>jI4=h05`YS*b< z@N_@2YPQ2s_@>@m%ARm1d+y)gaekbb_Hi49ib9ljb{n1#Wmb;Bfeo~Zy8#%~oC$82CF>ll$|$x57-ib9I*n27E%aNNI~cbhHYIw6!TWX28b|i-nyb>YdaM z+(K*+yx3Us%*WH!EDE>>d6jtZ%N8Ha7^i%P$FO8N$r@M&X1G$!>4Ss&V%tt?G}z|v!zmebFM zlg@wWNvlSol*`g?17RsMPGPF?vYR2E!ofbZ| znziyXFju52jzX@6(X7?K0K*YrN5e|>u~;vplRoNKNvZk%m-eB7zR}=2.1.0 mock -ipdb \ No newline at end of file +ipdb +cryptography==3.2.1 \ No newline at end of file diff --git a/tests_app/tests/unit/fields/__init__.py b/tests_app/tests/unit/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests_app/tests/unit/fields/tests.py b/tests_app/tests/unit/fields/tests.py new file mode 100644 index 0000000..27b6ce6 --- /dev/null +++ b/tests_app/tests/unit/fields/tests.py @@ -0,0 +1,166 @@ +import time + +from rest_framework import serializers +from rest_framework_extensions.fields import ( + CryptoBinaryField, + CryptoCharField, +) +import datetime +from django.test import TestCase +from django.conf import settings + +import base64 + +from cryptography.fernet import Fernet +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +DEFAULT_PASSWORD = b"Non_nobis1solum?nati!sumus" +DEFAULT_SALT = settings.SECRET_KEY + + +def _generate_password_key(salt=DEFAULT_SALT, password=DEFAULT_PASSWORD): + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=_to_bytes(salt), + iterations=100000, + ) + + key = base64.urlsafe_b64encode(kdf.derive(_to_bytes(password))) + return key + + +def _to_bytes(v): + if isinstance(v, str): + return v.encode("utf-8") + + if isinstance(v, bytes): + return v + + + +def _encrypt(token, value_in_str): + b_message = value_in_str.encode("utf-8") + encrypted_message = token.encrypt(b_message) + return encrypted_message + + +def _decrypt(token, value, ttl=None): + ttl = int(ttl) if ttl else None + decrypted_message = token.decrypt(_to_bytes(value), ttl) + return decrypted_message.decode("utf-8") + + +class SaveCrypto(object): + def __init__(self, message=None, created=None): + self.message = message + self.created = created or datetime.datetime.now() + + +class CryptoSerializer(serializers.Serializer): + message = CryptoBinaryField(required=False) + created = serializers.DateTimeField() + + def update(self, instance, validated_data): + instance.message = validated_data["message"] + return instance + + def create(self, validated_data): + return SaveCrypto(**validated_data) + + +class CryptoCharSerializer(serializers.Serializer): + message = CryptoCharField(required=False) + created = serializers.DateTimeField() + + +class SaltCryptoSerializerSerializer(CryptoSerializer): + message = CryptoBinaryField(salt="Salt") + created = serializers.DateTimeField() + + +class PasswordCryptoSerializerSerializer(CryptoSerializer): + message = CryptoBinaryField(password="Password") + created = serializers.DateTimeField() + + +class TtlCryptoSerializerSerializer(CryptoSerializer): + message = CryptoBinaryField(ttl=1) + created = serializers.DateTimeField() + + +class TestPointSerializer(TestCase): + def test_create(self): + """ + Test for creating CryptoBinaryField + """ + now = datetime.datetime.now() + message = "test message" + serializer = CryptoSerializer(data={"created": now, "message": message}) + model_data = SaveCrypto(message=message, created=now) + + self.assertTrue(serializer.is_valid()) + self.assertEqual(serializer.validated_data["created"], model_data.created) + self.assertFalse(serializer.validated_data is model_data) + self.assertIs(type(serializer.validated_data["message"]), bytes) + + def test_create_char(self): + """ + Test for creating CryptoCharField + """ + now = datetime.datetime.now() + message = "test message" + serializer = CryptoCharSerializer(data={"created": now, "message": message}) + model_data = SaveCrypto(message=message, created=now) + + self.assertTrue(serializer.is_valid()) + self.assertEqual(serializer.validated_data["created"], model_data.created) + self.assertFalse(serializer.validated_data is model_data) + self.assertIs(type(serializer.validated_data["message"]), str) + + def test_serialization(self): + """ + Regular JSON serialization should output float values + """ + now = datetime.datetime.now() + message = "test message" + key = _generate_password_key(DEFAULT_SALT, DEFAULT_PASSWORD) + token = Fernet(key) + encrypted_message = _encrypt(token, message) + model_data = SaveCrypto(message=encrypted_message, created=now) + serializer = CryptoSerializer(model_data) + self.assertEqual(serializer.data["message"], message) + + def test_serialization_salt(self): + now = datetime.datetime.now() + message = "test message" + key = _generate_password_key("Salt", DEFAULT_PASSWORD) + token = Fernet(key) + encrypted_message = _encrypt(token, message) + model_data = SaveCrypto(message=encrypted_message, created=now) + serializer = SaltCryptoSerializerSerializer(model_data) + time.sleep(3) + self.assertEqual(serializer.data["message"], message) + + def test_serialization_password(self): + now = datetime.datetime.now() + message = "test message" + key = _generate_password_key(DEFAULT_SALT, "Password") + token = Fernet(key) + encrypted_message = _encrypt(token, message) + model_data = SaveCrypto(message=encrypted_message, created=now) + serializer = PasswordCryptoSerializerSerializer(model_data) + time.sleep(3) + self.assertEqual(serializer.data["message"], message) + + def test_serialization_ttl(self): + now = datetime.datetime.now() + message = "test message" + key = _generate_password_key(DEFAULT_SALT, DEFAULT_PASSWORD) + token = Fernet(key) + encrypted_message = _encrypt(token, message) + model_data = SaveCrypto(message=encrypted_message, created=now) + serializer = TtlCryptoSerializerSerializer(model_data) + time.sleep(3) + self.assertEqual(serializer.data["message"], None)