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
@@ -24,50 +24,57 @@ Version: 1.0.0
24
24
  License: MIT
25
25
  """
26
26
 
27
- import click
28
27
  import json
29
28
  import os
30
- from pathlib import Path
31
- from typing import Optional, List
32
29
  from datetime import datetime
30
+ from pathlib import Path
31
+ from typing import List, Optional
32
+
33
+ import click
33
34
 
34
35
  from ..core.security_manager import SecurityManager
35
- from ..schemas.config import SecurityConfig, AuthConfig, RateLimitConfig, SSLConfig, PermissionConfig
36
+ from ..schemas.config import (
37
+ AuthConfig,
38
+ PermissionConfig,
39
+ RateLimitConfig,
40
+ SecurityConfig,
41
+ SSLConfig,
42
+ )
36
43
  from ..schemas.models import AuthResult, AuthStatus
37
44
 
38
45
 
39
46
  @click.group()
40
- @click.option('--config', '-c', 'config_path',
41
- help='Path to security configuration file')
42
- @click.option('--verbose', '-v', is_flag=True,
43
- help='Enable verbose output')
47
+ @click.option(
48
+ "--config", "-c", "config_path", help="Path to security configuration file"
49
+ )
50
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
44
51
  @click.pass_context
45
52
  def security_cli(ctx, config_path: Optional[str], verbose: bool):
46
53
  """
47
54
  Security Management CLI
48
-
55
+
49
56
  Manage security operations including authentication, authorization,
50
57
  and security configuration.
51
58
  """
52
59
  ctx.ensure_object(dict)
53
- ctx.obj['verbose'] = verbose
54
-
60
+ ctx.obj["verbose"] = verbose
61
+
55
62
  # Load configuration
56
63
  if config_path and os.path.exists(config_path):
57
- with open(config_path, 'r') as f:
64
+ with open(config_path, "r") as f:
58
65
  config_data = json.load(f)
59
- ctx.obj['config'] = SecurityConfig(**config_data)
66
+ ctx.obj["config"] = SecurityConfig(**config_data)
60
67
  else:
61
68
  # Use default configuration
62
- ctx.obj['config'] = SecurityConfig(
69
+ ctx.obj["config"] = SecurityConfig(
63
70
  auth=AuthConfig(enabled=True),
64
71
  rate_limit=RateLimitConfig(enabled=True),
65
72
  ssl=SSLConfig(enabled=False),
66
- permissions=PermissionConfig(enabled=True)
73
+ permissions=PermissionConfig(enabled=True),
67
74
  )
68
-
75
+
69
76
  # Create security manager
70
- ctx.obj['security_manager'] = SecurityManager(ctx.obj['config'])
77
+ ctx.obj["security_manager"] = SecurityManager(ctx.obj["config"])
71
78
 
72
79
 
73
80
  @security_cli.group()
@@ -75,7 +82,7 @@ def security_cli(ctx, config_path: Optional[str], verbose: bool):
75
82
  def auth(ctx):
76
83
  """
77
84
  Authentication operations.
78
-
85
+
79
86
  Manage authentication including API keys, user authentication,
80
87
  and authentication testing.
81
88
  """
@@ -83,92 +90,88 @@ def auth(ctx):
83
90
 
84
91
 
85
92
  @auth.command()
86
- @click.option('--username', '-u', required=True,
87
- help='Username for the API key')
88
- @click.option('--api-key', '-k', required=True,
89
- help='API key value')
93
+ @click.option("--username", "-u", required=True, help="Username for the API key")
94
+ @click.option("--api-key", "-k", required=True, help="API key value")
90
95
  @click.pass_context
91
96
  def add_api_key(ctx, username: str, api_key: str):
92
97
  """
93
98
  Add an API key for a user.
94
-
99
+
95
100
  This command adds an API key to the authentication system
96
101
  for the specified user.
97
102
  """
98
103
  try:
99
- security_manager = ctx.obj['security_manager']
100
- verbose = ctx.obj['verbose']
101
-
104
+ security_manager = ctx.obj["security_manager"]
105
+ verbose = ctx.obj["verbose"]
106
+
102
107
  if verbose:
103
108
  click.echo(f"Adding API key for user: {username}")
104
-
109
+
105
110
  # Add API key
106
111
  success = security_manager.auth_manager.add_api_key(username, api_key)
107
-
112
+
108
113
  if success:
109
114
  click.echo(f"✅ API key added successfully for user: {username}")
110
115
  else:
111
116
  click.echo(f"❌ Failed to add API key for user: {username}", err=True)
112
117
  raise click.Abort()
113
-
118
+
114
119
  except Exception as e:
115
120
  click.echo(f"❌ Failed to add API key: {str(e)}", err=True)
116
121
  raise click.Abort()
117
122
 
118
123
 
119
124
  @auth.command()
120
- @click.option('--username', '-u', required=True,
121
- help='Username to remove API key for')
125
+ @click.option("--username", "-u", required=True, help="Username to remove API key for")
122
126
  @click.pass_context
123
127
  def remove_api_key(ctx, username: str):
124
128
  """
125
129
  Remove an API key for a user.
126
-
130
+
127
131
  This command removes an API key from the authentication system
128
132
  for the specified user.
129
133
  """
130
134
  try:
131
- security_manager = ctx.obj['security_manager']
132
- verbose = ctx.obj['verbose']
133
-
135
+ security_manager = ctx.obj["security_manager"]
136
+ verbose = ctx.obj["verbose"]
137
+
134
138
  if verbose:
135
139
  click.echo(f"Removing API key for user: {username}")
136
-
140
+
137
141
  # Remove API key
138
142
  success = security_manager.auth_manager.remove_api_key(username)
139
-
143
+
140
144
  if success:
141
145
  click.echo(f"✅ API key removed successfully for user: {username}")
142
146
  else:
143
147
  click.echo(f"❌ Failed to remove API key for user: {username}", err=True)
144
148
  raise click.Abort()
145
-
149
+
146
150
  except Exception as e:
147
151
  click.echo(f"❌ Failed to remove API key: {str(e)}", err=True)
148
152
  raise click.Abort()
149
153
 
150
154
 
151
155
  @auth.command()
152
- @click.option('--api-key', '-k', required=True,
153
- help='API key to test')
156
+ @click.option("--api-key", "-k", required=True, help="API key to test")
154
157
  @click.pass_context
155
158
  def test_api_key(ctx, api_key: str):
156
159
  """
157
160
  Test API key authentication.
158
-
161
+
159
162
  This command tests if an API key is valid and returns
160
163
  authentication information.
161
164
  """
162
165
  try:
163
- security_manager = ctx.obj['security_manager']
164
- verbose = ctx.obj['verbose']
165
-
166
+ security_manager = ctx.obj["security_manager"]
167
+ verbose = ctx.obj["verbose"]
168
+
166
169
  if verbose:
167
170
  click.echo(f"Testing API key authentication")
168
-
171
+
169
172
  # Test API key
170
173
  auth_result = security_manager.auth_manager.authenticate_api_key(api_key)
171
-
174
+
172
175
  if auth_result.is_valid:
173
176
  click.echo(f"✅ API key authentication successful!")
174
177
  click.echo(f" Username: {auth_result.username}")
@@ -178,33 +181,32 @@ def test_api_key(ctx, api_key: str):
178
181
  click.echo(f"❌ API key authentication failed!", err=True)
179
182
  click.echo(f" Error: {auth_result.error_message}")
180
183
  raise click.Abort()
181
-
184
+
182
185
  except Exception as e:
183
186
  click.echo(f"❌ API key authentication failed: {str(e)}", err=True)
184
187
  raise click.Abort()
185
188
 
186
189
 
187
190
  @auth.command()
188
- @click.option('--token', '-t', required=True,
189
- help='JWT token to test')
191
+ @click.option("--token", "-t", required=True, help="JWT token to test")
190
192
  @click.pass_context
191
193
  def test_jwt(ctx, token: str):
192
194
  """
193
195
  Test JWT token authentication.
194
-
196
+
195
197
  This command tests if a JWT token is valid and returns
196
198
  authentication information.
197
199
  """
198
200
  try:
199
- security_manager = ctx.obj['security_manager']
200
- verbose = ctx.obj['verbose']
201
-
201
+ security_manager = ctx.obj["security_manager"]
202
+ verbose = ctx.obj["verbose"]
203
+
202
204
  if verbose:
203
205
  click.echo(f"Testing JWT token authentication")
204
-
206
+
205
207
  # Test JWT token
206
208
  auth_result = security_manager.auth_manager.authenticate_jwt_token(token)
207
-
209
+
208
210
  if auth_result.is_valid:
209
211
  click.echo(f"✅ JWT token authentication successful!")
210
212
  click.echo(f" Username: {auth_result.username}")
@@ -214,7 +216,7 @@ def test_jwt(ctx, token: str):
214
216
  click.echo(f"❌ JWT token authentication failed!", err=True)
215
217
  click.echo(f" Error: {auth_result.error_message}")
216
218
  raise click.Abort()
217
-
219
+
218
220
  except Exception as e:
219
221
  click.echo(f"❌ JWT token authentication failed: {str(e)}", err=True)
220
222
  raise click.Abort()
@@ -225,7 +227,7 @@ def test_jwt(ctx, token: str):
225
227
  def permissions(ctx):
226
228
  """
227
229
  Permission management operations.
228
-
230
+
229
231
  Manage permissions including role assignment, permission validation,
230
232
  and permission testing.
231
233
  """
@@ -233,37 +235,43 @@ def permissions(ctx):
233
235
 
234
236
 
235
237
  @permissions.command()
236
- @click.option('--username', '-u', required=True,
237
- help='Username to check permissions for')
238
- @click.option('--permissions', '-p', multiple=True, required=True,
239
- help='Permissions to check (can be specified multiple times)')
238
+ @click.option(
239
+ "--username", "-u", required=True, help="Username to check permissions for"
240
+ )
241
+ @click.option(
242
+ "--permissions",
243
+ "-p",
244
+ multiple=True,
245
+ required=True,
246
+ help="Permissions to check (can be specified multiple times)",
247
+ )
240
248
  @click.pass_context
241
249
  def check(ctx, username: str, permissions: tuple):
242
250
  """
243
251
  Check user permissions.
244
-
252
+
245
253
  This command checks if a user has the specified permissions.
246
254
  """
247
255
  try:
248
- security_manager = ctx.obj['security_manager']
249
- verbose = ctx.obj['verbose']
256
+ security_manager = ctx.obj["security_manager"]
257
+ verbose = ctx.obj["verbose"]
250
258
  permissions_list = list(permissions)
251
-
259
+
252
260
  if verbose:
253
261
  click.echo(f"Checking permissions for user: {username}")
254
262
  click.echo(f"Required permissions: {', '.join(permissions_list)}")
255
-
263
+
256
264
  # Get user roles
257
265
  user_roles = security_manager.auth_manager._get_user_roles(username)
258
-
266
+
259
267
  if verbose:
260
268
  click.echo(f"User roles: {', '.join(user_roles)}")
261
-
269
+
262
270
  # Check permissions
263
271
  has_permissions = security_manager.permission_manager.validate_access(
264
272
  user_roles, permissions_list
265
273
  )
266
-
274
+
267
275
  if has_permissions.is_valid:
268
276
  click.echo(f"✅ User has all required permissions!")
269
277
  click.echo(f" Username: {username}")
@@ -273,41 +281,42 @@ def check(ctx, username: str, permissions: tuple):
273
281
  click.echo(f"❌ User does not have required permissions!", err=True)
274
282
  click.echo(f" Username: {username}")
275
283
  click.echo(f" Roles: {', '.join(user_roles)}")
276
- click.echo(f" Missing permissions: {', '.join(has_permissions.missing_permissions)}")
284
+ click.echo(
285
+ f" Missing permissions: {', '.join(has_permissions.missing_permissions)}"
286
+ )
277
287
  raise click.Abort()
278
-
288
+
279
289
  except Exception as e:
280
290
  click.echo(f"❌ Permission check failed: {str(e)}", err=True)
281
291
  raise click.Abort()
282
292
 
283
293
 
284
294
  @permissions.command()
285
- @click.option('--role', '-r', required=True,
286
- help='Role name')
295
+ @click.option("--role", "-r", required=True, help="Role name")
287
296
  @click.pass_context
288
297
  def list_role_permissions(ctx, role: str):
289
298
  """
290
299
  List permissions for a role.
291
-
300
+
292
301
  This command displays all permissions assigned to a specific role.
293
302
  """
294
303
  try:
295
- security_manager = ctx.obj['security_manager']
296
- verbose = ctx.obj['verbose']
297
-
304
+ security_manager = ctx.obj["security_manager"]
305
+ verbose = ctx.obj["verbose"]
306
+
298
307
  if verbose:
299
308
  click.echo(f"Listing permissions for role: {role}")
300
-
309
+
301
310
  # Get role permissions
302
311
  permissions = security_manager.permission_manager.get_role_permissions(role)
303
-
312
+
304
313
  if permissions:
305
314
  click.echo(f"Permissions for role '{role}':")
306
315
  for permission in permissions:
307
316
  click.echo(f" - {permission}")
308
317
  else:
309
318
  click.echo(f"No permissions found for role: {role}")
310
-
319
+
311
320
  except Exception as e:
312
321
  click.echo(f"❌ Failed to list role permissions: {str(e)}", err=True)
313
322
  raise click.Abort()
@@ -318,7 +327,7 @@ def list_role_permissions(ctx, role: str):
318
327
  def rate_limit(ctx):
319
328
  """
320
329
  Rate limiting operations.
321
-
330
+
322
331
  Manage rate limiting including checking limits, resetting limits,
323
332
  and monitoring rate limit status.
324
333
  """
@@ -326,90 +335,96 @@ def rate_limit(ctx):
326
335
 
327
336
 
328
337
  @rate_limit.command()
329
- @click.option('--identifier', '-i', required=True,
330
- help='Rate limit identifier (IP, user ID, etc.)')
338
+ @click.option(
339
+ "--identifier",
340
+ "-i",
341
+ required=True,
342
+ help="Rate limit identifier (IP, user ID, etc.)",
343
+ )
331
344
  @click.pass_context
332
345
  def check(ctx, identifier: str):
333
346
  """
334
347
  Check rate limit status.
335
-
348
+
336
349
  This command checks the current rate limit status for an identifier.
337
350
  """
338
351
  try:
339
- security_manager = ctx.obj['security_manager']
340
- verbose = ctx.obj['verbose']
341
-
352
+ security_manager = ctx.obj["security_manager"]
353
+ verbose = ctx.obj["verbose"]
354
+
342
355
  if verbose:
343
356
  click.echo(f"Checking rate limit for identifier: {identifier}")
344
-
357
+
345
358
  # Check rate limit
346
359
  is_allowed = security_manager.rate_limiter.check_rate_limit(identifier)
347
-
360
+
348
361
  if is_allowed:
349
362
  click.echo(f"✅ Rate limit check passed for: {identifier}")
350
363
  else:
351
364
  click.echo(f"❌ Rate limit exceeded for: {identifier}", err=True)
352
365
  raise click.Abort()
353
-
366
+
354
367
  except Exception as e:
355
368
  click.echo(f"❌ Rate limit check failed: {str(e)}", err=True)
356
369
  raise click.Abort()
357
370
 
358
371
 
359
372
  @rate_limit.command()
360
- @click.option('--identifier', '-i', required=True,
361
- help='Rate limit identifier to reset')
373
+ @click.option(
374
+ "--identifier", "-i", required=True, help="Rate limit identifier to reset"
375
+ )
362
376
  @click.pass_context
363
377
  def reset(ctx, identifier: str):
364
378
  """
365
379
  Reset rate limit for an identifier.
366
-
380
+
367
381
  This command resets the rate limit counter for an identifier.
368
382
  """
369
383
  try:
370
- security_manager = ctx.obj['security_manager']
371
- verbose = ctx.obj['verbose']
372
-
384
+ security_manager = ctx.obj["security_manager"]
385
+ verbose = ctx.obj["verbose"]
386
+
373
387
  if verbose:
374
388
  click.echo(f"Resetting rate limit for identifier: {identifier}")
375
-
389
+
376
390
  # Reset rate limit
377
391
  security_manager.rate_limiter.reset_rate_limit(identifier)
378
-
392
+
379
393
  click.echo(f"✅ Rate limit reset successfully for: {identifier}")
380
-
394
+
381
395
  except Exception as e:
382
396
  click.echo(f"❌ Failed to reset rate limit: {str(e)}", err=True)
383
397
  raise click.Abort()
384
398
 
385
399
 
386
400
  @rate_limit.command()
387
- @click.option('--identifier', '-i', required=True,
388
- help='Rate limit identifier to get status for')
401
+ @click.option(
402
+ "--identifier", "-i", required=True, help="Rate limit identifier to get status for"
403
+ )
389
404
  @click.pass_context
390
405
  def status(ctx, identifier: str):
391
406
  """
392
407
  Get rate limit status for an identifier.
393
-
408
+
394
409
  This command displays detailed rate limit status information.
395
410
  """
396
411
  try:
397
- security_manager = ctx.obj['security_manager']
398
- verbose = ctx.obj['verbose']
399
-
412
+ security_manager = ctx.obj["security_manager"]
413
+ verbose = ctx.obj["verbose"]
414
+
400
415
  if verbose:
401
416
  click.echo(f"Getting rate limit status for identifier: {identifier}")
402
-
417
+
403
418
  # Get rate limit status
404
419
  status = security_manager.rate_limiter.get_rate_limit_status(identifier)
405
-
420
+
406
421
  click.echo(f"Rate Limit Status for '{identifier}':")
407
422
  click.echo(f" Current Count: {status.current_count}")
408
423
  click.echo(f" Limit: {status.limit}")
409
424
  click.echo(f" Window Start: {status.window_start}")
410
425
  click.echo(f" Window End: {status.window_end}")
411
426
  click.echo(f" Is Allowed: {status.is_allowed}")
412
-
427
+
413
428
  except Exception as e:
414
429
  click.echo(f"❌ Failed to get rate limit status: {str(e)}", err=True)
415
430
  raise click.Abort()
@@ -420,7 +435,7 @@ def status(ctx, identifier: str):
420
435
  def config(ctx):
421
436
  """
422
437
  Configuration management operations.
423
-
438
+
424
439
  Manage security configuration including validation, export,
425
440
  and configuration testing.
426
441
  """
@@ -432,35 +447,37 @@ def config(ctx):
432
447
  def validate(ctx):
433
448
  """
434
449
  Validate security configuration.
435
-
450
+
436
451
  This command validates the current security configuration
437
452
  and reports any issues.
438
453
  """
439
454
  try:
440
- config = ctx.obj['config']
441
- verbose = ctx.obj['verbose']
442
-
455
+ config = ctx.obj["config"]
456
+ verbose = ctx.obj["verbose"]
457
+
443
458
  if verbose:
444
459
  click.echo("Validating security configuration...")
445
-
460
+
446
461
  # Validate configuration
447
462
  issues = []
448
-
463
+
449
464
  # Check authentication configuration
450
465
  if config.auth and config.auth.enabled:
451
466
  if not config.auth.methods:
452
467
  issues.append("Authentication enabled but no methods specified")
453
-
468
+
454
469
  # Check rate limiting configuration
455
470
  if config.rate_limit and config.rate_limit.enabled:
456
471
  if config.rate_limit.default_requests_per_minute <= 0:
457
- issues.append("Invalid rate limit: requests per minute must be positive")
458
-
472
+ issues.append(
473
+ "Invalid rate limit: requests per minute must be positive"
474
+ )
475
+
459
476
  # Check SSL configuration
460
477
  if config.ssl and config.ssl.enabled:
461
478
  if not config.ssl.cert_file or not config.ssl.key_file:
462
479
  issues.append("SSL enabled but certificate or key file not specified")
463
-
480
+
464
481
  if issues:
465
482
  click.echo(f"❌ Configuration validation failed!", err=True)
466
483
  for issue in issues:
@@ -468,65 +485,68 @@ def validate(ctx):
468
485
  raise click.Abort()
469
486
  else:
470
487
  click.echo(f"✅ Configuration validation passed!")
471
-
488
+
472
489
  except Exception as e:
473
490
  click.echo(f"❌ Configuration validation failed: {str(e)}", err=True)
474
491
  raise click.Abort()
475
492
 
476
493
 
477
494
  @config.command()
478
- @click.option('--output', '-o', type=click.Path(),
479
- help='Output file path (default: stdout)')
495
+ @click.option(
496
+ "--output", "-o", type=click.Path(), help="Output file path (default: stdout)"
497
+ )
480
498
  @click.pass_context
481
499
  def export(ctx, output: Optional[str]):
482
500
  """
483
501
  Export security configuration.
484
-
502
+
485
503
  This command exports the current security configuration
486
504
  to JSON format.
487
505
  """
488
506
  try:
489
- config = ctx.obj['config']
490
- verbose = ctx.obj['verbose']
491
-
507
+ config = ctx.obj["config"]
508
+ verbose = ctx.obj["verbose"]
509
+
492
510
  if verbose:
493
511
  click.echo("Exporting security configuration...")
494
-
512
+
495
513
  # Export configuration
496
514
  config_json = config.model_dump_json(indent=2)
497
-
515
+
498
516
  if output:
499
- with open(output, 'w') as f:
517
+ with open(output, "w") as f:
500
518
  f.write(config_json)
501
519
  click.echo(f"✅ Configuration exported to: {output}")
502
520
  else:
503
521
  click.echo(config_json)
504
-
522
+
505
523
  except Exception as e:
506
524
  click.echo(f"❌ Failed to export configuration: {str(e)}", err=True)
507
525
  raise click.Abort()
508
526
 
509
527
 
510
528
  @security_cli.command()
511
- @click.option('--output', '-o', type=click.Path(),
512
- help='Output file path for roles configuration')
513
- @click.option('--template', '-t', is_flag=True,
514
- help='Generate template roles configuration')
529
+ @click.option(
530
+ "--output", "-o", type=click.Path(), help="Output file path for roles configuration"
531
+ )
532
+ @click.option(
533
+ "--template", "-t", is_flag=True, help="Generate template roles configuration"
534
+ )
515
535
  @click.pass_context
516
536
  def generate_roles(ctx, output: Optional[str], template: bool):
517
537
  """
518
538
  Generate roles configuration file.
519
-
539
+
520
540
  This command generates a roles configuration file with default
521
541
  roles and permissions or a template for customization.
522
542
  """
523
543
  try:
524
- security_manager = ctx.obj['security_manager']
525
- verbose = ctx.obj['verbose']
526
-
544
+ security_manager = ctx.obj["security_manager"]
545
+ verbose = ctx.obj["verbose"]
546
+
527
547
  if verbose:
528
548
  click.echo("Generating roles configuration...")
529
-
549
+
530
550
  if template:
531
551
  # Generate template roles configuration
532
552
  template_roles = {
@@ -534,31 +554,31 @@ def generate_roles(ctx, output: Optional[str], template: bool):
534
554
  "admin": {
535
555
  "description": "Administrator role with full access",
536
556
  "permissions": ["*"],
537
- "parent_roles": []
557
+ "parent_roles": [],
538
558
  },
539
559
  "user": {
540
560
  "description": "Standard user role",
541
561
  "permissions": ["read:own", "write:own"],
542
- "parent_roles": []
562
+ "parent_roles": [],
543
563
  },
544
564
  "guest": {
545
565
  "description": "Guest role with limited access",
546
566
  "permissions": ["read:public"],
547
- "parent_roles": []
548
- }
567
+ "parent_roles": [],
568
+ },
549
569
  },
550
570
  "permissions": {
551
571
  "read:own": "Read own resources",
552
572
  "write:own": "Write own resources",
553
573
  "read:public": "Read public resources",
554
- "*": "All permissions (wildcard)"
555
- }
574
+ "*": "All permissions (wildcard)",
575
+ },
556
576
  }
557
-
577
+
558
578
  roles_json = json.dumps(template_roles, indent=2)
559
-
579
+
560
580
  if output:
561
- with open(output, 'w') as f:
581
+ with open(output, "w") as f:
562
582
  f.write(roles_json)
563
583
  click.echo(f"✅ Template roles configuration generated: {output}")
564
584
  else:
@@ -566,40 +586,46 @@ def generate_roles(ctx, output: Optional[str], template: bool):
566
586
  else:
567
587
  # Generate current roles configuration
568
588
  roles_config = security_manager.permission_manager.export_roles_config()
569
-
589
+
570
590
  if output:
571
- with open(output, 'w') as f:
591
+ with open(output, "w") as f:
572
592
  json.dump(roles_config, f, indent=2)
573
593
  click.echo(f"✅ Current roles configuration exported: {output}")
574
594
  else:
575
595
  click.echo(json.dumps(roles_config, indent=2))
576
-
596
+
577
597
  except Exception as e:
578
598
  click.echo(f"❌ Failed to generate roles configuration: {str(e)}", err=True)
579
599
  raise click.Abort()
580
600
 
581
601
 
582
602
  @security_cli.command()
583
- @click.option('--output', '-o', type=click.Path(),
584
- help='Output file path for audit report')
585
- @click.option('--format', '-f', type=click.Choice(['json', 'text']), default='text',
586
- help='Output format for audit report')
603
+ @click.option(
604
+ "--output", "-o", type=click.Path(), help="Output file path for audit report"
605
+ )
606
+ @click.option(
607
+ "--format",
608
+ "-f",
609
+ type=click.Choice(["json", "text"]),
610
+ default="text",
611
+ help="Output format for audit report",
612
+ )
587
613
  @click.pass_context
588
614
  def security_audit(ctx, output: Optional[str], format: str):
589
615
  """
590
616
  Perform security audit.
591
-
617
+
592
618
  This command performs a comprehensive security audit of the
593
619
  system configuration and components.
594
620
  """
595
621
  try:
596
- config = ctx.obj['config']
597
- security_manager = ctx.obj['security_manager']
598
- verbose = ctx.obj['verbose']
599
-
622
+ config = ctx.obj["config"]
623
+ security_manager = ctx.obj["security_manager"]
624
+ verbose = ctx.obj["verbose"]
625
+
600
626
  if verbose:
601
627
  click.echo("Performing security audit...")
602
-
628
+
603
629
  # Perform security audit
604
630
  audit_results = {
605
631
  "timestamp": datetime.now().isoformat(),
@@ -607,65 +633,111 @@ def security_audit(ctx, output: Optional[str], format: str):
607
633
  "authentication": {
608
634
  "enabled": config.auth.enabled if config.auth else False,
609
635
  "methods": config.auth.methods if config.auth else [],
610
- "issues": []
636
+ "issues": [],
611
637
  },
612
638
  "rate_limiting": {
613
- "enabled": config.rate_limit.enabled if config.rate_limit else False,
614
- "default_limit": config.rate_limit.default_requests_per_minute if config.rate_limit else None,
615
- "issues": []
639
+ "enabled": (
640
+ config.rate_limit.enabled if config.rate_limit else False
641
+ ),
642
+ "default_limit": (
643
+ config.rate_limit.default_requests_per_minute
644
+ if config.rate_limit
645
+ else None
646
+ ),
647
+ "issues": [],
616
648
  },
617
649
  "ssl_tls": {
618
650
  "enabled": config.ssl.enabled if config.ssl else False,
619
651
  "cert_file": config.ssl.cert_file if config.ssl else None,
620
652
  "key_file": config.ssl.key_file if config.ssl else None,
621
- "issues": []
653
+ "issues": [],
622
654
  },
623
655
  "permissions": {
624
- "enabled": config.permissions.enabled if config.permissions else False,
625
- "roles_file": config.permissions.roles_file if config.permissions else None,
626
- "issues": []
627
- }
656
+ "enabled": (
657
+ config.permissions.enabled if config.permissions else False
658
+ ),
659
+ "roles_file": (
660
+ config.permissions.roles_file if config.permissions else None
661
+ ),
662
+ "issues": [],
663
+ },
628
664
  },
629
665
  "components": {
630
666
  "auth_manager": {
631
- "status": "✅ Initialized" if security_manager.auth_manager else "❌ Not Initialized",
632
- "api_keys_count": len(security_manager.auth_manager.api_keys) if security_manager.auth_manager else 0
667
+ "status": (
668
+ "✅ Initialized"
669
+ if security_manager.auth_manager
670
+ else "❌ Not Initialized"
671
+ ),
672
+ "api_keys_count": (
673
+ len(security_manager.auth_manager.api_keys)
674
+ if security_manager.auth_manager
675
+ else 0
676
+ ),
633
677
  },
634
678
  "permission_manager": {
635
- "status": "✅ Initialized" if security_manager.permission_manager else "❌ Not Initialized",
636
- "roles_count": len(security_manager.permission_manager.roles) if security_manager.permission_manager else 0
679
+ "status": (
680
+ "✅ Initialized"
681
+ if security_manager.permission_manager
682
+ else "❌ Not Initialized"
683
+ ),
684
+ "roles_count": (
685
+ len(security_manager.permission_manager.roles)
686
+ if security_manager.permission_manager
687
+ else 0
688
+ ),
637
689
  },
638
690
  "rate_limiter": {
639
- "status": "✅ Initialized" if security_manager.rate_limiter else "❌ Not Initialized"
691
+ "status": (
692
+ "✅ Initialized"
693
+ if security_manager.rate_limiter
694
+ else "❌ Not Initialized"
695
+ )
640
696
  },
641
697
  "ssl_manager": {
642
- "status": "✅ Initialized" if security_manager.ssl_manager else "❌ Not Initialized"
698
+ "status": (
699
+ "✅ Initialized"
700
+ if security_manager.ssl_manager
701
+ else "❌ Not Initialized"
702
+ )
643
703
  },
644
704
  "cert_manager": {
645
- "status": "✅ Initialized" if security_manager.cert_manager else "❌ Not Initialized"
646
- }
705
+ "status": (
706
+ "✅ Initialized"
707
+ if security_manager.cert_manager
708
+ else "❌ Not Initialized"
709
+ )
710
+ },
647
711
  },
648
- "recommendations": []
712
+ "recommendations": [],
649
713
  }
650
-
714
+
651
715
  # Add security recommendations
652
716
  if not config.auth.enabled:
653
- audit_results["recommendations"].append("Enable authentication for better security")
654
-
717
+ audit_results["recommendations"].append(
718
+ "Enable authentication for better security"
719
+ )
720
+
655
721
  if not config.rate_limit.enabled:
656
- audit_results["recommendations"].append("Enable rate limiting to prevent abuse")
657
-
722
+ audit_results["recommendations"].append(
723
+ "Enable rate limiting to prevent abuse"
724
+ )
725
+
658
726
  if not config.ssl.enabled:
659
- audit_results["recommendations"].append("Enable SSL/TLS for secure communication")
660
-
727
+ audit_results["recommendations"].append(
728
+ "Enable SSL/TLS for secure communication"
729
+ )
730
+
661
731
  if not config.permissions.enabled:
662
- audit_results["recommendations"].append("Enable permissions for access control")
663
-
732
+ audit_results["recommendations"].append(
733
+ "Enable permissions for access control"
734
+ )
735
+
664
736
  # Output audit results
665
- if format == 'json':
737
+ if format == "json":
666
738
  audit_json = json.dumps(audit_results, indent=2)
667
739
  if output:
668
- with open(output, 'w') as f:
740
+ with open(output, "w") as f:
669
741
  f.write(audit_json)
670
742
  click.echo(f"✅ Security audit report saved: {output}")
671
743
  else:
@@ -676,47 +748,57 @@ def security_audit(ctx, output: Optional[str], format: str):
676
748
  click.echo("=" * 50)
677
749
  click.echo(f"Timestamp: {audit_results['timestamp']}")
678
750
  click.echo()
679
-
751
+
680
752
  click.echo("Configuration:")
681
753
  click.echo("-" * 20)
682
- auth_config = audit_results['configuration']['authentication']
683
- click.echo(f"Authentication: {'✅ Enabled' if auth_config['enabled'] else '❌ Disabled'}")
684
- if auth_config['methods']:
754
+ auth_config = audit_results["configuration"]["authentication"]
755
+ click.echo(
756
+ f"Authentication: {'✅ Enabled' if auth_config['enabled'] else '❌ Disabled'}"
757
+ )
758
+ if auth_config["methods"]:
685
759
  click.echo(f" Methods: {', '.join(auth_config['methods'])}")
686
-
687
- rate_config = audit_results['configuration']['rate_limiting']
688
- click.echo(f"Rate Limiting: {'✅ Enabled' if rate_config['enabled'] else '❌ Disabled'}")
689
- if rate_config['default_limit']:
690
- click.echo(f" Default Limit: {rate_config['default_limit']} requests/minute")
691
-
692
- ssl_config = audit_results['configuration']['ssl_tls']
693
- click.echo(f"SSL/TLS: {'✅ Enabled' if ssl_config['enabled'] else '❌ Disabled'}")
694
-
695
- perm_config = audit_results['configuration']['permissions']
696
- click.echo(f"Permissions: {'✅ Enabled' if perm_config['enabled'] else '❌ Disabled'}")
697
-
760
+
761
+ rate_config = audit_results["configuration"]["rate_limiting"]
762
+ click.echo(
763
+ f"Rate Limiting: {'✅ Enabled' if rate_config['enabled'] else '❌ Disabled'}"
764
+ )
765
+ if rate_config["default_limit"]:
766
+ click.echo(
767
+ f" Default Limit: {rate_config['default_limit']} requests/minute"
768
+ )
769
+
770
+ ssl_config = audit_results["configuration"]["ssl_tls"]
771
+ click.echo(
772
+ f"SSL/TLS: {'✅ Enabled' if ssl_config['enabled'] else '❌ Disabled'}"
773
+ )
774
+
775
+ perm_config = audit_results["configuration"]["permissions"]
776
+ click.echo(
777
+ f"Permissions: {'✅ Enabled' if perm_config['enabled'] else '❌ Disabled'}"
778
+ )
779
+
698
780
  click.echo()
699
781
  click.echo("Components:")
700
782
  click.echo("-" * 20)
701
- for name, status in audit_results['components'].items():
783
+ for name, status in audit_results["components"].items():
702
784
  click.echo(f"{name.replace('_', ' ').title()}: {status['status']}")
703
- if 'api_keys_count' in status:
785
+ if "api_keys_count" in status:
704
786
  click.echo(f" API Keys: {status['api_keys_count']}")
705
- if 'roles_count' in status:
787
+ if "roles_count" in status:
706
788
  click.echo(f" Roles: {status['roles_count']}")
707
-
708
- if audit_results['recommendations']:
789
+
790
+ if audit_results["recommendations"]:
709
791
  click.echo()
710
792
  click.echo("Recommendations:")
711
793
  click.echo("-" * 20)
712
- for rec in audit_results['recommendations']:
794
+ for rec in audit_results["recommendations"]:
713
795
  click.echo(f"• {rec}")
714
-
796
+
715
797
  if output:
716
- with open(output, 'w') as f:
798
+ with open(output, "w") as f:
717
799
  f.write(json.dumps(audit_results, indent=2))
718
800
  click.echo(f"\n✅ Detailed audit report saved: {output}")
719
-
801
+
720
802
  except Exception as e:
721
803
  click.echo(f"❌ Failed to perform security audit: {str(e)}", err=True)
722
804
  raise click.Abort()
@@ -727,46 +809,54 @@ def security_audit(ctx, output: Optional[str], format: str):
727
809
  def status(ctx):
728
810
  """
729
811
  Display security status.
730
-
812
+
731
813
  This command displays the current status of all security
732
814
  components and their configuration.
733
815
  """
734
816
  try:
735
- config = ctx.obj['config']
736
- security_manager = ctx.obj['security_manager']
737
- verbose = ctx.obj['verbose']
738
-
817
+ config = ctx.obj["config"]
818
+ security_manager = ctx.obj["security_manager"]
819
+ verbose = ctx.obj["verbose"]
820
+
739
821
  if verbose:
740
822
  click.echo("Getting security status...")
741
-
823
+
742
824
  click.echo("Security Status:")
743
825
  click.echo("=" * 50)
744
-
826
+
745
827
  # Authentication status
746
828
  auth_enabled = config.auth.enabled if config.auth else False
747
829
  auth_methods = config.auth.methods if config.auth else []
748
830
  click.echo(f"Authentication: {'✅ Enabled' if auth_enabled else '❌ Disabled'}")
749
831
  if auth_methods:
750
832
  click.echo(f" Methods: {', '.join(auth_methods)}")
751
-
833
+
752
834
  # Rate limiting status
753
835
  rate_limit_enabled = config.rate_limit.enabled if config.rate_limit else False
754
- click.echo(f"Rate Limiting: {'✅ Enabled' if rate_limit_enabled else '❌ Disabled'}")
836
+ click.echo(
837
+ f"Rate Limiting: {'✅ Enabled' if rate_limit_enabled else '❌ Disabled'}"
838
+ )
755
839
  if rate_limit_enabled and config.rate_limit:
756
- click.echo(f" Default Limit: {config.rate_limit.default_requests_per_minute} requests/minute")
757
-
840
+ click.echo(
841
+ f" Default Limit: {config.rate_limit.default_requests_per_minute} requests/minute"
842
+ )
843
+
758
844
  # SSL/TLS status
759
845
  ssl_enabled = config.ssl.enabled if config.ssl else False
760
846
  click.echo(f"SSL/TLS: {'✅ Enabled' if ssl_enabled else '❌ Disabled'}")
761
-
847
+
762
848
  # Permissions status
763
- permissions_enabled = config.permissions.enabled if config.permissions else False
764
- click.echo(f"Permissions: {'✅ Enabled' if permissions_enabled else '❌ Disabled'}")
765
-
849
+ permissions_enabled = (
850
+ config.permissions.enabled if config.permissions else False
851
+ )
852
+ click.echo(
853
+ f"Permissions: {'✅ Enabled' if permissions_enabled else '❌ Disabled'}"
854
+ )
855
+
766
856
  # Component status
767
857
  click.echo("\nComponent Status:")
768
858
  click.echo("-" * 30)
769
-
859
+
770
860
  # Check if components are properly initialized
771
861
  components = [
772
862
  ("Auth Manager", security_manager.auth_manager),
@@ -775,17 +865,17 @@ def status(ctx):
775
865
  ("SSL Manager", security_manager.ssl_manager),
776
866
  ("Certificate Manager", security_manager.cert_manager),
777
867
  ]
778
-
868
+
779
869
  for name, component in components:
780
870
  if component:
781
871
  click.echo(f"{name}: ✅ Initialized")
782
872
  else:
783
873
  click.echo(f"{name}: ❌ Not Initialized")
784
-
874
+
785
875
  except Exception as e:
786
876
  click.echo(f"❌ Failed to get security status: {str(e)}", err=True)
787
877
  raise click.Abort()
788
878
 
789
879
 
790
- if __name__ == '__main__':
880
+ if __name__ == "__main__":
791
881
  security_cli()