| @ -0,0 +1,108 @@ | |||
| """ | |||
| Connection limitation. | |||
| Number of connections from one IP is limited. | |||
| We have nothing against scripting and automated queries. | |||
| Even the opposite, we encourage them. But there are some | |||
| connection limits that even we can't handle. | |||
| Currently the limits are quite restrictive, but they will be relaxed | |||
| in the future. | |||
| Usage: | |||
| limits = Limits() | |||
| not_allowed = limits.check_ip(ip_address) | |||
| if not_allowed: | |||
| return "ERROR: %s" % not_allowed | |||
| [Taken from github.com/chubin/cheat.sh] | |||
| """ | |||
| import time | |||
| from globals import log | |||
| def _time_caps(minutes, hours, days): | |||
| return { | |||
| 'min': minutes, | |||
| 'hour': hours, | |||
| 'day': days, | |||
| } | |||
| class Limits(object): | |||
| """ | |||
| Queries limitation (by IP). | |||
| Exports: | |||
| check_ip(ip_address) | |||
| """ | |||
| def __init__(self, whitelist=None, limits=None): | |||
| self.intervals = ['min', 'hour', 'day'] | |||
| self.divisor = _time_caps(60, 3600, 86400) | |||
| self.last_update = _time_caps(0, 0, 0) | |||
| if limits: | |||
| self.limit = _time_caps(*limits) | |||
| else: | |||
| self.limit = _time_caps(30, 600, 1000) | |||
| if whitelist: | |||
| self.whitelist = whitelist[:] | |||
| else: | |||
| self.whitelist = [] | |||
| self.counter = { | |||
| 'min': {}, | |||
| 'hour': {}, | |||
| 'day': {}, | |||
| } | |||
| self._clear_counters_if_needed() | |||
| def _log_visit(self, interval, ip_address): | |||
| if ip_address not in self.counter[interval]: | |||
| self.counter[interval][ip_address] = 0 | |||
| self.counter[interval][ip_address] += 1 | |||
| def _limit_exceeded(self, interval, ip_address): | |||
| visits = self.counter[interval][ip_address] | |||
| limit = self._get_limit(interval) | |||
| return visits > limit | |||
| def _get_limit(self, interval): | |||
| return self.limit[interval] | |||
| def _report_excessive_visits(self, interval, ip_address): | |||
| log("%s LIMITED [%s for %s]" % (ip_address, self._get_limit(interval), interval)) | |||
| def check_ip(self, ip_address): | |||
| """ | |||
| Check if `ip_address` is allowed, and if not raise an RuntimeError exception. | |||
| Return True otherwise | |||
| """ | |||
| if ip_address in self.whitelist: | |||
| return None | |||
| self._clear_counters_if_needed() | |||
| for interval in self.intervals: | |||
| self._log_visit(interval, ip_address) | |||
| if self._limit_exceeded(interval, ip_address): | |||
| self._report_excessive_visits(interval, ip_address) | |||
| return ("Not so fast! Number of queries per %s is limited to %s" | |||
| % (interval, self._get_limit(interval))) | |||
| return None | |||
| def reset(self): | |||
| """ | |||
| Reset all counters for all IPs | |||
| """ | |||
| for interval in self.intervals: | |||
| self.counter[interval] = {} | |||
| def _clear_counters_if_needed(self): | |||
| current_time = int(time.time()) | |||
| for interval in self.intervals: | |||
| if current_time // self.divisor[interval] != self.last_update[interval]: | |||
| self.counter[interval] = {} | |||
| self.last_update[interval] = current_time / self.divisor[interval] | |||