* feat: DraftAliasGenerator class Encapsulates logic from generate_draft_aliases.py * refactor: Avoid circular imports * feat: Add draft_aliases API endpoint * feat: Add @requires_api_token decorator Stolen from feat/rpc-api * feat: Add token auth to draft_aliases endpoint * feat: draft-aliases-from-json.py script Parses output from the draft_aliases API call * chore: Remove unused cruft * refactor: Avoid shadowing "draft" name * fix: Suppress empty lists from DraftAliasGenerator * refactor: Use a GET instead of POST * feat: GroupAliasGenerator class * feat: group aliases API view * fix: Handle domains array correctly * fix: Suppress empty group aliases * refactor: Generalize aliases-from-json.py script * refactor: Same output fmt for draft and group alias apis * feat: Sort addresses for stability * fix: Add "anything" virtual alias * test: Test requires_api_token decorator * feat: Harden is_valid_token against misconfig * test: Test is_valid_token * test: Test draft_aliases view * test: Test group_aliases view * test: Test DraftAliasGenerator * fix: ise group is type "ise" in test data * test: Fix logic in testManagementCommand The test was incorrect - and fails when fixed. :-( * test: Test GroupAliasGenerator Test currently fails * fix: Suppress empty -ads alias * test: Fix group acronym copy/paste error I *think* this must be what had been intended. The code does not look like it ever dealt with GroupHistory, so I'm pretty sure it wasn't meant to have the same acronym used by two different Groups at different times. * test: Check draft .notify alias generation * test: Cover get_draft_notify_emails()
77 lines
2.6 KiB
Python
77 lines
2.6 KiB
Python
# 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
|