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