Skip to content

Commit

Permalink
add custom message
Browse files Browse the repository at this point in the history
  • Loading branch information
Joaopeuko committed Apr 23, 2024
1 parent d29869c commit c412325
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 30 deletions.
4 changes: 2 additions & 2 deletions examples/examples.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from secured.attribute import AttrDict
from secured.secured import Secured

secured = Secured('examples/config-secrets.yaml', secure=True)
secured = Secured('examples/config-secrets.yaml', secure=True, message="<Custom Secured>")

print(secured.config_secrets.name)
print(secured.config_secrets["name"])

ad = AttrDict(secure=True)
ad = AttrDict(secure=True, message="<Custom Secured>")
ad['password'] = 'my_secret'
print((ad.password))
print((ad['password']))
24 changes: 14 additions & 10 deletions secured/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,33 @@ class AttrDict(dict):
A dictionary subclass that allows attribute-style access and can optionally secure its leaf values.
This class extends the standard dictionary to support access via attributes as well as keys. If initialized
with `secure=True`, all non-dictionary values are wrapped using the Secure class to obscure sensitive information.
with `secure=True`, all non-dictionary values are wrapped using the Secure class to obscure sensitive information
with an optional custom message.
Attributes:
secure (bool): Determines whether the dictionary's values should be automatically secured.
message (str): Custom message to display when values are secured.
Examples:
>>> ad = AttrDict(secure=True)
>>> ad = AttrDict(secure=True, message="<Custom Secured>")
>>> ad['password'] = 'my_secret'
>>> print(ad.password) # Assuming Secure.__str__() returns '<Secured>'
'<Secured>'
>>> print(ad.password)
'<Custom Secured>'
"""

def __init__(self, *args, secure: bool = False, **kwargs) -> None:
def __init__(self, *args, secure: bool = False, message: str = "<Sensitive data secured>", **kwargs) -> None:
"""
Initialize the AttrDict with the same arguments as a normal dict, plus a `secure` option.
Initialize the AttrDict with the same arguments as a normal dict, plus options to secure.
Args:
*args: Variable length argument list for dictionary items.
secure (bool): If True, non-dict values will be wrapped by the Secure class.
secure (bool): If True, non-dict values will be wrapped by the Secure class with the given message.
message (str): Custom message used when values are secured.
**kwargs: Arbitrary keyword arguments for dictionary items.
"""
super().__init__(*args, **kwargs)
self.secure = secure
self.message = message
self._convert_dicts()

def _convert_dicts(self) -> None:
Expand All @@ -50,9 +54,9 @@ def _convert_value(self, value: Any) -> Union[T, 'Secure']:
Union[T, Secure]: The converted value, secured if `secure` is True and not a dictionary.
"""
if isinstance(value, dict):
return AttrDict(value, secure=self.secure)
return AttrDict(value, secure=self.secure, message=self.message)
elif self.secure:
return Secure(value)
return Secure(value, self.message)
return value

def __getattr__(self, item: str) -> Union[T, 'Secure']:
Expand Down Expand Up @@ -83,7 +87,7 @@ def __setattr__(self, key: str, value: Any) -> None:
This directly modifies the dictionary if `key` is not a special attribute.
"""
if key in ['secure', '_initializing']:
if key in ['secure', 'message']:
super().__setattr__(key, value)
else:
self[key] = self._convert_value(value)
Expand Down
79 changes: 66 additions & 13 deletions secured/secure.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,88 @@
from typing import Union


class Secure(str):
"""
A class for securing sensitive data.
This class is designed to add a thing layer of protection by obscuring sensitive data, such as database URLs or API keys,
in order to prevent accidental exposure in logs or debug output.
This class is designed to add a thin layer of protection by obscuring sensitive data, such as database URLs or API keys,
in order to prevent accidental exposure in logs or debug output. The representation of the secured data can be customized
with a specific message.
Attributes:
message (str): Custom message to represent the secured data when printed or logged.
Example:
>>> DATABASE_URL = "your_actual_database_url"
>>> sensitive_data = Secure(DATABASE_URL)
>>> sensitive_data = Secure(DATABASE_URL, "<Data Hidden>")
>>> print(sensitive_data)
'<Sensitive data secured>'
'<Data Hidden>'
"""

def __repr__(self):
return "<Sensitive data secured>"
def __new__(cls, original: str, message: str = "<Sensitive data secured>"):
"""
Create a new Secure instance that appears as a custom message.
Args:
original (str): The original string to secure.
message (str): A placeholder message to display instead of the original content.
Returns:
Secure: A new Secure instance displaying the placeholder message.
"""
# Initialize the Secure instance with the message instead of the original content.
return super(Secure, cls).__new__(cls, message)

def __init__(self, original: str, message: str = "<Sensitive data secured>"):
"""
Initializes a Secure object. The initialization logic is handled by __new__; __init__ does not
need to handle the data directly.
Args:
original (str): The original data to secure.
message (str): A custom message to use for representing the secured data.
"""
super().__init__()
self.original = original
self.message = message

def __repr__(self) -> str:
"""
Represent the Secure object using the custom message.
Returns:
str: The custom message representing the secured data.
"""
return self.message

def __str__(self) -> str:
return repr(self)
"""
Convert the Secure object to string using the custom message.
Returns:
str: The custom message representing the secured data.
"""
return self.__repr__()

def to_int(self) -> Union[int, str]:
"""
Try converting the original secured data to an integer.
Returns:
Union[int, str]: The integer value of the original data, or the custom message if conversion fails.
"""
try:
return int(self)
return int(self.original)
except ValueError:
return self
return self.message

def to_float(self) -> Union[float, str]:
"""
Try converting the original secured data to a float.
Returns:
Union[float, str]: The float value of the original data, or the custom message if conversion fails.
"""
try:
return float(self)
return float(self.original)
except ValueError:
return self
return self.message
15 changes: 10 additions & 5 deletions secured/secured.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
from .attribute import AttrDict

class Secured:
def __init__(self, yaml_paths: Union[str, List[str]] = None, secure: bool = False, as_attrdict: bool = True):
def __init__(self, yaml_paths: Union[str, List[str]] = None, secure: bool = False,
as_attrdict: bool = True, message: str = "<Sensitive data secured>"):
self.as_attrdict = as_attrdict
self.secure = secure
self.message = message # Custom message for secured data
self.load_yaml(yaml_paths=yaml_paths, secure=secure)


def load_yaml(self, yaml_paths: Union[str, List[str]], secure: bool):
if not yaml_paths:
return
Expand All @@ -23,21 +27,22 @@ def load_yaml(self, yaml_paths: Union[str, List[str]], secure: bool):

def create_config(self, data: dict, secure: bool):
if self.as_attrdict:
return AttrDict(data, secure=secure)
return AttrDict(data, secure=secure, message=self.message)
else:
return {key: Secure(val) if secure and not isinstance(val, dict) else val
return {key: Secure(val, self.message) if secure and not isinstance(val, dict) else val
for key, val in self._recursive_dict(data).items()}


def _recursive_dict(self, data: dict):
return {key: self._recursive_dict(val) if isinstance(val, dict) else val for key, val in data.items()}

def get(self, key: str, required: bool = False, secure: bool = False) -> Any:
attr_value = getattr(self, key, None)
if attr_value is not None:
return Secure(attr_value) if secure and not isinstance(attr_value, (AttrDict, dict)) else attr_value
return Secure(attr_value, self.message) if secure and not isinstance(attr_value, (AttrDict, dict)) else attr_value
env_value = os.getenv(key)
if env_value is not None:
return Secure(env_value) if secure else env_value
return Secure(env_value, self.message) if secure else env_value
if required:
raise ValueError(f"Key '{key}' not found in configuration or OS environment.")
return None
Expand Down

0 comments on commit c412325

Please sign in to comment.