]> git.ipfire.org Git - ipfire.org.git/commitdiff
fireinfo: Implement a better rate-limiting for up to ten updates an hour
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 28 Apr 2015 08:45:20 +0000 (10:45 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 28 Apr 2015 08:45:20 +0000 (10:45 +0200)
webapp/backend/fireinfo.py

index f828743db9fef0b361c669344c56432f2ce5e656..a194282200327b4dd293a5e4370d1c2806358928 100644 (file)
@@ -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