mcp-security-framework 0.1.0__py3-none-any.whl → 1.1.0__py3-none-any.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.
Files changed (38) hide show
  1. mcp_security_framework/core/auth_manager.py +12 -2
  2. mcp_security_framework/core/cert_manager.py +247 -16
  3. mcp_security_framework/core/permission_manager.py +4 -0
  4. mcp_security_framework/core/rate_limiter.py +10 -0
  5. mcp_security_framework/core/security_manager.py +2 -0
  6. mcp_security_framework/examples/comprehensive_example.py +884 -0
  7. mcp_security_framework/examples/django_example.py +45 -12
  8. mcp_security_framework/examples/fastapi_example.py +826 -354
  9. mcp_security_framework/examples/flask_example.py +51 -11
  10. mcp_security_framework/examples/gateway_example.py +109 -17
  11. mcp_security_framework/examples/microservice_example.py +112 -16
  12. mcp_security_framework/examples/standalone_example.py +646 -430
  13. mcp_security_framework/examples/test_all_examples.py +556 -0
  14. mcp_security_framework/middleware/auth_middleware.py +1 -1
  15. mcp_security_framework/middleware/fastapi_auth_middleware.py +82 -14
  16. mcp_security_framework/middleware/flask_auth_middleware.py +154 -7
  17. mcp_security_framework/schemas/models.py +1 -0
  18. mcp_security_framework/utils/cert_utils.py +5 -5
  19. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/METADATA +1 -1
  20. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/RECORD +38 -32
  21. tests/conftest.py +306 -0
  22. tests/test_cli/test_cert_cli.py +13 -31
  23. tests/test_core/test_cert_manager.py +12 -12
  24. tests/test_examples/test_comprehensive_example.py +560 -0
  25. tests/test_examples/test_fastapi_example.py +214 -116
  26. tests/test_examples/test_flask_example.py +250 -131
  27. tests/test_examples/test_standalone_example.py +44 -99
  28. tests/test_integration/test_auth_flow.py +4 -4
  29. tests/test_integration/test_certificate_flow.py +1 -1
  30. tests/test_integration/test_fastapi_integration.py +39 -45
  31. tests/test_integration/test_flask_integration.py +4 -2
  32. tests/test_integration/test_standalone_integration.py +48 -48
  33. tests/test_middleware/test_fastapi_auth_middleware.py +724 -0
  34. tests/test_middleware/test_flask_auth_middleware.py +638 -0
  35. tests/test_middleware/test_security_middleware.py +9 -3
  36. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/WHEEL +0 -0
  37. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/entry_points.txt +0 -0
  38. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,724 @@
1
+ """
2
+ FastAPI Authentication Middleware Tests
3
+
4
+ This module contains comprehensive tests for the FastAPI Authentication Middleware
5
+ implementation of the MCP Security Framework.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ Version: 1.0.0
10
+ License: MIT
11
+ """
12
+
13
+ import json
14
+ import pytest
15
+ from unittest.mock import Mock, patch, AsyncMock
16
+ from typing import Dict, Any
17
+
18
+ from fastapi import Request, Response, status
19
+ from fastapi.responses import JSONResponse
20
+ from starlette.middleware.base import BaseHTTPMiddleware
21
+
22
+ from mcp_security_framework.middleware.fastapi_auth_middleware import FastAPIAuthMiddleware
23
+ from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig
24
+ from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
25
+ from mcp_security_framework.middleware.auth_middleware import AuthMiddlewareError
26
+
27
+
28
+ class TestFastAPIAuthMiddleware:
29
+ """Test suite for FastAPI Authentication Middleware."""
30
+
31
+ def setup_method(self):
32
+ """Set up test fixtures before each test method."""
33
+ # Create test configuration
34
+ self.config = SecurityConfig(
35
+ auth=AuthConfig(
36
+ enabled=True,
37
+ methods=["api_key", "jwt"],
38
+ api_keys={
39
+ "test_key_123": {"username": "testuser", "roles": ["user"]}
40
+ },
41
+ jwt_secret="test-jwt-secret-key-for-testing",
42
+ jwt_algorithm="HS256",
43
+ jwt_expiry_hours=24,
44
+ public_paths=["/health", "/metrics"]
45
+ )
46
+ )
47
+
48
+ # Create mock security manager
49
+ from mcp_security_framework.core.security_manager import SecurityManager
50
+ self.mock_security_manager = Mock(spec=SecurityManager)
51
+ self.mock_security_manager.authenticate_user = Mock()
52
+ self.mock_security_manager.config = self.config
53
+
54
+ # Create mock auth manager
55
+ self.mock_auth_manager = Mock()
56
+ self.mock_auth_manager.authenticate_api_key = Mock()
57
+ self.mock_auth_manager.authenticate_jwt_token = Mock()
58
+ self.mock_auth_manager.authenticate_certificate = Mock()
59
+ self.mock_security_manager.auth_manager = self.mock_auth_manager
60
+
61
+ # Create middleware instance
62
+ self.middleware = FastAPIAuthMiddleware(self.mock_security_manager)
63
+
64
+ def test_fastapi_auth_middleware_initialization(self):
65
+ """Test FastAPI Authentication Middleware initialization."""
66
+ assert self.middleware is not None
67
+ assert isinstance(self.middleware, FastAPIAuthMiddleware)
68
+ assert self.middleware.config == self.config
69
+ assert self.middleware.security_manager == self.mock_security_manager
70
+ assert hasattr(self.middleware, 'logger')
71
+
72
+ @pytest.mark.asyncio
73
+ async def test_fastapi_auth_middleware_public_path_bypass(self):
74
+ """Test that public paths bypass authentication."""
75
+ # Create mock request for public path
76
+ mock_request = Mock(spec=Request)
77
+ mock_request.url.path = "/health"
78
+ mock_request.headers = {}
79
+
80
+ # Create mock call_next
81
+ mock_call_next = AsyncMock()
82
+ mock_response = Mock(spec=Response)
83
+ mock_call_next.return_value = mock_response
84
+
85
+ # Process request
86
+ response = await self.middleware(mock_request, mock_call_next)
87
+
88
+ # Assertions
89
+ assert response == mock_response
90
+ mock_call_next.assert_called_once_with(mock_request)
91
+ # Security manager should not be called for public paths
92
+ self.mock_security_manager.authenticate_user.assert_not_called()
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_fastapi_auth_middleware_api_key_authentication_success(self):
96
+ """Test successful API key authentication."""
97
+ # Create mock request with API key
98
+ mock_request = Mock(spec=Request)
99
+ mock_request.url.path = "/api/v1/users/me"
100
+ mock_request.headers = {"X-API-Key": "test_key_123"}
101
+
102
+ # Mock successful authentication
103
+ auth_result = AuthResult(
104
+ is_valid=True,
105
+ status=AuthStatus.SUCCESS,
106
+ username="testuser",
107
+ roles=["user"],
108
+ auth_method=AuthMethod.API_KEY
109
+ )
110
+ self.mock_auth_manager.authenticate_api_key.return_value = auth_result
111
+
112
+ # Create mock call_next
113
+ mock_call_next = AsyncMock()
114
+ mock_response = Mock(spec=Response)
115
+ mock_call_next.return_value = mock_response
116
+
117
+ # Process request
118
+ response = await self.middleware(mock_request, mock_call_next)
119
+
120
+ # Assertions
121
+ assert response == mock_response
122
+ mock_call_next.assert_called_once_with(mock_request)
123
+ self.mock_auth_manager.authenticate_api_key.assert_called_once_with("test_key_123")
124
+
125
+ # Check that user info was added to request state
126
+ assert hasattr(mock_request.state, 'auth_result')
127
+ assert mock_request.state.auth_result.username == "testuser"
128
+ assert mock_request.state.auth_result.roles == ["user"]
129
+
130
+ @pytest.mark.asyncio
131
+ async def test_fastapi_auth_middleware_jwt_authentication_success(self):
132
+ """Test successful JWT authentication."""
133
+ # Create mock request with JWT token
134
+ mock_request = Mock(spec=Request)
135
+ mock_request.url.path = "/api/v1/users/me"
136
+ mock_request.headers = {"Authorization": "Bearer test_jwt_token"}
137
+
138
+ # Mock failed API key authentication first
139
+ failed_api_key_result = AuthResult(
140
+ is_valid=False,
141
+ status=AuthStatus.FAILED,
142
+ username=None,
143
+ roles=[],
144
+ auth_method=AuthMethod.API_KEY,
145
+ error_code=-32012,
146
+ error_message="API key not found in request"
147
+ )
148
+ self.mock_auth_manager.authenticate_api_key.return_value = failed_api_key_result
149
+
150
+ # Mock successful JWT authentication
151
+ auth_result = AuthResult(
152
+ is_valid=True,
153
+ status=AuthStatus.SUCCESS,
154
+ username="testuser",
155
+ roles=["user"],
156
+ auth_method=AuthMethod.JWT
157
+ )
158
+ self.mock_auth_manager.authenticate_jwt_token.return_value = auth_result
159
+
160
+ # Create mock call_next
161
+ mock_call_next = AsyncMock()
162
+ mock_response = Mock(spec=Response)
163
+ mock_call_next.return_value = mock_response
164
+
165
+ # Process request
166
+ response = await self.middleware(mock_request, mock_call_next)
167
+
168
+ # Assertions
169
+ assert response == mock_response
170
+ mock_call_next.assert_called_once_with(mock_request)
171
+ self.mock_auth_manager.authenticate_jwt_token.assert_called_once_with("test_jwt_token")
172
+
173
+ @pytest.mark.asyncio
174
+ async def test_fastapi_auth_middleware_certificate_authentication_success(self):
175
+ """Test successful certificate authentication."""
176
+ # Create mock request with certificate
177
+ mock_request = Mock(spec=Request)
178
+ mock_request.url.path = "/api/v1/users/me"
179
+ mock_request.headers = {"X-Client-Cert": "test_certificate_data"}
180
+
181
+ # Mock failed API key authentication first
182
+ failed_api_key_result = AuthResult(
183
+ is_valid=False,
184
+ status=AuthStatus.FAILED,
185
+ username=None,
186
+ roles=[],
187
+ auth_method=AuthMethod.API_KEY,
188
+ error_code=-32012,
189
+ error_message="API key not found in request"
190
+ )
191
+ self.mock_auth_manager.authenticate_api_key.return_value = failed_api_key_result
192
+
193
+ # Mock failed JWT authentication
194
+ failed_jwt_result = AuthResult(
195
+ is_valid=False,
196
+ status=AuthStatus.FAILED,
197
+ username=None,
198
+ roles=[],
199
+ auth_method=AuthMethod.JWT,
200
+ error_code=-32013,
201
+ error_message="JWT token not found in Authorization header"
202
+ )
203
+ self.mock_auth_manager.authenticate_jwt_token.return_value = failed_jwt_result
204
+
205
+ # Certificate authentication is not implemented, so it will fail
206
+ # We expect the middleware to return an error response
207
+
208
+ # Create mock call_next
209
+ mock_call_next = AsyncMock()
210
+ mock_response = Mock(spec=Response)
211
+ mock_call_next.return_value = mock_response
212
+
213
+ # Process request
214
+ response = await self.middleware(mock_request, mock_call_next)
215
+
216
+ # Assertions - certificate auth should fail
217
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
218
+ response_data = json.loads(response.body.decode())
219
+ assert response_data["error"] == "Authentication failed"
220
+ assert response_data["error_code"] == -32033 # All authentication methods failed
221
+
222
+ # call_next should not be called for failed authentication
223
+ mock_call_next.assert_not_called()
224
+
225
+ @pytest.mark.asyncio
226
+ async def test_fastapi_auth_middleware_authentication_failure(self):
227
+ """Test authentication failure handling."""
228
+ # Create mock request without authentication
229
+ mock_request = Mock(spec=Request)
230
+ mock_request.url.path = "/api/v1/users/me"
231
+ mock_request.headers = {}
232
+
233
+ # Mock failed authentication - this test expects all methods to fail
234
+ failed_auth_result = AuthResult(
235
+ is_valid=False,
236
+ status=AuthStatus.INVALID,
237
+ auth_method=AuthMethod.UNKNOWN,
238
+ error_code=-32001,
239
+ error_message="No authentication credentials provided"
240
+ )
241
+ self.mock_auth_manager.authenticate_api_key.return_value = failed_auth_result
242
+
243
+ # Create mock call_next
244
+ mock_call_next = AsyncMock()
245
+
246
+ # Process request
247
+ response = await self.middleware(mock_request, mock_call_next)
248
+
249
+ # Assertions
250
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
251
+ response_data = json.loads(response.body.decode())
252
+ assert response_data["error"] == "Authentication failed"
253
+ assert response_data["error_code"] == -32033 # All authentication methods failed
254
+ assert response_data["error_message"] == "All authentication methods failed"
255
+
256
+ # call_next should not be called for failed authentication
257
+ mock_call_next.assert_not_called()
258
+
259
+ @pytest.mark.asyncio
260
+ async def test_fastapi_auth_middleware_invalid_api_key(self):
261
+ """Test handling of invalid API key."""
262
+ # Create mock request with invalid API key
263
+ mock_request = Mock(spec=Request)
264
+ mock_request.url.path = "/api/v1/users/me"
265
+ mock_request.headers = {"X-API-Key": "invalid_key"}
266
+
267
+ # Mock failed authentication
268
+ auth_result = AuthResult(
269
+ is_valid=False,
270
+ status=AuthStatus.INVALID,
271
+ auth_method=AuthMethod.API_KEY,
272
+ error_code=-32002,
273
+ error_message="Invalid API key"
274
+ )
275
+ self.mock_auth_manager.authenticate_api_key.return_value = auth_result
276
+
277
+ # Create mock call_next
278
+ mock_call_next = AsyncMock()
279
+
280
+ # Process request
281
+ response = await self.middleware(mock_request, mock_call_next)
282
+
283
+ # Assertions
284
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
285
+ response_data = json.loads(response.body.decode())
286
+ assert response_data["error"] == "Authentication failed"
287
+ assert response_data["error_code"] == -32033 # All authentication methods failed
288
+
289
+ @pytest.mark.asyncio
290
+ async def test_fastapi_auth_middleware_invalid_jwt_token(self):
291
+ """Test handling of invalid JWT token."""
292
+ # Create mock request with invalid JWT token
293
+ mock_request = Mock(spec=Request)
294
+ mock_request.url.path = "/api/v1/users/me"
295
+ mock_request.headers = {"Authorization": "Bearer invalid_token"}
296
+
297
+ # Mock failed API key authentication first
298
+ failed_api_key_result = AuthResult(
299
+ is_valid=False,
300
+ status=AuthStatus.FAILED,
301
+ username=None,
302
+ roles=[],
303
+ auth_method=AuthMethod.API_KEY,
304
+ error_code=-32012,
305
+ error_message="API key not found in request"
306
+ )
307
+ self.mock_auth_manager.authenticate_api_key.return_value = failed_api_key_result
308
+
309
+ # Mock failed JWT authentication
310
+ auth_result = AuthResult(
311
+ is_valid=False,
312
+ status=AuthStatus.INVALID,
313
+ auth_method=AuthMethod.JWT,
314
+ error_code=-32003,
315
+ error_message="Invalid JWT token"
316
+ )
317
+ self.mock_auth_manager.authenticate_jwt_token.return_value = auth_result
318
+
319
+ # Create mock call_next
320
+ mock_call_next = AsyncMock()
321
+
322
+ # Process request
323
+ response = await self.middleware(mock_request, mock_call_next)
324
+
325
+ # Assertions
326
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
327
+ response_data = json.loads(response.body.decode())
328
+ assert response_data["error"] == "Authentication failed"
329
+ assert response_data["error_code"] == -32033 # All authentication methods failed
330
+
331
+ @pytest.mark.asyncio
332
+ async def test_fastapi_auth_middleware_exception_handling(self):
333
+ """Test exception handling in middleware."""
334
+ # Create mock request
335
+ mock_request = Mock(spec=Request)
336
+ mock_request.url.path = "/api/v1/users/me"
337
+ mock_request.headers = {"X-API-Key": "test_key_123"}
338
+
339
+ # Mock auth manager to raise exception
340
+ self.mock_auth_manager.authenticate_api_key.side_effect = Exception("Authentication error")
341
+
342
+ # Create mock call_next
343
+ mock_call_next = AsyncMock()
344
+
345
+ # Process request
346
+ response = await self.middleware(mock_request, mock_call_next)
347
+
348
+ # Assertions
349
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
350
+ response_data = json.loads(response.body.decode())
351
+ assert response_data["error"] == "Authentication failed"
352
+ assert response_data["error_code"] == -32033 # All authentication methods failed
353
+
354
+ @pytest.mark.asyncio
355
+ async def test_fastapi_auth_middleware_user_info_structure(self):
356
+ """Test that user info is properly structured in request state."""
357
+ # Create mock request with API key
358
+ mock_request = Mock(spec=Request)
359
+ mock_request.url.path = "/api/v1/users/me"
360
+ mock_request.headers = {"X-API-Key": "test_key_123"}
361
+
362
+ # Mock successful authentication
363
+ auth_result = AuthResult(
364
+ is_valid=True,
365
+ status=AuthStatus.SUCCESS,
366
+ username="testuser",
367
+ roles=["user", "admin"],
368
+ permissions=["read:own", "write:own"],
369
+ auth_method=AuthMethod.API_KEY
370
+ )
371
+ self.mock_auth_manager.authenticate_api_key.return_value = auth_result
372
+
373
+ # Create mock call_next
374
+ mock_call_next = AsyncMock()
375
+ mock_response = Mock(spec=Response)
376
+ mock_call_next.return_value = mock_response
377
+
378
+ # Process request
379
+ await self.middleware(mock_request, mock_call_next)
380
+
381
+ # Check user info structure
382
+ assert hasattr(mock_request.state, 'auth_result')
383
+ assert mock_request.state.auth_result.username == "testuser"
384
+ assert mock_request.state.auth_result.roles == ["user", "admin"]
385
+ assert mock_request.state.auth_result.permissions == {"read:own", "write:own"}
386
+ assert mock_request.state.auth_result.auth_method == AuthMethod.API_KEY
387
+
388
+ @pytest.mark.asyncio
389
+ async def test_fastapi_auth_middleware_multiple_public_paths(self):
390
+ """Test that multiple public paths are handled correctly."""
391
+ public_paths = ["/health", "/metrics", "/docs", "/openapi.json"]
392
+
393
+ for path in public_paths:
394
+ # Create mock request for public path
395
+ mock_request = Mock(spec=Request)
396
+ mock_request.url.path = path
397
+ mock_request.headers = {}
398
+
399
+ # Create mock call_next
400
+ mock_call_next = AsyncMock()
401
+ mock_response = Mock(spec=Response)
402
+ mock_call_next.return_value = mock_response
403
+
404
+ # Process request
405
+ response = await self.middleware(mock_request, mock_call_next)
406
+
407
+ # Assertions
408
+ assert response == mock_response
409
+ mock_call_next.assert_called_once_with(mock_request)
410
+
411
+ # Reset mock for next iteration
412
+ mock_call_next.reset_mock()
413
+
414
+ @pytest.mark.asyncio
415
+ async def test_fastapi_auth_middleware_disabled_authentication(self):
416
+ """Test middleware behavior when authentication is disabled."""
417
+ # Create config with disabled authentication
418
+ disabled_config = SecurityConfig(
419
+ auth=AuthConfig(
420
+ enabled=False,
421
+ methods=["api_key"],
422
+ api_keys={},
423
+ public_paths=[]
424
+ )
425
+ )
426
+
427
+ # Create middleware with disabled auth
428
+ from mcp_security_framework.core.security_manager import SecurityManager
429
+ disabled_security_manager = Mock(spec=SecurityManager)
430
+ disabled_security_manager.config = disabled_config
431
+ disabled_auth_manager = Mock()
432
+ disabled_security_manager.auth_manager = disabled_auth_manager
433
+ disabled_middleware = FastAPIAuthMiddleware(disabled_security_manager)
434
+
435
+ # Create mock request
436
+ mock_request = Mock(spec=Request)
437
+ mock_request.url.path = "/api/v1/users/me"
438
+ mock_request.headers = {}
439
+
440
+ # Create mock call_next
441
+ mock_call_next = AsyncMock()
442
+ mock_response = Mock(spec=Response)
443
+ mock_call_next.return_value = mock_response
444
+
445
+ # Process request
446
+ response = await disabled_middleware(mock_request, mock_call_next)
447
+
448
+ # Assertions - should bypass authentication when disabled
449
+ assert response == mock_response
450
+ mock_call_next.assert_called_once_with(mock_request)
451
+ self.mock_security_manager.authenticate_user.assert_not_called()
452
+
453
+ @pytest.mark.asyncio
454
+ async def test_fastapi_auth_middleware_logging(self):
455
+ """Test that authentication events are logged."""
456
+ with patch.object(self.middleware.logger, 'info') as mock_logger:
457
+ # Create mock request with API key
458
+ mock_request = Mock(spec=Request)
459
+ mock_request.url.path = "/api/v1/users/me"
460
+ mock_request.headers = {"X-API-Key": "test_key_123"}
461
+
462
+ # Mock successful authentication
463
+ auth_result = AuthResult(
464
+ is_valid=True,
465
+ status=AuthStatus.SUCCESS,
466
+ username="testuser",
467
+ roles=["user"],
468
+ auth_method=AuthMethod.API_KEY
469
+ )
470
+ self.mock_auth_manager.authenticate_api_key.return_value = auth_result
471
+
472
+ # Create mock call_next
473
+ mock_call_next = AsyncMock()
474
+ mock_response = Mock(spec=Response)
475
+ mock_call_next.return_value = mock_response
476
+
477
+ # Process request
478
+ await self.middleware(mock_request, mock_call_next)
479
+
480
+ # Assertions - should log authentication success
481
+ mock_logger.assert_called()
482
+
483
+ @pytest.mark.asyncio
484
+ async def test_fastapi_auth_middleware_error_logging(self):
485
+ """Test that authentication errors are logged."""
486
+ with patch.object(self.middleware.logger, 'error') as mock_logger:
487
+ # Create mock request
488
+ mock_request = Mock(spec=Request)
489
+ mock_request.url.path = "/api/v1/users/me"
490
+ mock_request.headers = {"X-API-Key": "test_key_123"}
491
+
492
+ # Mock auth manager to raise exception
493
+ self.mock_auth_manager.authenticate_api_key.side_effect = Exception("Authentication error")
494
+
495
+ # Create mock call_next
496
+ mock_call_next = AsyncMock()
497
+
498
+ # Process request
499
+ await self.middleware(mock_request, mock_call_next)
500
+
501
+ # Assertions - should log authentication error
502
+ mock_logger.assert_called()
503
+
504
+ def test_fastapi_auth_middleware_inheritance(self):
505
+ """Test that FastAPIAuthMiddleware properly inherits from AuthMiddleware."""
506
+ from mcp_security_framework.middleware.auth_middleware import AuthMiddleware
507
+
508
+ assert issubclass(FastAPIAuthMiddleware, AuthMiddleware)
509
+ assert isinstance(self.middleware, AuthMiddleware)
510
+
511
+ def test_fastapi_auth_middleware_config_validation(self):
512
+ """Test that middleware validates configuration properly."""
513
+ # Test with valid config
514
+ assert self.middleware.config is not None
515
+ assert self.middleware.config.auth.enabled is True
516
+ assert "api_key" in self.middleware.config.auth.methods
517
+
518
+ # Test with invalid config (should not raise during init)
519
+ try:
520
+ invalid_config = SecurityConfig(
521
+ auth=AuthConfig(
522
+ enabled=True,
523
+ methods=["invalid_method"],
524
+ api_keys={},
525
+ public_paths=[]
526
+ )
527
+ )
528
+ # If we get here, the config was accepted (which is wrong)
529
+ assert False, "Invalid config should have raised validation error"
530
+ except Exception as e:
531
+ # This is expected - invalid config should raise validation error
532
+ assert "invalid_method" in str(e)
533
+ # Don't try to create middleware with invalid config
534
+ # The middleware should be valid since we're testing the existing one
535
+ assert self.middleware is not None
536
+
537
+ @pytest.mark.asyncio
538
+ async def test_fastapi_auth_middleware_exception_in_call(self):
539
+ """Test exception handling in __call__ method."""
540
+ # Create mock request
541
+ mock_request = Mock(spec=Request)
542
+ mock_request.url.path = "/api/v1/users/me"
543
+ mock_request.headers = {"X-API-Key": "test_key_123"}
544
+
545
+ # Mock call_next to raise exception
546
+ mock_call_next = AsyncMock()
547
+ mock_call_next.side_effect = Exception("Test exception")
548
+
549
+ # Process request - should handle exception gracefully
550
+ with pytest.raises(Exception) as exc_info:
551
+ await self.middleware(mock_request, mock_call_next)
552
+
553
+ assert "Authentication processing failed" in str(exc_info.value)
554
+ assert hasattr(exc_info.value, 'error_code')
555
+ assert exc_info.value.error_code == -32035
556
+
557
+ def test_fastapi_auth_middleware_get_client_ip_x_forwarded_for(self):
558
+ """Test getting client IP from X-Forwarded-For header."""
559
+ mock_request = Mock(spec=Request)
560
+ mock_request.headers = {"X-Forwarded-For": "192.168.1.1, 10.0.0.1"}
561
+
562
+ ip = self.middleware._get_client_ip(mock_request)
563
+ assert ip == "192.168.1.1"
564
+
565
+ def test_fastapi_auth_middleware_get_client_ip_x_real_ip(self):
566
+ """Test getting client IP from X-Real-IP header."""
567
+ mock_request = Mock(spec=Request)
568
+ mock_request.headers = {"X-Real-IP": "192.168.1.2"}
569
+
570
+ ip = self.middleware._get_client_ip(mock_request)
571
+ assert ip == "192.168.1.2"
572
+
573
+ def test_fastapi_auth_middleware_get_client_ip_client_host(self):
574
+ """Test getting client IP from client.host."""
575
+ mock_request = Mock(spec=Request)
576
+ mock_request.headers = {}
577
+ mock_request.client = Mock()
578
+ mock_request.client.host = "192.168.1.3"
579
+
580
+ ip = self.middleware._get_client_ip(mock_request)
581
+ assert ip == "192.168.1.3"
582
+
583
+ def test_fastapi_auth_middleware_get_client_ip_no_client(self):
584
+ """Test getting client IP when client is None."""
585
+ mock_request = Mock(spec=Request)
586
+ mock_request.headers = {}
587
+ mock_request.client = None
588
+
589
+ ip = self.middleware._get_client_ip(mock_request)
590
+ assert ip == "127.0.0.1" # Default fallback from constants
591
+
592
+ def test_fastapi_auth_middleware_get_client_ip_with_default_ip_config(self):
593
+ """Test getting client IP with default_ip in config."""
594
+ mock_request = Mock(spec=Request)
595
+ mock_request.headers = {}
596
+ mock_request.client = None
597
+
598
+ # Mock the config to have default_client_ip attribute
599
+ self.middleware.config = Mock()
600
+ self.middleware.config.default_client_ip = "192.168.0.1"
601
+
602
+ ip = self.middleware._get_client_ip(mock_request)
603
+ assert ip == "192.168.0.1"
604
+
605
+ def test_fastapi_auth_middleware_get_cache_key(self):
606
+ """Test cache key generation."""
607
+ mock_request = Mock(spec=Request)
608
+ mock_request.headers = {"User-Agent": "test-browser/1.0"}
609
+ mock_request.client = Mock()
610
+ mock_request.client.host = "192.168.1.1"
611
+
612
+ cache_key = self.middleware._get_cache_key(mock_request)
613
+ assert cache_key.startswith("auth:192.168.1.1:")
614
+ assert isinstance(cache_key, str)
615
+ assert len(cache_key) > 0
616
+
617
+ def test_fastapi_auth_middleware_unsupported_auth_method(self):
618
+ """Test handling of unsupported authentication method."""
619
+ mock_request = Mock(spec=Request)
620
+
621
+ result = self.middleware._try_auth_method(mock_request, "unsupported_method")
622
+
623
+ assert result.is_valid is False
624
+ assert result.error_code == -32022
625
+ assert "Unsupported authentication method" in result.error_message
626
+
627
+ def test_fastapi_auth_middleware_auth_method_exception(self):
628
+ """Test exception handling in _try_auth_method."""
629
+ mock_request = Mock(spec=Request)
630
+
631
+ # Mock authenticate_api_key to raise exception
632
+ self.mock_auth_manager.authenticate_api_key.side_effect = Exception("Test auth error")
633
+
634
+ result = self.middleware._try_auth_method(mock_request, "api_key")
635
+
636
+ assert result.is_valid is False
637
+ assert result.error_code == -32023
638
+ assert "Authentication method api_key failed" in result.error_message
639
+
640
+ def test_fastapi_auth_middleware_api_key_from_authorization_header(self):
641
+ """Test API key authentication using Authorization header."""
642
+ mock_request = Mock(spec=Request)
643
+ mock_request.headers = {"Authorization": "Bearer api_key_123"}
644
+
645
+ # Mock successful authentication
646
+ auth_result = AuthResult(
647
+ is_valid=True,
648
+ status=AuthStatus.SUCCESS,
649
+ username="testuser",
650
+ roles=["user"],
651
+ auth_method=AuthMethod.API_KEY
652
+ )
653
+ self.mock_auth_manager.authenticate_api_key.return_value = auth_result
654
+
655
+ result = self.middleware._try_api_key_auth(mock_request)
656
+
657
+ assert result.is_valid is True
658
+ self.mock_auth_manager.authenticate_api_key.assert_called_once_with("api_key_123")
659
+
660
+ def test_fastapi_auth_middleware_api_key_no_key(self):
661
+ """Test API key authentication when no key is provided."""
662
+ mock_request = Mock(spec=Request)
663
+ mock_request.headers = {}
664
+
665
+ result = self.middleware._try_api_key_auth(mock_request)
666
+
667
+ assert result.is_valid is False
668
+ assert result.error_code == -32012
669
+ assert "API key not found in request" in result.error_message
670
+
671
+ def test_fastapi_auth_middleware_jwt_no_token(self):
672
+ """Test JWT authentication when no token is provided."""
673
+ mock_request = Mock(spec=Request)
674
+ mock_request.headers = {}
675
+
676
+ result = self.middleware._try_jwt_auth(mock_request)
677
+
678
+ assert result.is_valid is False
679
+ assert result.error_code == -32013
680
+ assert "JWT token not found in Authorization header" in result.error_message
681
+
682
+ def test_fastapi_auth_middleware_jwt_wrong_format(self):
683
+ """Test JWT authentication with wrong Authorization header format."""
684
+ mock_request = Mock(spec=Request)
685
+ mock_request.headers = {"Authorization": "Basic dGVzdDp0ZXN0"}
686
+
687
+ result = self.middleware._try_jwt_auth(mock_request)
688
+
689
+ assert result.is_valid is False
690
+ assert result.error_code == -32013
691
+ assert "JWT token not found in Authorization header" in result.error_message
692
+
693
+ def test_fastapi_auth_middleware_basic_auth_not_implemented(self):
694
+ """Test basic authentication (not implemented)."""
695
+ mock_request = Mock(spec=Request)
696
+ mock_request.headers = {"Authorization": "Basic dGVzdDp0ZXN0"}
697
+
698
+ result = self.middleware._try_basic_auth(mock_request)
699
+
700
+ assert result.is_valid is False
701
+ assert result.error_code == -32016
702
+ assert "Basic authentication not implemented" in result.error_message
703
+
704
+ def test_fastapi_auth_middleware_basic_auth_no_credentials(self):
705
+ """Test basic authentication when no credentials are provided."""
706
+ mock_request = Mock(spec=Request)
707
+ mock_request.headers = {}
708
+
709
+ result = self.middleware._try_basic_auth(mock_request)
710
+
711
+ assert result.is_valid is False
712
+ assert result.error_code == -32015
713
+ assert "Basic authentication credentials not found" in result.error_message
714
+
715
+ def test_fastapi_auth_middleware_basic_auth_wrong_format(self):
716
+ """Test basic authentication with wrong Authorization header format."""
717
+ mock_request = Mock(spec=Request)
718
+ mock_request.headers = {"Authorization": "Bearer token123"}
719
+
720
+ result = self.middleware._try_basic_auth(mock_request)
721
+
722
+ assert result.is_valid is False
723
+ assert result.error_code == -32015
724
+ assert "Basic authentication credentials not found" in result.error_message