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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
import datetime as dt
from typing import Callable
import pytest
from django.test import RequestFactory, override_settings
from rest_framework.decorators import api_view, permission_classes
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework_api_key.models import APIKey
from rest_framework_api_key.permissions import BaseHasAPIKey, HasAPIKey, KeyParser
pytestmark = pytest.mark.django_db
@api_view()
@permission_classes([HasAPIKey])
def view(request: Request) -> Response:
return Response()
def test_if_valid_api_key_then_permission_granted(rf: RequestFactory) -> None:
_, key = APIKey.objects.create_key(name="test")
authorization = f"Api-Key {key}"
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
response = view(request)
assert response.status_code == 200
def test_if_valid_api_key_custom_header_then_permission_granted(
rf: RequestFactory,
) -> None:
with override_settings(API_KEY_CUSTOM_HEADER="HTTP_X_API_KEY"):
_, key = APIKey.objects.create_key(name="test")
request = rf.get("/test/", HTTP_X_API_KEY=key)
response = view(request)
assert response.status_code == 200
def test_if_no_api_key_then_permission_denied(rf: RequestFactory) -> None:
request = rf.get("/test/")
response = view(request)
assert response.status_code == 403
def _scramble_prefix(key: str) -> str:
prefix, _, secret_key = key.partition(".")
truncated_prefix = prefix[:-1]
return truncated_prefix + "." + secret_key
@pytest.mark.parametrize(
"modifier",
[
lambda _: "",
lambda _: "abcd",
lambda _: "foo.bar",
lambda key: " " + key,
lambda key: key.upper(),
lambda key: key.lower(),
lambda key: _scramble_prefix(key),
],
)
def test_if_invalid_api_key_then_permission_denied(
rf: RequestFactory,
modifier: Callable[[str], str],
) -> None:
_, key = APIKey.objects.create_key(name="test")
authorization = f"Api-Key {modifier(key)}"
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
response = view(request)
assert response.status_code == 403
@pytest.mark.parametrize(
"authorization_fmt",
[
pytest.param("X-Key {key}", id="wrong-scheme"),
pytest.param("Api-Key:{key}", id="not-space-separated"),
],
)
def test_if_malformed_authorization_then_permission_denied(
rf: RequestFactory, authorization_fmt: str
) -> None:
_, key = APIKey.objects.create_key(name="test")
authorization = authorization_fmt.format(key=key)
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
response = view(request)
assert response.status_code == 403
def test_if_invalid_api_key_custom_header_then_permission_denied(
rf: RequestFactory,
) -> None:
with override_settings(API_KEY_CUSTOM_HEADER="HTTP_X_API_KEY"):
request = rf.get("/test/", HTTP_X_API_KEY="doesnotexist")
response = view(request)
assert response.status_code == 403
def test_if_revoked_then_permission_denied(rf: RequestFactory) -> None:
_, key = APIKey.objects.create_key(name="test", revoked=True)
authorization = f"Api-Key {key}"
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
response = view(request)
assert response.status_code == 403
NOW = dt.datetime.now()
TOMORROW = NOW + dt.timedelta(days=1)
TWO_DAYS_AGO = NOW - dt.timedelta(days=2)
@pytest.mark.parametrize("expiry_date, ok", [(TOMORROW, True), (TWO_DAYS_AGO, False)])
def test_expiry_date(rf: RequestFactory, expiry_date: dt.datetime, ok: bool) -> None:
_, key = APIKey.objects.create_key(name="test", expiry_date=expiry_date)
authorization = f"Api-Key {key}"
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
response = view(request)
status_code = 200 if ok else 403
assert response.status_code == status_code
def test_keyparser_keyword_override(rf: RequestFactory) -> None:
class BearerKeyParser(KeyParser):
keyword = "Bearer"
class BearerHasAPIKey(BaseHasAPIKey):
model = APIKey
key_parser = BearerKeyParser()
@api_view()
@permission_classes([BearerHasAPIKey])
def bearer_view(request: Request) -> Response:
return Response()
_, key = APIKey.objects.create_key(name="test")
authorization = f"Bearer {key}"
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
response = bearer_view(request)
assert response.status_code == 200
def test_keyparser_lookup_exact_keyword(rf: RequestFactory) -> None:
wrong_key = "My-Special-Api-Key 12345"
request = rf.get("/test/", HTTP_AUTHORIZATION=wrong_key)
assert KeyParser().get(request) is None
|