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