mirror of
https://github.com/jazzband/django-fernet-encrypted-fields.git
synced 2026-03-16 22:40:27 +00:00
setup github actions with black, flake8, testing and coverage
This commit is contained in:
parent
04625a8e40
commit
a20d77e256
9 changed files with 238 additions and 73 deletions
53
.github/lint-and-test.yml
vendored
Normal file
53
.github/lint-and-test.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Lint & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
django_version: [2.2, 3.0, 3.1, 3.2, 4.0a1]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -q Django==${{ matrix.django_version }}
|
||||
pip install flake8 coverage black
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- name: Lint with Black
|
||||
run: |
|
||||
black --check .
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
coverage3 run --source='./encrypted_fields' manage.py test
|
||||
coverage xml
|
||||
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
|
|
@ -12,15 +12,25 @@ class EncryptedFieldMixin(object):
|
|||
@cached_property
|
||||
def keys(self):
|
||||
keys = []
|
||||
salt_keys = settings.SALT_KEY if isinstance(settings.SALT_KEY, list) else [settings.SALT_KEY]
|
||||
salt_keys = (
|
||||
settings.SALT_KEY
|
||||
if isinstance(settings.SALT_KEY, list)
|
||||
else [settings.SALT_KEY]
|
||||
)
|
||||
for salt_key in salt_keys:
|
||||
salt = bytes(salt_key, 'utf-8')
|
||||
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
salt=salt,
|
||||
iterations=100000,
|
||||
backend=default_backend())
|
||||
keys.append(base64.urlsafe_b64encode(kdf.derive(settings.SECRET_KEY.encode('utf-8'))))
|
||||
salt = bytes(salt_key, "utf-8")
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
salt=salt,
|
||||
iterations=100000,
|
||||
backend=default_backend(),
|
||||
)
|
||||
keys.append(
|
||||
base64.urlsafe_b64encode(
|
||||
kdf.derive(settings.SECRET_KEY.encode("utf-8"))
|
||||
)
|
||||
)
|
||||
return keys
|
||||
|
||||
@cached_property
|
||||
|
|
@ -33,13 +43,13 @@ class EncryptedFieldMixin(object):
|
|||
"""
|
||||
To treat everything as text
|
||||
"""
|
||||
return 'TextField'
|
||||
return "TextField"
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if value:
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
return self.f.encrypt(bytes(value, 'utf-8')).decode('utf-8')
|
||||
return self.f.encrypt(bytes(value, "utf-8")).decode("utf-8")
|
||||
return None
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
|
|
@ -51,9 +61,13 @@ class EncryptedFieldMixin(object):
|
|||
return self.to_python(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if value is None or not isinstance(value, str) or hasattr(self, '_already_decrypted'):
|
||||
if (
|
||||
value is None
|
||||
or not isinstance(value, str)
|
||||
or hasattr(self, "_already_decrypted")
|
||||
):
|
||||
return value
|
||||
value = self.f.decrypt(bytes(value, 'utf-8')).decode('utf-8')
|
||||
value = self.f.decrypt(bytes(value, "utf-8")).decode("utf-8")
|
||||
return super(EncryptedFieldMixin, self).to_python(value)
|
||||
|
||||
def clean(self, value, model_instance):
|
||||
|
|
|
|||
53
lint-and-test.yml
Normal file
53
lint-and-test.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Lint & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
django_version: [2.2, 3.0, 3.1, 3.2, 4.0a1]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -q Django==${{ matrix.django_version }}
|
||||
pip install flake8 coverage black
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- name: Lint with Black
|
||||
run: |
|
||||
black --check .
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
coverage3 run --source='./encrypted_fields' manage.py test
|
||||
coverage xml
|
||||
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'package_test.settings'
|
||||
if __name__ == "__main__":
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "package_test.settings"
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:',
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": ":memory:",
|
||||
},
|
||||
}
|
||||
|
||||
SECRET_KEY = 'abc'
|
||||
SALT_KEY = 'xyz'
|
||||
SECRET_KEY = "abc"
|
||||
SALT_KEY = "xyz"
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'encrypted_fields',
|
||||
'package_test'
|
||||
)
|
||||
INSTALLED_APPS = ("encrypted_fields", "package_test")
|
||||
|
||||
MIDDLEWARE_CLASSES = []
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
|
|
|
|||
|
|
@ -11,38 +11,38 @@ class FieldTest(TestCase):
|
|||
def get_db_value(self, field, model_id):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(
|
||||
'select {0} '
|
||||
'from package_test_testmodel '
|
||||
'where id = {1};'.format(field, model_id)
|
||||
"select {0} "
|
||||
"from package_test_testmodel "
|
||||
"where id = {1};".format(field, model_id)
|
||||
)
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
def test_char_field_encrypted(self):
|
||||
plaintext = 'Oh hi, test reader!'
|
||||
plaintext = "Oh hi, test reader!"
|
||||
|
||||
model = TestModel()
|
||||
model.char = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('char', model.id)
|
||||
ciphertext = self.get_db_value("char", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertTrue('test' not in ciphertext)
|
||||
self.assertTrue("test" not in ciphertext)
|
||||
|
||||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.char, plaintext)
|
||||
|
||||
def test_text_field_encrypted(self):
|
||||
plaintext = 'Oh hi, test reader!' * 10
|
||||
plaintext = "Oh hi, test reader!" * 10
|
||||
|
||||
model = TestModel()
|
||||
model.text = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('text', model.id)
|
||||
ciphertext = self.get_db_value("text", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertTrue('test' not in ciphertext)
|
||||
self.assertTrue("test" not in ciphertext)
|
||||
|
||||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.text, plaintext)
|
||||
|
|
@ -54,10 +54,10 @@ class FieldTest(TestCase):
|
|||
model.datetime = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('datetime', model.id)
|
||||
ciphertext = self.get_db_value("datetime", model.id)
|
||||
|
||||
# Django's normal date serialization format
|
||||
self.assertTrue(re.search('^\d\d\d\d-\d\d-\d\d', ciphertext) is None)
|
||||
self.assertTrue(re.search("^\d\d\d\d-\d\d-\d\d", ciphertext) is None)
|
||||
|
||||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.datetime, plaintext)
|
||||
|
|
@ -69,7 +69,7 @@ class FieldTest(TestCase):
|
|||
model.integer = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('integer', model.id)
|
||||
ciphertext = self.get_db_value("integer", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertNotEqual(plaintext, str(ciphertext))
|
||||
|
|
@ -84,7 +84,7 @@ class FieldTest(TestCase):
|
|||
model.date = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('date', model.id)
|
||||
ciphertext = self.get_db_value("date", model.id)
|
||||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
|
||||
self.assertNotEqual(ciphertext, plaintext.isoformat())
|
||||
|
|
@ -97,7 +97,7 @@ class FieldTest(TestCase):
|
|||
model.floating = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('floating', model.id)
|
||||
ciphertext = self.get_db_value("floating", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertNotEqual(plaintext, str(ciphertext))
|
||||
|
|
@ -106,16 +106,16 @@ class FieldTest(TestCase):
|
|||
self.assertEqual(fresh_model.floating, plaintext)
|
||||
|
||||
def test_email_field_encrypted(self):
|
||||
plaintext = 'test@gmail.com'
|
||||
plaintext = "test@gmail.com"
|
||||
|
||||
model = TestModel()
|
||||
model.email = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('email', model.id)
|
||||
ciphertext = self.get_db_value("email", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertTrue('aron' not in ciphertext)
|
||||
self.assertTrue("aron" not in ciphertext)
|
||||
|
||||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.email, plaintext)
|
||||
|
|
@ -127,13 +127,13 @@ class FieldTest(TestCase):
|
|||
model.boolean = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = self.get_db_value('boolean', model.id)
|
||||
ciphertext = self.get_db_value("boolean", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertNotEqual(True, ciphertext)
|
||||
self.assertNotEqual('True', ciphertext)
|
||||
self.assertNotEqual('true', ciphertext)
|
||||
self.assertNotEqual('1', ciphertext)
|
||||
self.assertNotEqual("True", ciphertext)
|
||||
self.assertNotEqual("true", ciphertext)
|
||||
self.assertNotEqual("1", ciphertext)
|
||||
self.assertNotEqual(1, ciphertext)
|
||||
self.assertTrue(not isinstance(ciphertext, bool))
|
||||
|
||||
|
|
@ -142,27 +142,24 @@ class FieldTest(TestCase):
|
|||
|
||||
|
||||
class RotatedSaltTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
@override_settings(SALT_KEY=['abcdefghijklmnopqrstuvwxyz0123456789'])
|
||||
@override_settings(SALT_KEY=["abcdefghijklmnopqrstuvwxyz0123456789"])
|
||||
def setUpTestData(cls):
|
||||
"""Create the initial record using the old salt"""
|
||||
cls.original = TestModel.objects.create(
|
||||
text="Oh hi test reader"
|
||||
)
|
||||
cls.original = TestModel.objects.create(text="Oh hi test reader")
|
||||
|
||||
@override_settings(SALT_KEY=['newkeyhere', 'abcdefghijklmnopqrstuvwxyz0123456789'])
|
||||
@override_settings(SALT_KEY=["newkeyhere", "abcdefghijklmnopqrstuvwxyz0123456789"])
|
||||
def test_rotated_salt(self):
|
||||
"""Chage the salt, keep the old one as the last in the list for reading"""
|
||||
"""Change the salt, keep the old one as the last in the list for reading"""
|
||||
plaintext = "Oh hi test reader"
|
||||
model = TestModel()
|
||||
model.text = plaintext
|
||||
model.save()
|
||||
|
||||
ciphertext = FieldTest.get_db_value(self, 'text', model.id)
|
||||
ciphertext = FieldTest.get_db_value(self, "text", model.id)
|
||||
|
||||
self.assertNotEqual(plaintext, ciphertext)
|
||||
self.assertTrue('test' not in ciphertext)
|
||||
self.assertTrue("test" not in ciphertext)
|
||||
|
||||
fresh_model = TestModel.objects.get(id=model.id)
|
||||
self.assertEqual(fresh_model.text, plaintext)
|
||||
|
|
@ -170,5 +167,6 @@ class RotatedSaltTestCase(TestCase):
|
|||
old_record = TestModel.objects.get(id=self.original.id)
|
||||
self.assertEqual(fresh_model.text, old_record.text)
|
||||
|
||||
self.assertNotEqual(ciphertext, FieldTest.get_db_value(self, 'text', self.original.pk))
|
||||
|
||||
self.assertNotEqual(
|
||||
ciphertext, FieldTest.get_db_value(self, "text", self.original.pk)
|
||||
)
|
||||
|
|
|
|||
26
setup.py
26
setup.py
|
|
@ -2,20 +2,18 @@ from __future__ import print_function
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='django-fernet-encrypted-fields',
|
||||
description=(
|
||||
'This is inspired by django-encrypted-fields.'
|
||||
),
|
||||
long_description=open('README.md').read(),
|
||||
long_description_content_type='text/markdown',
|
||||
url='http://github.com/frgmt/django-fernet-encrypted-fields/',
|
||||
license='MIT',
|
||||
author='fragment.co.jp',
|
||||
author_email='info@fragment.co.jp',
|
||||
packages=['encrypted_fields'],
|
||||
version='0.1.1',
|
||||
name="django-fernet-encrypted-fields",
|
||||
description=("This is inspired by django-encrypted-fields."),
|
||||
long_description=open("README.md").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
url="http://github.com/frgmt/django-fernet-encrypted-fields/",
|
||||
license="MIT",
|
||||
author="fragment.co.jp",
|
||||
author_email="info@fragment.co.jp",
|
||||
packages=["encrypted_fields"],
|
||||
version="0.1.1",
|
||||
install_requires=[
|
||||
'Django>=2.2',
|
||||
'cryptography>=35.0.0',
|
||||
"Django>=2.2",
|
||||
"cryptography>=35.0.0",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
53
workflows/lint-and-test.yml
Normal file
53
workflows/lint-and-test.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Lint & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
django_version: [2.2, 3.0, 3.1, 3.2, 4.0a1]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -q Django==${{ matrix.django_version }}
|
||||
pip install flake8 coverage black
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- name: Lint with Black
|
||||
run: |
|
||||
black --check .
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
coverage3 run --source='./encrypted_fields' manage.py test
|
||||
coverage xml
|
||||
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
Loading…
Reference in a new issue