149 lines
5 KiB
Python
149 lines
5 KiB
Python
# Copyright The IETF Trust 2016-2020, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
import datetime
|
|
|
|
from functools import wraps
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import login
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import render
|
|
from django.utils import timezone
|
|
from django.utils.encoding import force_bytes
|
|
|
|
import debug # pyflakes:ignore
|
|
|
|
from ietf.utils.test_runner import set_coverage_checking
|
|
from ietf.person.models import Person, PersonalApiKey, PersonApiKeyEvent
|
|
from ietf.utils import log
|
|
|
|
def skip_coverage(f):
|
|
@wraps(f)
|
|
def _wrapper(*args, **kwargs):
|
|
if settings.TEST_CODE_COVERAGE_CHECKER:
|
|
set_coverage_checking(False)
|
|
result = f(*args, **kwargs)
|
|
set_coverage_checking(True)
|
|
return result
|
|
else:
|
|
return f(*args, **kwargs)
|
|
return _wrapper
|
|
|
|
def person_required(f):
|
|
@wraps(f)
|
|
def _wrapper(request, *args, **kwargs):
|
|
if not request.user.is_authenticated:
|
|
raise ValueError("The @person_required decorator should be called after @login_required.")
|
|
try:
|
|
request.user.person
|
|
except Person.DoesNotExist:
|
|
return render(request, 'registration/missing_person.html')
|
|
return f(request, *args, **kwargs)
|
|
return _wrapper
|
|
|
|
|
|
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):
|
|
@wraps(func)
|
|
def _memoize(self, *args, **kwargs):
|
|
'''Memoize wrapper for instance methods. Use @lru_cache for functions.'''
|
|
if kwargs: # frozenset is used to ensure hashability
|
|
key = args, frozenset(list(kwargs.items()))
|
|
else:
|
|
key = args
|
|
# instance method, set up cache if needed
|
|
if not hasattr(self, '_cache'):
|
|
self._cache = {}
|
|
if not func in self._cache:
|
|
self._cache[func] = {}
|
|
#
|
|
cache = self._cache[func]
|
|
if key not in cache:
|
|
cache[key] = func(self, *args, **kwargs)
|
|
return cache[key]
|
|
|
|
if not hasattr(func, '__class__'):
|
|
raise NotImplementedError("Use @lru_cache instead of memoize() for functions.")
|
|
# For methods, we want the cache on the object, not on the class, in order
|
|
# to not having to think about cache bloat and content becoming stale, so
|
|
# we cannot set up the cache here.
|
|
return _memoize
|
|
|
|
|
|
def ignore_view_kwargs(*args):
|
|
"""Ignore the specified kwargs if they are present
|
|
|
|
Usage:
|
|
@ignore_view_kwargs("ignore_arg1", "ignore_arg2")
|
|
def my_view(request, good_arg):
|
|
...
|
|
|
|
This will allow my_view() to be used in url() paths that have zero, one, or both of
|
|
ignore_arg1 and ignore_arg2 captured. These will be ignored, while good_arg will still
|
|
be captured as usual.
|
|
"""
|
|
kwargs_to_ignore = args
|
|
|
|
def decorate(view):
|
|
@wraps(view)
|
|
def wrapped(*args, **kwargs):
|
|
for kwarg in kwargs_to_ignore:
|
|
kwargs.pop(kwarg, None)
|
|
return view(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
return decorate
|
|
|
|
|