turbo_broccoli.custom.secret

Serialize secrets

  1"""Serialize secrets"""
  2
  3import json
  4from typing import Any, NoReturn
  5
  6from nacl.secret import SecretBox
  7
  8from ..context import Context
  9from ..exceptions import TypeNotSupported
 10
 11
 12class Secret:
 13    """
 14    A wrapper for a basic Python variable whose value is considered to be
 15    secret. Similar API as [`pydantic`'s secret
 16    types](https://pydantic-docs.helpmanual.io/usage/types/#secret-types)
 17    """
 18
 19    _value: Any
 20
 21    def __eq__(self, __o: object) -> bool:
 22        return False
 23
 24    def __init__(self, value: Any) -> None:
 25        self._value = value
 26
 27    def __ne__(self, __o: object) -> bool:
 28        return False
 29
 30    def __repr__(self) -> str:
 31        return "--REDACTED--"
 32
 33    def __str__(self) -> str:
 34        return "--REDACTED--"
 35
 36    def get_secret_value(self) -> Any:
 37        """Self-explanatory"""
 38        return self._value
 39
 40
 41class LockedSecret(Secret):
 42    """
 43    Represented a secret that could not be decrypted because the shared key was
 44    not provided. The `get_secret_value` method always raises a `RuntimeError`.
 45    """
 46
 47    def __init__(self) -> None:
 48        super().__init__(None)
 49
 50    def get_secret_value(self) -> NoReturn:
 51        raise RuntimeError("Cannot get the secret value of a locked secret")
 52
 53
 54class SecretDict(Secret):
 55    def __init__(self, value: dict) -> None:
 56        super().__init__(value)
 57
 58
 59class SecretFloat(Secret):
 60    def __init__(self, value: float) -> None:
 61        super().__init__(value)
 62
 63
 64class SecretInt(Secret):
 65    def __init__(self, value: int) -> None:
 66        super().__init__(value)
 67
 68
 69class SecretList(Secret):
 70    def __init__(self, value: list) -> None:
 71        super().__init__(value)
 72
 73
 74class SecretStr(Secret):
 75    def __init__(self, value: str) -> None:
 76        super().__init__(value)
 77
 78
 79def _from_json_v2(dct: dict, ctx: Context) -> Any:
 80    if ctx.nacl_shared_key is None:
 81        return LockedSecret()
 82    box = SecretBox(ctx.nacl_shared_key)
 83    return json.loads(box.decrypt(dct["data"]).decode("utf-8"))
 84
 85
 86def from_json(dct: dict, ctx: Context) -> Any:
 87    ctx.raise_if_nodecode("bytes")
 88    decoders = {
 89        # 1: _from_json_v1,  # Use turbo_broccoli v3
 90        2: _from_json_v2,
 91    }
 92    obj = decoders[dct["__version__"]](dct, ctx)
 93    if isinstance(obj, LockedSecret):
 94        return obj
 95    types = {
 96        dict: SecretDict,
 97        float: SecretFloat,
 98        int: SecretInt,
 99        list: SecretList,
100        str: SecretStr,
101    }
102    return types[type(obj)](obj)
103
104
105def to_json(obj: Secret, ctx: Context) -> dict:
106    """
107    Encrypts a JSON **string representation** of a secret document into a
108    new JSON document with the following structure:
109
110    ```py
111    {
112        "__type__": "secret",
113        "__version__": 2,
114        "data": <encrypted bytes>,
115    }
116    ```
117    """
118    if not isinstance(obj, Secret):
119        raise TypeNotSupported()
120    if ctx.nacl_shared_key is None:
121        raise RuntimeError(
122            "Attempting to serialize a secret type but no shared key is set. "
123            "Either set `nacl_shared_key` when constructing the encoding "
124            "torbo_broccoli.context.Context, or set the TB_SHARED_KEY "
125            "environment variable."
126        )
127    box = SecretBox(ctx.nacl_shared_key)
128    return {
129        "__type__": "secret",
130        "__version__": 2,
131        "data": box.encrypt(
132            json.dumps(obj.get_secret_value()).encode("utf-8")
133        ),
134    }
class Secret:
13class Secret:
14    """
15    A wrapper for a basic Python variable whose value is considered to be
16    secret. Similar API as [`pydantic`'s secret
17    types](https://pydantic-docs.helpmanual.io/usage/types/#secret-types)
18    """
19
20    _value: Any
21
22    def __eq__(self, __o: object) -> bool:
23        return False
24
25    def __init__(self, value: Any) -> None:
26        self._value = value
27
28    def __ne__(self, __o: object) -> bool:
29        return False
30
31    def __repr__(self) -> str:
32        return "--REDACTED--"
33
34    def __str__(self) -> str:
35        return "--REDACTED--"
36
37    def get_secret_value(self) -> Any:
38        """Self-explanatory"""
39        return self._value

A wrapper for a basic Python variable whose value is considered to be secret. Similar API as pydantic's secret types

Secret(value: Any)
25    def __init__(self, value: Any) -> None:
26        self._value = value
def get_secret_value(self) -> Any:
37    def get_secret_value(self) -> Any:
38        """Self-explanatory"""
39        return self._value

Self-explanatory

class LockedSecret(Secret):
42class LockedSecret(Secret):
43    """
44    Represented a secret that could not be decrypted because the shared key was
45    not provided. The `get_secret_value` method always raises a `RuntimeError`.
46    """
47
48    def __init__(self) -> None:
49        super().__init__(None)
50
51    def get_secret_value(self) -> NoReturn:
52        raise RuntimeError("Cannot get the secret value of a locked secret")

Represented a secret that could not be decrypted because the shared key was not provided. The get_secret_value method always raises a RuntimeError.

def get_secret_value(self) -> NoReturn:
51    def get_secret_value(self) -> NoReturn:
52        raise RuntimeError("Cannot get the secret value of a locked secret")

Self-explanatory

class SecretDict(Secret):
55class SecretDict(Secret):
56    def __init__(self, value: dict) -> None:
57        super().__init__(value)

A wrapper for a basic Python variable whose value is considered to be secret. Similar API as pydantic's secret types

SecretDict(value: dict)
56    def __init__(self, value: dict) -> None:
57        super().__init__(value)
Inherited Members
Secret
get_secret_value
class SecretFloat(Secret):
60class SecretFloat(Secret):
61    def __init__(self, value: float) -> None:
62        super().__init__(value)

A wrapper for a basic Python variable whose value is considered to be secret. Similar API as pydantic's secret types

SecretFloat(value: float)
61    def __init__(self, value: float) -> None:
62        super().__init__(value)
Inherited Members
Secret
get_secret_value
class SecretInt(Secret):
65class SecretInt(Secret):
66    def __init__(self, value: int) -> None:
67        super().__init__(value)

A wrapper for a basic Python variable whose value is considered to be secret. Similar API as pydantic's secret types

SecretInt(value: int)
66    def __init__(self, value: int) -> None:
67        super().__init__(value)
Inherited Members
Secret
get_secret_value
class SecretList(Secret):
70class SecretList(Secret):
71    def __init__(self, value: list) -> None:
72        super().__init__(value)

A wrapper for a basic Python variable whose value is considered to be secret. Similar API as pydantic's secret types

SecretList(value: list)
71    def __init__(self, value: list) -> None:
72        super().__init__(value)
Inherited Members
Secret
get_secret_value
class SecretStr(Secret):
75class SecretStr(Secret):
76    def __init__(self, value: str) -> None:
77        super().__init__(value)

A wrapper for a basic Python variable whose value is considered to be secret. Similar API as pydantic's secret types

SecretStr(value: str)
76    def __init__(self, value: str) -> None:
77        super().__init__(value)
Inherited Members
Secret
get_secret_value
def from_json(dct: dict, ctx: turbo_broccoli.context.Context) -> Any:
 87def from_json(dct: dict, ctx: Context) -> Any:
 88    ctx.raise_if_nodecode("bytes")
 89    decoders = {
 90        # 1: _from_json_v1,  # Use turbo_broccoli v3
 91        2: _from_json_v2,
 92    }
 93    obj = decoders[dct["__version__"]](dct, ctx)
 94    if isinstance(obj, LockedSecret):
 95        return obj
 96    types = {
 97        dict: SecretDict,
 98        float: SecretFloat,
 99        int: SecretInt,
100        list: SecretList,
101        str: SecretStr,
102    }
103    return types[type(obj)](obj)
def to_json( obj: Secret, ctx: turbo_broccoli.context.Context) -> dict:
106def to_json(obj: Secret, ctx: Context) -> dict:
107    """
108    Encrypts a JSON **string representation** of a secret document into a
109    new JSON document with the following structure:
110
111    ```py
112    {
113        "__type__": "secret",
114        "__version__": 2,
115        "data": <encrypted bytes>,
116    }
117    ```
118    """
119    if not isinstance(obj, Secret):
120        raise TypeNotSupported()
121    if ctx.nacl_shared_key is None:
122        raise RuntimeError(
123            "Attempting to serialize a secret type but no shared key is set. "
124            "Either set `nacl_shared_key` when constructing the encoding "
125            "torbo_broccoli.context.Context, or set the TB_SHARED_KEY "
126            "environment variable."
127        )
128    box = SecretBox(ctx.nacl_shared_key)
129    return {
130        "__type__": "secret",
131        "__version__": 2,
132        "data": box.encrypt(
133            json.dumps(obj.get_secret_value()).encode("utf-8")
134        ),
135    }

Encrypts a JSON string representation of a secret document into a new JSON document with the following structure:

{
    "__type__": "secret",
    "__version__": 2,
    "data": <encrypted bytes>,
}