mirror of
https://github.com/suitenumerique/django-lasuite
synced 2026-04-25 17:15:14 +02:00
✨(configuration) add configuration Value to support file path in env
This supports use of environment variables that either reference a value or a path to file containing the value. This is useful for secrets, to avoid the secret to be in a world-readable environment file.
This commit is contained in:
committed by
Manuel Raynaud
parent
88816d1de9
commit
06dc5aa836
@@ -10,6 +10,8 @@ and this project adheres to
|
||||
|
||||
### Changed
|
||||
|
||||
- ✨(configuration) add configuration Value to support file path
|
||||
in environment #15
|
||||
- ♻️(malware_detection) retry getting analyse result sooner
|
||||
|
||||
## [0.0.8] - 2025-05-06
|
||||
|
||||
@@ -52,8 +52,12 @@ dev = [
|
||||
malware_detection = [
|
||||
"celery>=5.0",
|
||||
]
|
||||
configuration = [
|
||||
"django-configurations>=2.5.1",
|
||||
]
|
||||
all=[
|
||||
"django-lasuite[malware_detection]"
|
||||
"django-lasuite[malware_detection]",
|
||||
"django-lasuite[configuration]",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
|
||||
50
src/lasuite/configuration/values.py
Normal file
50
src/lasuite/configuration/values.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Custom value classes for django-configurations."""
|
||||
|
||||
import os
|
||||
|
||||
from configurations import values
|
||||
|
||||
|
||||
class SecretFileValue(values.Value):
|
||||
"""
|
||||
Class used to interpret value from environment variables with reading file support.
|
||||
|
||||
The value set is either (in order of priority):
|
||||
* The content of the file referenced by the environment variable
|
||||
`{name}_{file_suffix}` if set.
|
||||
* The value of the environment variable `{name}` if set.
|
||||
* The default value
|
||||
"""
|
||||
|
||||
file_suffix = "FILE"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the value."""
|
||||
super().__init__(*args, **kwargs)
|
||||
if "file_suffix" in kwargs:
|
||||
self.file_suffix = kwargs["file_suffix"]
|
||||
|
||||
def setup(self, name):
|
||||
"""Get the value from environment variables."""
|
||||
value = self.default
|
||||
if self.environ:
|
||||
full_environ_name = self.full_environ_name(name)
|
||||
full_environ_name_file = f"{full_environ_name}_{self.file_suffix}"
|
||||
if full_environ_name_file in os.environ:
|
||||
filename = os.environ[full_environ_name_file]
|
||||
if not os.path.exists(filename):
|
||||
raise ValueError(f"Path {filename!r} does not exist.")
|
||||
try:
|
||||
with open(filename) as file:
|
||||
value = self.to_python(file.read().removesuffix("\n"))
|
||||
except (OSError, PermissionError) as err:
|
||||
raise ValueError(f"Path {filename!r} cannot be read: {err!r}") from err
|
||||
elif full_environ_name in os.environ:
|
||||
value = self.to_python(os.environ[full_environ_name])
|
||||
elif self.environ_required:
|
||||
raise ValueError(
|
||||
f"Value {name!r} is required to be set as the "
|
||||
f"environment variable {full_environ_name_file!r} or {full_environ_name!r}"
|
||||
)
|
||||
self.value = value
|
||||
return value
|
||||
1
tests/configuration/__init__.py
Normal file
1
tests/configuration/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Test configuration."""
|
||||
1
tests/configuration/test_secret
Normal file
1
tests/configuration/test_secret
Normal file
@@ -0,0 +1 @@
|
||||
TestSecretInFile
|
||||
79
tests/configuration/test_secret_file.py
Normal file
79
tests/configuration/test_secret_file.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Tests for SecretFileValue."""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from lasuite.configuration.values import SecretFileValue
|
||||
|
||||
FILE_SECRET_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_secret")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mock_clear_env(monkeypatch):
|
||||
"""Reset environment variables."""
|
||||
monkeypatch.delenv("DJANGO_TEST_SECRET_KEY", raising=False)
|
||||
monkeypatch.delenv("DJANGO_TEST_SECRET_KEY_FILE", raising=False)
|
||||
monkeypatch.delenv("DJANGO_TEST_SECRET_KEY_PATH", raising=False)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _mock_secret_key_env(monkeypatch):
|
||||
"""Set secret key in environment variable."""
|
||||
monkeypatch.setenv("DJANGO_TEST_SECRET_KEY", "TestSecretInEnv")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _mock_secret_key_file_env(monkeypatch):
|
||||
"""Set secret key path in environment variable."""
|
||||
monkeypatch.setenv("DJANGO_TEST_SECRET_KEY_FILE", FILE_SECRET_PATH)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _mock_secret_key_path_env(monkeypatch):
|
||||
"""Set secret key path in environment variable with another `file_suffix`."""
|
||||
monkeypatch.setenv("DJANGO_TEST_SECRET_KEY_PATH", FILE_SECRET_PATH)
|
||||
|
||||
|
||||
def test_secret_default():
|
||||
"""Test call with no environment variable."""
|
||||
value = SecretFileValue("DefaultTestSecret")
|
||||
assert value.setup("TEST_SECRET_KEY") == "DefaultTestSecret"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_mock_secret_key_env")
|
||||
def test_secret_in_env():
|
||||
"""Test call with secret key environment variable."""
|
||||
value = SecretFileValue("DefaultTestSecret")
|
||||
assert os.environ["DJANGO_TEST_SECRET_KEY"] == "TestSecretInEnv"
|
||||
assert value.setup("TEST_SECRET_KEY") == "TestSecretInEnv"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_mock_secret_key_file_env")
|
||||
def test_secret_in_file():
|
||||
"""Test call with secret key file environment variable."""
|
||||
value = SecretFileValue("DefaultTestSecret")
|
||||
assert os.environ["DJANGO_TEST_SECRET_KEY_FILE"] == FILE_SECRET_PATH
|
||||
assert value.setup("TEST_SECRET_KEY") == "TestSecretInFile"
|
||||
|
||||
|
||||
def test_secret_default_suffix():
|
||||
"""Test call with no environment variable and non default `file_suffix`."""
|
||||
value = SecretFileValue("DefaultTestSecret", file_suffix="PATH")
|
||||
assert value.setup("TEST_SECRET_KEY") == "DefaultTestSecret"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_mock_secret_key_env")
|
||||
def test_secret_in_env_suffix():
|
||||
"""Test call with secret key environment variable and non default `file_suffix`."""
|
||||
value = SecretFileValue("DefaultTestSecret", file_suffix="PATH")
|
||||
assert os.environ["DJANGO_TEST_SECRET_KEY"] == "TestSecretInEnv"
|
||||
assert value.setup("TEST_SECRET_KEY") == "TestSecretInEnv"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_mock_secret_key_path_env")
|
||||
def test_secret_in_file_suffix():
|
||||
"""Test call with secret key file environment variable and non default `file_suffix`."""
|
||||
value = SecretFileValue("DefaultTestSecret", file_suffix="PATH")
|
||||
assert os.environ["DJANGO_TEST_SECRET_KEY_PATH"] == FILE_SECRET_PATH
|
||||
assert value.setup("TEST_SECRET_KEY") == "TestSecretInFile"
|
||||
Reference in New Issue
Block a user