Add method for getting redacted copy of context

There are times, such as in oslo.messaging notifications, when we'd like
a context object to be stripped of any secrets. This adds a method to
RequestContext, redacted_copy(), which returns a copy of that context,
carrying over an allowlist of fields only, leaving secrets behind.

Related-bug: 2030976
Change-Id: Ie4a8eb784c505c41ec5fcd4cba091cc555146763
This commit is contained in:
Jay Faulkner 2023-08-16 08:34:24 -07:00
parent 5168ccdf01
commit 059f9ba16e
3 changed files with 58 additions and 1 deletions

View File

@ -387,6 +387,42 @@ class RequestContext(object):
""" """
return self.global_request_id or self.request_id return self.global_request_id or self.request_id
def redacted_copy(self) -> 'RequestContext':
"""Return a copy of the context with sensitive fields redacted.
This is useful for creating a context that can be safely logged.
:returns: A copy of the context with sensitive fields redacted.
"""
return self.__class__(
user_id=self.user_id,
project_id=self.project_id,
domain_id=self.domain_id,
user_domain_id=self.user_domain_id,
project_domain_id=self.project_domain_id,
request_id=self.request_id,
roles=self.roles,
user_name=self.user_name,
project_name=self.project_name,
domain_name=self.domain_name,
user_domain_name=self.user_domain_name,
project_domain_name=self.project_domain_name,
service_user_id=self.service_user_id,
service_user_domain_id=self.service_user_domain_id,
service_user_domain_name=self.service_user_domain_name,
service_project_id=self.service_project_id,
service_project_name=self.service_project_name,
service_project_domain_id=self.service_project_domain_id,
service_project_domain_name=self.service_project_domain_name,
service_roles=self.service_roles,
global_request_id=self.global_request_id,
system_scope=self.system_scope,
user=self.user,
domain=self.domain,
user_domain=self.user_domain,
project_domain=self.project_domain
)
@classmethod @classmethod
@_renamed_kwarg('user', 'user_id') @_renamed_kwarg('user', 'user_id')
@_renamed_kwarg('domain', 'domain_id') @_renamed_kwarg('domain', 'domain_id')

View File

@ -518,11 +518,25 @@ class ContextTest(test_base.BaseTestCase):
self.assertEqual(user_domain_name, d['user_domain_name']) self.assertEqual(user_domain_name, d['user_domain_name'])
self.assertEqual(project_domain_name, d['project_domain_name']) self.assertEqual(project_domain_name, d['project_domain_name'])
def test_auth_token_info_removed(self): def test_auth_token_info_removed_logging_values(self):
ctx = TestContext(auth_token_info={'auth_token': 'topsecret'}) ctx = TestContext(auth_token_info={'auth_token': 'topsecret'})
d = ctx.get_logging_values() d = ctx.get_logging_values()
self.assertNotIn('auth_token_info', d) self.assertNotIn('auth_token_info', d)
def test_auth_token_info_removed_redacted_context(self):
userid = 'foo'
ctx = TestContext(
auth_token_info={'auth_token': 'topsecret'},
service_token="1234567",
auth_token="8901234",
user_id=userid)
safe_ctxt = ctx.redacted_copy()
self.assertIsNone(safe_ctxt.auth_token_info)
self.assertIsNone(safe_ctxt.service_token)
self.assertIsNone(safe_ctxt.auth_token)
self.assertEqual(userid, safe_ctxt.user_id)
self.assertNotEqual(ctx, safe_ctxt)
def test_dict_empty_user_identity(self): def test_dict_empty_user_identity(self):
ctx = context.RequestContext() ctx = context.RequestContext()
d = ctx.to_dict() d = ctx.to_dict()

View File

@ -0,0 +1,7 @@
features:
- |
Added ``redacted_copy`` method to ``RequestContext``. This returns a copy
of the context with secrets redacted. This will allow downstreams that
inherit and enhance the ``RequestContext`` can include the additional data
in the redacted copy if they wish by overriding the ``redacted_copy``
method.