mcp-security-framework 0.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 (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,582 @@
1
+ """
2
+ Flask Security Middleware Tests
3
+
4
+ This module provides comprehensive unit tests for the FlaskSecurityMiddleware
5
+ class and its Flask-specific functionality.
6
+
7
+ Test Coverage:
8
+ - FlaskSecurityMiddleware initialization
9
+ - Flask request processing
10
+ - Flask authentication methods
11
+ - Flask response creation
12
+ - Flask error handling
13
+ - Flask header management
14
+ - Flask rate limiting integration
15
+ - Flask-specific request/response handling
16
+
17
+ Author: MCP Security Team
18
+ Version: 1.0.0
19
+ License: MIT
20
+ """
21
+
22
+ import pytest
23
+ import json
24
+ from unittest.mock import Mock, patch, MagicMock
25
+ from typing import Dict, List, Any
26
+
27
+ from flask import Request, Response
28
+
29
+ from mcp_security_framework.middleware.flask_middleware import (
30
+ FlaskSecurityMiddleware,
31
+ FlaskMiddlewareError
32
+ )
33
+ from mcp_security_framework.core.security_manager import SecurityManager
34
+ from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, RateLimitConfig
35
+ from mcp_security_framework.schemas.models import AuthResult, ValidationResult, ValidationStatus, AuthStatus, AuthMethod
36
+
37
+
38
+ class TestFlaskSecurityMiddleware:
39
+ """Test suite for FlaskSecurityMiddleware class."""
40
+
41
+ def setup_method(self):
42
+ """Set up test fixtures before each test method."""
43
+ # Create mock security manager
44
+ self.mock_security_manager = Mock(spec=SecurityManager)
45
+ self.mock_security_manager.config = SecurityConfig(
46
+ auth=AuthConfig(
47
+ enabled=True,
48
+ methods=["api_key", "jwt"],
49
+ public_paths=["/health", "/docs"],
50
+ jwt_secret="test_jwt_secret_key"
51
+ ),
52
+ rate_limit=RateLimitConfig(
53
+ enabled=True,
54
+ default_requests_per_minute=100,
55
+ window_size_seconds=60
56
+ )
57
+ )
58
+
59
+ # Create mock auth manager
60
+ self.mock_auth_manager = Mock()
61
+ self.mock_security_manager.auth_manager = self.mock_auth_manager
62
+
63
+ # Setup rate_limiter mock
64
+ self.mock_security_manager.rate_limiter = Mock()
65
+
66
+ # Create middleware instance
67
+ self.middleware = FlaskSecurityMiddleware(self.mock_security_manager)
68
+
69
+ def create_mock_request(self, path: str = "/api/test", headers: Dict[str, str] = None) -> Mock:
70
+ """Create a mock Flask request for testing."""
71
+ mock_request = Mock(spec=Request)
72
+ mock_request.path = path
73
+ mock_request.method = "GET"
74
+ mock_request.headers = headers or {}
75
+ mock_request.remote_addr = "127.0.0.1"
76
+ return mock_request
77
+
78
+ def create_mock_environ(self, path: str = "/api/test", headers: Dict[str, str] = None) -> Dict[str, Any]:
79
+ """Create a mock WSGI environ for testing."""
80
+ environ = {
81
+ 'REQUEST_METHOD': 'GET',
82
+ 'PATH_INFO': path,
83
+ 'QUERY_STRING': '',
84
+ 'SERVER_NAME': 'localhost',
85
+ 'SERVER_PORT': '5000',
86
+ 'HTTP_HOST': 'localhost:5000',
87
+ 'wsgi.url_scheme': 'http',
88
+ 'wsgi.input': Mock(),
89
+ 'wsgi.errors': Mock(),
90
+ 'wsgi.version': (1, 0),
91
+ 'wsgi.run_once': False,
92
+ 'wsgi.multithread': False,
93
+ 'wsgi.multiprocess': False,
94
+ }
95
+
96
+ # Add headers to environ
97
+ if headers:
98
+ for key, value in headers.items():
99
+ environ[f'HTTP_{key.upper().replace("-", "_")}'] = value
100
+
101
+ return environ
102
+
103
+ def test_initialization_success(self):
104
+ """Test successful Flask middleware initialization."""
105
+ assert isinstance(self.middleware, FlaskSecurityMiddleware)
106
+ assert self.middleware.security_manager == self.mock_security_manager
107
+ assert self.middleware.config == self.mock_security_manager.config
108
+
109
+ def test_call_success(self):
110
+ """Test successful middleware call."""
111
+ environ = self.create_mock_environ()
112
+ mock_start_response = Mock()
113
+
114
+ # Mock successful authentication and authorization
115
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
116
+
117
+ # Mock successful authentication
118
+ auth_result = AuthResult(
119
+ is_valid=True,
120
+ status=AuthStatus.SUCCESS,
121
+ username="test_user",
122
+ roles=["user"],
123
+ auth_method=AuthMethod.API_KEY
124
+ )
125
+ self.middleware._authenticate_request = Mock(return_value=auth_result)
126
+
127
+ self.middleware._validate_permissions = Mock(return_value=True)
128
+
129
+ # Mock successful response
130
+ mock_response = [b'{"message": "success"}']
131
+ self.middleware._process_request = Mock(return_value=mock_response)
132
+
133
+ result = self.middleware(environ, mock_start_response)
134
+
135
+ # Verify middleware processed successfully
136
+ assert result == mock_response
137
+ self.middleware._process_request.assert_called_once()
138
+
139
+ def test_call_rate_limit_exceeded(self):
140
+ """Test middleware call with rate limit exceeded."""
141
+ environ = self.create_mock_environ()
142
+ mock_start_response = Mock()
143
+
144
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = False
145
+
146
+ result = self.middleware(environ, mock_start_response)
147
+
148
+ # Verify rate limit response
149
+ assert isinstance(result, list)
150
+ assert len(result) == 1
151
+ response_data = json.loads(result[0].decode('utf-8'))
152
+ assert response_data["error"] == "Rate limit exceeded"
153
+
154
+ # Verify start_response was called with correct status
155
+ mock_start_response.assert_called_once()
156
+ call_args = mock_start_response.call_args[0]
157
+ assert call_args[0] == '429 Too Many Requests'
158
+
159
+ def test_call_public_path(self):
160
+ """Test middleware call with public path."""
161
+ environ = self.create_mock_environ(path="/health")
162
+ mock_start_response = Mock()
163
+
164
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
165
+
166
+ # Mock successful response for public path
167
+ mock_response = [b'{"status": "healthy"}']
168
+ self.middleware._process_request = Mock(return_value=mock_response)
169
+
170
+ result = self.middleware(environ, mock_start_response)
171
+
172
+ # Verify middleware processed successfully for public path
173
+ assert result == mock_response
174
+ self.middleware._process_request.assert_called_once()
175
+
176
+ def test_call_authentication_failed(self):
177
+ """Test middleware call with authentication failure."""
178
+ environ = self.create_mock_environ()
179
+ mock_start_response = Mock()
180
+
181
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
182
+
183
+ # Mock failed authentication
184
+ auth_result = AuthResult(
185
+ is_valid=False,
186
+ status=AuthStatus.FAILED,
187
+ username=None,
188
+ roles=[],
189
+ auth_method=AuthMethod.API_KEY,
190
+ error_code=-32005,
191
+ error_message="Authentication failed"
192
+ )
193
+ self.middleware._authenticate_request = Mock(return_value=auth_result)
194
+
195
+ result = self.middleware(environ, mock_start_response)
196
+
197
+ # Verify authentication error response
198
+ assert isinstance(result, list)
199
+ assert len(result) == 1
200
+ response_data = json.loads(result[0].decode('utf-8'))
201
+ assert response_data["error"] == "Authentication failed"
202
+
203
+ # Verify start_response was called with correct status
204
+ mock_start_response.assert_called_once()
205
+ call_args = mock_start_response.call_args[0]
206
+ assert call_args[0] == '401 Unauthorized'
207
+
208
+ def test_call_permission_denied(self):
209
+ """Test middleware call with permission denied."""
210
+ environ = self.create_mock_environ()
211
+ mock_start_response = Mock()
212
+
213
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
214
+
215
+ # Mock successful authentication
216
+ auth_result = AuthResult(
217
+ is_valid=True,
218
+ status=AuthStatus.SUCCESS,
219
+ username="test_user",
220
+ roles=["user"],
221
+ auth_method=AuthMethod.API_KEY
222
+ )
223
+ self.middleware._authenticate_request = Mock(return_value=auth_result)
224
+
225
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
226
+ is_valid=False,
227
+ status=ValidationStatus.INVALID,
228
+ error_code=-32007,
229
+ error_message="Permission denied"
230
+ )
231
+
232
+ # Mock permission error response
233
+ mock_response = [b'{"error": "Permission denied"}']
234
+ self.middleware._process_request = Mock(return_value=mock_response)
235
+
236
+ result = self.middleware(environ, mock_start_response)
237
+
238
+ # Verify permission error response
239
+ assert result == mock_response
240
+ self.middleware._process_request.assert_called_once()
241
+
242
+ def test_get_rate_limit_identifier(self):
243
+ """Test getting rate limit identifier from request."""
244
+ mock_request = self.create_mock_request()
245
+ result = self.middleware._get_rate_limit_identifier(mock_request)
246
+ assert result == "127.0.0.1"
247
+
248
+ def test_get_rate_limit_identifier_with_forwarded_for(self):
249
+ """Test getting rate limit identifier with X-Forwarded-For header."""
250
+ headers = {"X-Forwarded-For": "192.168.1.1, 10.0.0.1"}
251
+ mock_request = self.create_mock_request(headers=headers)
252
+ result = self.middleware._get_rate_limit_identifier(mock_request)
253
+ assert result == "192.168.1.1"
254
+
255
+ def test_get_rate_limit_identifier_with_real_ip(self):
256
+ """Test getting rate limit identifier with X-Real-IP header."""
257
+ headers = {"X-Real-IP": "192.168.1.100"}
258
+ mock_request = self.create_mock_request(headers=headers)
259
+ result = self.middleware._get_rate_limit_identifier(mock_request)
260
+ assert result == "192.168.1.100"
261
+
262
+ def test_get_request_path(self):
263
+ """Test getting request path from Flask request."""
264
+ mock_request = self.create_mock_request(path="/api/users")
265
+ result = self.middleware._get_request_path(mock_request)
266
+ assert result == "/api/users"
267
+
268
+ def test_get_required_permissions_from_request(self):
269
+ """Test getting required permissions from request."""
270
+ mock_request = self.create_mock_request()
271
+ mock_request.required_permissions = ["read", "write"]
272
+
273
+ result = self.middleware._get_required_permissions(mock_request)
274
+ assert result == ["read", "write"]
275
+
276
+ def test_get_required_permissions_default(self):
277
+ """Test getting required permissions when not set."""
278
+ mock_request = self.create_mock_request()
279
+ # Create a clean endpoint mock without any attributes
280
+ mock_endpoint = Mock()
281
+ mock_endpoint.required_permissions = None
282
+ mock_endpoint.required_roles = None
283
+ mock_endpoint.__permissions__ = None
284
+ mock_request.endpoint = mock_endpoint
285
+ result = self.middleware._get_required_permissions(mock_request)
286
+ assert result == []
287
+
288
+ def test_try_api_key_auth_success(self):
289
+ """Test successful API key authentication."""
290
+ headers = {"X-API-Key": "valid_key"}
291
+ mock_request = self.create_mock_request(headers=headers)
292
+
293
+ expected_result = AuthResult(
294
+ is_valid=True,
295
+ status=AuthStatus.SUCCESS,
296
+ username="test_user",
297
+ roles=["user"],
298
+ auth_method=AuthMethod.API_KEY
299
+ )
300
+ self.mock_auth_manager.authenticate_api_key.return_value = expected_result
301
+
302
+ result = self.middleware._try_api_key_auth(mock_request)
303
+
304
+ assert result == expected_result
305
+ self.mock_auth_manager.authenticate_api_key.assert_called_once_with("valid_key")
306
+
307
+ def test_try_api_key_auth_from_authorization_header(self):
308
+ """Test API key authentication from Authorization header."""
309
+ headers = {"Authorization": "Bearer api_key_123"}
310
+ mock_request = self.create_mock_request(headers=headers)
311
+
312
+ expected_result = AuthResult(
313
+ is_valid=True,
314
+ status=AuthStatus.SUCCESS,
315
+ username="test_user",
316
+ roles=["user"],
317
+ auth_method=AuthMethod.API_KEY
318
+ )
319
+ self.mock_auth_manager.authenticate_api_key.return_value = expected_result
320
+
321
+ result = self.middleware._try_api_key_auth(mock_request)
322
+
323
+ assert result == expected_result
324
+ self.mock_auth_manager.authenticate_api_key.assert_called_once_with("api_key_123")
325
+
326
+ def test_try_api_key_auth_no_key(self):
327
+ """Test API key authentication with no key provided."""
328
+ mock_request = self.create_mock_request()
329
+
330
+ result = self.middleware._try_api_key_auth(mock_request)
331
+
332
+ assert result.is_valid is False
333
+ assert result.error_code == -32024
334
+ assert "API key not found" in result.error_message
335
+
336
+ def test_try_jwt_auth_success(self):
337
+ """Test successful JWT authentication."""
338
+ headers = {"Authorization": "Bearer jwt_token_123"}
339
+ mock_request = self.create_mock_request(headers=headers)
340
+
341
+ expected_result = AuthResult(
342
+ is_valid=True,
343
+ status=AuthStatus.SUCCESS,
344
+ username="test_user",
345
+ roles=["user"],
346
+ auth_method=AuthMethod.JWT
347
+ )
348
+ self.mock_auth_manager.authenticate_jwt_token.return_value = expected_result
349
+
350
+ result = self.middleware._try_jwt_auth(mock_request)
351
+
352
+ assert result == expected_result
353
+ self.mock_auth_manager.authenticate_jwt_token.assert_called_once_with("jwt_token_123")
354
+
355
+ def test_try_jwt_auth_no_token(self):
356
+ """Test JWT authentication with no token provided."""
357
+ mock_request = self.create_mock_request()
358
+
359
+ result = self.middleware._try_jwt_auth(mock_request)
360
+
361
+ assert result.is_valid is False
362
+ assert result.error_code == -32025
363
+ assert "JWT token not found" in result.error_message
364
+
365
+ def test_try_jwt_auth_invalid_header(self):
366
+ """Test JWT authentication with invalid Authorization header."""
367
+ headers = {"Authorization": "Invalid jwt_token_123"}
368
+ mock_request = self.create_mock_request(headers=headers)
369
+
370
+ result = self.middleware._try_jwt_auth(mock_request)
371
+
372
+ assert result.is_valid is False
373
+ assert result.error_code == -32025
374
+ assert "JWT token not found" in result.error_message
375
+
376
+ def test_try_certificate_auth_not_implemented(self):
377
+ """Test certificate authentication (not implemented)."""
378
+ mock_request = self.create_mock_request()
379
+
380
+ result = self.middleware._try_certificate_auth(mock_request)
381
+
382
+ assert result.is_valid is False
383
+ assert result.error_code == -32026
384
+ assert "not implemented" in result.error_message
385
+
386
+ def test_try_basic_auth_not_implemented(self):
387
+ """Test basic authentication (not implemented)."""
388
+ mock_request = self.create_mock_request()
389
+
390
+ result = self.middleware._try_basic_auth(mock_request)
391
+
392
+ assert result.is_valid is False
393
+ assert result.error_code == -32027
394
+ assert "Basic authentication credentials not found" in result.error_message
395
+
396
+ def test_try_auth_method_unsupported(self):
397
+ """Test authentication with unsupported method."""
398
+ mock_request = self.create_mock_request()
399
+
400
+ result = self.middleware._try_auth_method(mock_request, "unsupported_method")
401
+
402
+ assert result.is_valid is False
403
+ assert result.error_code == -32022
404
+ assert "Unsupported authentication method" in result.error_message
405
+
406
+ def test_apply_security_headers(self):
407
+ """Test applying security headers to Flask response."""
408
+ mock_response = Mock(spec=Response)
409
+ mock_response.headers = {}
410
+
411
+ headers = {
412
+ "X-Content-Type-Options": "nosniff",
413
+ "X-Frame-Options": "DENY",
414
+ "X-XSS-Protection": "1; mode=block"
415
+ }
416
+
417
+ self.middleware._apply_security_headers(mock_response, headers)
418
+
419
+ assert mock_response.headers["X-Content-Type-Options"] == "nosniff"
420
+ assert mock_response.headers["X-Frame-Options"] == "DENY"
421
+ assert mock_response.headers["X-XSS-Protection"] == "1; mode=block"
422
+
423
+ def test_create_error_response(self):
424
+ """Test creating error response."""
425
+ from flask import Flask
426
+ app = Flask(__name__)
427
+
428
+ with app.app_context():
429
+ result = self.middleware._create_error_response(400, "Bad request")
430
+
431
+ assert isinstance(result, Response)
432
+ assert result.status_code == 400
433
+ response_data = json.loads(result.get_data(as_text=True))
434
+ assert response_data["error"] == "Security violation"
435
+ assert response_data["message"] == "Bad request"
436
+
437
+ def test_rate_limit_response(self):
438
+ """Test creating rate limit response."""
439
+ mock_start_response = Mock()
440
+
441
+ result = self.middleware._rate_limit_response(mock_start_response)
442
+
443
+ assert isinstance(result, list)
444
+ assert len(result) == 1
445
+ response_data = json.loads(result[0].decode('utf-8'))
446
+ assert response_data["error"] == "Rate limit exceeded"
447
+
448
+ # Verify start_response was called
449
+ mock_start_response.assert_called_once()
450
+ call_args = mock_start_response.call_args[0]
451
+ assert call_args[0] == '429 Too Many Requests'
452
+
453
+ # Check for Retry-After header
454
+ headers = call_args[1]
455
+ retry_after_header = next((h for h in headers if h[0] == 'Retry-After'), None)
456
+ assert retry_after_header is not None
457
+ assert retry_after_header[1] == '60'
458
+
459
+ def test_auth_error_response(self):
460
+ """Test creating authentication error response."""
461
+ auth_result = AuthResult(
462
+ is_valid=False,
463
+ status=AuthStatus.FAILED,
464
+ username=None,
465
+ roles=[],
466
+ auth_method=AuthMethod.API_KEY,
467
+ error_code=-32005,
468
+ error_message="Invalid API key"
469
+ )
470
+
471
+ mock_start_response = Mock()
472
+ result = self.middleware._auth_error_response(auth_result, mock_start_response)
473
+
474
+ assert isinstance(result, list)
475
+ assert len(result) == 1
476
+ response_data = json.loads(result[0].decode('utf-8'))
477
+ assert response_data["error"] == "Authentication failed"
478
+ assert response_data["message"] == "Invalid API key"
479
+
480
+ # Verify start_response was called
481
+ mock_start_response.assert_called_once()
482
+ call_args = mock_start_response.call_args[0]
483
+ assert call_args[0] == '401 Unauthorized'
484
+
485
+ # Check for WWW-Authenticate header
486
+ headers = call_args[1]
487
+ www_auth_header = next((h for h in headers if h[0] == 'WWW-Authenticate'), None)
488
+ assert www_auth_header is not None
489
+ assert www_auth_header[1] == 'Bearer, ApiKey'
490
+
491
+ def test_permission_error_response(self):
492
+ """Test creating permission error response."""
493
+ mock_start_response = Mock()
494
+
495
+ result = self.middleware._permission_error_response(mock_start_response)
496
+
497
+ assert isinstance(result, list)
498
+ assert len(result) == 1
499
+ response_data = json.loads(result[0].decode('utf-8'))
500
+ assert response_data["error"] == "Permission denied"
501
+ assert response_data["message"] == "Insufficient permissions to access this resource"
502
+
503
+ # Verify start_response was called
504
+ mock_start_response.assert_called_once()
505
+ call_args = mock_start_response.call_args[0]
506
+ assert call_args[0] == '403 Forbidden'
507
+
508
+ def test_get_client_ip_from_forwarded_for(self):
509
+ """Test getting client IP from X-Forwarded-For header."""
510
+ headers = {"X-Forwarded-For": "192.168.1.1, 10.0.0.1"}
511
+ mock_request = self.create_mock_request(headers=headers)
512
+
513
+ result = self.middleware._get_client_ip(mock_request)
514
+ assert result == "192.168.1.1"
515
+
516
+ def test_get_client_ip_from_real_ip(self):
517
+ """Test getting client IP from X-Real-IP header."""
518
+ headers = {"X-Real-IP": "192.168.1.100"}
519
+ mock_request = self.create_mock_request(headers=headers)
520
+
521
+ result = self.middleware._get_client_ip(mock_request)
522
+ assert result == "192.168.1.100"
523
+
524
+ def test_get_client_ip_from_remote_addr(self):
525
+ """Test getting client IP from remote_addr."""
526
+ mock_request = self.create_mock_request()
527
+ mock_request.remote_addr = "192.168.1.50"
528
+
529
+ result = self.middleware._get_client_ip(mock_request)
530
+ assert result == "192.168.1.50"
531
+
532
+ def test_get_client_ip_fallback(self):
533
+ """Test getting client IP with fallback."""
534
+ mock_request = self.create_mock_request()
535
+ mock_request.remote_addr = None
536
+
537
+ result = self.middleware._get_client_ip(mock_request)
538
+ assert result == "127.0.0.1"
539
+
540
+ def test_get_security_headers(self):
541
+ """Test getting security headers."""
542
+ result = self.middleware._get_security_headers()
543
+
544
+ assert isinstance(result, list)
545
+ assert all(isinstance(header, tuple) and len(header) == 2 for header in result)
546
+
547
+ # Check for standard security headers
548
+ header_names = [header[0] for header in result]
549
+ assert 'X-Content-Type-Options' in header_names
550
+ assert 'X-Frame-Options' in header_names
551
+ assert 'X-XSS-Protection' in header_names
552
+ assert 'Strict-Transport-Security' in header_names
553
+ assert 'Content-Security-Policy' in header_names
554
+ assert 'Referrer-Policy' in header_names
555
+
556
+ def test_get_security_headers_with_custom_headers(self):
557
+ """Test getting security headers with custom headers from config."""
558
+ self.mock_security_manager.config.auth.security_headers = {
559
+ "Custom-Security-Header": "custom_value"
560
+ }
561
+
562
+ result = self.middleware._get_security_headers()
563
+
564
+ header_names = [header[0] for header in result]
565
+ assert 'Custom-Security-Header' in header_names
566
+
567
+ custom_header = next(h for h in result if h[0] == 'Custom-Security-Header')
568
+ assert custom_header[1] == 'custom_value'
569
+
570
+ def test_call_with_exception(self):
571
+ """Test middleware call with exception."""
572
+ environ = self.create_mock_environ()
573
+ mock_start_response = Mock()
574
+
575
+ # Mock an exception during processing
576
+ self.mock_security_manager.rate_limiter.check_rate_limit.side_effect = Exception("Test error")
577
+
578
+ with pytest.raises(FlaskMiddlewareError) as exc_info:
579
+ self.middleware(environ, mock_start_response)
580
+
581
+ assert "Middleware processing failed" in str(exc_info.value)
582
+ assert exc_info.value.error_code == -32003