diff --git a/oslo_context/context.py b/oslo_context/context.py index 148817e..09a2cc1 100644 --- a/oslo_context/context.py +++ b/oslo_context/context.py @@ -387,6 +387,42 @@ class RequestContext(object): """ 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 @_renamed_kwarg('user', 'user_id') @_renamed_kwarg('domain', 'domain_id') diff --git a/oslo_context/tests/test_context.py b/oslo_context/tests/test_context.py index 341dc39..795f282 100644 --- a/oslo_context/tests/test_context.py +++ b/oslo_context/tests/test_context.py @@ -518,11 +518,25 @@ class ContextTest(test_base.BaseTestCase): self.assertEqual(user_domain_name, d['user_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'}) d = ctx.get_logging_values() 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): ctx = context.RequestContext() d = ctx.to_dict() diff --git a/releasenotes/notes/redacted-copy-efd02c83c287451f.yaml b/releasenotes/notes/redacted-copy-efd02c83c287451f.yaml new file mode 100644 index 0000000..47ac6db --- /dev/null +++ b/releasenotes/notes/redacted-copy-efd02c83c287451f.yaml @@ -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.