Merge "Add a mechanism to mask passwords in dictionaries"
This commit is contained in:
commit
a3e0be7e36
@ -287,6 +287,79 @@ def mask_password(message, secret="***"): # nosec
|
||||
return message
|
||||
|
||||
|
||||
def mask_dict_password(dictionary, secret="***"): # nosec
|
||||
"""Replace password with *secret* in a dictionary recursively.
|
||||
|
||||
:param dictionary: The dictionary which includes secret information.
|
||||
:param secret: value with which to replace secret information.
|
||||
:returns: The dictionary with string substitutions.
|
||||
|
||||
A dictionary (which may contain nested dictionaries) contains
|
||||
information (such as passwords) which should not be revealed, and
|
||||
this function helps detect and replace those with the 'secret'
|
||||
provided (or '***' if none is provided).
|
||||
|
||||
Substitution is performed in one of three situations:
|
||||
|
||||
If the key is something that is considered to be indicative of a
|
||||
secret, then the corresponding value is replaced with the secret
|
||||
provided (or '***' if none is provided).
|
||||
|
||||
If a value in the dictionary is a string, then it is masked
|
||||
using the mask_password() function.
|
||||
|
||||
Finally, if a value is a dictionary, this function will
|
||||
recursively mask that dictionary as well.
|
||||
|
||||
For example:
|
||||
|
||||
>>> mask_dict_password({'password': 'd81juxmEW_',
|
||||
>>> 'user': 'admin',
|
||||
>>> 'home-dir': '/home/admin'},
|
||||
>>> '???')
|
||||
{'password': '???', 'user': 'admin', 'home-dir': '/home/admin'}
|
||||
|
||||
For example (the value is masked using mask_password())
|
||||
|
||||
>>> mask_dict_password({'password': '--password d81juxmEW_',
|
||||
>>> 'user': 'admin',
|
||||
>>> 'home-dir': '/home/admin'},
|
||||
>>> '???')
|
||||
{'password': '--password ???', 'user': 'admin',
|
||||
'home-dir': '/home/admin'}
|
||||
|
||||
|
||||
For example (a nested dictionary is masked):
|
||||
|
||||
>>> mask_dict_password({"nested": {'password': 'd81juxmEW_',
|
||||
>>> 'user': 'admin',
|
||||
>>> 'home': '/home/admin'}},
|
||||
>>> '???')
|
||||
{"nested": {'password': '???', 'user': 'admin', 'home': '/home/admin'}}
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(dictionary, dict):
|
||||
raise TypeError("Expected a dictionary, got %s instead."
|
||||
% type(dictionary))
|
||||
|
||||
out = {}
|
||||
|
||||
for k, v in dictionary.items():
|
||||
if isinstance(v, dict):
|
||||
v = mask_dict_password(v, secret=secret)
|
||||
elif k in _SANITIZE_KEYS:
|
||||
v = secret
|
||||
elif isinstance(v, six.string_types):
|
||||
v = mask_password(v, secret=secret)
|
||||
|
||||
out[k] = v
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def is_int_like(val):
|
||||
"""Check if a value looks like an integer with base 10.
|
||||
|
||||
|
@ -587,6 +587,55 @@ class MaskPasswordTestCase(test_base.BaseTestCase):
|
||||
self.assertEqual(expected, strutils.mask_password(payload))
|
||||
|
||||
|
||||
class MaskDictionaryPasswordTestCase(test_base.BaseTestCase):
|
||||
|
||||
def test_dictionary(self):
|
||||
payload = {'password': 'mypassword'}
|
||||
expected = {'password': '***'}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
payload = {'user': 'admin', 'password': 'mypassword'}
|
||||
expected = {'user': 'admin', 'password': '***'}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
payload = {'strval': 'somestring',
|
||||
'dictval': {'user': 'admin', 'password': 'mypassword'}}
|
||||
expected = {'strval': 'somestring',
|
||||
'dictval': {'user': 'admin', 'password': '***'}}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
payload = {'strval': '--password abc',
|
||||
'dont_change': 'this is fine',
|
||||
'dictval': {'user': 'admin', 'password': b'mypassword'}}
|
||||
expected = {'strval': '--password ***',
|
||||
'dont_change': 'this is fine',
|
||||
'dictval': {'user': 'admin', 'password': '***'}}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
def test_do_no_harm(self):
|
||||
payload = {}
|
||||
expected = {}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
payload = {'somekey': 'somevalue',
|
||||
'anotherkey': 'anothervalue'}
|
||||
expected = {'somekey': 'somevalue',
|
||||
'anotherkey': 'anothervalue'}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
def test_mask_values(self):
|
||||
payload = {'somekey': 'test = cmd --password my\xe9\x80\x80pass'}
|
||||
expected = {'somekey': 'test = cmd --password ***'}
|
||||
self.assertEqual(expected,
|
||||
strutils.mask_dict_password(payload))
|
||||
|
||||
|
||||
class IsIntLikeTestCase(test_base.BaseTestCase):
|
||||
def test_is_int_like_true(self):
|
||||
self.assertTrue(strutils.is_int_like(1))
|
||||
|
Loading…
x
Reference in New Issue
Block a user