Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/actions/lint/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: "Linting"
runs:
using: "composite"
steps:
- uses: actions/[email protected]

- name: Set up Python 3.9
uses: actions/[email protected]
with:
python-version: "3.9"

- name: pre-commit
uses: pre-commit/[email protected]
50 changes: 0 additions & 50 deletions .github/workflows/lint.yml

This file was deleted.

16 changes: 12 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,31 @@ on:
- master

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/[email protected]

- uses: ./.github/actions/lint

tests:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest

strategy:
matrix:
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4.2.2

- uses: actions/setup-python@v5
- uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}

Expand Down
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ repos:
exclude: ^docs/
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.261
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.2
hooks:
- id: ruff
alias: autoformat
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.6
hooks:
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ bi-directional data retrieval.
Requirements
------------

* Python_ (3.7, 3.8, 3.9, 3.10, 3.11)
* Python_ (3.9, 3.10, 3.11, 3.12, 3.13)
* Cryptography_ (2.0+)
* Django_ (3.2, 4.1, 4.2)
* Django_ (3.2, 4.1, 4.2, 5.0, 5.1)

Installation
------------
Expand Down
8 changes: 2 additions & 6 deletions django_cryptography/conf.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from typing import Any, Dict
from typing import Any

from appconf import AppConf
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf import pbkdf2
from django.conf import settings
from django.utils.encoding import force_bytes


class CryptographyConf(AppConf):
BACKEND = default_backend()
DIGEST = hashes.SHA256()
KEY = None
SALT = "django-cryptography"
Expand All @@ -21,8 +19,7 @@ class Meta:
def configure_salt(self, value: Any) -> bytes:
return force_bytes(value)

def configure(self) -> Dict[str, Any]:
backend = self.configured_data["BACKEND"]
def configure(self) -> dict[str, Any]:
digest = self.configured_data["DIGEST"]
salt = self.configured_data["SALT"]
# Key Derivation Function
Expand All @@ -31,7 +28,6 @@ def configure(self) -> Dict[str, Any]:
length=digest.digest_size,
salt=salt,
iterations=30000,
backend=backend,
)
self.configured_data["KEY"] = kdf.derive(
force_bytes(self.configured_data["KEY"] or settings.SECRET_KEY)
Expand Down
31 changes: 15 additions & 16 deletions django_cryptography/core/signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import struct
import time
import zlib
from typing import Any, Optional, Type, Union
from typing import Any, Optional, Union

from cryptography.hazmat.primitives.hmac import HMAC
from django.conf import settings
Expand Down Expand Up @@ -64,7 +64,7 @@ def dumps(
obj: Any,
key: Optional[Union[bytes, str]] = None,
salt: str = "django.core.signing",
serializer: Type[Serializer] = JSONSerializer,
serializer: type[Serializer] = JSONSerializer,
compress: bool = False,
) -> str:
"""
Expand Down Expand Up @@ -92,7 +92,7 @@ def loads(
s: str,
key: Optional[Union[bytes, str]] = None,
salt: str = "django.core.signing",
serializer: Type[Serializer] = JSONSerializer,
serializer: type[Serializer] = JSONSerializer,
max_age: Optional[Union[int, datetime.timedelta]] = None,
) -> Any:
"""
Expand All @@ -118,8 +118,8 @@ def __init__(
self.sep = sep
if _SEP_UNSAFE.match(self.sep):
raise ValueError(
"Unsafe Signer separator: %r (cannot be empty or consist of "
"only A-z0-9-_=)" % sep,
f"Unsafe Signer separator: {sep!r} (cannot be empty or consist of "
"only A-z0-9-_=)",
)
self.salt = salt or f"{self.__class__.__module__}.{self.__class__.__name__}"
self.algorithm = algorithm or "sha256"
Expand All @@ -134,16 +134,16 @@ def sign(self, value: str) -> str:

def unsign(self, signed_value: str) -> str:
if self.sep not in signed_value:
raise BadSignature('No "%s" found in value' % self.sep)
raise BadSignature(f'No "{self.sep}" found in value')
value, sig = signed_value.rsplit(self.sep, 1)
if constant_time_compare(sig, self.signature(value)):
return value
raise BadSignature('Signature "%s" does not match' % sig)
raise BadSignature(f'Signature "{sig}" does not match')

def sign_object(
self,
obj: Any,
serializer: Type[Serializer] = JSONSerializer,
serializer: type[Serializer] = JSONSerializer,
compress: bool = False,
) -> str:
"""
Expand Down Expand Up @@ -173,7 +173,7 @@ def sign_object(
def unsign_object(
self,
signed_obj: str,
serializer: Type[Serializer] = JSONSerializer,
serializer: type[Serializer] = JSONSerializer,
**kwargs: Any,
) -> Any:
# Signer.unsign() returns str but base64 and zlib compression operate
Expand Down Expand Up @@ -233,8 +233,8 @@ def __init__(
hasher = HASHES[self.algorithm]
except KeyError as e:
raise InvalidAlgorithm(
"%r is not an algorithm accepted by the cryptography module."
% algorithm
f"{algorithm!r} is not an algorithm accepted by the cryptography "
f"module."
) from e

self._digest_size = hasher.digest_size
Expand All @@ -254,7 +254,7 @@ def unsign(self, signed_value: bytes) -> bytes:
)
if constant_time_compare(sig, self.signature(value)):
return value
raise BadSignature('Signature "%r" does not match' % binascii.b2a_base64(sig))
raise BadSignature(f'Signature "{binascii.b2a_base64(sig)!r}" does not match')


class FernetSigner:
Expand All @@ -272,8 +272,8 @@ def __init__(
hasher = HASHES[self.algorithm]
except KeyError as e:
raise InvalidAlgorithm(
"%r is not an algorithm accepted by the cryptography module."
% algorithm
f"{algorithm!r} is not an algorithm accepted by the cryptography "
f"module."
) from e

self.hasher = hasher
Expand All @@ -282,7 +282,6 @@ def signature(self, value: Union[bytes, str]) -> bytes:
h = HMAC(
force_bytes(self.key),
self.hasher,
backend=settings.CRYPTOGRAPHY_BACKEND, # type: ignore
)
h.update(force_bytes(value))
return h.finalize()
Expand Down Expand Up @@ -318,4 +317,4 @@ def unsign(
raise SignatureExpired(f"Signature age {age} > {max_age} seconds")
if constant_time_compare(sig, self.signature(signed_value[:-d_size])):
return value
raise BadSignature('Signature "%r" does not match' % binascii.b2a_base64(sig))
raise BadSignature(f'Signature "{binascii.b2a_base64(sig)!r}" does not match')
28 changes: 11 additions & 17 deletions django_cryptography/fields.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import pickle
from base64 import b64decode, b64encode
from collections.abc import Sequence
from typing import (
Any,
Dict,
List,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
cast,
Expand All @@ -27,7 +23,7 @@
from django_cryptography.utils.crypto import FernetBytes

F = TypeVar("F", bound=models.Field)
FIELD_CACHE: Dict[type, type] = {}
FIELD_CACHE: dict[type, type] = {}

Expired = object()
"""Represents an expired encryption value."""
Expand All @@ -39,7 +35,7 @@ class PickledField(models.BinaryField):
"""

description = _("Pickled data")
empty_values = [None, b""]
empty_values = (None, b"")
supported_lookups = ("exact", "in", "isnull")

def _dump(self, value: Any) -> bytes:
Expand All @@ -48,12 +44,12 @@ def _dump(self, value: Any) -> bytes:
def _load(self, value: bytes) -> Any:
return pickle.loads(value)

def get_lookup(self, lookup_name: str) -> Optional[Type[Lookup]]:
def get_lookup(self, lookup_name: str) -> Optional[type[Lookup]]:
if lookup_name not in self.supported_lookups:
return None
return super().get_lookup(lookup_name)

def get_transform(self, lookup_name: str) -> Optional[Type[Transform]]:
def get_transform(self, lookup_name: str) -> Optional[type[Transform]]:
if lookup_name not in self.supported_lookups:
return None
return super().get_transform(lookup_name)
Expand Down Expand Up @@ -98,7 +94,7 @@ class EncryptedMixin(models.Field):
supported_lookups = ("isnull",)

def __init__(self, *args, **kwargs) -> None:
self.base_class: Type[models.Field]
self.base_class: type[models.Field]
self.wasinstance: bool

self.key: Union[bytes, str] = kwargs.pop("key", None)
Expand All @@ -121,7 +117,7 @@ def _load(self, value: bytes) -> Any:
except SignatureExpired:
return Expired

def check(self, **kwargs: Any) -> List[CheckMessage]:
def check(self, **kwargs: Any) -> list[CheckMessage]:
errors = super().check(**kwargs)
if getattr(self, "remote_field", None):
errors.append(
Expand All @@ -141,7 +137,7 @@ def clone(self):
return encrypt(self.base_class(*args, **kwargs), self.key, self.ttl)
return self.__class__(*args, **kwargs)

def deconstruct(self) -> Tuple[str, str, Sequence[Any], Dict[str, Any]]:
def deconstruct(self) -> tuple[str, str, Sequence[Any], dict[str, Any]]:
name, path, args, kwargs = super().deconstruct()
if self.wasinstance is False:
path = f"{self.base_class.__module__}.{self.base_class.__name__}"
Expand Down Expand Up @@ -183,7 +179,7 @@ def from_db_value(self, value, *args, **kwargs) -> Any:
return value


def get_encrypted_field(base_class: Type[F], wasinstance: bool) -> Type[F]:
def get_encrypted_field(base_class: type[F], wasinstance: bool) -> type[F]:
"""
A get or create method for encrypted fields, we cache the field in
the module to avoid recreation. This also allows us to always return
Expand All @@ -201,13 +197,11 @@ def get_encrypted_field(base_class: Type[F], wasinstance: bool) -> Type[F]:


@overload
def encrypt(base_field: F, key=None, ttl=None) -> F:
...
def encrypt(base_field: F, key=None, ttl=None) -> F: ...


@overload
def encrypt(base_field: Type[F]) -> Type[F]:
...
def encrypt(base_field: type[F]) -> type[F]: ...


def encrypt(
Expand Down
Loading
Loading