def test_find_account_rate_limiting(self) -> None: def assert_func(result: HttpResponse) -> None: self.assertEqual(result.status_code, 429) self.assert_in_response("Rate limit exceeded.", result) with rate_limit_rule(1, 5, domain="find_account_by_ip"): RateLimitedIPAddr("127.0.0.1", domain="find_account_by_ip").clear_history() self.do_test_hit_ratelimits( lambda: self.client_post("/accounts/find/", {"emails": "*****@*****.**"}), assert_func=assert_func, ) # Now test whether submitting multiple emails is handled correctly. # The limit is set to 10 per second, so 5 requests with 2 emails # submitted in each should be allowed. with rate_limit_rule(1, 10, domain="find_account_by_ip"): RateLimitedIPAddr("127.0.0.1", domain="find_account_by_ip").clear_history() self.do_test_hit_ratelimits( lambda: self. client_post("/accounts/find/", {"emails": "[email protected],[email protected]"}), assert_func=assert_func, )
def test_used_in_tornado(self) -> None: user_profile = self.example_user("hamlet") ip_addr = "192.168.0.123" with self.settings(RUNNING_INSIDE_TORNADO=True): user_obj = RateLimitedUser(user_profile, domain="api_by_user") ip_obj = RateLimitedIPAddr(ip_addr, domain="api_by_ip") self.assertEqual(user_obj.backend, TornadoInMemoryRateLimiterBackend) self.assertEqual(ip_obj.backend, TornadoInMemoryRateLimiterBackend) with self.settings(RUNNING_INSIDE_TORNADO=True): user_obj = RateLimitedUser(user_profile, domain="some_domain") ip_obj = RateLimitedIPAddr(ip_addr, domain="some_domain") self.assertEqual(user_obj.backend, RedisRateLimiterBackend) self.assertEqual(ip_obj.backend, RedisRateLimiterBackend)
def test_tor_file_not_found(self) -> None: for ip in ["1.2.3.4", "5.6.7.8", "tor-exit-node"]: RateLimitedIPAddr(ip, domain="api_by_ip").clear_history() with self.tor_mock( side_effect=FileNotFoundError("File not found")) as tor_open: # If we cannot get a list of TOR exit nodes, then # rate-limiting works as normal, per-IP with self.assertLogs("zerver.lib.rate_limiter", level="WARNING") as log_mock: self.do_test_hit_ratelimits( lambda: self.send_unauthed_api_request(REMOTE_ADDR= "1.2.3.4")) resp = self.send_unauthed_api_request(REMOTE_ADDR="5.6.7.8") self.assertNotEqual(resp.status_code, 429) # Tries twice before hitting the circuit-breaker, and stopping trying tor_open.assert_has_calls([ mock.call(settings.TOR_EXIT_NODE_FILE_PATH, "rb"), mock.call(settings.TOR_EXIT_NODE_FILE_PATH, "rb"), ]) self.assert_length(log_mock.output, 8) self.assertEqual( log_mock.output[0:2], [ "WARNING:zerver.lib.rate_limiter:Failed to fetch TOR exit node list: {}" .format("File not found") ] * 2, ) self.assertIn( 'Failed to fetch TOR exit node list: Circuit "get_tor_ips" OPEN', log_mock.output[3], )
def rate_limit_rule(range_seconds: int, num_requests: int, domain: str) -> Iterator[None]: RateLimitedIPAddr("127.0.0.1", domain=domain).clear_history() add_ratelimit_rule(range_seconds, num_requests, domain=domain) try: yield finally: # We need this in a finally block to ensure the test cleans up after itself # even in case of failure, to avoid polluting the rules state. remove_ratelimit_rule(range_seconds, num_requests, domain=domain)
def test_hit_ratelimits_as_ip(self) -> None: add_ratelimit_rule(1, 5, domain="api_by_ip") try: RateLimitedIPAddr("127.0.0.1").clear_history() self.do_test_hit_ratelimits(self.send_unauthed_api_request) finally: # We need this in a finally block to ensure the test cleans up after itself # even in case of failure, to avoid polluting the rules state. remove_ratelimit_rule(1, 5, domain="api_by_ip")
def test_create_realm_rate_limiting(self) -> None: def assert_func(result: HttpResponse) -> None: self.assertEqual(result.status_code, 429) self.assert_in_response("Rate limit exceeded.", result) with self.settings(OPEN_REALM_CREATION=True): RateLimitedIPAddr("127.0.0.1", domain="create_realm_by_ip").clear_history() self.do_test_hit_ratelimits( lambda: self.client_post("/new/", {"email": "*****@*****.**"}), assert_func=assert_func, )
def test_tor_file_empty(self) -> None: for ip in ["1.2.3.4", "5.6.7.8", "tor-exit-node"]: RateLimitedIPAddr(ip, domain="api_by_ip").clear_history() # An empty list of IPs is treated as some error in parsing the # input, and as such should not be cached; rate-limiting # should work as normal, per-IP with self.tor_mock(read_data=[]) as tor_open: with self.assertLogs("zerver.lib.rate_limiter", level="WARNING"): self.do_test_hit_ratelimits( lambda: self.send_unauthed_api_request(REMOTE_ADDR="1.2.3.4") ) resp = self.send_unauthed_api_request(REMOTE_ADDR="5.6.7.8") self.assertNotEqual(resp.status_code, 429) # Was not cached, so tried to read twice before hitting the # circuit-breaker, and stopping trying tor_open().read.assert_has_calls([mock.call(), mock.call()])
def test_tor_ip_limits(self) -> None: request_count = 0 for ip in ["1.2.3.4", "5.6.7.8", "tor-exit-node"]: RateLimitedIPAddr(ip, domain="api_by_ip").clear_history() def alternate_requests() -> HttpResponse: nonlocal request_count request_count += 1 if request_count % 2 == 1: return self.send_unauthed_api_request(REMOTE_ADDR="1.2.3.4") else: return self.send_unauthed_api_request(REMOTE_ADDR="5.6.7.8") with self.tor_mock(read_data=["1.2.3.4", "5.6.7.8"]) as tor_open: self.do_test_hit_ratelimits(alternate_requests) # This is only read once, despite being used on each request tor_open.assert_called_once_with(settings.TOR_EXIT_NODE_FILE_PATH, "rb") tor_open().read.assert_called_once()
def rate_limit_ip(request: HttpRequest, ip_addr: str, domain: str) -> None: RateLimitedIPAddr(ip_addr, domain=domain).rate_limit_request(request)
def test_hit_ratelimits_as_ip(self) -> None: RateLimitedIPAddr("127.0.0.1").clear_history() self.do_test_hit_ratelimits(self.send_unauthed_api_request)