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