django-bolt 0.1.0__cp310-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-bolt might be problematic. Click here for more details.
- django_bolt/__init__.py +147 -0
- django_bolt/_core.pyd +0 -0
- django_bolt/admin/__init__.py +25 -0
- django_bolt/admin/admin_detection.py +179 -0
- django_bolt/admin/asgi_bridge.py +267 -0
- django_bolt/admin/routes.py +91 -0
- django_bolt/admin/static.py +155 -0
- django_bolt/admin/static_routes.py +111 -0
- django_bolt/api.py +1011 -0
- django_bolt/apps.py +7 -0
- django_bolt/async_collector.py +228 -0
- django_bolt/auth/README.md +464 -0
- django_bolt/auth/REVOCATION_EXAMPLE.md +391 -0
- django_bolt/auth/__init__.py +84 -0
- django_bolt/auth/backends.py +236 -0
- django_bolt/auth/guards.py +224 -0
- django_bolt/auth/jwt_utils.py +212 -0
- django_bolt/auth/revocation.py +286 -0
- django_bolt/auth/token.py +335 -0
- django_bolt/binding.py +363 -0
- django_bolt/bootstrap.py +77 -0
- django_bolt/cli.py +133 -0
- django_bolt/compression.py +104 -0
- django_bolt/decorators.py +159 -0
- django_bolt/dependencies.py +128 -0
- django_bolt/error_handlers.py +305 -0
- django_bolt/exceptions.py +294 -0
- django_bolt/health.py +129 -0
- django_bolt/logging/__init__.py +6 -0
- django_bolt/logging/config.py +357 -0
- django_bolt/logging/middleware.py +296 -0
- django_bolt/management/__init__.py +1 -0
- django_bolt/management/commands/__init__.py +0 -0
- django_bolt/management/commands/runbolt.py +427 -0
- django_bolt/middleware/__init__.py +32 -0
- django_bolt/middleware/compiler.py +131 -0
- django_bolt/middleware/middleware.py +247 -0
- django_bolt/openapi/__init__.py +23 -0
- django_bolt/openapi/config.py +196 -0
- django_bolt/openapi/plugins.py +439 -0
- django_bolt/openapi/routes.py +152 -0
- django_bolt/openapi/schema_generator.py +581 -0
- django_bolt/openapi/spec/__init__.py +68 -0
- django_bolt/openapi/spec/base.py +74 -0
- django_bolt/openapi/spec/callback.py +24 -0
- django_bolt/openapi/spec/components.py +72 -0
- django_bolt/openapi/spec/contact.py +21 -0
- django_bolt/openapi/spec/discriminator.py +25 -0
- django_bolt/openapi/spec/encoding.py +67 -0
- django_bolt/openapi/spec/enums.py +41 -0
- django_bolt/openapi/spec/example.py +36 -0
- django_bolt/openapi/spec/external_documentation.py +21 -0
- django_bolt/openapi/spec/header.py +132 -0
- django_bolt/openapi/spec/info.py +50 -0
- django_bolt/openapi/spec/license.py +28 -0
- django_bolt/openapi/spec/link.py +66 -0
- django_bolt/openapi/spec/media_type.py +51 -0
- django_bolt/openapi/spec/oauth_flow.py +36 -0
- django_bolt/openapi/spec/oauth_flows.py +28 -0
- django_bolt/openapi/spec/open_api.py +87 -0
- django_bolt/openapi/spec/operation.py +105 -0
- django_bolt/openapi/spec/parameter.py +147 -0
- django_bolt/openapi/spec/path_item.py +78 -0
- django_bolt/openapi/spec/paths.py +27 -0
- django_bolt/openapi/spec/reference.py +38 -0
- django_bolt/openapi/spec/request_body.py +38 -0
- django_bolt/openapi/spec/response.py +48 -0
- django_bolt/openapi/spec/responses.py +44 -0
- django_bolt/openapi/spec/schema.py +678 -0
- django_bolt/openapi/spec/security_requirement.py +28 -0
- django_bolt/openapi/spec/security_scheme.py +69 -0
- django_bolt/openapi/spec/server.py +34 -0
- django_bolt/openapi/spec/server_variable.py +32 -0
- django_bolt/openapi/spec/tag.py +32 -0
- django_bolt/openapi/spec/xml.py +44 -0
- django_bolt/pagination.py +669 -0
- django_bolt/param_functions.py +49 -0
- django_bolt/params.py +337 -0
- django_bolt/request_parsing.py +128 -0
- django_bolt/responses.py +214 -0
- django_bolt/router.py +48 -0
- django_bolt/serialization.py +193 -0
- django_bolt/status_codes.py +321 -0
- django_bolt/testing/__init__.py +10 -0
- django_bolt/testing/client.py +274 -0
- django_bolt/testing/helpers.py +93 -0
- django_bolt/tests/__init__.py +0 -0
- django_bolt/tests/admin_tests/__init__.py +1 -0
- django_bolt/tests/admin_tests/conftest.py +6 -0
- django_bolt/tests/admin_tests/test_admin_with_django.py +278 -0
- django_bolt/tests/admin_tests/urls.py +9 -0
- django_bolt/tests/cbv/__init__.py +0 -0
- django_bolt/tests/cbv/test_class_views.py +570 -0
- django_bolt/tests/cbv/test_class_views_django_orm.py +703 -0
- django_bolt/tests/cbv/test_class_views_features.py +1173 -0
- django_bolt/tests/cbv/test_class_views_with_client.py +622 -0
- django_bolt/tests/conftest.py +165 -0
- django_bolt/tests/test_action_decorator.py +399 -0
- django_bolt/tests/test_auth_secret_key.py +83 -0
- django_bolt/tests/test_decorator_syntax.py +159 -0
- django_bolt/tests/test_error_handling.py +481 -0
- django_bolt/tests/test_file_response.py +192 -0
- django_bolt/tests/test_global_cors.py +172 -0
- django_bolt/tests/test_guards_auth.py +441 -0
- django_bolt/tests/test_guards_integration.py +303 -0
- django_bolt/tests/test_health.py +283 -0
- django_bolt/tests/test_integration_validation.py +400 -0
- django_bolt/tests/test_json_validation.py +536 -0
- django_bolt/tests/test_jwt_auth.py +327 -0
- django_bolt/tests/test_jwt_token.py +458 -0
- django_bolt/tests/test_logging.py +837 -0
- django_bolt/tests/test_logging_merge.py +419 -0
- django_bolt/tests/test_middleware.py +492 -0
- django_bolt/tests/test_middleware_server.py +230 -0
- django_bolt/tests/test_model_viewset.py +323 -0
- django_bolt/tests/test_models.py +24 -0
- django_bolt/tests/test_pagination.py +1258 -0
- django_bolt/tests/test_parameter_validation.py +178 -0
- django_bolt/tests/test_syntax.py +626 -0
- django_bolt/tests/test_testing_utilities.py +163 -0
- django_bolt/tests/test_testing_utilities_simple.py +123 -0
- django_bolt/tests/test_viewset_unified.py +346 -0
- django_bolt/typing.py +273 -0
- django_bolt/views.py +1110 -0
- django_bolt-0.1.0.dist-info/METADATA +629 -0
- django_bolt-0.1.0.dist-info/RECORD +128 -0
- django_bolt-0.1.0.dist-info/WHEEL +4 -0
- django_bolt-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for JWT authentication and Django User integration.
|
|
3
|
+
|
|
4
|
+
Tests JWT token creation, validation, and Django User model integration
|
|
5
|
+
with type-safe dependency injection.
|
|
6
|
+
|
|
7
|
+
Uses pytest-django for proper Django configuration.
|
|
8
|
+
"""
|
|
9
|
+
import pytest
|
|
10
|
+
import jwt
|
|
11
|
+
import time
|
|
12
|
+
from django_bolt.auth import (
|
|
13
|
+
create_jwt_for_user,
|
|
14
|
+
get_current_user,
|
|
15
|
+
extract_user_id_from_context,
|
|
16
|
+
get_auth_context,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Mark all tests to use Django DB
|
|
20
|
+
pytestmark = pytest.mark.django_db
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_create_jwt_for_user():
|
|
24
|
+
"""Test creating JWT tokens for Django users."""
|
|
25
|
+
from django.contrib.auth import get_user_model
|
|
26
|
+
User = get_user_model()
|
|
27
|
+
|
|
28
|
+
# Create a test user
|
|
29
|
+
user = User.objects.create(
|
|
30
|
+
username="testuser",
|
|
31
|
+
email="test@example.com",
|
|
32
|
+
is_staff=True,
|
|
33
|
+
is_superuser=False
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Create JWT token
|
|
37
|
+
token = create_jwt_for_user(user, secret="my-secret")
|
|
38
|
+
|
|
39
|
+
# Decode and verify
|
|
40
|
+
decoded = jwt.decode(token, "my-secret", algorithms=["HS256"])
|
|
41
|
+
|
|
42
|
+
assert decoded["sub"] == str(user.id)
|
|
43
|
+
assert decoded["username"] == "testuser"
|
|
44
|
+
assert decoded["email"] == "test@example.com"
|
|
45
|
+
assert decoded["is_staff"] is True
|
|
46
|
+
assert decoded["is_superuser"] is False
|
|
47
|
+
assert "exp" in decoded
|
|
48
|
+
assert "iat" in decoded
|
|
49
|
+
|
|
50
|
+
# Clean up
|
|
51
|
+
user.delete()
|
|
52
|
+
print("✓ JWT token creation for user works correctly")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_create_jwt_with_extra_claims():
|
|
56
|
+
"""Test creating JWT tokens with custom claims."""
|
|
57
|
+
from django.contrib.auth import get_user_model
|
|
58
|
+
User = get_user_model()
|
|
59
|
+
|
|
60
|
+
user = User.objects.create(
|
|
61
|
+
username="admin",
|
|
62
|
+
email="admin@example.com",
|
|
63
|
+
is_staff=True,
|
|
64
|
+
is_superuser=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Create token with extra claims
|
|
68
|
+
token = create_jwt_for_user(
|
|
69
|
+
user,
|
|
70
|
+
secret="secret",
|
|
71
|
+
extra_claims={
|
|
72
|
+
"permissions": ["users.create", "users.delete"],
|
|
73
|
+
"role": "admin",
|
|
74
|
+
"department": "engineering"
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Decode and verify
|
|
79
|
+
decoded = jwt.decode(token, "secret", algorithms=["HS256"])
|
|
80
|
+
|
|
81
|
+
assert decoded["sub"] == str(user.id)
|
|
82
|
+
assert decoded["permissions"] == ["users.create", "users.delete"]
|
|
83
|
+
assert decoded["role"] == "admin"
|
|
84
|
+
assert decoded["department"] == "engineering"
|
|
85
|
+
|
|
86
|
+
# Clean up
|
|
87
|
+
user.delete()
|
|
88
|
+
print("✓ JWT with extra claims works correctly")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_jwt_authentication_with_django_user():
|
|
92
|
+
"""Test JWT authentication extracts correct user data."""
|
|
93
|
+
from django.contrib.auth import get_user_model
|
|
94
|
+
from django_bolt import BoltAPI
|
|
95
|
+
from django_bolt.auth import JWTAuthentication
|
|
96
|
+
from django_bolt.auth import IsAuthenticated
|
|
97
|
+
User = get_user_model()
|
|
98
|
+
|
|
99
|
+
# Create test user
|
|
100
|
+
user = User.objects.create(
|
|
101
|
+
username="jwtuser",
|
|
102
|
+
email="jwt@example.com",
|
|
103
|
+
is_staff=False,
|
|
104
|
+
is_superuser=False
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Create API with JWT auth
|
|
108
|
+
api = BoltAPI()
|
|
109
|
+
|
|
110
|
+
@api.get(
|
|
111
|
+
"/protected",
|
|
112
|
+
auth=[JWTAuthentication(secret="test-secret")],
|
|
113
|
+
guards=[IsAuthenticated()]
|
|
114
|
+
)
|
|
115
|
+
async def protected_endpoint(request: dict):
|
|
116
|
+
context = request["context"]
|
|
117
|
+
return {
|
|
118
|
+
"user_id": context.get("user_id"),
|
|
119
|
+
"is_staff": context.get("is_staff"),
|
|
120
|
+
"is_admin": context.get("is_admin"),
|
|
121
|
+
"auth_backend": context.get("auth_backend"),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Verify route registered
|
|
125
|
+
assert len(api._routes) == 1
|
|
126
|
+
|
|
127
|
+
# Clean up
|
|
128
|
+
user.delete()
|
|
129
|
+
print("✓ JWT authentication with Django User works")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_django_user_dependency_injection():
|
|
133
|
+
"""Test type-safe Django User dependency injection with Depends()."""
|
|
134
|
+
from django_bolt import BoltAPI
|
|
135
|
+
from django_bolt.auth import JWTAuthentication
|
|
136
|
+
from django_bolt.auth import IsAuthenticated
|
|
137
|
+
from django_bolt.params import Depends
|
|
138
|
+
|
|
139
|
+
# Create API with the dependency (using imported get_current_user)
|
|
140
|
+
api = BoltAPI()
|
|
141
|
+
|
|
142
|
+
@api.get(
|
|
143
|
+
"/me",
|
|
144
|
+
auth=[JWTAuthentication(secret="test-secret")],
|
|
145
|
+
guards=[IsAuthenticated()]
|
|
146
|
+
)
|
|
147
|
+
async def get_current_user_endpoint(user=Depends(get_current_user)):
|
|
148
|
+
"""
|
|
149
|
+
Endpoint that receives Django User instance via dependency injection.
|
|
150
|
+
|
|
151
|
+
The 'user' parameter is the actual Django User model instance,
|
|
152
|
+
not just a dictionary. You have full access to:
|
|
153
|
+
- user.id, user.username, user.email
|
|
154
|
+
- user.is_staff, user.is_superuser, user.is_active
|
|
155
|
+
- user.first_name, user.last_name, user.date_joined
|
|
156
|
+
- All custom fields if using a custom User model
|
|
157
|
+
- All User model methods
|
|
158
|
+
"""
|
|
159
|
+
if user is None:
|
|
160
|
+
return {"error": "User not found"}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
"id": user.id,
|
|
164
|
+
"username": user.username,
|
|
165
|
+
"email": user.email,
|
|
166
|
+
"is_staff": user.is_staff,
|
|
167
|
+
"is_superuser": user.is_superuser,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Verify the route was registered correctly
|
|
171
|
+
assert len(api._routes) == 1
|
|
172
|
+
method, path, _, handler_fn = api._routes[0]
|
|
173
|
+
assert method == "GET"
|
|
174
|
+
assert path == "/me"
|
|
175
|
+
|
|
176
|
+
# Verify handler metadata
|
|
177
|
+
assert handler_fn in api._handler_meta
|
|
178
|
+
meta = api._handler_meta[handler_fn]
|
|
179
|
+
assert "params" in meta
|
|
180
|
+
|
|
181
|
+
# Check that the dependency parameter was detected
|
|
182
|
+
params = meta["params"]
|
|
183
|
+
user_param = next((p for p in params if p["name"] == "user"), None)
|
|
184
|
+
assert user_param is not None
|
|
185
|
+
assert user_param["source"] == "dependency"
|
|
186
|
+
assert user_param["dependency"] is not None
|
|
187
|
+
|
|
188
|
+
print("✓ Django User dependency injection metadata correct")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_jwt_utils_extract_user_id():
|
|
192
|
+
"""Test extracting user_id from request context."""
|
|
193
|
+
# Mock request with context
|
|
194
|
+
request = {
|
|
195
|
+
"context": {
|
|
196
|
+
"user_id": "123",
|
|
197
|
+
"is_staff": True,
|
|
198
|
+
"auth_backend": "jwt"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
user_id = extract_user_id_from_context(request)
|
|
203
|
+
assert user_id == "123"
|
|
204
|
+
|
|
205
|
+
# Test with missing context
|
|
206
|
+
empty_request = {}
|
|
207
|
+
user_id = extract_user_id_from_context(empty_request)
|
|
208
|
+
assert user_id is None
|
|
209
|
+
|
|
210
|
+
print("✓ extract_user_id_from_context works correctly")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_jwt_utils_get_auth_context():
|
|
214
|
+
"""Test getting full auth context."""
|
|
215
|
+
request = {
|
|
216
|
+
"context": {
|
|
217
|
+
"user_id": "456",
|
|
218
|
+
"is_staff": False,
|
|
219
|
+
"is_admin": True,
|
|
220
|
+
"auth_backend": "jwt",
|
|
221
|
+
"permissions": ["read", "write"]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
ctx = get_auth_context(request)
|
|
226
|
+
assert ctx["user_id"] == "456"
|
|
227
|
+
assert ctx["is_staff"] is False
|
|
228
|
+
assert ctx["is_admin"] is True
|
|
229
|
+
assert ctx["auth_backend"] == "jwt"
|
|
230
|
+
assert ctx["permissions"] == ["read", "write"]
|
|
231
|
+
|
|
232
|
+
print("✓ get_auth_context works correctly")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_jwt_claims_stored_in_context():
|
|
236
|
+
"""Test that JWT claims are properly stored in request context."""
|
|
237
|
+
from django.contrib.auth import get_user_model
|
|
238
|
+
User = get_user_model()
|
|
239
|
+
|
|
240
|
+
user = User.objects.create(
|
|
241
|
+
username="claimsuser",
|
|
242
|
+
email="claims@example.com",
|
|
243
|
+
is_staff=True,
|
|
244
|
+
is_superuser=False
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Create token with custom claims
|
|
248
|
+
token = create_jwt_for_user(
|
|
249
|
+
user,
|
|
250
|
+
secret="secret",
|
|
251
|
+
extra_claims={
|
|
252
|
+
"permissions": ["read", "write"],
|
|
253
|
+
"tenant_id": "tenant123",
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Decode to verify claims are there
|
|
258
|
+
decoded = jwt.decode(token, "secret", algorithms=["HS256"])
|
|
259
|
+
assert decoded["permissions"] == ["read", "write"]
|
|
260
|
+
assert decoded["tenant_id"] == "tenant123"
|
|
261
|
+
|
|
262
|
+
# Clean up
|
|
263
|
+
user.delete()
|
|
264
|
+
print("✓ JWT claims storage verified")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_jwt_expiration():
|
|
268
|
+
"""Test JWT expiration handling."""
|
|
269
|
+
from django.contrib.auth import get_user_model
|
|
270
|
+
User = get_user_model()
|
|
271
|
+
|
|
272
|
+
user = User.objects.create(username="expuser")
|
|
273
|
+
|
|
274
|
+
# Create an expired token
|
|
275
|
+
expired_payload = {
|
|
276
|
+
"sub": str(user.id),
|
|
277
|
+
"exp": int(time.time()) - 100, # Expired 100 seconds ago
|
|
278
|
+
"iat": int(time.time()) - 200,
|
|
279
|
+
}
|
|
280
|
+
expired_token = jwt.encode(expired_payload, "secret", algorithm="HS256")
|
|
281
|
+
|
|
282
|
+
# Verify it's expired
|
|
283
|
+
try:
|
|
284
|
+
jwt.decode(expired_token, "secret", algorithms=["HS256"])
|
|
285
|
+
assert False, "Should have raised ExpiredSignatureError"
|
|
286
|
+
except jwt.ExpiredSignatureError:
|
|
287
|
+
pass # Expected
|
|
288
|
+
|
|
289
|
+
# Create a valid token
|
|
290
|
+
valid_token = create_jwt_for_user(user, secret="secret")
|
|
291
|
+
decoded = jwt.decode(valid_token, "secret", algorithms=["HS256"])
|
|
292
|
+
assert decoded["sub"] == str(user.id)
|
|
293
|
+
|
|
294
|
+
# Clean up
|
|
295
|
+
user.delete()
|
|
296
|
+
print("✓ JWT expiration handling works")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def test_jwt_uses_django_secret_key():
|
|
300
|
+
"""Test that JWTAuthentication uses Django SECRET_KEY by default."""
|
|
301
|
+
from django.conf import settings
|
|
302
|
+
from django_bolt.auth import JWTAuthentication
|
|
303
|
+
|
|
304
|
+
# Create JWT auth without explicit secret
|
|
305
|
+
auth = JWTAuthentication()
|
|
306
|
+
|
|
307
|
+
# Should use Django's SECRET_KEY
|
|
308
|
+
assert auth.secret == settings.SECRET_KEY
|
|
309
|
+
print("✓ JWT uses Django SECRET_KEY by default")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_jwt_custom_secret_overrides():
|
|
313
|
+
"""Test that explicit secret overrides Django SECRET_KEY."""
|
|
314
|
+
from django.conf import settings
|
|
315
|
+
from django_bolt.auth import JWTAuthentication
|
|
316
|
+
|
|
317
|
+
# Create with explicit secret
|
|
318
|
+
auth = JWTAuthentication(secret="custom-secret")
|
|
319
|
+
|
|
320
|
+
# Should use the explicit secret, not Django's
|
|
321
|
+
assert auth.secret == "custom-secret"
|
|
322
|
+
assert auth.secret != settings.SECRET_KEY
|
|
323
|
+
print("✓ Explicit JWT secret overrides Django SECRET_KEY")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == "__main__":
|
|
327
|
+
pytest.main([__file__, "-v", "-s"])
|