forked from ietf-tools/datatracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathietf_utils.py
More file actions
76 lines (60 loc) · 2.61 KB
/
ietf_utils.py
File metadata and controls
76 lines (60 loc) · 2.61 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
# Copyright The IETF Trust 2023, All Rights Reserved
# This is not utils.py because Tastypie implicitly consumes ietf.api.utils.
# See ietf.api.__init__.py for details.
from functools import wraps
from typing import Callable, Optional, Union
from django.conf import settings
from django.http import HttpResponseForbidden
def is_valid_token(endpoint, token):
# This is where we would consider integration with vault
# Settings implementation for now.
if hasattr(settings, "APP_API_TOKENS"):
token_store = settings.APP_API_TOKENS
if endpoint in token_store:
endpoint_tokens = token_store[endpoint]
# Be sure endpoints is a list or tuple so we don't accidentally use substring matching!
if not isinstance(endpoint_tokens, (list, tuple)):
endpoint_tokens = [endpoint_tokens]
if token in endpoint_tokens:
return True
return False
def requires_api_token(func_or_endpoint: Optional[Union[Callable, str]] = None):
"""Validate API token before executing the wrapped method
Usage:
* Basic: endpoint defaults to the qualified name of the wrapped method. E.g., in ietf.api.views,
@requires_api_token
def my_view(request):
...
will require a token for "ietf.api.views.my_view"
* Custom endpoint: specify the endpoint explicitly
@requires_api_token("ietf.api.views.some_other_thing")
def my_view(request):
...
will require a token for "ietf.api.views.some_other_thing"
"""
def decorate(f):
if _endpoint is None:
fname = getattr(f, "__qualname__", None)
if fname is None:
raise TypeError(
"Cannot automatically decorate function that does not support __qualname__. "
"Explicitly set the endpoint."
)
endpoint = "{}.{}".format(f.__module__, fname)
else:
endpoint = _endpoint
@wraps(f)
def wrapped(request, *args, **kwargs):
authtoken = request.META.get("HTTP_X_API_KEY", None)
if authtoken is None or not is_valid_token(endpoint, authtoken):
return HttpResponseForbidden()
return f(request, *args, **kwargs)
return wrapped
# Magic to allow decorator to be used with or without parentheses
if callable(func_or_endpoint):
func = func_or_endpoint
_endpoint = None
return decorate(func)
else:
_endpoint = func_or_endpoint
return decorate