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.
- mcp_security_framework/__init__.py +26 -15
- mcp_security_framework/cli/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +233 -197
- mcp_security_framework/cli/security_cli.py +324 -234
- mcp_security_framework/constants.py +21 -27
- mcp_security_framework/core/auth_manager.py +49 -20
- mcp_security_framework/core/cert_manager.py +398 -104
- mcp_security_framework/core/permission_manager.py +13 -9
- mcp_security_framework/core/rate_limiter.py +10 -0
- mcp_security_framework/core/security_manager.py +286 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +954 -0
- mcp_security_framework/examples/django_example.py +276 -202
- mcp_security_framework/examples/fastapi_example.py +897 -393
- mcp_security_framework/examples/flask_example.py +311 -200
- mcp_security_framework/examples/gateway_example.py +373 -214
- mcp_security_framework/examples/microservice_example.py +337 -172
- mcp_security_framework/examples/standalone_example.py +719 -478
- mcp_security_framework/examples/test_all_examples.py +572 -0
- mcp_security_framework/middleware/__init__.py +46 -55
- mcp_security_framework/middleware/auth_middleware.py +62 -63
- mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
- mcp_security_framework/middleware/flask_middleware.py +183 -157
- mcp_security_framework/middleware/mtls_middleware.py +106 -117
- mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
- mcp_security_framework/middleware/security_middleware.py +109 -124
- mcp_security_framework/schemas/config.py +2 -1
- mcp_security_framework/schemas/models.py +19 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
- mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
- tests/conftest.py +303 -0
- tests/test_cli/test_cert_cli.py +194 -174
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +33 -19
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +613 -0
- tests/test_examples/test_fastapi_example.py +290 -169
- tests/test_examples/test_flask_example.py +304 -162
- tests/test_examples/test_standalone_example.py +106 -168
- tests/test_integration/test_auth_flow.py +214 -198
- tests/test_integration/test_certificate_flow.py +181 -150
- tests/test_integration/test_fastapi_integration.py +140 -149
- tests/test_integration/test_flask_integration.py +144 -141
- tests/test_integration/test_standalone_integration.py +331 -300
- tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +696 -0
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +151 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.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, {
|
145
|
-
|
144
|
+
return True, {
|
145
|
+
"strategy": "disabled",
|
146
|
+
"reason": "Rate limiting disabled",
|
147
|
+
}
|
148
|
+
|
146
149
|
# Get rate limiting strategy from config
|
147
|
-
strategy =
|
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(
|
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(
|
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(
|
303
|
-
|
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(
|
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
|
-
|
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)}
|