mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. mcp_security_framework/__init__.py +26 -15
  2. mcp_security_framework/cli/__init__.py +1 -1
  3. mcp_security_framework/cli/cert_cli.py +233 -197
  4. mcp_security_framework/cli/security_cli.py +324 -234
  5. mcp_security_framework/constants.py +21 -27
  6. mcp_security_framework/core/auth_manager.py +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.1.dist-info}/METADATA +2 -1
  34. mcp_security_framework-1.1.1.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.1.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
  58. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -17,34 +17,39 @@ Version: 1.0.0
17
17
  License: MIT
18
18
  """
19
19
 
20
- import os
21
20
  import json
22
21
  import logging
23
- from typing import Dict, List, Any, Optional
22
+ import os
24
23
  from datetime import datetime, timedelta, timezone
24
+ from typing import Any, Dict, List, Optional
25
25
 
26
- from flask import Flask, request, jsonify, g, session, current_app
27
- from flask_cors import CORS
28
26
  import werkzeug
27
+ from flask import Flask, current_app, g, jsonify, request, session
28
+ from flask_cors import CORS
29
29
 
30
- from mcp_security_framework.core.security_manager import SecurityManager
30
+ from mcp_security_framework.constants import (
31
+ AUTH_METHODS,
32
+ DEFAULT_CLIENT_IP,
33
+ DEFAULT_SECURITY_HEADERS,
34
+ HTTP_FORBIDDEN,
35
+ HTTP_TOO_MANY_REQUESTS,
36
+ HTTP_UNAUTHORIZED,
37
+ ErrorCodes,
38
+ )
31
39
  from mcp_security_framework.core.auth_manager import AuthManager
32
- from mcp_security_framework.core.ssl_manager import SSLManager
33
40
  from mcp_security_framework.core.permission_manager import PermissionManager
34
41
  from mcp_security_framework.core.rate_limiter import RateLimiter
35
- from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, SSLConfig
36
- from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
42
+ from mcp_security_framework.core.security_manager import SecurityManager
43
+ from mcp_security_framework.core.ssl_manager import SSLManager
37
44
  from mcp_security_framework.middleware.flask_middleware import FlaskSecurityMiddleware
38
- from mcp_security_framework.constants import (
39
- DEFAULT_CLIENT_IP, DEFAULT_SECURITY_HEADERS, AUTH_METHODS,
40
- ErrorCodes, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_TOO_MANY_REQUESTS
41
- )
45
+ from mcp_security_framework.schemas.config import AuthConfig, SecurityConfig, SSLConfig
46
+ from mcp_security_framework.schemas.models import AuthMethod, AuthResult, AuthStatus
42
47
 
43
48
 
44
49
  class FlaskExample:
45
50
  """
46
51
  Complete Flask Example with Security Framework Implementation
47
-
52
+
48
53
  This class demonstrates a production-ready Flask application
49
54
  with comprehensive security features including:
50
55
  - Multi-method authentication (API Key, JWT, Certificate)
@@ -54,11 +59,11 @@ class FlaskExample:
54
59
  - Security headers and CORS
55
60
  - Comprehensive logging and monitoring
56
61
  """
57
-
62
+
58
63
  def __init__(self, config_path: Optional[str] = None):
59
64
  """
60
65
  Initialize Flask example with security configuration.
61
-
66
+
62
67
  Args:
63
68
  config_path: Path to security configuration file
64
69
  """
@@ -68,37 +73,41 @@ class FlaskExample:
68
73
  self._setup_middleware()
69
74
  self._setup_routes()
70
75
  self._setup_error_handlers()
71
-
76
+
72
77
  def _load_config(self, config_path: Optional[str]) -> SecurityConfig:
73
78
  """
74
79
  Load security configuration from file or create default.
75
-
80
+
76
81
  Args:
77
82
  config_path: Path to configuration file
78
-
83
+
79
84
  Returns:
80
85
  SecurityConfig: Loaded configuration
81
86
  """
82
87
  if config_path and os.path.exists(config_path):
83
- with open(config_path, 'r') as f:
88
+ with open(config_path, "r") as f:
84
89
  config_data = json.load(f)
85
90
  return SecurityConfig(**config_data)
86
-
91
+
87
92
  # Create production-ready default configuration
88
93
  return SecurityConfig(
89
94
  auth=AuthConfig(
90
95
  enabled=True,
91
- methods=[AUTH_METHODS["API_KEY"], AUTH_METHODS["JWT"], AUTH_METHODS["CERTIFICATE"]],
96
+ methods=[
97
+ AUTH_METHODS["API_KEY"],
98
+ AUTH_METHODS["JWT"],
99
+ AUTH_METHODS["CERTIFICATE"],
100
+ ],
92
101
  api_keys={
93
102
  "admin_key_123": {"username": "admin", "roles": ["admin", "user"]},
94
103
  "user_key_456": {"username": "user", "roles": ["user"]},
95
- "readonly_key_789": {"username": "readonly", "roles": ["readonly"]}
104
+ "readonly_key_789": {"username": "readonly", "roles": ["readonly"]},
96
105
  },
97
106
  jwt_secret="your-super-secret-jwt-key-change-in-production",
98
107
  jwt_algorithm="HS256",
99
108
  jwt_expiry_hours=24,
100
109
  public_paths=["/health", "/docs", "/metrics"],
101
- security_headers=DEFAULT_SECURITY_HEADERS
110
+ security_headers=DEFAULT_SECURITY_HEADERS,
102
111
  ),
103
112
  ssl=SSLConfig(
104
113
  enabled=False, # Disable SSL for testing
@@ -106,7 +115,7 @@ class FlaskExample:
106
115
  key_file=None,
107
116
  ca_cert_file=None,
108
117
  verify_mode="CERT_REQUIRED",
109
- min_version="TLSv1.2"
118
+ min_version="TLSv1.2",
110
119
  ),
111
120
  rate_limit={
112
121
  "enabled": True,
@@ -119,16 +128,16 @@ class FlaskExample:
119
128
  "host": "localhost",
120
129
  "port": 6379,
121
130
  "db": 0,
122
- "password": None
131
+ "password": None,
123
132
  },
124
133
  "exempt_paths": ["/health", "/metrics"],
125
- "exempt_roles": ["admin"]
134
+ "exempt_roles": ["admin"],
126
135
  },
127
136
  permissions={
128
137
  "enabled": True,
129
138
  "roles_file": "config/roles.json",
130
139
  "default_role": "user",
131
- "hierarchy_enabled": True
140
+ "hierarchy_enabled": True,
132
141
  },
133
142
  logging={
134
143
  "enabled": True,
@@ -138,100 +147,108 @@ class FlaskExample:
138
147
  "max_file_size": 10,
139
148
  "backup_count": 5,
140
149
  "console_output": True,
141
- "json_format": False
142
- }
150
+ "json_format": False,
151
+ },
143
152
  )
144
-
153
+
145
154
  def _create_flask_app(self) -> Flask:
146
155
  """
147
156
  Create Flask application with security features.
148
-
157
+
149
158
  Returns:
150
159
  Flask: Configured Flask application
151
160
  """
152
161
  app = Flask(__name__)
153
- app.config['SECRET_KEY'] = 'your-secret-key-change-in-production'
154
- app.config['JSON_SORT_KEYS'] = False
155
-
162
+ app.config["SECRET_KEY"] = "your-secret-key-change-in-production"
163
+ app.config["JSON_SORT_KEYS"] = False
164
+
156
165
  # Add CORS support
157
166
  CORS(app, origins=["https://trusted-domain.com"])
158
-
167
+
159
168
  return app
160
-
169
+
161
170
  def _setup_middleware(self):
162
171
  """Setup security middleware."""
163
172
  # For now, skip middleware setup to avoid WSGI issues
164
173
  # and use fallback authentication for testing
165
174
  print("Middleware setup skipped - using fallback authentication")
166
175
  self._setup_test_authentication()
167
-
176
+
168
177
  def _setup_test_authentication(self):
169
178
  """Setup authentication for testing environment."""
170
179
  security_manager = self.security_manager
171
-
180
+
172
181
  def get_current_user():
173
182
  """Get current user from request headers."""
174
183
  api_key = request.headers.get("X-API-Key")
175
184
  if api_key:
176
185
  try:
177
- auth_result = security_manager.auth_manager.authenticate_api_key(api_key)
186
+ auth_result = security_manager.auth_manager.authenticate_api_key(
187
+ api_key
188
+ )
178
189
  if auth_result.is_valid:
179
190
  return auth_result
180
191
  except Exception as e:
181
192
  print(f"Authentication error: {e}")
182
193
  pass
183
-
194
+
184
195
  # Check for JWT token
185
196
  auth_header = request.headers.get("Authorization")
186
197
  if auth_header and auth_header.startswith("Bearer "):
187
198
  token = auth_header.split(" ")[1]
188
199
  try:
189
- auth_result = security_manager.auth_manager.authenticate_jwt_token(token)
200
+ auth_result = security_manager.auth_manager.authenticate_jwt_token(
201
+ token
202
+ )
190
203
  if auth_result.is_valid:
191
204
  return auth_result
192
205
  except Exception as e:
193
206
  print(f"JWT authentication error: {e}")
194
207
  pass
195
-
208
+
196
209
  return None
197
-
210
+
198
211
  # Store function for use in routes
199
212
  self.get_current_user = get_current_user
200
-
213
+
201
214
  # Make function available in app context
202
215
  self.app.get_current_user = get_current_user
203
-
216
+
204
217
  def _setup_routes(self):
205
218
  """Setup application routes with security."""
206
-
219
+
207
220
  # Get reference to authentication function
208
221
  get_current_user_func = self.get_current_user
209
-
210
- @self.app.route('/health', methods=['GET'])
222
+
223
+ @self.app.route("/health", methods=["GET"])
211
224
  def health_check():
212
225
  """Health check endpoint (public)."""
213
- return jsonify({
214
- "status": "healthy",
215
- "timestamp": datetime.now(timezone.utc).isoformat(),
216
- "version": "1.0.0"
217
- })
218
-
219
- @self.app.route('/metrics', methods=['GET'])
226
+ return jsonify(
227
+ {
228
+ "status": "healthy",
229
+ "timestamp": datetime.now(timezone.utc).isoformat(),
230
+ "version": "1.0.0",
231
+ }
232
+ )
233
+
234
+ @self.app.route("/metrics", methods=["GET"])
220
235
  def metrics():
221
236
  """Metrics endpoint (public)."""
222
- return jsonify({
223
- "requests_total": 1000,
224
- "requests_per_minute": 60,
225
- "active_connections": 25,
226
- "uptime_seconds": 3600
227
- })
228
-
229
- @self.app.route('/api/v1/users/me', methods=['GET'])
237
+ return jsonify(
238
+ {
239
+ "requests_total": 1000,
240
+ "requests_per_minute": 60,
241
+ "active_connections": 25,
242
+ "uptime_seconds": 3600,
243
+ }
244
+ )
245
+
246
+ @self.app.route("/api/v1/users/me", methods=["GET"])
230
247
  def get_current_user_route():
231
248
  """Get current user information (authenticated)."""
232
249
  # Try to get user info from middleware
233
- user_info = getattr(g, 'user_info', None)
234
-
250
+ user_info = getattr(g, "user_info", None)
251
+
235
252
  # If middleware didn't set user_info, try authentication
236
253
  if not user_info:
237
254
  auth_result = get_current_user_func()
@@ -239,24 +256,26 @@ class FlaskExample:
239
256
  user_info = {
240
257
  "username": auth_result.username,
241
258
  "roles": auth_result.roles,
242
- "permissions": auth_result.permissions
259
+ "permissions": auth_result.permissions,
243
260
  }
244
261
  else:
245
262
  return jsonify({"error": "Authentication required"}), 401
246
-
247
- return jsonify({
248
- "username": user_info.get("username"),
249
- "roles": user_info.get("roles", []),
250
- "permissions": list(user_info.get("permissions", [])),
251
- "last_login": datetime.now(timezone.utc).isoformat()
252
- })
253
-
254
- @self.app.route('/api/v1/admin/users', methods=['GET'])
263
+
264
+ return jsonify(
265
+ {
266
+ "username": user_info.get("username"),
267
+ "roles": user_info.get("roles", []),
268
+ "permissions": list(user_info.get("permissions", [])),
269
+ "last_login": datetime.now(timezone.utc).isoformat(),
270
+ }
271
+ )
272
+
273
+ @self.app.route("/api/v1/admin/users", methods=["GET"])
255
274
  def get_all_users():
256
275
  """Get all users (admin only)."""
257
276
  # Try to get user info from middleware
258
- user_info = getattr(g, 'user_info', None)
259
-
277
+ user_info = getattr(g, "user_info", None)
278
+
260
279
  # If middleware didn't set user_info, try authentication
261
280
  if not user_info:
262
281
  auth_result = get_current_user_func()
@@ -264,29 +283,35 @@ class FlaskExample:
264
283
  user_info = {
265
284
  "username": auth_result.username,
266
285
  "roles": auth_result.roles,
267
- "permissions": auth_result.permissions
286
+ "permissions": auth_result.permissions,
268
287
  }
269
288
  else:
270
289
  return jsonify({"error": "Authentication required"}), 401
271
-
290
+
272
291
  # Check admin permission
273
292
  if "admin" not in user_info.get("roles", []):
274
293
  return jsonify({"error": "Admin access required"}), 403
275
-
276
- return jsonify({
277
- "users": [
278
- {"username": "admin", "roles": ["admin"], "status": "active"},
279
- {"username": "user", "roles": ["user"], "status": "active"},
280
- {"username": "readonly", "roles": ["readonly"], "status": "active"}
281
- ]
282
- })
283
-
284
- @self.app.route('/api/v1/data', methods=['POST'])
294
+
295
+ return jsonify(
296
+ {
297
+ "users": [
298
+ {"username": "admin", "roles": ["admin"], "status": "active"},
299
+ {"username": "user", "roles": ["user"], "status": "active"},
300
+ {
301
+ "username": "readonly",
302
+ "roles": ["readonly"],
303
+ "status": "active",
304
+ },
305
+ ]
306
+ }
307
+ )
308
+
309
+ @self.app.route("/api/v1/data", methods=["POST"])
285
310
  def create_data():
286
311
  """Create data (authenticated users)."""
287
312
  # Try to get user info from middleware
288
- user_info = getattr(g, 'user_info', None)
289
-
313
+ user_info = getattr(g, "user_info", None)
314
+
290
315
  # If middleware didn't set user_info, try authentication
291
316
  if not user_info:
292
317
  auth_result = get_current_user_func()
@@ -294,30 +319,32 @@ class FlaskExample:
294
319
  user_info = {
295
320
  "username": auth_result.username,
296
321
  "roles": auth_result.roles,
297
- "permissions": auth_result.permissions
322
+ "permissions": auth_result.permissions,
298
323
  }
299
324
  else:
300
325
  return jsonify({"error": "Authentication required"}), 401
301
-
326
+
302
327
  # Check write permission
303
328
  if "readonly" in user_info.get("roles", []):
304
329
  return jsonify({"error": "Write permission required"}), 403
305
-
330
+
306
331
  # Process request data
307
332
  data = request.get_json()
308
- return jsonify({
309
- "id": "data_123",
310
- "created_by": user_info.get("username"),
311
- "data": data,
312
- "created_at": datetime.now(timezone.utc).isoformat()
313
- })
314
-
315
- @self.app.route('/api/v1/data/<data_id>', methods=['GET'])
333
+ return jsonify(
334
+ {
335
+ "id": "data_123",
336
+ "created_by": user_info.get("username"),
337
+ "data": data,
338
+ "created_at": datetime.now(timezone.utc).isoformat(),
339
+ }
340
+ )
341
+
342
+ @self.app.route("/api/v1/data/<data_id>", methods=["GET"])
316
343
  def get_data(data_id):
317
344
  """Get data by ID (authenticated users)."""
318
345
  # Try to get user info from middleware
319
- user_info = getattr(g, 'user_info', None)
320
-
346
+ user_info = getattr(g, "user_info", None)
347
+
321
348
  # If middleware didn't set user_info, try authentication
322
349
  if not user_info:
323
350
  auth_result = get_current_user_func()
@@ -325,91 +352,129 @@ class FlaskExample:
325
352
  user_info = {
326
353
  "username": auth_result.username,
327
354
  "roles": auth_result.roles,
328
- "permissions": auth_result.permissions
355
+ "permissions": auth_result.permissions,
329
356
  }
330
357
  else:
331
358
  return jsonify({"error": "Authentication required"}), 401
332
-
333
- return jsonify({
334
- "id": data_id,
335
- "data": {"example": "data"},
336
- "created_by": user_info.get("username"),
337
- "created_at": datetime.now(timezone.utc).isoformat()
338
- })
339
-
359
+
360
+ return jsonify(
361
+ {
362
+ "id": data_id,
363
+ "data": {"example": "data"},
364
+ "created_by": user_info.get("username"),
365
+ "created_at": datetime.now(timezone.utc).isoformat(),
366
+ }
367
+ )
368
+
340
369
  def _setup_error_handlers(self):
341
370
  """Setup custom error handlers."""
342
-
371
+
343
372
  @self.app.errorhandler(401)
344
373
  def unauthorized(error):
345
374
  """Handle unauthorized errors."""
346
- return jsonify({
347
- "error": "Unauthorized",
348
- "message": "Authentication required",
349
- "status_code": 401,
350
- "timestamp": datetime.now(timezone.utc).isoformat(),
351
- "path": request.path
352
- }), 401
353
-
375
+ return (
376
+ jsonify(
377
+ {
378
+ "error": "Unauthorized",
379
+ "message": "Authentication required",
380
+ "status_code": 401,
381
+ "timestamp": datetime.now(timezone.utc).isoformat(),
382
+ "path": request.path,
383
+ }
384
+ ),
385
+ 401,
386
+ )
387
+
354
388
  @self.app.errorhandler(403)
355
389
  def forbidden(error):
356
390
  """Handle forbidden errors."""
357
- return jsonify({
358
- "error": "Forbidden",
359
- "message": "Insufficient permissions",
360
- "status_code": 403,
361
- "timestamp": datetime.now(timezone.utc).isoformat(),
362
- "path": request.path
363
- }), 403
364
-
391
+ return (
392
+ jsonify(
393
+ {
394
+ "error": "Forbidden",
395
+ "message": "Insufficient permissions",
396
+ "status_code": 403,
397
+ "timestamp": datetime.now(timezone.utc).isoformat(),
398
+ "path": request.path,
399
+ }
400
+ ),
401
+ 403,
402
+ )
403
+
365
404
  @self.app.errorhandler(404)
366
405
  def not_found(error):
367
406
  """Handle not found errors."""
368
- return jsonify({
369
- "error": "Not Found",
370
- "message": "Resource not found",
371
- "status_code": 404,
372
- "timestamp": datetime.now(timezone.utc).isoformat(),
373
- "path": request.path
374
- }), 404
375
-
407
+ return (
408
+ jsonify(
409
+ {
410
+ "error": "Not Found",
411
+ "message": "Resource not found",
412
+ "status_code": 404,
413
+ "timestamp": datetime.now(timezone.utc).isoformat(),
414
+ "path": request.path,
415
+ }
416
+ ),
417
+ 404,
418
+ )
419
+
376
420
  @self.app.errorhandler(429)
377
421
  def too_many_requests(error):
378
422
  """Handle rate limit errors."""
379
- return jsonify({
380
- "error": "Too Many Requests",
381
- "message": "Rate limit exceeded",
382
- "status_code": 429,
383
- "timestamp": datetime.now(timezone.utc).isoformat(),
384
- "path": request.path
385
- }), 429
386
-
423
+ return (
424
+ jsonify(
425
+ {
426
+ "error": "Too Many Requests",
427
+ "message": "Rate limit exceeded",
428
+ "status_code": 429,
429
+ "timestamp": datetime.now(timezone.utc).isoformat(),
430
+ "path": request.path,
431
+ }
432
+ ),
433
+ 429,
434
+ )
435
+
387
436
  @self.app.errorhandler(500)
388
437
  def internal_error(error):
389
438
  """Handle internal server errors."""
390
- return jsonify({
391
- "error": "Internal Server Error",
392
- "message": "An unexpected error occurred",
393
- "status_code": 500,
394
- "timestamp": datetime.now(timezone.utc).isoformat(),
395
- "path": request.path
396
- }), 500
397
-
439
+ return (
440
+ jsonify(
441
+ {
442
+ "error": "Internal Server Error",
443
+ "message": "An unexpected error occurred",
444
+ "status_code": 500,
445
+ "timestamp": datetime.now(timezone.utc).isoformat(),
446
+ "path": request.path,
447
+ }
448
+ ),
449
+ 500,
450
+ )
451
+
398
452
  @self.app.errorhandler(Exception)
399
453
  def handle_exception(error):
400
454
  """Handle general exceptions."""
401
- return jsonify({
402
- "error": "Internal Server Error",
403
- "message": "An unexpected error occurred",
404
- "status_code": 500,
405
- "timestamp": datetime.now(timezone.utc).isoformat(),
406
- "path": request.path
407
- }), 500
408
-
409
- def run(self, host: str = "0.0.0.0", port: int = 5000, ssl_context: Optional[tuple] = None, debug: bool = False):
455
+ return (
456
+ jsonify(
457
+ {
458
+ "error": "Internal Server Error",
459
+ "message": "An unexpected error occurred",
460
+ "status_code": 500,
461
+ "timestamp": datetime.now(timezone.utc).isoformat(),
462
+ "path": request.path,
463
+ }
464
+ ),
465
+ 500,
466
+ )
467
+
468
+ def run(
469
+ self,
470
+ host: str = "0.0.0.0",
471
+ port: int = 5000,
472
+ ssl_context: Optional[tuple] = None,
473
+ debug: bool = False,
474
+ ):
410
475
  """
411
476
  Run the Flask application with security features.
412
-
477
+
413
478
  Args:
414
479
  host: Host to bind to
415
480
  port: Port to bind to
@@ -420,81 +485,84 @@ class FlaskExample:
420
485
  print(f"SSL Enabled: {self.config.ssl.enabled}")
421
486
  print(f"Authentication Methods: {self.config.auth.methods}")
422
487
  print(f"Rate Limiting: {self.config.rate_limit.enabled}")
423
-
488
+
424
489
  if self.config.ssl.enabled and not ssl_context:
425
490
  ssl_context = (self.config.ssl.cert_file, self.config.ssl.key_file)
426
-
427
- self.app.run(
428
- host=host,
429
- port=port,
430
- ssl_context=ssl_context,
431
- debug=debug
432
- )
491
+
492
+ self.app.run(host=host, port=port, ssl_context=ssl_context, debug=debug)
433
493
 
434
494
 
435
495
  # Example usage and testing
436
496
  class FlaskExampleTest:
437
497
  """Test class for Flask example functionality."""
438
-
498
+
439
499
  @staticmethod
440
500
  def test_authentication():
441
501
  """Test authentication functionality."""
442
502
  example = FlaskExample()
443
-
503
+
444
504
  # Test API key authentication
445
- auth_result = example.security_manager.auth_manager.authenticate_api_key("admin_key_123")
505
+ auth_result = example.security_manager.auth_manager.authenticate_api_key(
506
+ "admin_key_123"
507
+ )
446
508
  assert auth_result.is_valid
447
509
  assert auth_result.username == "admin"
448
510
  assert "admin" in auth_result.roles
449
-
511
+
450
512
  print("✅ API Key authentication test passed")
451
-
513
+
452
514
  @staticmethod
453
515
  def test_rate_limiting():
454
516
  """Test rate limiting functionality."""
455
517
  example = FlaskExample()
456
-
518
+
457
519
  # Test rate limiting
458
520
  identifier = "test_user"
459
521
  for i in range(5):
460
- is_allowed = example.security_manager.rate_limiter.check_rate_limit(identifier)
522
+ is_allowed = example.security_manager.rate_limiter.check_rate_limit(
523
+ identifier
524
+ )
461
525
  print(f"Request {i+1}: {'Allowed' if is_allowed else 'Blocked'}")
462
-
526
+
463
527
  print("✅ Rate limiting test completed")
464
-
528
+
465
529
  @staticmethod
466
530
  def test_permissions():
467
531
  """Test permission checking."""
468
532
  example = FlaskExample()
469
-
533
+
470
534
  # Test admin permissions
471
535
  admin_roles = ["admin"]
472
536
  user_roles = ["user"]
473
537
  readonly_roles = ["readonly"]
474
-
538
+
475
539
  # Admin should have all permissions
476
540
  admin_result = example.security_manager.permission_manager.validate_access(
477
541
  admin_roles, ["read", "write", "delete"]
478
542
  )
479
543
  assert admin_result.is_valid
480
-
544
+
481
545
  # User should have read and write permissions
482
546
  user_result = example.security_manager.permission_manager.validate_access(
483
547
  user_roles, ["read", "write"]
484
548
  )
485
549
  assert user_result.is_valid
486
-
550
+
487
551
  # Readonly should only have read permission
488
- readonly_read_result = example.security_manager.permission_manager.validate_access(
489
- readonly_roles, ["read"]
552
+ readonly_read_result = (
553
+ example.security_manager.permission_manager.validate_access(
554
+ readonly_roles, ["read"]
555
+ )
490
556
  )
491
557
  assert readonly_read_result.is_valid
492
-
493
- readonly_write_result = example.security_manager.permission_manager.validate_access(
494
- readonly_roles, ["write"]
558
+
559
+ readonly_write_result = (
560
+ example.security_manager.permission_manager.validate_access(
561
+ readonly_roles, ["write"]
562
+ )
495
563
  )
496
564
  assert not readonly_write_result.is_valid
497
-
565
+
498
566
  print("✅ Permission checking test passed")
499
567
 
500
568
 
@@ -504,43 +572,46 @@ if __name__ == "__main__":
504
572
  FlaskExampleTest.test_authentication()
505
573
  FlaskExampleTest.test_rate_limiting()
506
574
  FlaskExampleTest.test_permissions()
507
-
575
+
508
576
  # Start server in background thread for testing
509
577
  print("\nStarting Flask Example Server in background...")
510
578
  example = FlaskExample()
511
-
579
+
512
580
  import threading
513
581
  import time
582
+
514
583
  import requests
515
-
584
+
516
585
  # Start server in background thread
517
586
  server_thread = threading.Thread(target=example.run, daemon=True)
518
587
  server_thread.start()
519
-
588
+
520
589
  # Wait for server to start
521
590
  time.sleep(3)
522
-
591
+
523
592
  try:
524
593
  # Test server endpoints
525
594
  print("Testing server endpoints...")
526
-
595
+
527
596
  # Test health endpoint
528
597
  response = requests.get("http://localhost:5000/health", timeout=5)
529
598
  print(f"Health endpoint: {response.status_code}")
530
-
599
+
531
600
  # Test metrics endpoint
532
601
  response = requests.get("http://localhost:5000/metrics", timeout=5)
533
602
  print(f"Metrics endpoint: {response.status_code}")
534
-
603
+
535
604
  # Test protected endpoint with API key
536
605
  headers = {"X-API-Key": "admin_key_123"}
537
- response = requests.get("http://localhost:5000/api/v1/users/me", headers=headers, timeout=5)
606
+ response = requests.get(
607
+ "http://localhost:5000/api/v1/users/me", headers=headers, timeout=5
608
+ )
538
609
  print(f"Protected endpoint: {response.status_code}")
539
-
610
+
540
611
  print("✅ Server testing completed successfully")
541
-
612
+
542
613
  except requests.exceptions.RequestException as e:
543
614
  print(f"⚠️ Server testing failed: {e}")
544
-
615
+
545
616
  # Server will automatically stop when main thread exits
546
617
  print("Flask example completed")