X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=aspects%2Fratelimitaspects.py;h=836b2babb54ddf25ecfa61cd213ca569973d16c7;hb=997c5bbe5b0576ae8c9c8d52a80dab1dcecf615a;hp=dbe024242d9572664f0e2e0bc5e5b240da0f03bc;hpb=32e6b3ac389e522e05b89833e9bd13ad853b6885;p=plcapi.git diff --git a/aspects/ratelimitaspects.py b/aspects/ratelimitaspects.py index dbe0242..836b2ba 100644 --- a/aspects/ratelimitaspects.py +++ b/aspects/ratelimitaspects.py @@ -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): @@ -26,11 +34,40 @@ class BaseRateLimit(object): self.whitelist = [] def log(self, line): - log = open("/var/log/plc_ratelimit.log", "a") + 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 @@ -41,23 +78,49 @@ class BaseRateLimit(object): api_method_name = wobj.name api_method_source = wobj.source - # FIXME: Support SessionAuth, GPGAuth, BootAuth and AnonymousAuth try: - api_method_caller = args[0]["Username"] - except KeyError: - api_method_caller = "_" + api_method = args[0]["AuthMethod"] + except: + return - if api_method_source == None or api_method_source[0] == self.config.PLC_API_IP or api_method_source[0] in self.whitelist: + # 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 from %s with Username = None" % (api_method_name, 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_%s" % (self.prefix, api_method_caller, api_method_source[0], now.strftime("%Y%m%d%H%M")) + 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: @@ -68,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):