From: Michael Tremer Date: Tue, 28 Apr 2015 08:45:20 +0000 (+0200) Subject: fireinfo: Implement a better rate-limiting for up to ten updates an hour X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f5e427300433e0037a2155f012071d455d6f248a;p=ipfire.org.git fireinfo: Implement a better rate-limiting for up to ten updates an hour --- diff --git a/webapp/backend/fireinfo.py b/webapp/backend/fireinfo.py index f828743d..a1942822 100644 --- a/webapp/backend/fireinfo.py +++ b/webapp/backend/fireinfo.py @@ -498,6 +498,13 @@ class Profile(Object): if location: self.set_location(location) + self.log_profile_update() + + def log_profile_update(self): + # Log that an update was performed for this profile id + self.db.execute("INSERT INTO fireinfo_profiles_log(public_id) \ + VALUES(%s)", self.public_id) + def expired(self, when=None): self.db.execute("UPDATE fireinfo_profiles \ SET time_valid = then_or_now(%s) WHERE id = %s", when, self.id) @@ -1461,19 +1468,20 @@ class Fireinfo(Object): return False - def can_update_profile(self, public_id, private_id, when=None): - # Check if the profile has been updated very recently. If so we deny - # any new updates for some time. - res = self.db.get("WITH profiles AS (SELECT * FROM fireinfo_profiles \ - WHERE public_id = %s AND private_id = %s AND time_valid >= then_or_now(%s) \ - ORDER BY time_updated DESC LIMIT 1) \ - SELECT 1 FROM profiles WHERE then_or_now(%s) <= \ - time_updated + INTERVAL '60 minutes'", - public_id, private_id, when, when) + def profile_rate_limit_active(self, public_id, when=None): + # Remove all outdated entries + self.db.execute("DELETE FROM fireinfo_profiles_log \ + WHERE ts <= then_or_now(%s) - INTERVAL '60 minutes'", when) - if res: - return False + res = self.db.get("SELECT COUNT(*) AS count FROM fireinfo_profiles_log \ + WHERE public_id = %s", public_id) + + if res and res.count >= 10: + return True + return False + + def is_private_id_change_permitted(self, public_id, private_id, when=None): # Check if a profile exists with a different private id that is still valid res = self.db.get("SELECT 1 FROM fireinfo_profiles \ WHERE public_id = %s AND NOT private_id = %s \ @@ -1516,7 +1524,10 @@ class Fireinfo(Object): public_id, private_id, when, when, when, valid) if res: - return Profile(self.backend, res.id) + p = Profile(self.backend, res.id) + p.log_profile_update() + + return p # Devices @@ -1675,9 +1686,12 @@ class Fireinfo(Object): profile = self.fireinfo.get_profile(public_id, private_id=private_id, when=when) # Check if the update can actually be updated - if profile and not self.fireinfo.can_update_profile(public_id, private_id, when=when): - logging.warning("Profile not updated because private ID does not" - " match or last update is too recent: %s" % public_id) + if profile and self.fireinfo.profile_rate_limit_active(public_id, when=when): + logging.warning("There were too many updates for this profile in the last hour: %s" % public_id) + return + + elif not self.is_private_id_change_permitted(public_id, private_id, when=when): + logging.warning("Changing private id is not permitted for profile: %s" % public_id) return # Parse the profile