def make_reset_token(token_cls, user, issue_limit=3): """Generate a password reset token or account recovery token. Checks a ratelimit to ensure that the token isn't being reset too often. There is also a global check on resets, such that more than 1000 per hour will trigger obvious and loud breakage via a ValueError. Issuing (or failing to issue) a token are added to user's notes. """ # check if we've hit the ratelimit reset_count_key = "token:%s_count:%s" % (token_cls.__name__, user._id) time_slice = ratelimit.get_timeslice(int(token_cls._ttl)) usage = ratelimit.record_usage(reset_count_key, time_slice) if usage > issue_limit: user.add_note("Exceeded password/email reset max attempts.") return None # check if we've hit the global rate limit and fail badly reset_count_global = "token:%s_count_global" % (token_cls.__name__, ) global_time_slice = ratelimit.get_timeslice(60 * 60) global_usage = ratelimit.record_usage(reset_count_global, global_time_slice) if global_usage > 1000: raise ValueError( "Somebody's beating the hell out of the password reset endpoint") # all is well. issue the token. token = token_cls._new(user) return token
def make_reset_token(token_cls, user, issue_limit=3): """Generate a password reset token or account recovery token. Checks a ratelimit to ensure that the token isn't being reset too often. There is also a global check on resets, such that more than 1000 per hour will trigger obvious and loud breakage via a ValueError. Issuing (or failing to issue) a token are added to user's notes. """ # check if we've hit the ratelimit reset_count_key = "token:%s_count:%s" % (token_cls.__name__, user._id) time_slice = ratelimit.get_timeslice(int(token_cls._ttl)) usage = ratelimit.record_usage(reset_count_key, time_slice) if usage > issue_limit: user.add_note("Exceeded password/email reset max attempts.") return None # check if we've hit the global rate limit and fail badly reset_count_global = "token:%s_count_global" % (token_cls.__name__,) global_time_slice = ratelimit.get_timeslice(60 * 60) global_usage = ratelimit.record_usage(reset_count_global, global_time_slice) if global_usage > 1000: raise ValueError( "Somebody's beating the hell out of the password reset endpoint" ) # all is well. issue the token. token = token_cls._new(user) return token
def ratelimit_expired(self, limit=3): key = "ratelimit:%s:%s" % (self.__class__.__name__, self.user_id) time_slice = ratelimit.get_timeslice(60 * 60) usage = ratelimit.record_usage(key, time_slice) if usage > limit: self.consume() return True
def test_record_usage_across_slice_expiration(self): self.now = 24 * 3600 + 5 ts = ratelimit.get_timeslice(3600) real_incr = self.cache.incr evicted = False def fake_incr(key, delta=1): if evicted: del self.cache[key] raise pylibmc.NotFound() return real_incr(key) with patch.object(self.cache, 'incr', fake_incr): # Forcibly evict the key before incr is called, but after the # initial add call inside record_usage. evicted = True ratelimit.record_usage('a', ts) self.assertEquals(1, self.cache['rl:a-000000'])
def _has_exceeded_ratelimit(self, form, room): # grab the ratelimit (as average events per second) for the room's # current level, using the highest level configured that's not bigger # than the room. e.g. if ratelimits are defined for levels 1, 2, and 4 # and the room is level 3, this will give us the ratelimit specified # for 2. desired_avg_per_sec = 1 by_level = g.live_config.get("robin_ratelimit_avg_per_sec", {}) for level, avg_per_sec in sorted(by_level.items(), key=lambda (x, y): int(x)): if int(level) > room.level: break desired_avg_per_sec = avg_per_sec # now figure out how many events per window that means window_size = g.live_config.get("robin_ratelimit_window", 10) allowed_events_per_window = int(desired_avg_per_sec * window_size) try: # now figure out how much they've actually used ratelimit_key = "robin/{}".format(c.user._id36) time_slice = ratelimit.get_timeslice(window_size) usage = ratelimit.get_usage(ratelimit_key, time_slice) # ratelimit them if too much if usage >= allowed_events_per_window: g.stats.simple_event("robin.ratelimit.exceeded") period_end = datetime.datetime.utcfromtimestamp(time_slice.end) period_end_utc = period_end.replace(tzinfo=pytz.UTC) until_reset = utils.timeuntil(period_end_utc) c.errors.add(errors.RATELIMIT, {"time": until_reset}, field="ratelimit", code=429) form.has_errors("ratelimit", errors.RATELIMIT) return True # or record the usage and move on ratelimit.record_usage(ratelimit_key, time_slice) except ratelimit.RatelimitError as exc: g.log.warning("ratelimit error: %s", exc) return False
def _has_exceeded_ratelimit(self, form, room): # grab the ratelimit (as average events per second) for the room's # current level, using the highest level configured that's not bigger # than the room. e.g. if ratelimits are defined for levels 1, 2, and 4 # and the room is level 3, this will give us the ratelimit specified # for 2. desired_avg_per_sec = 1 by_level = g.live_config.get("robin_ratelimit_avg_per_sec", {}) for level, avg_per_sec in sorted(by_level.items(), key=lambda (x,y): int(x)): if int(level) > room.level: break desired_avg_per_sec = avg_per_sec # now figure out how many events per window that means window_size = g.live_config.get("robin_ratelimit_window", 10) allowed_events_per_window = int(desired_avg_per_sec * window_size) try: # now figure out how much they've actually used ratelimit_key = "robin/{}".format(c.user._id36) time_slice = ratelimit.get_timeslice(window_size) usage = ratelimit.get_usage(ratelimit_key, time_slice) # ratelimit them if too much if usage >= allowed_events_per_window: g.stats.simple_event("robin.ratelimit.exceeded") period_end = datetime.datetime.utcfromtimestamp(time_slice.end) period_end_utc = period_end.replace(tzinfo=pytz.UTC) until_reset = utils.timeuntil(period_end_utc) c.errors.add(errors.RATELIMIT, {"time": until_reset}, field="ratelimit", code=429) form.has_errors("ratelimit", errors.RATELIMIT) return True # or record the usage and move on ratelimit.record_usage(ratelimit_key, time_slice) except ratelimit.RatelimitError as exc: g.log.warning("ratelimit error: %s", exc) return False
def test_record_usage(self): self.now = 24 * 3600 + 5 ts = ratelimit.get_timeslice(3600) ratelimit.record_usage('a', ts) self.assertEquals(1, self.cache['rl:a-000000']) ratelimit.record_usage('a', ts) self.assertEquals(2, self.cache['rl:a-000000']) self.now = 24 * 3600 + 5 * 3600 ts = ratelimit.get_timeslice(3600) ratelimit.record_usage('a', ts) self.assertEquals(1, self.cache['rl:a-050000'])
def test_get_usage(self): self.now = 24 * 3600 + 5 * 3600 ts = ratelimit.get_timeslice(3600) self.assertEquals(None, ratelimit.get_usage('a', ts)) ratelimit.record_usage('a', ts) self.assertEquals(1, ratelimit.get_usage('a', ts))