diff --git a/.env b/.env index 6ef60c42..43d673d1 100644 --- a/.env +++ b/.env @@ -8,4 +8,5 @@ DJANGO_SETTINGS_MODULE=testproject.settings STRIPE_SECRET_KEY="123" STRIPE_WEBHOOK_SECRET="321" BA_API_KEY_REQUEST_HEADER="HTTP_API_KEY" +# Dummy key for local development - generate real key with: python manage.py api_key --model baseapp_api_key.APIKey --generate_encryption_key BA_API_KEY_ENCRYPTION_KEY="jCe8USiQJDatFT5T0WCIl86QBxYs0-Q7iDJQc77Dh7LR1VAnWW3PA9UyXK-V7LhFnKq9sLd3xDw5FTrrYYtj2Q==" diff --git a/baseapp_api_key/managers.py b/baseapp_api_key/managers.py index d3ed99c6..680ef20d 100644 --- a/baseapp_api_key/managers.py +++ b/baseapp_api_key/managers.py @@ -68,8 +68,7 @@ def encrypt(self, unencrypted_value: str | bytes, encryption_key: str | None = N else: raise TypeError("unencrypted_value must be str or bytes") - if not isinstance(encryption_key, str): - encryption_key = settings.BA_API_KEY_ENCRYPTION_KEY + encryption_key = self._get_encryption_key(encryption_key) aessiv = AESSIV(base64.urlsafe_b64decode(encryption_key)) associated_data = [] @@ -87,9 +86,7 @@ def decrypt(self, encrypted_value: bytes, encryption_key: str | None = None) -> Returns: bytes: The decrypted value as str. """ - - if not isinstance(encryption_key, str): - encryption_key = settings.BA_API_KEY_ENCRYPTION_KEY + encryption_key = self._get_encryption_key(encryption_key) aessiv = AESSIV(base64.urlsafe_b64decode(encryption_key)) associated_data = [] @@ -118,3 +115,10 @@ def rotate_encryption_key(self, encryption_key_old: str, encryption_key_new: str def get_queryset(self, *args, **kwargs) -> models.QuerySet: return super().get_queryset(*args, **kwargs).add_is_expired() + + def _get_encryption_key(self, encryption_key: str | None = None) -> str: + if not isinstance(encryption_key, str): + if not settings.BA_API_KEY_ENCRYPTION_KEY: + raise ValueError("BA_API_KEY_ENCRYPTION_KEY is not set") + encryption_key = settings.BA_API_KEY_ENCRYPTION_KEY + return encryption_key diff --git a/baseapp_api_key/tests/unit/test_api_key.py b/baseapp_api_key/tests/unit/test_api_key.py index 2f72596b..939dacbe 100644 --- a/baseapp_api_key/tests/unit/test_api_key.py +++ b/baseapp_api_key/tests/unit/test_api_key.py @@ -72,6 +72,31 @@ def test_can_rotate_encryption_key(self): ): APIKey.objects.decrypt(encrypted_value=api_key.encrypted_api_key) + def test_encrypt_raises_if_encryption_key_not_set(self): + with override_settings(BA_API_KEY_ENCRYPTION_KEY=None): + with self.assertRaises(ValueError) as excinfo: + APIKey.objects.encrypt("some-value") + assert "BA_API_KEY_ENCRYPTION_KEY is not set" in str(excinfo.exception) + + def test_decrypt_raises_if_encryption_key_not_set(self): + with override_settings(BA_API_KEY_ENCRYPTION_KEY=None): + with self.assertRaises(ValueError) as excinfo: + APIKey.objects.decrypt(b"00") + assert "BA_API_KEY_ENCRYPTION_KEY is not set" in str(excinfo.exception) + + def test_rotate_encryption_key_raises_if_encryption_key_not_set(self): + with override_settings( + BA_API_KEY_ENCRYPTION_KEY="jCe8USiQJDatFT5T0WCIl86QBxYs0-Q7iDJQc77Dh7LR1VAnWW3PA9UyXK-V7LhFnKq9sLd3xDw5FTrrYYtj2Q==" + ): + f.APIKeyFactory() + with override_settings(BA_API_KEY_ENCRYPTION_KEY=None): + with self.assertRaises(ValueError) as excinfo: + APIKey.objects.rotate_encryption_key( + encryption_key_old=None, + encryption_key_new=None, + ) + assert "BA_API_KEY_ENCRYPTION_KEY is not set" in str(excinfo.exception) + class TestAPIKeyAuthentication(APITestCase, URLPatternsTestCase): class DummyViewSet(viewsets.GenericViewSet): diff --git a/testproject/settings.py b/testproject/settings.py index 8494d0c8..d69ea17e 100644 --- a/testproject/settings.py +++ b/testproject/settings.py @@ -169,8 +169,8 @@ BASEAPP_REACTIONS_ENABLE_NOTIFICATIONS = False # API Key -BA_API_KEY_REQUEST_HEADER = env("BA_API_KEY_REQUEST_HEADER") -BA_API_KEY_ENCRYPTION_KEY = env("BA_API_KEY_ENCRYPTION_KEY") +BA_API_KEY_REQUEST_HEADER = env("BA_API_KEY_REQUEST_HEADER", default="HTTP_API_KEY") +BA_API_KEY_ENCRYPTION_KEY = env("BA_API_KEY_ENCRYPTION_KEY", default=None) # Graphene query optimizer GRAPHQL_QUERY_OPTIMIZER = {