startup scripts : assume initscripts is not installed, only use systemctl
[plcapi.git] / aspects / ratelimitaspects.py
index 346e137..836b2ba 100644 (file)
@@ -6,12 +6,20 @@
 from PLC.Config import Config
 from PLC.Faults import PLCPermissionDenied
 
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.Sessions import Session, Sessions
+
 from datetime import datetime, timedelta
 
 from pyaspects.meta import MetaAspect
 
 import memcache
 
+import os
+import sys
+import socket
+
 class BaseRateLimit(object):
 
     def __init__(self):
@@ -25,12 +33,41 @@ class BaseRateLimit(object):
 
         self.whitelist = []
 
-    def log(self, line)
-        log = open("/var/log/plc_ratelimit.log", "a")
+    def log(self, line):
+        log = open("/var/log/plc_api_ratelimit.log", "a")
         date = datetime.now().strftime("%d/%m/%y %H:%M")
         log.write("%s - %s\n" % (date, line))
         log.flush()
 
+    def mail(self, to):
+        sendmail = os.popen("/usr/sbin/sendmail -N never -t -f%s" % self.config.PLC_MAIL_SUPPORT_ADDRESS, "w")
+
+        subject = "[PLCAPI] Maximum allowed number of API calls exceeded"
+
+        header = {'from': "%s Support <%s>" % (self.config.PLC_NAME, self.config.PLC_MAIL_SUPPORT_ADDRESS),
+               'to': "%s, %s" % (to, self.config.PLC_MAIL_SUPPORT_ADDRESS),
+               'version': sys.version.split(" ")[0],
+               'subject': subject}
+
+        body = "Maximum allowed number of API calls exceeded for the user %s within the last %s minutes." % (to, self.minutes)
+
+        # Write headers
+        sendmail.write(
+"""
+Content-type: text/plain
+From: %(from)s
+Reply-To: %(from)s
+To: %(to)s
+X-Mailer: Python/%(version)s
+Subject: %(subject)s
+
+""".lstrip() % header)
+
+        # Write body
+        sendmail.write(body)
+        # Done
+        sendmail.close()
+
     def before(self, wobj, data, *args, **kwargs):
         # ratelimit_128.112.139.115_201011091532 = 1
         # ratelimit_128.112.139.115_201011091533 = 14
@@ -40,20 +77,51 @@ class BaseRateLimit(object):
 
         api_method_name = wobj.name
         api_method_source = wobj.source
-        api_method_caller = args[0]["Username"]
 
-        if api_method_source == None or api_method_source[0] == self.config.PLC_API_IP or api_method_source[0] in self.whitelist:
+        try:
+            api_method = args[0]["AuthMethod"]
+        except:
+            return
+
+        # decode api_method_caller
+        if api_method == "session":
+            api_method_caller = Sessions(wobj.api, {'session_id': args[0]["session"]})
+            if api_method_caller == []:
+                return
+            elif api_method_caller[0]["person_id"] != None:
+                api_method_caller = Persons(wobj.api, api_method_caller[0]["person_id"])[0]["email"]
+            elif api_method_caller[0]["node_id"] != None:
+                api_method_caller = Nodes(wobj.api, api_method_caller[0]["node_id"])[0]["hostname"]
+            else:
+                api_method_caller = args[0]["session"]
+        elif api_method == "password" or api_method == "capability":
+            api_method_caller = args[0]["Username"]
+        elif api_method == "gpg":
+            api_method_caller = args[0]["name"]
+        elif api_method == "hmac" or api_method == "hmac_dummybox":
+            api_method_caller = args[0]["node_id"]
+        elif api_method == "anonymous":
+            api_method_caller = "anonymous"
+        else:
+            api_method_caller = "unknown"
+
+        # excludes
+        if api_method_source == None or api_method_source[0] == socket.gethostbyname(self.config.PLC_API_HOST) or api_method_source[0] in self.whitelist:
             return
 
+        # sanity check
         if api_method_caller == None:
-            self.log("%s called with Username = None" % api_method_source[0])
+            self.log("%s called from %s with Username = None?" % (api_method_name, api_method_source[0]))
             return
 
+        # normalize unicode string otherwise memcache throws an exception
+        api_method_caller = str(api_method_caller)
+
         mc = memcache.Client(["%s:11211" % self.config.PLC_API_HOST])
         now = datetime.now()
-        current_key = "%s_%s_%s" % (self.prefix, api_method_source[0], now.strftime("%Y%m%d%H%M"))
 
-        keys_to_check = ["%s_%s_%s" % (self.prefix, api_method_source[0], (now - timedelta(minutes = minute)).strftime("%Y%m%d%H%M")) for minute in range(self.minutes + 1)]
+        current_key = "%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], now.strftime("%Y%m%d%H%M"))
+        keys_to_check = ["%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], (now - timedelta(minutes = minute)).strftime("%Y%m%d%H%M")) for minute in range(self.minutes + 1)]
 
         try:
             value = mc.incr(current_key)
@@ -63,13 +131,20 @@ class BaseRateLimit(object):
         if value == None:
             mc.set(current_key, 1, time=self.expire_after)
 
-        result = mc.get_multi(keys_to_check)
+        results = mc.get_multi(keys_to_check)
         total_requests = 0
-        for i in result:
-            total_requests += result[i]
+        for i in results:
+            total_requests += results[i]
 
         if total_requests > self.requests:
             self.log("%s - %s" % (api_method_source[0], api_method_caller))
+
+            caller_key = "%s_%s" % (self.prefix, api_method_caller)
+            if mc.get(caller_key) == None:
+                mc.set(caller_key, 1, time = self.expire_after)
+                if (api_method == "session" and api_method_caller.__contains__("@")) or (api_method == "password" or api_method == "capability"):
+                    self.mail(api_method_caller)
+
             raise PLCPermissionDenied, "Maximum allowed number of API calls exceeded"
 
     def after(self, wobj, data, *args, **kwargs):