diff --git a/awx/api/views.py b/awx/api/views.py index 05c15b9243..efb3f80a95 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -77,6 +77,7 @@ from awx.main.utils import ( from awx.main.utils.encryption import encrypt_value from awx.main.utils.filters import SmartFilter from awx.main.utils.insights import filter_insights_api_response +from awx.main.redact import UriCleaner from awx.api.permissions import ( JobTemplateCallbackPermission, TaskPermission, @@ -4645,9 +4646,17 @@ class UnifiedJobList(ListAPIView): serializer_class = UnifiedJobListSerializer -class StdoutANSIFilter(object): +def redact_ansi(line): + # Remove ANSI escape sequences used to embed event data. + line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line) + # Remove ANSI color escape sequences. + return re.sub(r'\x1b[^m]*m', '', line) + + +class StdoutFilter(object): def __init__(self, fileobj): + self._functions = [] self.fileobj = fileobj self.extra_data = '' if hasattr(fileobj, 'close'): @@ -4659,10 +4668,7 @@ class StdoutANSIFilter(object): line = self.fileobj.readline(size) if not line: break - # Remove ANSI escape sequences used to embed event data. - line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line) - # Remove ANSI color escape sequences. - line = re.sub(r'\x1b[^m]*m', '', line) + line = self.process_line(line) data += line if size > 0 and len(data) > size: self.extra_data = data[size:] @@ -4671,6 +4677,14 @@ class StdoutANSIFilter(object): self.extra_data = '' return data + def register(self, func): + self._functions.append(func) + + def process_line(self, line): + for func in self._functions: + line = func(line) + return line + class UnifiedJobStdout(RetrieveAPIView): @@ -4728,9 +4742,12 @@ class UnifiedJobStdout(RetrieveAPIView): suffix='.ansi' if target_format == 'ansi_download' else '' ) content_fd = unified_job.result_stdout_raw_handle(enforce_max_bytes=False) + redactor = StdoutFilter(content_fd) if target_format == 'txt_download': - content_fd = StdoutANSIFilter(content_fd) - response = HttpResponse(FileWrapper(content_fd), content_type='text/plain') + redactor.register(redact_ansi) + if type(unified_job) == ProjectUpdate: + redactor.register(UriCleaner.remove_sensitive) + response = HttpResponse(FileWrapper(redactor), content_type='text/plain') response["Content-Disposition"] = 'attachment; filename="{}"'.format(filename) return response else: diff --git a/awx/main/tests/unit/test_redact.py b/awx/main/tests/unit/test_redact.py index 17d948cd27..fa59ca43d7 100644 --- a/awx/main/tests/unit/test_redact.py +++ b/awx/main/tests/unit/test_redact.py @@ -79,20 +79,22 @@ TEST_CLEARTEXT.append({ }) -@pytest.mark.parametrize('username, password, not_uri', [ - ('', '', 'www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), - ('', '', 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), - ('root', 'gigity', 'https://root@gigity@www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), - ('root', 'gigity@', 'https://root:gigity@@@www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), +@pytest.mark.parametrize('username, password, not_uri, expected', [ + ('', '', 'www.famfamfam.com](http://www.famfamfam.com/fijdlfd', 'www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), + ('', '', 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd', '$encrypted$'), + ('root', 'gigity', 'https://root@gigity@www.famfamfam.com](http://www.famfamfam.com/fijdlfd', '$encrypted$'), + ('root', 'gigity@', 'https://root:gigity@@@www.famfamfam.com](http://www.famfamfam.com/fijdlfd', '$encrypted$'), ]) # should redact sensitive usernames and passwords -def test_non_uri_redact(username, password, not_uri): +def test_non_uri_redact(username, password, not_uri, expected): redacted_str = UriCleaner.remove_sensitive(not_uri) if username: assert username not in redacted_str if password: assert password not in redacted_str + assert redacted_str == expected + def test_multiple_non_uri_redact(): non_uri = 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd hi '