From 5cd3a8610698d596391046bf3d41e0834638310b Mon Sep 17 00:00:00 2001 From: Finn-Thorben Sell Date: Thu, 24 Mar 2022 14:25:28 +0100 Subject: [PATCH] implement example generators Example generators are an addition to the 'Value' classes that allow the error handler to display example values for the failed value. For example if no django secret key is found to be defined in the environment, an example (secure) value for such a secret key can now be generated on the fly and be suggested to the user --- configurations/errors.py | 3 ++ configurations/example_generators.py | 47 ++++++++++++++++++++++++++++ configurations/values.py | 5 +-- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 configurations/example_generators.py diff --git a/configurations/errors.py b/configurations/errors.py index cfca60e..1f4a984 100644 --- a/configurations/errors.py +++ b/configurations/errors.py @@ -26,6 +26,9 @@ def extract_explanation_lines_from_value(value_instance: 'Value') -> List[str]: result.append(f"{value_instance.destination_name} is taken from the environment variable " f"{value_instance.full_environ_name} as a {type(value_instance).__name__}") + if value_instance.example_generator is not None: + result.append(f"Example value: '{value_instance.example_generator()}'") + return result diff --git a/configurations/example_generators.py b/configurations/example_generators.py new file mode 100644 index 0000000..e52983c --- /dev/null +++ b/configurations/example_generators.py @@ -0,0 +1,47 @@ +from typing import Callable +import secrets +import base64 +from django.core.management.utils import get_random_secret_key +from django.utils.crypto import get_random_string, RANDOM_STRING_CHARS + + +def gen_django_secret_key() -> str: + """ + Generate a cryptographically secure random string that can safely be used as a SECRET_KEY in django + """ + return get_random_secret_key() + + +def gen_random_string(length: int, allowed_chars: str = RANDOM_STRING_CHARS) -> Callable[[], str]: + """ + Create a parameterized generator which generates a cryptographically secure random string of the given length + containing the given characters. + """ + def generate() -> str: + return get_random_string(length, allowed_chars) + return generate + + +def gen_bytes(length: int, encoding: str) -> Callable[[], str]: + """ + Create a parameterized generator which generates a cryptographically secure random assortments of bytes of the given + length and encoded in the given format + + :param length: How many bytes should be generated. Not how long the encoded string will be. + :param encoding: How the generated bytes should be encoded. + Accepted values are "base64", "base64_urlsafe" and "hex" (case is ignored) + """ + encoding = encoding.lower() + if encoding not in ("base64", "base64_urlsafe", "hex"): + raise ValueError(f"Cannot gen_bytes with encoding '{encoding}'. Valid encodings are 'base64', 'base64_urlsafe'" + f" and 'hex'") + + def generate() -> str: + b = secrets.token_bytes(length) + if encoding == "base64": + return base64.standard_b64encode(b).decode("ASCII") + elif encoding == "base64_urlsafe": + return base64.urlsafe_b64encode(b).decode("ASCII") + elif encoding == "hex": + return b.hex().upper() + return generate diff --git a/configurations/values.py b/configurations/values.py index 73d2046..98d3c26 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -58,8 +58,8 @@ class Value: return instance def __init__(self, default=None, environ=True, environ_name=None, - environ_prefix='DJANGO', environ_required=False, help_text=None, - help_reference=None, *args, **kwargs): + environ_prefix='DJANGO', environ_required=False, example_generator=None, + help_text=None, help_reference=None, *args, **kwargs): if isinstance(default, Value) and default.default is not None: self.default = copy.copy(default.default) else: @@ -73,6 +73,7 @@ class Value: self.destination_name = None self.help_text = help_text self.help_reference = help_reference + self.example_generator = example_generator def __str__(self): return str(self.value)