Add SensitiveString field type
There are objects that define a String field to hold a serialized json blob and that blob can have sensitive information in it (tokens, passwords, etc). When the object is logged, repr will log that sensitive information. This puts a burden on the code that's doing the logging to remember to sanitize the object's string form before logging it. It'd be simpler and cleaner to just have a field type that extends String so it's the same in all respects except for when it's stringified. So in SensitiveString's stringify method it just masks the password on the field value before returning it. And no SensitiveString does not play an acoustic guitar at your local coffee shop, he's actually super tough and emotionally walled up, that's why he doesn't like exposing too much information about himself in the logs. Closes-Bug: #1321785 Change-Id: I56466eb8b5d8000ef010f2015f453a9e74dc66b8
This commit is contained in:
@@ -253,6 +253,16 @@ class String(FieldType):
|
||||
return '\'%s\'' % value
|
||||
|
||||
|
||||
class SensitiveString(String):
|
||||
"""A string field type that may contain sensitive (password) information.
|
||||
|
||||
Passwords in the string value are masked when stringified.
|
||||
"""
|
||||
def stringify(self, value):
|
||||
return super(SensitiveString, self).stringify(
|
||||
strutils.mask_password(value))
|
||||
|
||||
|
||||
class VersionPredicate(String):
|
||||
@staticmethod
|
||||
def coerce(obj, attr, value):
|
||||
@@ -547,6 +557,11 @@ class StringField(AutoTypedField):
|
||||
AUTO_TYPE = String()
|
||||
|
||||
|
||||
class SensitiveStringField(AutoTypedField):
|
||||
"""Field type that masks passwords when the field is stringified."""
|
||||
AUTO_TYPE = SensitiveString()
|
||||
|
||||
|
||||
class VersionPredicateField(AutoTypedField):
|
||||
AUTO_TYPE = VersionPredicate()
|
||||
|
||||
|
||||
@@ -119,6 +119,17 @@ class TestString(TestField):
|
||||
self.assertEqual("'123'", self.field.stringify(123))
|
||||
|
||||
|
||||
class TestSensitiveString(TestString):
|
||||
def setUp(self):
|
||||
super(TestSensitiveString, self).setUp()
|
||||
self.field = fields.SensitiveStringField()
|
||||
|
||||
def test_stringify(self):
|
||||
payload = """{'admin_password':'mypassword'}"""
|
||||
expected = """'{'admin_password':'***'}'"""
|
||||
self.assertEqual(expected, self.field.stringify(payload))
|
||||
|
||||
|
||||
class TestVersionPredicate(TestString):
|
||||
def setUp(self):
|
||||
super(TestVersionPredicate, self).setUp()
|
||||
|
||||
@@ -144,6 +144,18 @@ class MyObj2(base.VersionedObject):
|
||||
pass
|
||||
|
||||
|
||||
@base.VersionedObjectRegistry.register_if(False)
|
||||
class MySensitiveObj(base.VersionedObject):
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'data': fields.SensitiveStringField(nullable=True)
|
||||
}
|
||||
|
||||
@base.remotable_classmethod
|
||||
def query(cls, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class RandomMixInWithNoFields(object):
|
||||
"""Used to test object inheritance using a mixin that has no fields."""
|
||||
pass
|
||||
@@ -1123,6 +1135,14 @@ class _TestObject(object):
|
||||
'rel_object=<?>,rel_objects=<?>,timestamp=<?>)',
|
||||
repr(obj))
|
||||
|
||||
def test_obj_repr_sensitive(self):
|
||||
obj = MySensitiveObj(data="""{'admin_password':'mypassword'}""")
|
||||
self.assertEqual(
|
||||
'MySensitiveObj(data=\'{\'admin_password\':\'***\'}\')', repr(obj))
|
||||
|
||||
obj2 = MySensitiveObj()
|
||||
self.assertEqual('MySensitiveObj(data=<?>)', repr(obj2))
|
||||
|
||||
def test_obj_make_obj_compatible_with_relationships(self):
|
||||
subobj = MyOwnedObject(baz=1)
|
||||
obj = MyObj(rel_object=subobj)
|
||||
|
||||
Reference in New Issue
Block a user