File: test_permissions.py

package info (click to toggle)
djangorestframework-api-key 3.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 392 kB
  • sloc: python: 926; makefile: 53; sh: 3
file content (155 lines) | stat: -rw-r--r-- 4,765 bytes parent folder | download
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