From 8cf609bfa93ccf5f3094467083e4452de74cffc1 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 17 May 2023 09:45:07 -0300 Subject: [PATCH] refactor: Implement require_api_key with functools.wraps The @decorator mechanism does not seem to work with @method_decorator in Django 4.0, have not tracked down why. --- ietf/utils/decorators.py | 95 +++++++++++++++++++++------------------- ietf/utils/urls.py | 4 +- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/ietf/utils/decorators.py b/ietf/utils/decorators.py index d37d255a3..db919bd4b 100644 --- a/ietf/utils/decorators.py +++ b/ietf/utils/decorators.py @@ -5,6 +5,7 @@ import datetime from decorator import decorator, decorate +from functools import wraps from django.conf import settings from django.contrib.auth import login @@ -39,52 +40,54 @@ def person_required(f, request, *args, **kwargs): return render(request, 'registration/missing_person.html') return f(request, *args, **kwargs) -@decorator -def require_api_key(f, request, *args, **kwargs): - - def err(code, text): - return HttpResponse(text, status=code, content_type='text/plain') - # Check method and get hash - if request.method == 'POST': - hash = request.POST.get('apikey') - elif request.method == 'GET': - hash = request.GET.get('apikey') - else: - return err(405, "Method not allowed") - if not hash: - return err(400, "Missing apikey parameter") - # Check hash - key = PersonalApiKey.validate_key(force_bytes(hash)) - if not key: - return err(403, "Invalid apikey") - # Check endpoint - urlpath = request.META.get('PATH_INFO') - if not (urlpath and urlpath == key.endpoint): - return err(400, "Apikey endpoint mismatch") - # Check time since regular login - person = key.person - last_login = person.user.last_login - if not person.user.is_staff: - time_limit = (timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS)) - if last_login == None or last_login < time_limit: - return err(400, "Too long since last regular login") - # Log in - login(request, person.user) - # restore the user.last_login field, so it reflects only gui logins - person.user.last_login = last_login - person.user.save() - # Update stats - key.count += 1 - key.latest = timezone.now() - key.save() - PersonApiKeyEvent.objects.create(person=person, type='apikey_login', key=key, desc="Logged in with key ID %s, endpoint %s" % (key.id, key.endpoint)) - # Execute decorated function - try: - ret = f(request, *args, **kwargs) - except AttributeError as e: - log.log("Bad API call: args: %s, kwargs: %s, exception: %s" % (args, kwargs, e)) - return err(400, "Bad or missing parameters") - return ret + +def require_api_key(f): + @wraps(f) + def _wrapper(request, *args, **kwargs): + def err(code, text): + return HttpResponse(text, status=code, content_type='text/plain') + # Check method and get hash + if request.method == 'POST': + hash = request.POST.get('apikey') + elif request.method == 'GET': + hash = request.GET.get('apikey') + else: + return err(405, "Method not allowed") + if not hash: + return err(400, "Missing apikey parameter") + # Check hash + key = PersonalApiKey.validate_key(force_bytes(hash)) + if not key: + return err(403, "Invalid apikey") + # Check endpoint + urlpath = request.META.get('PATH_INFO') + if not (urlpath and urlpath == key.endpoint): + return err(400, "Apikey endpoint mismatch") + # Check time since regular login + person = key.person + last_login = person.user.last_login + if not person.user.is_staff: + time_limit = (timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS)) + if last_login == None or last_login < time_limit: + return err(400, "Too long since last regular login") + # Log in + login(request, person.user) + # restore the user.last_login field, so it reflects only gui logins + person.user.last_login = last_login + person.user.save() + # Update stats + key.count += 1 + key.latest = timezone.now() + key.save() + PersonApiKeyEvent.objects.create(person=person, type='apikey_login', key=key, desc="Logged in with key ID %s, endpoint %s" % (key.id, key.endpoint)) + # Execute decorated function + try: + ret = f(request, *args, **kwargs) + except AttributeError as e: + log.log("Bad API call: args: %s, kwargs: %s, exception: %s" % (args, kwargs, e)) + return err(400, "Bad or missing parameters") + return ret + return _wrapper def _memoize(func, self, *args, **kwargs): diff --git a/ietf/utils/urls.py b/ietf/utils/urls.py index 9c26da724..6abda9b97 100644 --- a/ietf/utils/urls.py +++ b/ietf/utils/urls.py @@ -11,7 +11,9 @@ from django.utils.encoding import force_str from django.views.generic import View def url(regex, view, kwargs=None, name=None): - if callable(view) and hasattr(view, '__name__'): + if hasattr(view, "view_class"): + view_name = "%s.%s" % (view.__module__, view.view_class.__name__) + elif callable(view) and hasattr(view, '__name__'): view_name = "%s.%s" % (view.__module__, view.__name__) else: view_name = regex