kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
				
				
				
			
		
			
				
	
	
		
			151 wiersze
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			151 wiersze
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
| import collections
 | |
| 
 | |
| from django.conf import settings
 | |
| from django.core.cache import cache
 | |
| from rest_framework import throttling as rest_throttling
 | |
| 
 | |
| 
 | |
| def get_ident(user, request):
 | |
|     if user and user.is_authenticated:
 | |
|         return {"type": "authenticated", "id": user.pk}
 | |
|     ident = rest_throttling.BaseThrottle().get_ident(request)
 | |
| 
 | |
|     return {"type": "anonymous", "id": ident}
 | |
| 
 | |
| 
 | |
| def get_cache_key(scope, ident):
 | |
|     parts = ["throttling", scope, ident["type"], str(ident["id"])]
 | |
|     return ":".join(parts)
 | |
| 
 | |
| 
 | |
| def get_scope_for_action_and_ident_type(action, ident_type, view_conf={}):
 | |
|     config = collections.ChainMap(view_conf, settings.THROTTLING_SCOPES)
 | |
| 
 | |
|     try:
 | |
|         action_config = config[action]
 | |
|     except KeyError:
 | |
|         action_config = config.get("*", {})
 | |
| 
 | |
|     try:
 | |
|         return action_config[ident_type]
 | |
|     except KeyError:
 | |
|         return
 | |
| 
 | |
| 
 | |
| def get_status(ident, now):
 | |
|     data = []
 | |
|     throttle = FunkwhaleThrottle()
 | |
|     for key in sorted(settings.THROTTLING_RATES.keys()):
 | |
|         conf = settings.THROTTLING_RATES[key]
 | |
|         row_data = {"id": key, "rate": conf["rate"], "description": conf["description"]}
 | |
|         if conf["rate"]:
 | |
|             num_requests, duration = throttle.parse_rate(conf["rate"])
 | |
|             history = cache.get(get_cache_key(key, ident)) or []
 | |
| 
 | |
|             relevant_history = [h for h in history if h > now - duration]
 | |
|             row_data["limit"] = num_requests
 | |
|             row_data["duration"] = duration
 | |
|             row_data["remaining"] = num_requests - len(relevant_history)
 | |
|             if relevant_history and len(relevant_history) >= num_requests:
 | |
|                 # At this point, the endpoint becomes available again
 | |
|                 now_request = relevant_history[-1]
 | |
|                 remaining = duration - (now - int(now_request))
 | |
|                 row_data["available"] = int(now + remaining) or None
 | |
|                 row_data["available_seconds"] = int(remaining) or None
 | |
|             else:
 | |
|                 row_data["available"] = None
 | |
|                 row_data["available_seconds"] = None
 | |
| 
 | |
|             if relevant_history:
 | |
|                 # At this point, all Rate Limit is reset to 0
 | |
|                 latest_request = relevant_history[0]
 | |
|                 remaining = duration - (now - int(latest_request))
 | |
|                 row_data["reset"] = int(now + remaining)
 | |
|                 row_data["reset_seconds"] = int(remaining)
 | |
|             else:
 | |
|                 row_data["reset"] = None
 | |
|                 row_data["reset_seconds"] = None
 | |
|         else:
 | |
|             row_data["limit"] = None
 | |
|             row_data["duration"] = None
 | |
|             row_data["remaining"] = None
 | |
|             row_data["available"] = None
 | |
|             row_data["available_seconds"] = None
 | |
|             row_data["reset"] = None
 | |
|             row_data["reset_seconds"] = None
 | |
| 
 | |
|         data.append(row_data)
 | |
| 
 | |
|     return data
 | |
| 
 | |
| 
 | |
| class FunkwhaleThrottle(rest_throttling.SimpleRateThrottle):
 | |
|     def __init__(self):
 | |
|         pass
 | |
| 
 | |
|     def get_cache_key(self, request, view):
 | |
|         return get_cache_key(self.scope, self.ident)
 | |
| 
 | |
|     def allow_request(self, request, view):
 | |
|         self.request = request
 | |
|         self.ident = get_ident(getattr(request, "user", None), request)
 | |
|         action = getattr(view, "action", "*")
 | |
|         view_scopes = getattr(view, "throttling_scopes", {})
 | |
|         if view_scopes is None:
 | |
|             return True
 | |
|         self.scope = get_scope_for_action_and_ident_type(
 | |
|             action=action, ident_type=self.ident["type"], view_conf=view_scopes
 | |
|         )
 | |
|         if not self.scope or self.scope not in settings.THROTTLING_RATES:
 | |
|             return True
 | |
|         self.rate = settings.THROTTLING_RATES[self.scope].get("rate")
 | |
|         self.num_requests, self.duration = self.parse_rate(self.rate)
 | |
|         self.request = request
 | |
| 
 | |
|         return super().allow_request(request, view)
 | |
| 
 | |
|     def attach_info(self):
 | |
|         info = {
 | |
|             "num_requests": self.num_requests,
 | |
|             "duration": self.duration,
 | |
|             "scope": self.scope,
 | |
|             "history": self.history or [],
 | |
|             "wait": self.wait(),
 | |
|         }
 | |
|         setattr(self.request, "_throttle_status", info)
 | |
| 
 | |
|     def throttle_success(self):
 | |
|         self.attach_info()
 | |
|         return super().throttle_success()
 | |
| 
 | |
|     def throttle_failure(self):
 | |
|         self.attach_info()
 | |
|         return super().throttle_failure()
 | |
| 
 | |
| 
 | |
| class TooManyRequests(Exception):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| DummyView = collections.namedtuple("DummyView", "action throttling_scopes")
 | |
| 
 | |
| 
 | |
| def check_request(request, scope):
 | |
|     """
 | |
|     A simple wrapper around FunkwhaleThrottle for views that aren't API views
 | |
|     or cannot use rest_framework automatic throttling.
 | |
| 
 | |
|     Raise TooManyRequests if limit is reached.
 | |
|     """
 | |
|     if not settings.THROTTLING_ENABLED:
 | |
|         return True
 | |
| 
 | |
|     view = DummyView(
 | |
|         action=scope,
 | |
|         throttling_scopes={scope: {"anonymous": scope, "authenticated": scope}},
 | |
|     )
 | |
|     throttle = FunkwhaleThrottle()
 | |
|     if not throttle.allow_request(request, view):
 | |
|         raise TooManyRequests()
 | |
|     return True
 |