# -*- coding: utf-8 -*- from mock import Mock import raven from raven.utils.testutils import TestCase from raven.processors import SanitizeKeysProcessor, \ SanitizePasswordsProcessor, RemovePostDataProcessor, RemoveStackLocalsProcessor VARS = { 'foo': 'bar', 'vars_dict': { 42: 'bar', ('foo', 'bar'): 'hello', 'password': 'hello', }, 'password': 'hello', 'the_secret': 'hello', 'a_password_here': 'hello', 'api_key': 'secret_key', 'apiKey': 'secret_key', 'access_token': 'oauth2 access token', 'custom_key1': 'you should not see this', 'custom_key2': 'you should not see this', 'custom_key3': {'mask': 'This entire dict'} } def get_stack_trace_data_real(exception_class=TypeError, **kwargs): def _will_throw_type_error(foo, **kwargs): vars_dict = VARS['vars_dict'] password = "you should not see this" # NOQA F841 the_secret = "nor this" # NOQA F841 a_password_here = "Don't look at me!" # NOQA F841 api_key = "I'm hideous!" # NOQA F841 apiKey = "4567000012345678" # NOQA F841 access_token = "secret stuff!" # NOQA F841 custom_key1 = "you shouldn't see this" # NOQA F841 custom_key2 = "you shouldn't see this" # NOQA F841 custom_key3 = "you shouldn't see this" # NOQA F841 # TypeError: unsupported operand type(s) for /: 'str' and 'str' raise exception_class() client = raven.Client('http://public:secret@sentry.local/1') try: _will_throw_type_error('bar') except exception_class: data = client.build_msg('raven.events.Exception') return data def get_http_data(): """ This is not so real as the data retrieved from the `get_stack_trace_data_real()` because we're still injecting HTTP data. Otherwise, we have to hard code the structure of a traceback, and this goes out of date when the format of data returned by ``raven.base.Client/build_msg`` changes. In that case, the tests pass, but the data is still dirty. This is a dangerous situation to be in. """ data = get_stack_trace_data_real() data['request'] = { 'cookies': VARS, 'data': VARS, 'env': VARS, 'headers': VARS, 'method': 'GET', 'query_string': '', 'url': 'http://localhost/', } return data def get_extra_data(): data = get_stack_trace_data_real() data['extra'] = VARS return data class SanitizeKeysProcessorTest(TestCase): def setUp(self): client = Mock( sanitize_keys=['custom_key1', 'custom_key2', 'custom_key3'] ) self.proc = SanitizeKeysProcessor(client) def _check_vars_sanitized(self, vars, MASK): """ Helper to check that keys have been sanitized. """ self.assertTrue('custom_key1' in vars) self.assertEquals(vars['custom_key1'], MASK) self.assertTrue('custom_key2' in vars) self.assertEquals(vars['custom_key2'], MASK) self.assertTrue('custom_key3' in vars) self.assertEquals(vars['custom_key3'], MASK) def test_stacktrace(self, *args, **kwargs): data = get_stack_trace_data_real() result = self.proc.process(data) self.assertTrue('exception' in result) exception = result['exception'] self.assertTrue('values' in exception) values = exception['values'] stack = values[-1]['stacktrace'] self.assertTrue('frames' in stack) self.assertEquals(len(stack['frames']), 2) frame = stack['frames'][1] # frame of will_throw_type_error() self.assertTrue('vars' in frame) self._check_vars_sanitized(frame['vars'], self.proc.MASK) def test_http(self): data = get_http_data() result = self.proc.process(data) self.assertTrue('request' in result) http = result['request'] for n in ('data', 'env', 'headers', 'cookies'): self.assertTrue(n in http) self._check_vars_sanitized(http[n], self.proc.MASK) def test_extra(self): data = get_extra_data() result = self.proc.process(data) self.assertTrue('extra' in result) extra = result['extra'] self._check_vars_sanitized(extra, self.proc.MASK) def test_querystring_as_string(self): data = get_http_data() data['request']['query_string'] = 'foo=bar&custom_key1=nope&custom_key2=nope&custom_key3=%7B%27key2%27%3A+%27nope%27%2C+%27key1%27%3A+%27nope%27%7D' result = self.proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['query_string'], "foo=bar&custom_key1=%(m)s&custom_key2=%(m)s&custom_key3=%(m)s" % {'m': self.proc.MASK} ) def test_querystring_as_string_with_partials(self): data = get_http_data() data['request']['query_string'] = 'foo=bar&custom_key1&baz=bar' result = self.proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['query_string'], 'foo=bar&custom_key1&baz=bar' % {'m': self.proc.MASK} ) def test_cookie_as_string(self): data = get_http_data() data['request']['cookies'] = \ 'foo=bar;custom_key1=nope;custom_key2=nope;' result = self.proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['cookies'], 'foo=bar;custom_key1=%(m)s;custom_key2=%(m)s;' % {'m': self.proc.MASK}) def test_cookie_as_string_with_partials(self): data = get_http_data() data['request']['cookies'] = 'foo=bar;custom_key1;baz=bar' result = self.proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['cookies'], 'foo=bar;custom_key1;baz=bar' % dict(m=self.proc.MASK) ) def test_cookie_header(self): data = get_http_data() data['request']['headers']['Cookie'] = 'foo=bar;custom_key1=nope;custom_key2=nope;' result = self.proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['headers']['Cookie'], 'foo=bar;custom_key1=%(m)s;custom_key2=%(m)s;' % {'m': self.proc.MASK}) def test_sanitize_non_ascii(self): result = self.proc.sanitize('__repr__: жили-были', '42') self.assertEquals(result, '42') class SanitizePasswordsProcessorTest(TestCase): def _check_vars_sanitized(self, vars, proc): """ Helper to check that keys have been sanitized. """ self.assertTrue('foo' in vars) self.assertIn(vars['foo'], ( VARS['foo'], "'%s'" % VARS['foo'], '"%s"' % VARS['foo']) ) self.assertTrue('vars_dict' in vars) vars_dict = vars['vars_dict'] ref_dict = VARS['vars_dict'].copy() ref_dict['password'] = proc.MASK self.assertTrue(42 in vars_dict or '42' in vars_dict) if 42 in vars_dict: # Extra data - dictionary keys are not changed. self.assertDictEqual(vars_dict, ref_dict) else: # Stack trace - dictionary keys are converted to strings. self.assertTrue('42' in vars_dict) self.assertIn(vars_dict['42'], "'%s'" % ref_dict[42], '"%s"' % ref_dict[42]) self.assertTrue('("\'foo\'", "\'bar\'")' in vars_dict or "('\"foo\"', '\"bar\"')" in vars_dict) self.assertTrue('"password"' in vars_dict or "'password'" in vars_dict) if "'password'" in vars_dict: self.assertEqual(vars_dict["'password'"], proc.MASK) else: self.assertEqual(vars_dict['"password"'], proc.MASK) self.assertTrue('password' in vars) self.assertEquals(vars['password'], proc.MASK) self.assertTrue('the_secret' in vars) self.assertEquals(vars['the_secret'], proc.MASK) self.assertTrue('a_password_here' in vars) self.assertEquals(vars['a_password_here'], proc.MASK) self.assertTrue('api_key' in vars) self.assertEquals(vars['api_key'], proc.MASK) self.assertTrue('apiKey' in vars) self.assertEquals(vars['apiKey'], proc.MASK) self.assertTrue('access_token' in vars) self.assertEquals(vars['access_token'], proc.MASK) def test_stacktrace(self, *args, **kwargs): """ Check whether sensitive variables are properly stripped from stack-trace messages. """ data = get_stack_trace_data_real() proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) # data['exception']['values'][-1]['stacktrace']['frames'][0]['vars'] self.assertTrue('exception' in result) exception = result['exception'] self.assertTrue('values' in exception) values = exception['values'] stack = values[-1]['stacktrace'] self.assertTrue('frames' in stack) self.assertEquals(len(stack['frames']), 2) frame = stack['frames'][1] # frame of will_throw_type_error() self.assertTrue('vars' in frame) self._check_vars_sanitized(frame['vars'], proc) def test_http(self): data = get_http_data() proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] for n in ('data', 'env', 'headers', 'cookies'): self.assertTrue(n in http) self._check_vars_sanitized(http[n], proc) def test_extra(self): data = get_extra_data() proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('extra' in result) extra = result['extra'] self._check_vars_sanitized(extra, proc) def test_querystring_as_string(self): data = get_http_data() data['request']['query_string'] = 'foo=bar&password=hello&the_secret=hello'\ '&a_password_here=hello&api_key=secret_key' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['query_string'], 'foo=bar&password=%(m)s&the_secret=%(m)s' '&a_password_here=%(m)s&api_key=%(m)s' % dict(m=proc.MASK)) def test_querystring_as_string_with_partials(self): data = get_http_data() data['request']['query_string'] = 'foo=bar&password&baz=bar' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals(http['query_string'], 'foo=bar&password&baz=bar' % dict(m=proc.MASK)) def test_cookie_as_string(self): data = get_http_data() data['request']['cookies'] = 'foo=bar;password=hello;the_secret=hello'\ ';a_password_here=hello;api_key=secret_key' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['cookies'], 'foo=bar;password=%(m)s;the_secret=%(m)s' ';a_password_here=%(m)s;api_key=%(m)s' % dict(m=proc.MASK)) def test_cookie_as_string_with_partials(self): data = get_http_data() data['request']['cookies'] = 'foo=bar;password;baz=bar' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals(http['cookies'], 'foo=bar;password;baz=bar' % dict(m=proc.MASK)) def test_cookie_header(self): data = get_http_data() data['request']['headers']['Cookie'] = 'foo=bar;password=hello'\ ';the_secret=hello;a_password_here=hello;api_key=secret_key'\ ';access_token=at' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertEquals( http['headers']['Cookie'], 'foo=bar;password=%(m)s' ';the_secret=%(m)s;a_password_here=%(m)s;api_key=%(m)s' ';access_token=%(m)s' % dict(m=proc.MASK)) def test_sanitize_credit_card(self): proc = SanitizePasswordsProcessor(Mock()) result = proc.sanitize('foo', '4242424242424242') self.assertEquals(result, proc.MASK) def test_sanitize_credit_card_amex(self): # AMEX numbers are 15 digits, not 16 proc = SanitizePasswordsProcessor(Mock()) result = proc.sanitize('foo', '424242424242424') self.assertEquals(result, proc.MASK) def test_sanitize_credit_card_in_stack_local(self): proc = SanitizePasswordsProcessor(Mock()) result = proc.sanitize('foo', "'424242424242424'") self.assertEquals(result, proc.MASK) def test_sanitize_non_ascii(self): proc = SanitizePasswordsProcessor(Mock()) result = proc.sanitize('__repr__: жили-были', '42') self.assertEquals(result, '42') def test_sanitize_bytes(self): proc = SanitizePasswordsProcessor(Mock()) data = {'data': b'password=1234'} result = proc.filter_http(data) self.assertIn(data['data'], 'password=%s' % proc.MASK) class RemovePostDataProcessorTest(TestCase): def test_does_remove_data(self): data = get_http_data() data['request']['data'] = 'foo' proc = RemovePostDataProcessor(Mock()) result = proc.process(data) self.assertTrue('request' in result) http = result['request'] self.assertFalse('data' in http) class RemoveStackLocalsProcessorTest(TestCase): def test_does_remove_data(self): data = get_stack_trace_data_real() proc = RemoveStackLocalsProcessor(Mock()) result = proc.process(data) for value in result['exception']['values']: for frame in value['stacktrace']['frames']: self.assertFalse('vars' in frame)