forked from ietf-tools/datatracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecorators.py
More file actions
136 lines (114 loc) · 4.65 KB
/
decorators.py
File metadata and controls
136 lines (114 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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.person.models import Person, PersonalApiKey, PersonApiKeyEvent
from ietf.utils import log
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=f"text/plain; charset={settings.DEFAULT_CHARSET}")
# 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