mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.1__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.1.dist-info}/METADATA +2 -1
  34. mcp_security_framework-1.1.1.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.1.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
  58. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -28,70 +28,70 @@ Last Modified: 2024-01-20
28
28
 
29
29
  import json
30
30
  import logging
31
- from typing import Any, Dict, Optional, List
31
+ from typing import Any, Dict, List, Optional
32
32
 
33
33
  from fastapi import Request, Response, status
34
34
  from fastapi.responses import JSONResponse
35
35
  from starlette.middleware.base import BaseHTTPMiddleware
36
36
 
37
37
  from mcp_security_framework.middleware.auth_middleware import AuthMiddleware
38
- from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
39
38
  from mcp_security_framework.schemas.config import SecurityConfig
39
+ from mcp_security_framework.schemas.models import AuthMethod, AuthResult, AuthStatus
40
40
 
41
41
 
42
42
  class FastAPIAuthMiddleware(AuthMiddleware):
43
43
  """
44
44
  FastAPI Authentication Middleware Implementation
45
-
45
+
46
46
  This class provides FastAPI-specific authentication middleware that
47
47
  handles authentication-only processing for FastAPI applications.
48
-
48
+
49
49
  The middleware implements:
50
50
  - FastAPI request/response handling
51
51
  - Authentication caching and optimization
52
52
  - Framework-specific error responses
53
53
  - Security event logging
54
-
54
+
55
55
  Attributes:
56
56
  config (SecurityConfig): Security configuration settings
57
57
  security_manager: Security manager instance
58
58
  logger (Logger): Logger instance for authentication operations
59
59
  _auth_cache (Dict): Authentication result cache
60
-
60
+
61
61
  Example:
62
62
  >>> from mcp_security_framework.middleware import create_auth_middleware
63
63
  >>> middleware = create_auth_middleware(config, framework="fastapi")
64
64
  >>> app.add_middleware(middleware)
65
-
65
+
66
66
  Raises:
67
67
  AuthMiddlewareError: When authentication processing fails
68
68
  """
69
-
69
+
70
70
  def __init__(self, security_manager: Any):
71
71
  """
72
72
  Initialize FastAPI Authentication Middleware.
73
-
73
+
74
74
  Args:
75
75
  security_manager: Security manager instance
76
76
  """
77
77
  super().__init__(security_manager)
78
78
  self.logger = logging.getLogger(__name__)
79
-
79
+
80
80
  async def __call__(self, request: Request, call_next: Any) -> Response:
81
81
  """
82
82
  Process FastAPI request through authentication middleware.
83
-
83
+
84
84
  This method implements the authentication-only processing
85
85
  pipeline for FastAPI requests, focusing solely on user
86
86
  authentication without authorization checks.
87
-
87
+
88
88
  Args:
89
89
  request (Request): FastAPI request object
90
90
  call_next: FastAPI call_next function
91
-
91
+
92
92
  Returns:
93
93
  Response: FastAPI response object
94
-
94
+
95
95
  Raises:
96
96
  AuthMiddlewareError: If authentication processing fails
97
97
  """
@@ -99,73 +99,75 @@ class FastAPIAuthMiddleware(AuthMiddleware):
99
99
  # Check if path is public (bypasses authentication)
100
100
  if self._is_public_path(request):
101
101
  return await call_next(request)
102
-
102
+
103
103
  # Perform authentication
104
104
  auth_result = self._authenticate_only(request)
105
-
105
+
106
106
  if not auth_result.is_valid:
107
107
  return self._auth_error_response(auth_result)
108
-
108
+
109
109
  # Add authentication info to request state
110
110
  request.state.auth_result = auth_result
111
111
  request.state.username = auth_result.username
112
112
  request.state.user_roles = auth_result.roles
113
113
  request.state.auth_method = auth_result.auth_method
114
-
114
+
115
115
  # Process request
116
116
  response = await call_next(request)
117
-
117
+
118
118
  # Log successful authentication
119
- self._log_auth_event("authentication_successful", {
120
- "ip_address": self._get_client_ip(request),
121
- "username": auth_result.username,
122
- "path": str(request.url.path),
123
- "method": request.method,
124
- "auth_method": auth_result.auth_method
125
- })
126
-
119
+ self._log_auth_event(
120
+ "authentication_successful",
121
+ {
122
+ "ip_address": self._get_client_ip(request),
123
+ "username": auth_result.username,
124
+ "path": str(request.url.path),
125
+ "method": request.method,
126
+ "auth_method": auth_result.auth_method,
127
+ },
128
+ )
129
+
127
130
  return response
128
-
131
+
129
132
  except Exception as e:
130
133
  self.logger.error(
131
134
  "FastAPI authentication middleware processing failed",
132
135
  extra={"error": str(e)},
133
- exc_info=True
136
+ exc_info=True,
134
137
  )
135
138
  raise AuthMiddlewareError(
136
- f"Authentication processing failed: {str(e)}",
137
- error_code=-32035
139
+ f"Authentication processing failed: {str(e)}", error_code=-32035
138
140
  )
139
-
141
+
140
142
  def _is_public_path(self, request: Request) -> bool:
141
143
  """
142
144
  Check if the request path is public (bypasses authentication).
143
-
145
+
144
146
  Args:
145
147
  request (Request): FastAPI request object
146
-
148
+
147
149
  Returns:
148
150
  bool: True if path is public, False otherwise
149
151
  """
150
152
  path = str(request.url.path)
151
-
153
+
152
154
  # Check configured public paths
153
- if hasattr(self.config.auth, 'public_paths'):
155
+ if hasattr(self.config.auth, "public_paths"):
154
156
  for public_path in self.config.auth.public_paths:
155
157
  if path.startswith(public_path):
156
158
  return True
157
-
159
+
158
160
  # Check common public paths
159
161
  public_paths = ["/health", "/status", "/metrics", "/docs", "/openapi.json"]
160
162
  return any(path.startswith(public_path) for public_path in public_paths)
161
-
163
+
162
164
  def _get_client_ip(self, request: Request) -> str:
163
165
  """
164
166
  Get client IP address from FastAPI request.
165
-
167
+
166
168
  Args:
167
169
  request (Request): FastAPI request object
168
-
170
+
169
171
  Returns:
170
172
  str: Client IP address
171
173
  """
@@ -173,33 +175,35 @@ class FastAPIAuthMiddleware(AuthMiddleware):
173
175
  forwarded_for = request.headers.get("X-Forwarded-For")
174
176
  if forwarded_for:
175
177
  return forwarded_for.split(",")[0].strip()
176
-
178
+
177
179
  # Try X-Real-IP header
178
180
  real_ip = request.headers.get("X-Real-IP")
179
181
  if real_ip:
180
182
  return real_ip
181
-
183
+
182
184
  # Use client host
183
- if hasattr(request, 'client') and request.client:
185
+ if hasattr(request, "client") and request.client:
184
186
  return request.client.host
185
-
187
+
186
188
  # Fallback to default IP from config or environment
187
- default_ip = getattr(self.config, 'default_client_ip', None)
189
+ default_ip = getattr(self.config, "default_client_ip", None)
188
190
  if default_ip:
189
191
  return default_ip
190
-
192
+
191
193
  # Use environment variable or default
192
194
  import os
195
+
193
196
  from ..constants import DEFAULT_CLIENT_IP
194
- return os.environ.get('DEFAULT_CLIENT_IP', DEFAULT_CLIENT_IP)
195
-
197
+
198
+ return os.environ.get("DEFAULT_CLIENT_IP", DEFAULT_CLIENT_IP)
199
+
196
200
  def _get_cache_key(self, request: Request) -> str:
197
201
  """
198
202
  Generate cache key for authentication result.
199
-
203
+
200
204
  Args:
201
205
  request (Request): FastAPI request object
202
-
206
+
203
207
  Returns:
204
208
  str: Cache key
205
209
  """
@@ -207,15 +211,15 @@ class FastAPIAuthMiddleware(AuthMiddleware):
207
211
  ip = self._get_client_ip(request)
208
212
  user_agent = request.headers.get("User-Agent", "")
209
213
  return f"auth:{ip}:{hash(user_agent)}"
210
-
214
+
211
215
  def _try_auth_method(self, request: Request, method: str) -> AuthResult:
212
216
  """
213
217
  Try authentication using specific method with FastAPI request.
214
-
218
+
215
219
  Args:
216
220
  request (Request): FastAPI request object
217
221
  method (str): Authentication method to try
218
-
222
+
219
223
  Returns:
220
224
  AuthResult: Authentication result
221
225
  """
@@ -236,13 +240,13 @@ class FastAPIAuthMiddleware(AuthMiddleware):
236
240
  roles=[],
237
241
  auth_method=None,
238
242
  error_code=-32022,
239
- error_message=f"Unsupported authentication method: {method}"
243
+ error_message=f"Unsupported authentication method: {method}",
240
244
  )
241
245
  except Exception as e:
242
246
  self.logger.error(
243
247
  f"Authentication method {method} failed",
244
248
  extra={"error": str(e)},
245
- exc_info=True
249
+ exc_info=True,
246
250
  )
247
251
  return AuthResult(
248
252
  is_valid=False,
@@ -251,16 +255,16 @@ class FastAPIAuthMiddleware(AuthMiddleware):
251
255
  roles=[],
252
256
  auth_method=None,
253
257
  error_code=-32023,
254
- error_message=f"Authentication method {method} failed: {str(e)}"
258
+ error_message=f"Authentication method {method} failed: {str(e)}",
255
259
  )
256
-
260
+
257
261
  def _try_api_key_auth(self, request: Request) -> AuthResult:
258
262
  """
259
263
  Try API key authentication with FastAPI request.
260
-
264
+
261
265
  Args:
262
266
  request (Request): FastAPI request object
263
-
267
+
264
268
  Returns:
265
269
  AuthResult: Authentication result
266
270
  """
@@ -271,7 +275,7 @@ class FastAPIAuthMiddleware(AuthMiddleware):
271
275
  auth_header = request.headers.get("Authorization")
272
276
  if auth_header and auth_header.startswith("Bearer "):
273
277
  api_key = auth_header[7:] # Remove "Bearer " prefix
274
-
278
+
275
279
  if not api_key:
276
280
  return AuthResult(
277
281
  is_valid=False,
@@ -280,19 +284,19 @@ class FastAPIAuthMiddleware(AuthMiddleware):
280
284
  roles=[],
281
285
  auth_method=AuthMethod.API_KEY,
282
286
  error_code=-32012,
283
- error_message="API key not found in request"
287
+ error_message="API key not found in request",
284
288
  )
285
-
289
+
286
290
  # Authenticate using security manager
287
291
  return self.security_manager.auth_manager.authenticate_api_key(api_key)
288
-
292
+
289
293
  def _try_jwt_auth(self, request: Request) -> AuthResult:
290
294
  """
291
295
  Try JWT authentication with FastAPI request.
292
-
296
+
293
297
  Args:
294
298
  request (Request): FastAPI request object
295
-
299
+
296
300
  Returns:
297
301
  AuthResult: Authentication result
298
302
  """
@@ -306,21 +310,21 @@ class FastAPIAuthMiddleware(AuthMiddleware):
306
310
  roles=[],
307
311
  auth_method=AuthMethod.JWT,
308
312
  error_code=-32013,
309
- error_message="JWT token not found in Authorization header"
313
+ error_message="JWT token not found in Authorization header",
310
314
  )
311
-
315
+
312
316
  token = auth_header[7:] # Remove "Bearer " prefix
313
-
317
+
314
318
  # Authenticate using security manager
315
319
  return self.security_manager.auth_manager.authenticate_jwt_token(token)
316
-
320
+
317
321
  def _try_certificate_auth(self, request: Request) -> AuthResult:
318
322
  """
319
323
  Try certificate authentication with FastAPI request.
320
-
324
+
321
325
  Args:
322
326
  request (Request): FastAPI request object
323
-
327
+
324
328
  Returns:
325
329
  AuthResult: Authentication result
326
330
  """
@@ -334,16 +338,16 @@ class FastAPIAuthMiddleware(AuthMiddleware):
334
338
  roles=[],
335
339
  auth_method=AuthMethod.CERTIFICATE,
336
340
  error_code=-32014,
337
- error_message="Certificate authentication not implemented"
341
+ error_message="Certificate authentication not implemented",
338
342
  )
339
-
343
+
340
344
  def _try_basic_auth(self, request: Request) -> AuthResult:
341
345
  """
342
346
  Try basic authentication with FastAPI request.
343
-
347
+
344
348
  Args:
345
349
  request (Request): FastAPI request object
346
-
350
+
347
351
  Returns:
348
352
  AuthResult: Authentication result
349
353
  """
@@ -357,9 +361,9 @@ class FastAPIAuthMiddleware(AuthMiddleware):
357
361
  roles=[],
358
362
  auth_method=AuthMethod.BASIC,
359
363
  error_code=-32015,
360
- error_message="Basic authentication credentials not found"
364
+ error_message="Basic authentication credentials not found",
361
365
  )
362
-
366
+
363
367
  # Basic auth is not implemented in this version
364
368
  return AuthResult(
365
369
  is_valid=False,
@@ -368,16 +372,16 @@ class FastAPIAuthMiddleware(AuthMiddleware):
368
372
  roles=[],
369
373
  auth_method=AuthMethod.BASIC,
370
374
  error_code=-32016,
371
- error_message="Basic authentication not implemented"
375
+ error_message="Basic authentication not implemented",
372
376
  )
373
-
377
+
374
378
  def _auth_error_response(self, auth_result: AuthResult) -> JSONResponse:
375
379
  """
376
380
  Create authentication error response for FastAPI.
377
-
381
+
378
382
  Args:
379
383
  auth_result (AuthResult): Authentication result with error
380
-
384
+
381
385
  Returns:
382
386
  JSONResponse: FastAPI error response
383
387
  """
@@ -385,24 +389,23 @@ class FastAPIAuthMiddleware(AuthMiddleware):
385
389
  "error": "Authentication failed",
386
390
  "error_code": auth_result.error_code,
387
391
  "error_message": auth_result.error_message,
388
- "auth_method": auth_result.auth_method.value if auth_result.auth_method else None
389
- }
390
-
391
- headers = {
392
- "WWW-Authenticate": "Bearer",
393
- "Content-Type": "application/json"
392
+ "auth_method": (
393
+ auth_result.auth_method.value if auth_result.auth_method else None
394
+ ),
394
395
  }
395
-
396
+
397
+ headers = {"WWW-Authenticate": "Bearer", "Content-Type": "application/json"}
398
+
396
399
  return JSONResponse(
397
400
  status_code=status.HTTP_401_UNAUTHORIZED,
398
401
  content=error_data,
399
- headers=headers
402
+ headers=headers,
400
403
  )
401
-
404
+
402
405
  def _log_auth_event(self, event_type: str, details: Dict[str, Any]) -> None:
403
406
  """
404
407
  Log authentication event.
405
-
408
+
406
409
  Args:
407
410
  event_type (str): Type of authentication event
408
411
  details (Dict[str, Any]): Event details
@@ -418,97 +421,95 @@ class FastAPIAuthMiddleware(AuthMiddleware):
418
421
  "path": details.get("path"),
419
422
  "method": details.get("method"),
420
423
  "auth_method": details.get("auth_method"),
421
- **details
422
- }
424
+ **details,
425
+ },
423
426
  )
424
427
  except Exception as e:
425
428
  self.logger.error(
426
429
  "Failed to log authentication event",
427
430
  extra={"error": str(e)},
428
- exc_info=True
431
+ exc_info=True,
429
432
  )
430
-
433
+
431
434
  # Abstract method implementations
432
-
435
+
433
436
  def _get_rate_limit_identifier(self, request: Request) -> str:
434
437
  """
435
438
  Get rate limit identifier from FastAPI request.
436
-
439
+
437
440
  Args:
438
441
  request (Request): FastAPI request object
439
-
442
+
440
443
  Returns:
441
444
  str: Rate limit identifier (IP address)
442
445
  """
443
446
  return self._get_client_ip(request)
444
-
447
+
445
448
  def _get_request_path(self, request: Request) -> str:
446
449
  """
447
450
  Get request path from FastAPI request.
448
-
451
+
449
452
  Args:
450
453
  request (Request): FastAPI request object
451
-
454
+
452
455
  Returns:
453
456
  str: Request path
454
457
  """
455
458
  return str(request.url.path)
456
-
459
+
457
460
  def _get_required_permissions(self, request: Request) -> List[str]:
458
461
  """
459
462
  Get required permissions for FastAPI request.
460
-
463
+
461
464
  Args:
462
465
  request (Request): FastAPI request object
463
-
466
+
464
467
  Returns:
465
468
  List[str]: List of required permissions
466
469
  """
467
470
  # For authentication-only middleware, no permissions are required
468
471
  return []
469
-
470
- def _apply_security_headers(self, response: Response, headers: Dict[str, str]) -> None:
472
+
473
+ def _apply_security_headers(
474
+ self, response: Response, headers: Dict[str, str]
475
+ ) -> None:
471
476
  """
472
477
  Apply security headers to FastAPI response.
473
-
478
+
474
479
  Args:
475
480
  response (Response): FastAPI response object
476
481
  headers (Dict[str, str]): Headers to apply
477
482
  """
478
483
  for header, value in headers.items():
479
484
  response.headers[header] = value
480
-
485
+
481
486
  def _create_error_response(self, status_code: int, message: str) -> JSONResponse:
482
487
  """
483
488
  Create error response for FastAPI.
484
-
489
+
485
490
  Args:
486
491
  status_code (int): HTTP status code
487
492
  message (str): Error message
488
-
493
+
489
494
  Returns:
490
495
  JSONResponse: FastAPI error response object
491
496
  """
492
497
  return JSONResponse(
493
- status_code=status_code,
494
- content={
495
- "error": message,
496
- "error_code": -32000
497
- }
498
+ status_code=status_code, content={"error": message, "error_code": -32000}
498
499
  )
499
500
 
500
501
 
501
502
  class AuthMiddlewareError(Exception):
502
503
  """
503
504
  Authentication Middleware Error
504
-
505
+
505
506
  This exception is raised when authentication middleware processing fails.
506
-
507
+
507
508
  Attributes:
508
509
  message (str): Error message
509
510
  error_code (int): Error code for programmatic handling
510
511
  """
511
-
512
+
512
513
  def __init__(self, message: str, error_code: int = -32030):
513
514
  self.message = message
514
515
  self.error_code = error_code