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
@@ -27,13 +27,13 @@ from typing import Any, Dict, List, Optional, Union
|
|
27
27
|
|
28
28
|
from ..core.security_manager import SecurityManager
|
29
29
|
from ..schemas.config import SecurityConfig
|
30
|
-
from ..schemas.models import AuthResult, ValidationResult, ValidationStatus
|
31
|
-
from ..schemas.responses import
|
30
|
+
from ..schemas.models import AuthResult, AuthStatus, ValidationResult, ValidationStatus
|
31
|
+
from ..schemas.responses import ResponseStatus, SecurityResponse
|
32
32
|
|
33
33
|
|
34
34
|
class SecurityMiddlewareError(Exception):
|
35
35
|
"""Raised when security middleware encounters an error."""
|
36
|
-
|
36
|
+
|
37
37
|
def __init__(self, message: str, error_code: int = -32003):
|
38
38
|
self.message = message
|
39
39
|
self.error_code = error_code
|
@@ -43,12 +43,12 @@ class SecurityMiddlewareError(Exception):
|
|
43
43
|
class SecurityMiddleware(ABC):
|
44
44
|
"""
|
45
45
|
Abstract Security Middleware Class
|
46
|
-
|
46
|
+
|
47
47
|
This is the base class for all framework-specific security middleware
|
48
48
|
implementations. It provides common security logic and a unified
|
49
49
|
interface for request processing, authentication, authorization,
|
50
50
|
and rate limiting.
|
51
|
-
|
51
|
+
|
52
52
|
The SecurityMiddleware implements the security processing pipeline:
|
53
53
|
1. Rate limiting check
|
54
54
|
2. Public path validation
|
@@ -56,7 +56,7 @@ class SecurityMiddleware(ABC):
|
|
56
56
|
4. Authorization
|
57
57
|
5. Security headers addition
|
58
58
|
6. Response processing
|
59
|
-
|
59
|
+
|
60
60
|
Key Responsibilities:
|
61
61
|
- Process incoming requests through security pipeline
|
62
62
|
- Handle authentication using multiple methods
|
@@ -65,7 +65,7 @@ class SecurityMiddleware(ABC):
|
|
65
65
|
- Add security headers to responses
|
66
66
|
- Log security events and violations
|
67
67
|
- Provide framework-specific request/response handling
|
68
|
-
|
68
|
+
|
69
69
|
Attributes:
|
70
70
|
security_manager (SecurityManager): Main security manager instance
|
71
71
|
config (SecurityConfig): Security configuration
|
@@ -73,32 +73,32 @@ class SecurityMiddleware(ABC):
|
|
73
73
|
_public_paths (List[str]): List of public paths that bypass security
|
74
74
|
_rate_limit_cache (Dict): Cache for rate limiting data
|
75
75
|
_auth_cache (Dict): Cache for authentication results
|
76
|
-
|
76
|
+
|
77
77
|
Example:
|
78
78
|
>>> config = SecurityConfig(auth=AuthConfig(enabled=True))
|
79
79
|
>>> security_manager = SecurityManager(config)
|
80
80
|
>>> middleware = FastAPISecurityMiddleware(security_manager)
|
81
81
|
>>> app.add_middleware(middleware)
|
82
|
-
|
82
|
+
|
83
83
|
Note:
|
84
84
|
This is an abstract base class. Implementations must provide
|
85
85
|
framework-specific request/response handling methods.
|
86
86
|
"""
|
87
|
-
|
87
|
+
|
88
88
|
def __init__(self, security_manager: SecurityManager):
|
89
89
|
"""
|
90
90
|
Initialize Security Middleware.
|
91
|
-
|
91
|
+
|
92
92
|
Args:
|
93
93
|
security_manager (SecurityManager): Security manager instance
|
94
94
|
containing all security components and configuration.
|
95
95
|
Must be a properly initialized SecurityManager with
|
96
96
|
valid configuration.
|
97
|
-
|
97
|
+
|
98
98
|
Raises:
|
99
99
|
SecurityMiddlewareError: If security manager is invalid or
|
100
100
|
configuration is missing.
|
101
|
-
|
101
|
+
|
102
102
|
Example:
|
103
103
|
>>> security_manager = SecurityManager(config)
|
104
104
|
>>> middleware = FastAPISecurityMiddleware(security_manager)
|
@@ -106,43 +106,43 @@ class SecurityMiddleware(ABC):
|
|
106
106
|
if not isinstance(security_manager, SecurityManager):
|
107
107
|
raise SecurityMiddlewareError(
|
108
108
|
"Invalid security manager: must be SecurityManager instance",
|
109
|
-
error_code=-32003
|
109
|
+
error_code=-32003,
|
110
110
|
)
|
111
|
-
|
111
|
+
|
112
112
|
self.security_manager = security_manager
|
113
113
|
self.config = security_manager.config
|
114
114
|
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
115
|
-
|
115
|
+
|
116
116
|
# Initialize caches and state
|
117
117
|
self._public_paths = self.config.auth.public_paths if self.config.auth else []
|
118
118
|
self._rate_limit_cache: Dict[str, Dict[str, Any]] = {}
|
119
119
|
self._auth_cache: Dict[str, AuthResult] = {}
|
120
|
-
|
120
|
+
|
121
121
|
self.logger.info(
|
122
122
|
"Security middleware initialized",
|
123
123
|
extra={
|
124
124
|
"middleware_type": self.__class__.__name__,
|
125
125
|
"auth_enabled": self.config.auth.enabled if self.config.auth else False,
|
126
|
-
"public_paths_count": len(self._public_paths)
|
127
|
-
}
|
126
|
+
"public_paths_count": len(self._public_paths),
|
127
|
+
},
|
128
128
|
)
|
129
|
-
|
129
|
+
|
130
130
|
@abstractmethod
|
131
131
|
def __call__(self, request: Any, call_next: Any) -> Any:
|
132
132
|
"""
|
133
133
|
Process request through security middleware.
|
134
|
-
|
134
|
+
|
135
135
|
This is the main entry point for the middleware. It implements
|
136
136
|
the security processing pipeline and delegates framework-specific
|
137
137
|
operations to abstract methods.
|
138
|
-
|
138
|
+
|
139
139
|
Args:
|
140
140
|
request: Framework-specific request object
|
141
141
|
call_next: Framework-specific call_next function
|
142
|
-
|
142
|
+
|
143
143
|
Returns:
|
144
144
|
Framework-specific response object
|
145
|
-
|
145
|
+
|
146
146
|
Raises:
|
147
147
|
SecurityMiddlewareError: If security processing fails
|
148
148
|
AuthenticationError: If authentication fails
|
@@ -150,71 +150,68 @@ class SecurityMiddleware(ABC):
|
|
150
150
|
RateLimitExceededError: If rate limit is exceeded
|
151
151
|
"""
|
152
152
|
pass
|
153
|
-
|
153
|
+
|
154
154
|
def _check_rate_limit(self, request: Any) -> bool:
|
155
155
|
"""
|
156
156
|
Check if request is within rate limits.
|
157
|
-
|
157
|
+
|
158
158
|
This method checks if the current request exceeds rate limits
|
159
159
|
based on the request identifier (IP, user, etc.).
|
160
|
-
|
160
|
+
|
161
161
|
Args:
|
162
162
|
request: Framework-specific request object
|
163
|
-
|
163
|
+
|
164
164
|
Returns:
|
165
165
|
bool: True if request is within rate limits, False otherwise
|
166
|
-
|
166
|
+
|
167
167
|
Raises:
|
168
168
|
SecurityMiddlewareError: If rate limiting check fails
|
169
169
|
"""
|
170
170
|
try:
|
171
171
|
if not self.config.rate_limit.enabled:
|
172
172
|
return True
|
173
|
-
|
173
|
+
|
174
174
|
identifier = self._get_rate_limit_identifier(request)
|
175
175
|
if not identifier:
|
176
176
|
self.logger.warning("Could not determine rate limit identifier")
|
177
177
|
return True
|
178
|
-
|
178
|
+
|
179
179
|
# Check rate limit using security manager
|
180
180
|
is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
|
181
|
-
|
181
|
+
|
182
182
|
if not is_allowed:
|
183
183
|
self.logger.warning(
|
184
184
|
"Rate limit exceeded",
|
185
185
|
extra={
|
186
186
|
"identifier": identifier,
|
187
187
|
"rate_limit": self.config.rate_limit.default_requests_per_minute,
|
188
|
-
"window_seconds": self.config.rate_limit.window_size_seconds
|
189
|
-
}
|
188
|
+
"window_seconds": self.config.rate_limit.window_size_seconds,
|
189
|
+
},
|
190
190
|
)
|
191
|
-
|
191
|
+
|
192
192
|
return is_allowed
|
193
|
-
|
193
|
+
|
194
194
|
except Exception as e:
|
195
195
|
self.logger.error(
|
196
|
-
"Rate limit check failed",
|
197
|
-
extra={"error": str(e)},
|
198
|
-
exc_info=True
|
196
|
+
"Rate limit check failed", extra={"error": str(e)}, exc_info=True
|
199
197
|
)
|
200
198
|
raise SecurityMiddlewareError(
|
201
|
-
f"Rate limit check failed: {str(e)}",
|
202
|
-
error_code=-32004
|
199
|
+
f"Rate limit check failed: {str(e)}", error_code=-32004
|
203
200
|
)
|
204
|
-
|
201
|
+
|
205
202
|
def _authenticate_request(self, request: Any) -> AuthResult:
|
206
203
|
"""
|
207
204
|
Authenticate the request using configured methods.
|
208
|
-
|
205
|
+
|
209
206
|
This method attempts to authenticate the request using all
|
210
207
|
configured authentication methods in order of preference.
|
211
|
-
|
208
|
+
|
212
209
|
Args:
|
213
210
|
request: Framework-specific request object
|
214
|
-
|
211
|
+
|
215
212
|
Returns:
|
216
213
|
AuthResult: Authentication result with user information
|
217
|
-
|
214
|
+
|
218
215
|
Raises:
|
219
216
|
SecurityMiddlewareError: If authentication process fails
|
220
217
|
"""
|
@@ -225,39 +222,40 @@ class SecurityMiddleware(ABC):
|
|
225
222
|
status=AuthStatus.SUCCESS,
|
226
223
|
username="anonymous",
|
227
224
|
roles=[],
|
228
|
-
auth_method=None
|
225
|
+
auth_method=None,
|
229
226
|
)
|
230
|
-
|
227
|
+
|
231
228
|
# Try each authentication method in order
|
232
229
|
for method in self.config.auth.methods:
|
233
230
|
auth_result = self._try_auth_method(request, method)
|
234
231
|
# Handle async methods
|
235
|
-
if hasattr(auth_result,
|
232
|
+
if hasattr(auth_result, "__await__"):
|
236
233
|
import asyncio
|
234
|
+
|
237
235
|
try:
|
238
236
|
auth_result = asyncio.run(auth_result)
|
239
237
|
except RuntimeError:
|
240
238
|
# If we're already in an event loop, use create_task
|
241
239
|
loop = asyncio.get_event_loop()
|
242
240
|
auth_result = loop.run_until_complete(auth_result)
|
243
|
-
|
241
|
+
|
244
242
|
if auth_result.is_valid:
|
245
243
|
self.logger.info(
|
246
244
|
"Authentication successful",
|
247
245
|
extra={
|
248
246
|
"username": auth_result.username,
|
249
247
|
"auth_method": auth_result.auth_method,
|
250
|
-
"user_roles": auth_result.roles
|
251
|
-
}
|
248
|
+
"user_roles": auth_result.roles,
|
249
|
+
},
|
252
250
|
)
|
253
251
|
return auth_result
|
254
|
-
|
252
|
+
|
255
253
|
# All authentication methods failed
|
256
254
|
self.logger.warning(
|
257
255
|
"All authentication methods failed",
|
258
|
-
extra={"auth_methods": self.config.auth.methods}
|
256
|
+
extra={"auth_methods": self.config.auth.methods},
|
259
257
|
)
|
260
|
-
|
258
|
+
|
261
259
|
return AuthResult(
|
262
260
|
is_valid=False,
|
263
261
|
status=AuthStatus.FAILED,
|
@@ -265,53 +263,49 @@ class SecurityMiddleware(ABC):
|
|
265
263
|
roles=[],
|
266
264
|
auth_method=None,
|
267
265
|
error_code=-32005,
|
268
|
-
error_message="All authentication methods failed"
|
266
|
+
error_message="All authentication methods failed",
|
269
267
|
)
|
270
|
-
|
268
|
+
|
271
269
|
except Exception as e:
|
272
270
|
self.logger.error(
|
273
|
-
"Authentication process failed",
|
274
|
-
extra={"error": str(e)},
|
275
|
-
exc_info=True
|
271
|
+
"Authentication process failed", extra={"error": str(e)}, exc_info=True
|
276
272
|
)
|
277
273
|
raise SecurityMiddlewareError(
|
278
|
-
f"Authentication process failed: {str(e)}",
|
279
|
-
error_code=-32006
|
274
|
+
f"Authentication process failed: {str(e)}", error_code=-32006
|
280
275
|
)
|
281
|
-
|
276
|
+
|
282
277
|
def _validate_permissions(self, request: Any, auth_result: AuthResult) -> bool:
|
283
278
|
"""
|
284
279
|
Validate user permissions for the requested resource.
|
285
|
-
|
280
|
+
|
286
281
|
This method checks if the authenticated user has the required
|
287
282
|
permissions to access the requested resource.
|
288
|
-
|
283
|
+
|
289
284
|
Args:
|
290
285
|
request: Framework-specific request object
|
291
286
|
auth_result (AuthResult): Authentication result with user info
|
292
|
-
|
287
|
+
|
293
288
|
Returns:
|
294
289
|
bool: True if user has required permissions, False otherwise
|
295
|
-
|
290
|
+
|
296
291
|
Raises:
|
297
292
|
SecurityMiddlewareError: If permission validation fails
|
298
293
|
"""
|
299
294
|
try:
|
300
295
|
if not auth_result.is_valid:
|
301
296
|
return False
|
302
|
-
|
297
|
+
|
303
298
|
# Get required permissions for the request
|
304
299
|
required_permissions = self._get_required_permissions(request)
|
305
300
|
if not required_permissions:
|
306
301
|
# No specific permissions required
|
307
302
|
return True
|
308
|
-
|
303
|
+
|
309
304
|
# Check permissions using security manager
|
310
305
|
validation_result = self.security_manager.check_permissions(
|
311
|
-
auth_result.roles,
|
312
|
-
required_permissions
|
306
|
+
auth_result.roles, required_permissions
|
313
307
|
)
|
314
|
-
|
308
|
+
|
315
309
|
if not validation_result.is_valid:
|
316
310
|
self.logger.warning(
|
317
311
|
"Permission validation failed",
|
@@ -319,30 +313,27 @@ class SecurityMiddleware(ABC):
|
|
319
313
|
"username": auth_result.username,
|
320
314
|
"user_roles": auth_result.roles,
|
321
315
|
"required_permissions": required_permissions,
|
322
|
-
"error_message": validation_result.error_message
|
323
|
-
}
|
316
|
+
"error_message": validation_result.error_message,
|
317
|
+
},
|
324
318
|
)
|
325
|
-
|
319
|
+
|
326
320
|
return validation_result.is_valid
|
327
|
-
|
321
|
+
|
328
322
|
except Exception as e:
|
329
323
|
self.logger.error(
|
330
|
-
"Permission validation failed",
|
331
|
-
extra={"error": str(e)},
|
332
|
-
exc_info=True
|
324
|
+
"Permission validation failed", extra={"error": str(e)}, exc_info=True
|
333
325
|
)
|
334
326
|
raise SecurityMiddlewareError(
|
335
|
-
f"Permission validation failed: {str(e)}",
|
336
|
-
error_code=-32007
|
327
|
+
f"Permission validation failed: {str(e)}", error_code=-32007
|
337
328
|
)
|
338
|
-
|
329
|
+
|
339
330
|
def _is_public_path(self, request: Any) -> bool:
|
340
331
|
"""
|
341
332
|
Check if the request path is public (bypasses security).
|
342
|
-
|
333
|
+
|
343
334
|
Args:
|
344
335
|
request: Framework-specific request object
|
345
|
-
|
336
|
+
|
346
337
|
Returns:
|
347
338
|
bool: True if path is public, False otherwise
|
348
339
|
"""
|
@@ -350,26 +341,24 @@ class SecurityMiddleware(ABC):
|
|
350
341
|
path = self._get_request_path(request)
|
351
342
|
if not path:
|
352
343
|
return False
|
353
|
-
|
344
|
+
|
354
345
|
# Check if path matches any public path pattern
|
355
346
|
for public_path in self._public_paths:
|
356
347
|
if path == public_path or path.startswith(public_path):
|
357
348
|
return True
|
358
|
-
|
349
|
+
|
359
350
|
return False
|
360
|
-
|
351
|
+
|
361
352
|
except Exception as e:
|
362
353
|
self.logger.error(
|
363
|
-
"Public path check failed",
|
364
|
-
extra={"error": str(e)},
|
365
|
-
exc_info=True
|
354
|
+
"Public path check failed", extra={"error": str(e)}, exc_info=True
|
366
355
|
)
|
367
356
|
return False
|
368
|
-
|
357
|
+
|
369
358
|
def _add_security_headers(self, response: Any) -> None:
|
370
359
|
"""
|
371
360
|
Add security headers to the response.
|
372
|
-
|
361
|
+
|
373
362
|
Args:
|
374
363
|
response: Framework-specific response object
|
375
364
|
"""
|
@@ -381,27 +370,25 @@ class SecurityMiddleware(ABC):
|
|
381
370
|
"X-XSS-Protection": "1; mode=block",
|
382
371
|
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
|
383
372
|
"Content-Security-Policy": "default-src 'self'",
|
384
|
-
"Referrer-Policy": "strict-origin-when-cross-origin"
|
373
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
385
374
|
}
|
386
|
-
|
375
|
+
|
387
376
|
# Add custom security headers from config
|
388
377
|
if self.config.auth and self.config.auth.security_headers:
|
389
378
|
headers.update(self.config.auth.security_headers)
|
390
|
-
|
379
|
+
|
391
380
|
# Apply headers using framework-specific method
|
392
381
|
self._apply_security_headers(response, headers)
|
393
|
-
|
382
|
+
|
394
383
|
except Exception as e:
|
395
384
|
self.logger.error(
|
396
|
-
"Failed to add security headers",
|
397
|
-
extra={"error": str(e)},
|
398
|
-
exc_info=True
|
385
|
+
"Failed to add security headers", extra={"error": str(e)}, exc_info=True
|
399
386
|
)
|
400
|
-
|
387
|
+
|
401
388
|
def _log_security_event(self, event_type: str, details: Dict[str, Any]) -> None:
|
402
389
|
"""
|
403
390
|
Log security event for monitoring and auditing.
|
404
|
-
|
391
|
+
|
405
392
|
Args:
|
406
393
|
event_type (str): Type of security event
|
407
394
|
details (Dict[str, Any]): Event details
|
@@ -416,91 +403,89 @@ class SecurityMiddleware(ABC):
|
|
416
403
|
"username": details.get("username"),
|
417
404
|
"path": details.get("path"),
|
418
405
|
"method": details.get("method"),
|
419
|
-
**details
|
420
|
-
}
|
406
|
+
**details,
|
407
|
+
},
|
421
408
|
)
|
422
409
|
except Exception as e:
|
423
410
|
self.logger.error(
|
424
|
-
"Failed to log security event",
|
425
|
-
extra={"error": str(e)},
|
426
|
-
exc_info=True
|
411
|
+
"Failed to log security event", extra={"error": str(e)}, exc_info=True
|
427
412
|
)
|
428
|
-
|
413
|
+
|
429
414
|
# Abstract methods for framework-specific implementations
|
430
|
-
|
415
|
+
|
431
416
|
@abstractmethod
|
432
417
|
def _get_rate_limit_identifier(self, request: Any) -> str:
|
433
418
|
"""
|
434
419
|
Get rate limit identifier from request.
|
435
|
-
|
420
|
+
|
436
421
|
Args:
|
437
422
|
request: Framework-specific request object
|
438
|
-
|
423
|
+
|
439
424
|
Returns:
|
440
425
|
str: Rate limit identifier (IP, user ID, etc.)
|
441
426
|
"""
|
442
427
|
pass
|
443
|
-
|
428
|
+
|
444
429
|
@abstractmethod
|
445
430
|
def _get_request_path(self, request: Any) -> str:
|
446
431
|
"""
|
447
432
|
Get request path from request object.
|
448
|
-
|
433
|
+
|
449
434
|
Args:
|
450
435
|
request: Framework-specific request object
|
451
|
-
|
436
|
+
|
452
437
|
Returns:
|
453
438
|
str: Request path
|
454
439
|
"""
|
455
440
|
pass
|
456
|
-
|
441
|
+
|
457
442
|
@abstractmethod
|
458
443
|
def _get_required_permissions(self, request: Any) -> List[str]:
|
459
444
|
"""
|
460
445
|
Get required permissions for the request.
|
461
|
-
|
446
|
+
|
462
447
|
Args:
|
463
448
|
request: Framework-specific request object
|
464
|
-
|
449
|
+
|
465
450
|
Returns:
|
466
451
|
List[str]: List of required permissions
|
467
452
|
"""
|
468
453
|
pass
|
469
|
-
|
454
|
+
|
470
455
|
@abstractmethod
|
471
456
|
def _try_auth_method(self, request: Any, method: str) -> AuthResult:
|
472
457
|
"""
|
473
458
|
Try authentication using specific method.
|
474
|
-
|
459
|
+
|
475
460
|
Args:
|
476
461
|
request: Framework-specific request object
|
477
462
|
method (str): Authentication method to try
|
478
|
-
|
463
|
+
|
479
464
|
Returns:
|
480
465
|
AuthResult: Authentication result
|
481
466
|
"""
|
482
467
|
pass
|
483
|
-
|
468
|
+
|
484
469
|
@abstractmethod
|
485
470
|
def _apply_security_headers(self, response: Any, headers: Dict[str, str]) -> None:
|
486
471
|
"""
|
487
472
|
Apply security headers to response.
|
488
|
-
|
473
|
+
|
489
474
|
Args:
|
490
475
|
response: Framework-specific response object
|
491
476
|
headers (Dict[str, str]): Headers to apply
|
492
477
|
"""
|
493
478
|
pass
|
494
|
-
|
479
|
+
|
495
480
|
@abstractmethod
|
496
481
|
def _create_error_response(self, status_code: int, message: str) -> Any:
|
497
482
|
"""
|
498
483
|
Create error response for security violations.
|
499
|
-
|
484
|
+
|
500
485
|
Args:
|
501
486
|
status_code (int): HTTP status code
|
502
487
|
message (str): Error message
|
503
|
-
|
488
|
+
|
504
489
|
Returns:
|
505
490
|
Framework-specific error response object
|
506
491
|
"""
|
@@ -209,7 +209,8 @@ class AuthConfig(BaseModel):
|
|
209
209
|
default=None, description="OAuth2 configuration"
|
210
210
|
)
|
211
211
|
public_paths: List[str] = Field(
|
212
|
-
default_factory=list,
|
212
|
+
default_factory=list,
|
213
|
+
description="List of public paths that bypass authentication",
|
213
214
|
)
|
214
215
|
security_headers: Optional[Dict[str, str]] = Field(
|
215
216
|
default=None, description="Custom security headers to add to responses"
|
@@ -88,6 +88,7 @@ class AuthMethod(str, Enum):
|
|
88
88
|
CERTIFICATE = "certificate"
|
89
89
|
BASIC = "basic"
|
90
90
|
OAUTH2 = "oauth2"
|
91
|
+
UNKNOWN = "unknown"
|
91
92
|
|
92
93
|
|
93
94
|
class AuthResult(BaseModel):
|
@@ -322,7 +323,11 @@ class CertificateInfo(BaseModel):
|
|
322
323
|
"""Check if certificate is expired."""
|
323
324
|
now = datetime.now(timezone.utc)
|
324
325
|
# Ensure not_after has timezone info
|
325
|
-
not_after =
|
326
|
+
not_after = (
|
327
|
+
self.not_after.replace(tzinfo=timezone.utc)
|
328
|
+
if self.not_after.tzinfo is None
|
329
|
+
else self.not_after
|
330
|
+
)
|
326
331
|
return now > not_after
|
327
332
|
|
328
333
|
@property
|
@@ -330,7 +335,11 @@ class CertificateInfo(BaseModel):
|
|
330
335
|
"""Check if certificate expires soon (within 30 days)."""
|
331
336
|
now = datetime.now(timezone.utc)
|
332
337
|
# Ensure not_after has timezone info
|
333
|
-
not_after =
|
338
|
+
not_after = (
|
339
|
+
self.not_after.replace(tzinfo=timezone.utc)
|
340
|
+
if self.not_after.tzinfo is None
|
341
|
+
else self.not_after
|
342
|
+
)
|
334
343
|
return now + timedelta(days=30) > not_after
|
335
344
|
|
336
345
|
@property
|
@@ -340,7 +349,11 @@ class CertificateInfo(BaseModel):
|
|
340
349
|
return 0
|
341
350
|
now = datetime.now(timezone.utc)
|
342
351
|
# Ensure not_after has timezone info
|
343
|
-
not_after =
|
352
|
+
not_after = (
|
353
|
+
self.not_after.replace(tzinfo=timezone.utc)
|
354
|
+
if self.not_after.tzinfo is None
|
355
|
+
else self.not_after
|
356
|
+
)
|
344
357
|
delta = not_after - now
|
345
358
|
return delta.days
|
346
359
|
|
@@ -430,9 +443,9 @@ class CertificatePair(BaseModel):
|
|
430
443
|
"-----BEGIN RSA PRIVATE KEY-----"
|
431
444
|
):
|
432
445
|
raise ValueError("Invalid private key PEM format")
|
433
|
-
if not v.strip().endswith(
|
434
|
-
"-----END
|
435
|
-
):
|
446
|
+
if not v.strip().endswith(
|
447
|
+
"-----END PRIVATE KEY-----"
|
448
|
+
) and not v.strip().endswith("-----END RSA PRIVATE KEY-----"):
|
436
449
|
raise ValueError("Invalid private key PEM format")
|
437
450
|
return v
|
438
451
|
|