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
@@ -30,7 +30,7 @@ from .security_middleware import SecurityMiddleware, SecurityMiddlewareError
30
30
 
31
31
  class RateLimitMiddlewareError(SecurityMiddlewareError):
32
32
  """Raised when rate limit middleware encounters an error."""
33
-
33
+
34
34
  def __init__(self, message: str, error_code: int = -32035):
35
35
  self.message = message
36
36
  self.error_code = error_code
@@ -40,161 +40,167 @@ class RateLimitMiddlewareError(SecurityMiddlewareError):
40
40
  class RateLimitMiddleware(SecurityMiddleware):
41
41
  """
42
42
  Rate Limiting-Only Middleware Class
43
-
43
+
44
44
  This class provides rate limiting-only middleware that focuses
45
45
  solely on request rate limiting without performing authentication
46
46
  or authorization checks. It's useful for scenarios where rate
47
47
  limiting is handled separately from other security concerns.
48
-
48
+
49
49
  The RateLimitMiddleware implements:
50
50
  - Rate limiting-only request processing
51
51
  - Multiple rate limiting strategies
52
52
  - Rate limit result caching
53
53
  - Rate limit event logging
54
54
  - Framework-agnostic design
55
-
55
+
56
56
  Key Responsibilities:
57
57
  - Process requests through rate limiting pipeline only
58
58
  - Handle multiple rate limiting strategies
59
59
  - Cache rate limit results for performance
60
60
  - Log rate limit events and violations
61
61
  - Provide rate limit status to downstream components
62
-
62
+
63
63
  Attributes:
64
64
  Inherits all attributes from SecurityMiddleware
65
65
  _rate_limit_cache (Dict): Cache for rate limit results
66
66
  _rate_limit_strategies (Dict): Available rate limiting strategies
67
-
67
+
68
68
  Example:
69
69
  >>> from mcp_security_framework.middleware import RateLimitMiddleware
70
- >>>
70
+ >>>
71
71
  >>> security_manager = SecurityManager(config)
72
72
  >>> rate_limit_middleware = RateLimitMiddleware(security_manager)
73
73
  >>> app.add_middleware(rate_limit_middleware)
74
-
74
+
75
75
  Note:
76
76
  This middleware only handles rate limiting. Authentication and
77
77
  authorization should be handled separately by other middleware
78
78
  or application logic.
79
79
  """
80
-
80
+
81
81
  def __init__(self, security_manager):
82
82
  """
83
83
  Initialize Rate Limiting-Only Middleware.
84
-
84
+
85
85
  Args:
86
86
  security_manager: Security manager instance containing
87
87
  all security components and configuration.
88
-
88
+
89
89
  Raises:
90
90
  RateLimitMiddlewareError: If initialization fails
91
91
  """
92
92
  super().__init__(security_manager)
93
93
  self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
94
-
94
+
95
95
  # Initialize rate limiting strategies
96
96
  self._rate_limit_strategies = {
97
97
  "ip": self._rate_limit_by_ip,
98
98
  "user": self._rate_limit_by_user,
99
99
  "global": self._rate_limit_global,
100
100
  "path": self._rate_limit_by_path,
101
- "method": self._rate_limit_by_method
101
+ "method": self._rate_limit_by_method,
102
102
  }
103
-
103
+
104
104
  self.logger.info("Rate limit middleware initialized")
105
-
105
+
106
106
  @abstractmethod
107
107
  def __call__(self, request: Any, call_next: Any) -> Any:
108
108
  """
109
109
  Process request through rate limiting middleware.
110
-
110
+
111
111
  This method implements the rate limiting-only processing
112
112
  pipeline, focusing solely on request rate limiting.
113
-
113
+
114
114
  Args:
115
115
  request: Framework-specific request object
116
116
  call_next: Framework-specific call_next function
117
-
117
+
118
118
  Returns:
119
119
  Framework-specific response object
120
-
120
+
121
121
  Raises:
122
122
  RateLimitMiddlewareError: If rate limiting processing fails
123
123
  """
124
124
  pass
125
-
125
+
126
126
  def _check_rate_limit_only(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
127
127
  """
128
128
  Perform rate limiting-only processing.
129
-
129
+
130
130
  This method handles rate limiting without authentication
131
131
  or authorization checks.
132
-
132
+
133
133
  Args:
134
134
  request: Framework-specific request object
135
-
135
+
136
136
  Returns:
137
137
  Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
138
-
138
+
139
139
  Raises:
140
140
  RateLimitMiddlewareError: If rate limiting process fails
141
141
  """
142
142
  try:
143
143
  if not self.config.rate_limit.enabled:
144
- return True, {"strategy": "disabled", "reason": "Rate limiting disabled"}
145
-
144
+ return True, {
145
+ "strategy": "disabled",
146
+ "reason": "Rate limiting disabled",
147
+ }
148
+
146
149
  # Get rate limiting strategy from config
147
- strategy = self.config.rate_limit.strategy if hasattr(self.config.rate_limit, 'strategy') else "ip"
148
-
150
+ strategy = (
151
+ self.config.rate_limit.strategy
152
+ if hasattr(self.config.rate_limit, "strategy")
153
+ else "ip"
154
+ )
155
+
149
156
  if strategy not in self._rate_limit_strategies:
150
- self.logger.warning(f"Unknown rate limiting strategy: {strategy}, using 'ip'")
157
+ self.logger.warning(
158
+ f"Unknown rate limiting strategy: {strategy}, using 'ip'"
159
+ )
151
160
  strategy = "ip"
152
-
161
+
153
162
  # Apply rate limiting strategy
154
163
  is_allowed, rate_limit_info = self._rate_limit_strategies[strategy](request)
155
-
164
+
156
165
  # Log rate limit event
157
166
  self._log_rate_limit_event(request, is_allowed, rate_limit_info)
158
-
167
+
159
168
  return is_allowed, rate_limit_info
160
-
169
+
161
170
  except Exception as e:
162
171
  self.logger.error(
163
- "Rate limiting process failed",
164
- extra={"error": str(e)},
165
- exc_info=True
172
+ "Rate limiting process failed", extra={"error": str(e)}, exc_info=True
166
173
  )
167
174
  raise RateLimitMiddlewareError(
168
- f"Rate limiting process failed: {str(e)}",
169
- error_code=-32036
175
+ f"Rate limiting process failed: {str(e)}", error_code=-32036
170
176
  )
171
-
177
+
172
178
  def _rate_limit_by_ip(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
173
179
  """
174
180
  Rate limit by IP address.
175
-
181
+
176
182
  Args:
177
183
  request: Framework-specific request object
178
-
184
+
179
185
  Returns:
180
186
  Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
181
187
  """
182
188
  identifier = self._get_rate_limit_identifier(request)
183
189
  is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
184
-
190
+
185
191
  return is_allowed, {
186
192
  "strategy": "ip",
187
193
  "identifier": identifier,
188
- "is_allowed": is_allowed
194
+ "is_allowed": is_allowed,
189
195
  }
190
-
196
+
191
197
  def _rate_limit_by_user(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
192
198
  """
193
199
  Rate limit by user (requires authentication).
194
-
200
+
195
201
  Args:
196
202
  request: Framework-specific request object
197
-
203
+
198
204
  Returns:
199
205
  Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
200
206
  """
@@ -203,114 +209,117 @@ class RateLimitMiddleware(SecurityMiddleware):
203
209
  if not user_id:
204
210
  # Fall back to IP-based rate limiting
205
211
  return self._rate_limit_by_ip(request)
206
-
207
- is_allowed = self.security_manager.rate_limiter.check_rate_limit(f"user:{user_id}")
208
-
212
+
213
+ is_allowed = self.security_manager.rate_limiter.check_rate_limit(
214
+ f"user:{user_id}"
215
+ )
216
+
209
217
  return is_allowed, {
210
218
  "strategy": "user",
211
219
  "identifier": user_id,
212
- "is_allowed": is_allowed
220
+ "is_allowed": is_allowed,
213
221
  }
214
-
222
+
215
223
  def _rate_limit_global(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
216
224
  """
217
225
  Global rate limiting (all requests).
218
-
226
+
219
227
  Args:
220
228
  request: Framework-specific request object
221
-
229
+
222
230
  Returns:
223
231
  Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
224
232
  """
225
233
  is_allowed = self.security_manager.rate_limiter.check_rate_limit("global")
226
-
234
+
227
235
  return is_allowed, {
228
236
  "strategy": "global",
229
237
  "identifier": "global",
230
- "is_allowed": is_allowed
238
+ "is_allowed": is_allowed,
231
239
  }
232
-
240
+
233
241
  def _rate_limit_by_path(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
234
242
  """
235
243
  Rate limit by request path.
236
-
244
+
237
245
  Args:
238
246
  request: Framework-specific request object
239
-
247
+
240
248
  Returns:
241
249
  Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
242
250
  """
243
251
  path = self._get_request_path(request)
244
252
  identifier = f"path:{path}"
245
253
  is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
246
-
254
+
247
255
  return is_allowed, {
248
256
  "strategy": "path",
249
257
  "identifier": identifier,
250
258
  "path": path,
251
- "is_allowed": is_allowed
259
+ "is_allowed": is_allowed,
252
260
  }
253
-
261
+
254
262
  def _rate_limit_by_method(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
255
263
  """
256
264
  Rate limit by HTTP method.
257
-
265
+
258
266
  Args:
259
267
  request: Framework-specific request object
260
-
268
+
261
269
  Returns:
262
270
  Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
263
271
  """
264
272
  method = self._get_request_method(request)
265
273
  identifier = f"method:{method}"
266
274
  is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
267
-
275
+
268
276
  return is_allowed, {
269
277
  "strategy": "method",
270
278
  "identifier": identifier,
271
279
  "method": method,
272
- "is_allowed": is_allowed
280
+ "is_allowed": is_allowed,
273
281
  }
274
-
282
+
275
283
  def _get_user_identifier(self, request: Any) -> Optional[str]:
276
284
  """
277
285
  Get user identifier from request.
278
-
286
+
279
287
  Args:
280
288
  request: Framework-specific request object
281
-
289
+
282
290
  Returns:
283
291
  Optional[str]: User identifier if available, None otherwise
284
292
  """
285
293
  # This should be implemented by framework-specific subclasses
286
294
  # For now, return None to indicate no user identifier available
287
295
  return None
288
-
296
+
289
297
  def _get_request_method(self, request: Any) -> str:
290
298
  """
291
299
  Get HTTP method from request.
292
-
300
+
293
301
  Args:
294
302
  request: Framework-specific request object
295
-
303
+
296
304
  Returns:
297
305
  str: HTTP method
298
306
  """
299
307
  # This should be implemented by framework-specific subclasses
300
308
  return "GET"
301
-
302
- def _log_rate_limit_event(self, request: Any, is_allowed: bool,
303
- rate_limit_info: Dict[str, Any]) -> None:
309
+
310
+ def _log_rate_limit_event(
311
+ self, request: Any, is_allowed: bool, rate_limit_info: Dict[str, Any]
312
+ ) -> None:
304
313
  """
305
314
  Log rate limit event.
306
-
315
+
307
316
  Args:
308
317
  request: Framework-specific request object
309
318
  is_allowed (bool): Whether request is allowed
310
319
  rate_limit_info (Dict[str, Any]): Rate limit information
311
320
  """
312
321
  log_level = logging.WARNING if not is_allowed else logging.DEBUG
313
-
322
+
314
323
  self.logger.log(
315
324
  log_level,
316
325
  f"Rate limit event: {'allowed' if is_allowed else 'blocked'}",
@@ -320,68 +329,64 @@ class RateLimitMiddleware(SecurityMiddleware):
320
329
  "ip_address": self._get_rate_limit_identifier(request),
321
330
  "path": self._get_request_path(request),
322
331
  "method": self._get_request_method(request),
323
- **rate_limit_info
324
- }
332
+ **rate_limit_info,
333
+ },
325
334
  )
326
-
335
+
327
336
  def get_rate_limit_status(self, identifier: str) -> Dict[str, Any]:
328
337
  """
329
338
  Get current rate limit status for an identifier.
330
-
339
+
331
340
  Args:
332
341
  identifier (str): Rate limit identifier
333
-
342
+
334
343
  Returns:
335
344
  Dict[str, Any]: Rate limit status information
336
345
  """
337
346
  try:
338
- status = self.security_manager.rate_limiter.get_rate_limit_status(identifier)
347
+ status = self.security_manager.rate_limiter.get_rate_limit_status(
348
+ identifier
349
+ )
339
350
  return {
340
351
  "identifier": identifier,
341
352
  "current_count": status.current_count,
342
353
  "limit": status.limit,
343
354
  "window_seconds": status.window_seconds,
344
355
  "remaining": status.remaining,
345
- "reset_time": status.reset_time
356
+ "reset_time": status.reset_time,
346
357
  }
347
358
  except Exception as e:
348
359
  self.logger.error(
349
360
  "Failed to get rate limit status",
350
- extra={"identifier": identifier, "error": str(e)}
361
+ extra={"identifier": identifier, "error": str(e)},
351
362
  )
352
- return {
353
- "identifier": identifier,
354
- "error": str(e)
355
- }
356
-
363
+ return {"identifier": identifier, "error": str(e)}
364
+
357
365
  def reset_rate_limit(self, identifier: str) -> bool:
358
366
  """
359
367
  Reset rate limit for an identifier.
360
-
368
+
361
369
  Args:
362
370
  identifier (str): Rate limit identifier
363
-
371
+
364
372
  Returns:
365
373
  bool: True if reset successful, False otherwise
366
374
  """
367
375
  try:
368
376
  self.security_manager.rate_limiter.reset_rate_limit(identifier)
369
- self.logger.info(
370
- "Rate limit reset",
371
- extra={"identifier": identifier}
372
- )
377
+ self.logger.info("Rate limit reset", extra={"identifier": identifier})
373
378
  return True
374
379
  except Exception as e:
375
380
  self.logger.error(
376
381
  "Failed to reset rate limit",
377
- extra={"identifier": identifier, "error": str(e)}
382
+ extra={"identifier": identifier, "error": str(e)},
378
383
  )
379
384
  return False
380
-
385
+
381
386
  def get_rate_limit_statistics(self) -> Dict[str, Any]:
382
387
  """
383
388
  Get rate limiting statistics.
384
-
389
+
385
390
  Returns:
386
391
  Dict[str, Any]: Rate limiting statistics
387
392
  """
@@ -393,11 +398,10 @@ class RateLimitMiddleware(SecurityMiddleware):
393
398
  "blocked_requests": 0,
394
399
  "active_identifiers": 0,
395
400
  "cache_hits": 0,
396
- "cache_misses": 0
401
+ "cache_misses": 0,
397
402
  }
398
403
  except Exception as e:
399
404
  self.logger.error(
400
- "Failed to get rate limit statistics",
401
- extra={"error": str(e)}
405
+ "Failed to get rate limit statistics", extra={"error": str(e)}
402
406
  )
403
407
  return {"error": str(e)}