mcp-security-framework 0.1.0__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 +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,757 @@
|
|
1
|
+
"""
|
2
|
+
FastAPI Security Middleware Module
|
3
|
+
|
4
|
+
This module provides FastAPI-specific security middleware implementation
|
5
|
+
that integrates with FastAPI's middleware system and request/response
|
6
|
+
handling.
|
7
|
+
|
8
|
+
Key Features:
|
9
|
+
- FastAPI-specific request/response processing
|
10
|
+
- Integration with FastAPI middleware system
|
11
|
+
- FastAPI-specific authentication methods
|
12
|
+
- FastAPI-specific error responses
|
13
|
+
- FastAPI-specific header management
|
14
|
+
- FastAPI-specific rate limiting
|
15
|
+
|
16
|
+
Classes:
|
17
|
+
FastAPISecurityMiddleware: FastAPI-specific security middleware
|
18
|
+
FastAPIMiddlewareError: FastAPI middleware-specific error exception
|
19
|
+
|
20
|
+
Author: MCP Security Team
|
21
|
+
Version: 1.0.0
|
22
|
+
License: MIT
|
23
|
+
"""
|
24
|
+
|
25
|
+
import json
|
26
|
+
import logging
|
27
|
+
from typing import Any, Dict, List, Optional, Union
|
28
|
+
|
29
|
+
from fastapi import Request, Response, HTTPException, status
|
30
|
+
from fastapi.responses import JSONResponse
|
31
|
+
|
32
|
+
from .security_middleware import SecurityMiddleware, SecurityMiddlewareError
|
33
|
+
from ..schemas.models import AuthResult, AuthStatus, AuthMethod, ValidationResult, ValidationStatus
|
34
|
+
|
35
|
+
|
36
|
+
class FastAPIMiddlewareError(SecurityMiddlewareError):
|
37
|
+
"""Raised when FastAPI middleware encounters an error."""
|
38
|
+
|
39
|
+
def __init__(self, message: str, error_code: int = -32008):
|
40
|
+
self.message = message
|
41
|
+
self.error_code = error_code
|
42
|
+
super().__init__(self.message)
|
43
|
+
|
44
|
+
|
45
|
+
class FastAPISecurityMiddleware(SecurityMiddleware):
|
46
|
+
"""
|
47
|
+
FastAPI Security Middleware Class
|
48
|
+
|
49
|
+
This class provides FastAPI-specific implementation of the security
|
50
|
+
middleware. It integrates with FastAPI's middleware system and
|
51
|
+
handles FastAPI Request/Response objects.
|
52
|
+
|
53
|
+
The FastAPISecurityMiddleware implements:
|
54
|
+
- FastAPI-specific request processing
|
55
|
+
- FastAPI authentication method handling
|
56
|
+
- FastAPI response creation and modification
|
57
|
+
- FastAPI-specific error handling
|
58
|
+
- FastAPI header management
|
59
|
+
- FastAPI rate limiting integration
|
60
|
+
|
61
|
+
Key Responsibilities:
|
62
|
+
- Process FastAPI requests through security pipeline
|
63
|
+
- Extract authentication credentials from FastAPI requests
|
64
|
+
- Create FastAPI-specific error responses
|
65
|
+
- Add security headers to FastAPI responses
|
66
|
+
- Handle FastAPI-specific request/response objects
|
67
|
+
- Integrate with FastAPI middleware system
|
68
|
+
|
69
|
+
Attributes:
|
70
|
+
Inherits all attributes from SecurityMiddleware
|
71
|
+
_fastapi_app: Reference to FastAPI application (if available)
|
72
|
+
|
73
|
+
Example:
|
74
|
+
>>> from fastapi import FastAPI
|
75
|
+
>>> from mcp_security_framework.middleware import FastAPISecurityMiddleware
|
76
|
+
>>>
|
77
|
+
>>> app = FastAPI()
|
78
|
+
>>> security_manager = SecurityManager(config)
|
79
|
+
>>> middleware = FastAPISecurityMiddleware(security_manager)
|
80
|
+
>>> app.add_middleware(middleware)
|
81
|
+
|
82
|
+
Note:
|
83
|
+
This middleware should be added to FastAPI applications using
|
84
|
+
the add_middleware method or as a dependency.
|
85
|
+
"""
|
86
|
+
|
87
|
+
def __init__(self, security_manager=None, app=None):
|
88
|
+
"""
|
89
|
+
Initialize FastAPI Security Middleware.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
security_manager: Security manager instance containing
|
93
|
+
all security components and configuration.
|
94
|
+
app: FastAPI application instance (optional, for compatibility)
|
95
|
+
|
96
|
+
Raises:
|
97
|
+
FastAPIMiddlewareError: If initialization fails
|
98
|
+
"""
|
99
|
+
if security_manager is None:
|
100
|
+
raise FastAPIMiddlewareError("Security manager is required")
|
101
|
+
|
102
|
+
super().__init__(security_manager)
|
103
|
+
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
104
|
+
self.app = app
|
105
|
+
|
106
|
+
self.logger.info("FastAPI Security Middleware initialized")
|
107
|
+
|
108
|
+
async def __call__(self, request: Request, call_next):
|
109
|
+
"""
|
110
|
+
Process FastAPI request through security middleware.
|
111
|
+
|
112
|
+
This method implements the security processing pipeline for
|
113
|
+
FastAPI requests, including rate limiting, authentication,
|
114
|
+
authorization, and security header management.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
request (Request): FastAPI request object
|
118
|
+
call_next: FastAPI call_next function
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
Response: FastAPI response object
|
122
|
+
|
123
|
+
Raises:
|
124
|
+
HTTPException: For security violations (rate limit, auth, permissions)
|
125
|
+
FastAPIMiddlewareError: For middleware processing errors
|
126
|
+
"""
|
127
|
+
try:
|
128
|
+
# Check if public path first (no rate limiting for public paths)
|
129
|
+
if self._is_public_path(request):
|
130
|
+
response = await call_next(request)
|
131
|
+
self._add_security_headers(response)
|
132
|
+
return response
|
133
|
+
|
134
|
+
# Check rate limit
|
135
|
+
if not self._check_rate_limit(request):
|
136
|
+
return self._rate_limit_response()
|
137
|
+
|
138
|
+
# Authenticate request
|
139
|
+
auth_result = await self._authenticate_request(request)
|
140
|
+
if not auth_result.is_valid:
|
141
|
+
return self._auth_error_response(auth_result)
|
142
|
+
|
143
|
+
# Validate permissions
|
144
|
+
if not self._validate_permissions(request, auth_result):
|
145
|
+
return self._permission_error_response()
|
146
|
+
|
147
|
+
# Process request
|
148
|
+
response = await call_next(request)
|
149
|
+
self._add_security_headers(response)
|
150
|
+
|
151
|
+
# Log successful request
|
152
|
+
self._log_security_event("request_processed", {
|
153
|
+
"ip_address": self._get_client_ip(request),
|
154
|
+
"username": auth_result.username,
|
155
|
+
"path": str(request.url.path),
|
156
|
+
"method": request.method,
|
157
|
+
"status_code": response.status_code
|
158
|
+
})
|
159
|
+
|
160
|
+
return response
|
161
|
+
|
162
|
+
except HTTPException:
|
163
|
+
# Re-raise FastAPI HTTP exceptions
|
164
|
+
raise
|
165
|
+
except Exception as e:
|
166
|
+
self.logger.error(
|
167
|
+
"FastAPI middleware processing failed",
|
168
|
+
extra={"error": str(e)},
|
169
|
+
exc_info=True
|
170
|
+
)
|
171
|
+
raise FastAPIMiddlewareError(
|
172
|
+
f"Middleware processing failed: {str(e)}",
|
173
|
+
error_code=-32009
|
174
|
+
)
|
175
|
+
|
176
|
+
def _check_rate_limit(self, request: Request) -> bool:
|
177
|
+
"""Check rate limit for request."""
|
178
|
+
identifier = self._get_rate_limit_identifier(request)
|
179
|
+
return self.security_manager.check_rate_limit(identifier)
|
180
|
+
|
181
|
+
def _is_public_path(self, request: Request) -> bool:
|
182
|
+
"""Check if request path is public."""
|
183
|
+
path = str(request.url.path)
|
184
|
+
return path in self.config.auth.public_paths
|
185
|
+
|
186
|
+
async def _authenticate_request(self, request: Request) -> AuthResult:
|
187
|
+
"""Authenticate request using configured methods."""
|
188
|
+
# Try API key authentication first
|
189
|
+
auth_result = self._try_api_key_auth(request)
|
190
|
+
if auth_result.is_valid:
|
191
|
+
return auth_result
|
192
|
+
|
193
|
+
# Try JWT authentication
|
194
|
+
auth_result = self._try_jwt_auth(request)
|
195
|
+
if auth_result.is_valid:
|
196
|
+
return auth_result
|
197
|
+
|
198
|
+
# Return failed authentication
|
199
|
+
return AuthResult(
|
200
|
+
is_valid=False,
|
201
|
+
status=AuthStatus.INVALID,
|
202
|
+
auth_method="unknown",
|
203
|
+
error_code=-32001,
|
204
|
+
error_message="Authentication required"
|
205
|
+
)
|
206
|
+
|
207
|
+
def _validate_permissions(self, request: Request, auth_result: AuthResult) -> bool:
|
208
|
+
"""Validate permissions for request."""
|
209
|
+
# Get required permissions from request state or default
|
210
|
+
required_permissions = self._get_required_permissions_from_state(request)
|
211
|
+
if not required_permissions:
|
212
|
+
return True # No permissions required
|
213
|
+
|
214
|
+
# Check permissions
|
215
|
+
validation_result = self.security_manager.check_permissions(
|
216
|
+
auth_result.roles, required_permissions
|
217
|
+
)
|
218
|
+
return validation_result.is_valid
|
219
|
+
|
220
|
+
def _rate_limit_response(self) -> JSONResponse:
|
221
|
+
"""Create rate limit exceeded response."""
|
222
|
+
return JSONResponse(
|
223
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
224
|
+
content={
|
225
|
+
"error": "Rate limit exceeded",
|
226
|
+
"error_code": -32010,
|
227
|
+
"retry_after": 60
|
228
|
+
}
|
229
|
+
)
|
230
|
+
|
231
|
+
def _auth_error_response(self, auth_result: AuthResult) -> JSONResponse:
|
232
|
+
"""Create authentication error response."""
|
233
|
+
return JSONResponse(
|
234
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
235
|
+
content={
|
236
|
+
"error": "Authentication failed",
|
237
|
+
"error_code": auth_result.error_code,
|
238
|
+
"error_message": auth_result.error_message
|
239
|
+
}
|
240
|
+
)
|
241
|
+
|
242
|
+
def _permission_error_response(self) -> JSONResponse:
|
243
|
+
"""Create permission denied response."""
|
244
|
+
return JSONResponse(
|
245
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
246
|
+
content={
|
247
|
+
"error": "Permission denied",
|
248
|
+
"error_code": -32004,
|
249
|
+
"error_message": "Insufficient permissions"
|
250
|
+
}
|
251
|
+
)
|
252
|
+
|
253
|
+
def _add_security_headers(self, response: Response) -> None:
|
254
|
+
"""Add security headers to response."""
|
255
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
256
|
+
response.headers["X-Frame-Options"] = "DENY"
|
257
|
+
response.headers["X-XSS-Protection"] = "1; mode=block"
|
258
|
+
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
259
|
+
|
260
|
+
def _log_security_event(self, event_type: str, data: Dict[str, Any]) -> None:
|
261
|
+
"""Log security event."""
|
262
|
+
self.logger.info(
|
263
|
+
f"Security event: {event_type}",
|
264
|
+
extra=data
|
265
|
+
)
|
266
|
+
|
267
|
+
def _get_required_permissions_from_state(self, request: Request) -> List[str]:
|
268
|
+
"""Get required permissions from request state."""
|
269
|
+
# This would typically be set by route decorators or middleware
|
270
|
+
# For now, return empty list (no permissions required)
|
271
|
+
return []
|
272
|
+
|
273
|
+
def _get_required_permissions_default(self, request: Request) -> List[str]:
|
274
|
+
"""Get default required permissions for request."""
|
275
|
+
# Default permissions based on HTTP method
|
276
|
+
method_permissions = {
|
277
|
+
"GET": ["read"],
|
278
|
+
"POST": ["write"],
|
279
|
+
"PUT": ["write"],
|
280
|
+
"DELETE": ["delete"],
|
281
|
+
"PATCH": ["write"]
|
282
|
+
}
|
283
|
+
return method_permissions.get(request.method, [])
|
284
|
+
|
285
|
+
def _try_api_key_auth(self, request: Request) -> AuthResult:
|
286
|
+
"""Try API key authentication."""
|
287
|
+
# Check for API key in headers
|
288
|
+
api_key = request.headers.get("X-API-Key")
|
289
|
+
if api_key:
|
290
|
+
return self.security_manager.auth_manager.authenticate_api_key(api_key)
|
291
|
+
|
292
|
+
# Check for API key in Authorization header
|
293
|
+
auth_header = request.headers.get("Authorization")
|
294
|
+
if auth_header and auth_header.startswith("Bearer "):
|
295
|
+
api_key = auth_header[7:] # Remove "Bearer " prefix
|
296
|
+
return self.security_manager.auth_manager.authenticate_api_key(api_key)
|
297
|
+
|
298
|
+
return AuthResult(
|
299
|
+
is_valid=False,
|
300
|
+
status=AuthStatus.INVALID,
|
301
|
+
auth_method="api_key",
|
302
|
+
error_code=-32001,
|
303
|
+
error_message="API key not provided"
|
304
|
+
)
|
305
|
+
|
306
|
+
def _try_jwt_auth(self, request: Request) -> AuthResult:
|
307
|
+
"""Try JWT authentication."""
|
308
|
+
auth_header = request.headers.get("Authorization")
|
309
|
+
if not auth_header or not auth_header.startswith("Bearer "):
|
310
|
+
return AuthResult(
|
311
|
+
is_valid=False,
|
312
|
+
status=AuthStatus.INVALID,
|
313
|
+
auth_method="jwt",
|
314
|
+
error_code=-32001,
|
315
|
+
error_message="JWT token not provided"
|
316
|
+
)
|
317
|
+
|
318
|
+
token = auth_header[7:] # Remove "Bearer " prefix
|
319
|
+
return self.security_manager.auth_manager.authenticate_jwt_token(token)
|
320
|
+
|
321
|
+
def _get_client_ip(self, request: Request) -> str:
|
322
|
+
"""Get client IP address from request."""
|
323
|
+
# Check for forwarded headers
|
324
|
+
forwarded_for = request.headers.get("X-Forwarded-For")
|
325
|
+
if forwarded_for:
|
326
|
+
return forwarded_for.split(",")[0].strip()
|
327
|
+
|
328
|
+
# Check for real IP header
|
329
|
+
real_ip = request.headers.get("X-Real-IP")
|
330
|
+
if real_ip:
|
331
|
+
return real_ip
|
332
|
+
|
333
|
+
# Check for client host
|
334
|
+
if request.client and request.client.host:
|
335
|
+
return request.client.host
|
336
|
+
|
337
|
+
# Fallback
|
338
|
+
return "unknown"
|
339
|
+
|
340
|
+
def _get_rate_limit_identifier(self, request: Request) -> str:
|
341
|
+
"""
|
342
|
+
Get rate limit identifier from FastAPI request.
|
343
|
+
|
344
|
+
This method extracts the rate limit identifier from the FastAPI
|
345
|
+
request, typically using the client IP address.
|
346
|
+
|
347
|
+
Args:
|
348
|
+
request (Request): FastAPI request object
|
349
|
+
|
350
|
+
Returns:
|
351
|
+
str: Rate limit identifier (IP address)
|
352
|
+
"""
|
353
|
+
return self._get_client_ip(request)
|
354
|
+
|
355
|
+
def _get_request_path(self, request: Request) -> str:
|
356
|
+
"""
|
357
|
+
Get request path from FastAPI request.
|
358
|
+
|
359
|
+
Args:
|
360
|
+
request (Request): FastAPI request object
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
str: Request path
|
364
|
+
"""
|
365
|
+
return str(request.url.path)
|
366
|
+
|
367
|
+
def _get_required_permissions(self, request: Request) -> List[str]:
|
368
|
+
"""
|
369
|
+
Get required permissions for FastAPI request.
|
370
|
+
|
371
|
+
This method extracts required permissions from the FastAPI request,
|
372
|
+
typically from route dependencies or request state.
|
373
|
+
|
374
|
+
Args:
|
375
|
+
request (Request): FastAPI request object
|
376
|
+
|
377
|
+
Returns:
|
378
|
+
List[str]: List of required permissions
|
379
|
+
"""
|
380
|
+
# Try to get permissions from request state
|
381
|
+
if hasattr(request.state, 'required_permissions'):
|
382
|
+
return request.state.required_permissions
|
383
|
+
|
384
|
+
# Try to get permissions from route dependencies
|
385
|
+
if hasattr(request, 'route') and hasattr(request.route, 'dependencies'):
|
386
|
+
# Check if route has permission dependencies
|
387
|
+
dependencies = request.route.dependencies
|
388
|
+
for dep in dependencies:
|
389
|
+
if hasattr(dep, 'dependency') and hasattr(dep.dependency, 'required_permissions'):
|
390
|
+
return dep.dependency.required_permissions
|
391
|
+
# Check for permission decorators
|
392
|
+
if hasattr(dep, 'dependency') and hasattr(dep.dependency, '__permissions__'):
|
393
|
+
return dep.dependency.__permissions__
|
394
|
+
|
395
|
+
# Default: no specific permissions required
|
396
|
+
return []
|
397
|
+
|
398
|
+
async def _authenticate_request(self, request: Request) -> AuthResult:
|
399
|
+
"""
|
400
|
+
Authenticate the request using configured authentication methods.
|
401
|
+
|
402
|
+
This method attempts to authenticate the request using all configured
|
403
|
+
authentication methods in order until one succeeds.
|
404
|
+
|
405
|
+
Args:
|
406
|
+
request (Request): FastAPI request object
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
AuthResult: Authentication result
|
410
|
+
|
411
|
+
Raises:
|
412
|
+
SecurityMiddlewareError: If authentication process fails
|
413
|
+
"""
|
414
|
+
try:
|
415
|
+
if not self.config.auth.enabled:
|
416
|
+
return AuthResult(
|
417
|
+
is_valid=True,
|
418
|
+
status=AuthStatus.SUCCESS,
|
419
|
+
username="anonymous",
|
420
|
+
roles=[],
|
421
|
+
auth_method=None
|
422
|
+
)
|
423
|
+
|
424
|
+
# Try each authentication method in order
|
425
|
+
for method in self.config.auth.methods:
|
426
|
+
auth_result = await self._try_auth_method(request, method)
|
427
|
+
if auth_result.is_valid:
|
428
|
+
self.logger.info(
|
429
|
+
"Authentication successful",
|
430
|
+
extra={
|
431
|
+
"username": auth_result.username,
|
432
|
+
"auth_method": auth_result.auth_method,
|
433
|
+
"user_roles": auth_result.roles
|
434
|
+
}
|
435
|
+
)
|
436
|
+
return auth_result
|
437
|
+
|
438
|
+
# All authentication methods failed
|
439
|
+
self.logger.warning(
|
440
|
+
"All authentication methods failed",
|
441
|
+
extra={"auth_methods": self.config.auth.methods}
|
442
|
+
)
|
443
|
+
|
444
|
+
return AuthResult(
|
445
|
+
is_valid=False,
|
446
|
+
status=AuthStatus.FAILED,
|
447
|
+
username=None,
|
448
|
+
roles=[],
|
449
|
+
auth_method=None,
|
450
|
+
error_code=-32005,
|
451
|
+
error_message="All authentication methods failed"
|
452
|
+
)
|
453
|
+
|
454
|
+
except Exception as e:
|
455
|
+
self.logger.error(
|
456
|
+
"Authentication process failed",
|
457
|
+
extra={"error": str(e)},
|
458
|
+
exc_info=True
|
459
|
+
)
|
460
|
+
raise SecurityMiddlewareError(
|
461
|
+
f"Authentication process failed: {str(e)}",
|
462
|
+
error_code=-32006
|
463
|
+
)
|
464
|
+
|
465
|
+
async def _try_auth_method(self, request: Request, method: str) -> AuthResult:
|
466
|
+
"""
|
467
|
+
Try authentication using specific method with FastAPI request.
|
468
|
+
|
469
|
+
This method attempts to authenticate the FastAPI request using
|
470
|
+
the specified authentication method.
|
471
|
+
|
472
|
+
Args:
|
473
|
+
request (Request): FastAPI request object
|
474
|
+
method (str): Authentication method to try
|
475
|
+
|
476
|
+
Returns:
|
477
|
+
AuthResult: Authentication result
|
478
|
+
"""
|
479
|
+
try:
|
480
|
+
if method == "api_key":
|
481
|
+
return await self._try_api_key_auth(request)
|
482
|
+
elif method == "jwt":
|
483
|
+
return await self._try_jwt_auth(request)
|
484
|
+
elif method == "certificate":
|
485
|
+
return await self._try_certificate_auth(request)
|
486
|
+
elif method == "basic":
|
487
|
+
return await self._try_basic_auth(request)
|
488
|
+
else:
|
489
|
+
return AuthResult(
|
490
|
+
is_valid=False,
|
491
|
+
status=AuthStatus.FAILED,
|
492
|
+
username=None,
|
493
|
+
roles=[],
|
494
|
+
auth_method=None,
|
495
|
+
error_code=-32010,
|
496
|
+
error_message=f"Unsupported authentication method: {method}"
|
497
|
+
)
|
498
|
+
except Exception as e:
|
499
|
+
self.logger.error(
|
500
|
+
f"Authentication method {method} failed",
|
501
|
+
extra={"error": str(e)},
|
502
|
+
exc_info=True
|
503
|
+
)
|
504
|
+
return AuthResult(
|
505
|
+
is_valid=False,
|
506
|
+
status=AuthStatus.FAILED,
|
507
|
+
username=None,
|
508
|
+
roles=[],
|
509
|
+
auth_method=None,
|
510
|
+
error_code=-32011,
|
511
|
+
error_message=f"Authentication method {method} failed: {str(e)}"
|
512
|
+
)
|
513
|
+
|
514
|
+
async def _try_api_key_auth(self, request: Request) -> AuthResult:
|
515
|
+
"""
|
516
|
+
Try API key authentication with FastAPI request.
|
517
|
+
|
518
|
+
Args:
|
519
|
+
request (Request): FastAPI request object
|
520
|
+
|
521
|
+
Returns:
|
522
|
+
AuthResult: Authentication result
|
523
|
+
"""
|
524
|
+
# Try to get API key from headers
|
525
|
+
api_key = request.headers.get("X-API-Key")
|
526
|
+
if not api_key:
|
527
|
+
# Try Authorization header
|
528
|
+
auth_header = request.headers.get("Authorization")
|
529
|
+
if auth_header and auth_header.startswith("Bearer "):
|
530
|
+
api_key = auth_header[7:] # Remove "Bearer " prefix
|
531
|
+
|
532
|
+
if not api_key:
|
533
|
+
return AuthResult(
|
534
|
+
is_valid=False,
|
535
|
+
status=AuthStatus.FAILED,
|
536
|
+
username=None,
|
537
|
+
roles=[],
|
538
|
+
auth_method=AuthMethod.API_KEY,
|
539
|
+
error_code=-32012,
|
540
|
+
error_message="API key not found in request"
|
541
|
+
)
|
542
|
+
|
543
|
+
# Authenticate using security manager
|
544
|
+
return self.security_manager.auth_manager.authenticate_api_key(api_key)
|
545
|
+
|
546
|
+
async def _try_jwt_auth(self, request: Request) -> AuthResult:
|
547
|
+
"""
|
548
|
+
Try JWT authentication with FastAPI request.
|
549
|
+
|
550
|
+
Args:
|
551
|
+
request (Request): FastAPI request object
|
552
|
+
|
553
|
+
Returns:
|
554
|
+
AuthResult: Authentication result
|
555
|
+
"""
|
556
|
+
# Try to get JWT token from Authorization header
|
557
|
+
auth_header = request.headers.get("Authorization")
|
558
|
+
if not auth_header or not auth_header.startswith("Bearer "):
|
559
|
+
return AuthResult(
|
560
|
+
is_valid=False,
|
561
|
+
status=AuthStatus.FAILED,
|
562
|
+
username=None,
|
563
|
+
roles=[],
|
564
|
+
auth_method=AuthMethod.JWT,
|
565
|
+
error_code=-32013,
|
566
|
+
error_message="JWT token not found in Authorization header"
|
567
|
+
)
|
568
|
+
|
569
|
+
token = auth_header[7:] # Remove "Bearer " prefix
|
570
|
+
|
571
|
+
# Authenticate using security manager
|
572
|
+
return self.security_manager.auth_manager.authenticate_jwt_token(token)
|
573
|
+
|
574
|
+
async def _try_certificate_auth(self, request: Request) -> AuthResult:
|
575
|
+
"""
|
576
|
+
Try certificate authentication with FastAPI request.
|
577
|
+
|
578
|
+
Args:
|
579
|
+
request (Request): FastAPI request object
|
580
|
+
|
581
|
+
Returns:
|
582
|
+
AuthResult: Authentication result
|
583
|
+
"""
|
584
|
+
# For certificate authentication, we would typically need
|
585
|
+
# to access the client certificate from the SSL context
|
586
|
+
# This is more complex and depends on the SSL configuration
|
587
|
+
|
588
|
+
# For now, return not implemented
|
589
|
+
return AuthResult(
|
590
|
+
is_valid=False,
|
591
|
+
status=AuthStatus.FAILED,
|
592
|
+
username=None,
|
593
|
+
roles=[],
|
594
|
+
auth_method=AuthMethod.CERTIFICATE,
|
595
|
+
error_code=-32014,
|
596
|
+
error_message="Certificate authentication not implemented"
|
597
|
+
)
|
598
|
+
|
599
|
+
async def _try_basic_auth(self, request: Request) -> AuthResult:
|
600
|
+
"""
|
601
|
+
Try basic authentication with FastAPI request.
|
602
|
+
|
603
|
+
Args:
|
604
|
+
request (Request): FastAPI request object
|
605
|
+
|
606
|
+
Returns:
|
607
|
+
AuthResult: Authentication result
|
608
|
+
"""
|
609
|
+
# Try to get basic auth from Authorization header
|
610
|
+
auth_header = request.headers.get("Authorization")
|
611
|
+
if not auth_header or not auth_header.startswith("Basic "):
|
612
|
+
return AuthResult(
|
613
|
+
is_valid=False,
|
614
|
+
status=AuthStatus.FAILED,
|
615
|
+
username=None,
|
616
|
+
roles=[],
|
617
|
+
auth_method=AuthMethod.BASIC,
|
618
|
+
error_code=-32015,
|
619
|
+
error_message="Basic authentication credentials not found"
|
620
|
+
)
|
621
|
+
|
622
|
+
# Basic auth implementation would go here
|
623
|
+
# For now, return not implemented
|
624
|
+
return AuthResult(
|
625
|
+
is_valid=False,
|
626
|
+
status=AuthStatus.FAILED,
|
627
|
+
username=None,
|
628
|
+
roles=[],
|
629
|
+
auth_method=AuthMethod.BASIC,
|
630
|
+
error_code=-32016,
|
631
|
+
error_message="Basic authentication not implemented"
|
632
|
+
)
|
633
|
+
|
634
|
+
def _apply_security_headers(self, response: Response, headers: Dict[str, str]) -> None:
|
635
|
+
"""
|
636
|
+
Apply security headers to FastAPI response.
|
637
|
+
|
638
|
+
Args:
|
639
|
+
response (Response): FastAPI response object
|
640
|
+
headers (Dict[str, str]): Headers to apply
|
641
|
+
"""
|
642
|
+
for header_name, header_value in headers.items():
|
643
|
+
response.headers[header_name] = header_value
|
644
|
+
|
645
|
+
def _create_error_response(self, status_code: int, message: str) -> Response:
|
646
|
+
"""
|
647
|
+
Create error response for security violations.
|
648
|
+
|
649
|
+
Args:
|
650
|
+
status_code (int): HTTP status code
|
651
|
+
message (str): Error message
|
652
|
+
|
653
|
+
Returns:
|
654
|
+
Response: FastAPI error response
|
655
|
+
"""
|
656
|
+
return JSONResponse(
|
657
|
+
status_code=status_code,
|
658
|
+
content={
|
659
|
+
"error": "Security violation",
|
660
|
+
"message": message,
|
661
|
+
"error_code": -32017
|
662
|
+
}
|
663
|
+
)
|
664
|
+
|
665
|
+
def _rate_limit_response(self) -> Response:
|
666
|
+
"""
|
667
|
+
Create rate limit exceeded response.
|
668
|
+
|
669
|
+
Returns:
|
670
|
+
Response: FastAPI rate limit response
|
671
|
+
"""
|
672
|
+
return JSONResponse(
|
673
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
674
|
+
content={
|
675
|
+
"error": "Rate limit exceeded",
|
676
|
+
"message": "Too many requests, please try again later",
|
677
|
+
"error_code": -32018
|
678
|
+
},
|
679
|
+
headers={
|
680
|
+
"Retry-After": str(self.config.rate_limit.window_size_seconds)
|
681
|
+
}
|
682
|
+
)
|
683
|
+
|
684
|
+
def _auth_error_response(self, auth_result: AuthResult) -> Response:
|
685
|
+
"""
|
686
|
+
Create authentication error response.
|
687
|
+
|
688
|
+
Args:
|
689
|
+
auth_result (AuthResult): Authentication result
|
690
|
+
|
691
|
+
Returns:
|
692
|
+
Response: FastAPI authentication error response
|
693
|
+
"""
|
694
|
+
return JSONResponse(
|
695
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
696
|
+
content={
|
697
|
+
"error": "Authentication failed",
|
698
|
+
"message": auth_result.error_message or "Invalid credentials",
|
699
|
+
"error_code": auth_result.error_code,
|
700
|
+
"auth_method": auth_result.auth_method
|
701
|
+
},
|
702
|
+
headers={
|
703
|
+
"WWW-Authenticate": "Bearer, ApiKey"
|
704
|
+
}
|
705
|
+
)
|
706
|
+
|
707
|
+
def _permission_error_response(self) -> Response:
|
708
|
+
"""
|
709
|
+
Create permission denied response.
|
710
|
+
|
711
|
+
Returns:
|
712
|
+
Response: FastAPI permission error response
|
713
|
+
"""
|
714
|
+
return JSONResponse(
|
715
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
716
|
+
content={
|
717
|
+
"error": "Permission denied",
|
718
|
+
"message": "Insufficient permissions to access this resource",
|
719
|
+
"error_code": -32019
|
720
|
+
}
|
721
|
+
)
|
722
|
+
|
723
|
+
def _get_client_ip(self, request: Request) -> str:
|
724
|
+
"""
|
725
|
+
Get client IP address from FastAPI request.
|
726
|
+
|
727
|
+
Args:
|
728
|
+
request (Request): FastAPI request object
|
729
|
+
|
730
|
+
Returns:
|
731
|
+
str: Client IP address
|
732
|
+
"""
|
733
|
+
# Try to get IP from X-Forwarded-For header (for proxy scenarios)
|
734
|
+
forwarded_for = request.headers.get("X-Forwarded-For")
|
735
|
+
if forwarded_for:
|
736
|
+
# Take the first IP in the chain
|
737
|
+
return forwarded_for.split(",")[0].strip()
|
738
|
+
|
739
|
+
# Try to get IP from X-Real-IP header
|
740
|
+
real_ip = request.headers.get("X-Real-IP")
|
741
|
+
if real_ip:
|
742
|
+
return real_ip
|
743
|
+
|
744
|
+
# Fall back to client host
|
745
|
+
if request.client:
|
746
|
+
return request.client.host
|
747
|
+
|
748
|
+
# Default fallback
|
749
|
+
# Fallback to default IP from config or environment
|
750
|
+
default_ip = getattr(self.config, 'default_client_ip', None)
|
751
|
+
if default_ip:
|
752
|
+
return default_ip
|
753
|
+
|
754
|
+
# Use environment variable or default
|
755
|
+
import os
|
756
|
+
from ..constants import DEFAULT_CLIENT_IP
|
757
|
+
return os.environ.get('DEFAULT_CLIENT_IP', DEFAULT_CLIENT_IP)
|