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.
Files changed (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,1011 @@
1
+ """
2
+ Authentication Manager Module
3
+
4
+ This module provides comprehensive authentication management for the
5
+ MCP Security Framework. It handles multiple authentication methods,
6
+ JWT token management, and integration with permission management.
7
+
8
+ Key Features:
9
+ - Multiple authentication methods (API key, JWT, certificate)
10
+ - JWT token creation and validation
11
+ - Integration with PermissionManager for role extraction
12
+ - Secure credential validation
13
+ - Authentication result management
14
+ - Session management utilities
15
+
16
+ Classes:
17
+ AuthManager: Main authentication management class
18
+ AuthResult: Authentication result container
19
+ JWTManager: JWT token management utilities
20
+
21
+ Author: MCP Security Team
22
+ Version: 1.0.0
23
+ License: MIT
24
+ """
25
+
26
+ import logging
27
+ import time
28
+ from datetime import datetime, timedelta, timezone
29
+ from typing import Dict, List, Optional, Union, Any
30
+
31
+ import jwt
32
+ from cryptography import x509
33
+ from cryptography.hazmat.primitives import hashes, serialization
34
+
35
+ from ..core.permission_manager import PermissionManager
36
+ from ..schemas.config import AuthConfig
37
+ from ..schemas.models import AuthResult, AuthStatus, ValidationResult
38
+ from ..utils.cert_utils import (
39
+ extract_permissions_from_certificate,
40
+ extract_roles_from_certificate,
41
+ parse_certificate,
42
+ validate_certificate_chain,
43
+ )
44
+ from ..utils.crypto_utils import (
45
+ generate_secure_token,
46
+ hash_password,
47
+ validate_api_key_format,
48
+ verify_password,
49
+ )
50
+
51
+
52
+ class AuthManager:
53
+ """
54
+ Authentication Manager Class
55
+
56
+ This class provides comprehensive authentication management including
57
+ multiple authentication methods, JWT token management, and integration
58
+ with permission management.
59
+
60
+ The AuthManager handles:
61
+ - API key authentication with secure validation
62
+ - JWT token creation, validation, and management
63
+ - Certificate-based authentication with role extraction
64
+ - Integration with PermissionManager for role validation
65
+ - Secure credential storage and validation
66
+ - Session management and token refresh
67
+ - Authentication result management with detailed status
68
+
69
+ Attributes:
70
+ config (AuthConfig): Authentication configuration settings
71
+ logger (Logger): Logger instance for authentication operations
72
+ permission_manager (PermissionManager): Permission manager instance
73
+ _api_keys (Dict): Stored API keys for validation
74
+ _jwt_secret (str): JWT signing secret
75
+ _token_cache (Dict): Cache for validated tokens
76
+ _session_store (Dict): Active session storage
77
+
78
+ Example:
79
+ >>> config = AuthConfig(api_keys={"user1": "key123"}, jwt_secret="secret")
80
+ >>> auth_manager = AuthManager(config, permission_manager)
81
+ >>> result = auth_manager.authenticate_api_key("key123")
82
+
83
+ Raises:
84
+ AuthenticationConfigurationError: When authentication configuration is invalid
85
+ AuthenticationError: When authentication fails
86
+ JWTValidationError: When JWT token validation fails
87
+ """
88
+
89
+ def __init__(self, config: AuthConfig, permission_manager: PermissionManager):
90
+ """
91
+ Initialize Authentication Manager.
92
+
93
+ Args:
94
+ config (AuthConfig): Authentication configuration settings containing
95
+ API keys, JWT settings, and authentication parameters.
96
+ Must be a valid AuthConfig instance with proper authentication
97
+ settings and security parameters.
98
+ permission_manager (PermissionManager): Permission manager instance
99
+ for role and permission validation. Must be a valid
100
+ PermissionManager instance.
101
+
102
+ Raises:
103
+ AuthenticationConfigurationError: If configuration is invalid or
104
+ permission manager is not provided.
105
+
106
+ Example:
107
+ >>> config = AuthConfig(api_keys={"user1": "key123"})
108
+ >>> perm_manager = PermissionManager(perm_config)
109
+ >>> auth_manager = AuthManager(config, perm_manager)
110
+ """
111
+ if not config:
112
+ raise AuthenticationConfigurationError(
113
+ "Authentication configuration is required"
114
+ )
115
+
116
+ if not permission_manager:
117
+ raise AuthenticationConfigurationError("Permission manager is required")
118
+
119
+ self.config = config
120
+ self.permission_manager = permission_manager
121
+ self.logger = logging.getLogger(__name__)
122
+
123
+ # Initialize storage
124
+ # Convert API keys from "key": "user" format to "user": "key" format
125
+ # or handle new format "key": {"username": "user", "roles": ["role1", "role2"]}
126
+ if config.api_keys:
127
+ self._api_keys = {}
128
+ self._api_key_metadata = {}
129
+ for key, value in config.api_keys.items():
130
+ if isinstance(value, str):
131
+ # Old format: "key": "user"
132
+ self._api_keys[value] = key
133
+ elif isinstance(value, dict):
134
+ # New format: "key": {"username": "user", "roles": ["role1", "role2"]}
135
+ username = value.get("username", key)
136
+ self._api_keys[username] = key
137
+ self._api_key_metadata[key] = value
138
+ else:
139
+ self.logger.warning(f"Invalid API key format for key {key}: {value}")
140
+ else:
141
+ self._api_keys = {}
142
+ self._api_key_metadata = {}
143
+ self._jwt_secret = (
144
+ config.jwt_secret.get_secret_value()
145
+ if config.jwt_secret
146
+ else generate_secure_token(32)
147
+ )
148
+ self._token_cache: Dict[str, Dict] = {}
149
+ self._session_store: Dict[str, Dict] = {}
150
+
151
+ # Validate configuration
152
+ self._validate_configuration()
153
+
154
+ self.logger.info(
155
+ "AuthManager initialized successfully",
156
+ extra={
157
+ "api_keys_count": len(self._api_keys),
158
+ "jwt_enabled": bool(self._jwt_secret),
159
+ "jwt_expiry_hours": config.jwt_expiry_hours,
160
+ },
161
+ )
162
+
163
+ def authenticate_api_key(self, api_key: str) -> AuthResult:
164
+ """
165
+ Authenticate user using API key.
166
+
167
+ This method validates an API key against the configured key store and
168
+ returns authentication result with user information and permissions.
169
+
170
+ The method performs comprehensive API key validation including:
171
+ - Key format validation and sanitization
172
+ - Key existence verification in configured store
173
+ - User role and permission extraction
174
+ - Authentication result generation with metadata
175
+
176
+ Args:
177
+ api_key (str): API key string to validate. Must be a non-empty
178
+ string containing the API key value. The key should be
179
+ provided in the format expected by the authentication system.
180
+ Valid keys are typically 32-64 character alphanumeric strings.
181
+
182
+ Returns:
183
+ AuthResult: Authentication result object containing:
184
+ - is_valid (bool): True if authentication succeeded
185
+ - username (str): Authenticated username if valid
186
+ - roles (List[str]): User roles and permissions
187
+ - auth_method (str): Authentication method used ("api_key")
188
+ - error_code (int): Error code if authentication failed
189
+ - error_message (str): Human-readable error message
190
+
191
+ Raises:
192
+ AuthenticationError: When API key is invalid, expired, or
193
+ authentication process fails
194
+ AuthenticationConfigurationError: When authentication configuration
195
+ is invalid or missing
196
+ ValueError: When API key parameter is empty or malformed
197
+
198
+ Example:
199
+ >>> auth_manager = AuthManager(config, permission_manager)
200
+ >>> result = auth_manager.authenticate_api_key("user_api_key_123")
201
+ >>> if result.is_valid:
202
+ ... print(f"Authenticated user: {result.username}")
203
+ ... print(f"User roles: {result.roles}")
204
+ >>> else:
205
+ ... print(f"Authentication failed: {result.error_message}")
206
+
207
+ Note:
208
+ This method is thread-safe and can be called concurrently.
209
+ API keys are validated against the in-memory key store for
210
+ performance. For production use, consider implementing
211
+ persistent key storage with proper encryption.
212
+
213
+ See Also:
214
+ authenticate_jwt_token: JWT token authentication method
215
+ authenticate_certificate: Certificate-based authentication
216
+ """
217
+ try:
218
+ # Validate input
219
+ if not api_key:
220
+ return AuthResult(
221
+ is_valid=False,
222
+ status=AuthStatus.INVALID,
223
+ auth_method="api_key",
224
+ error_code=-32001,
225
+ error_message="API key is required",
226
+ )
227
+
228
+ # Validate API key format
229
+ if not validate_api_key_format(api_key):
230
+ return AuthResult(
231
+ is_valid=False,
232
+ status=AuthStatus.INVALID,
233
+ auth_method="api_key",
234
+ error_code=-32002,
235
+ error_message="Invalid API key format",
236
+ )
237
+
238
+ # Find user by API key
239
+ username = None
240
+ user_roles = []
241
+ for user, api_key_in_config in self._api_keys.items():
242
+ if api_key_in_config == api_key:
243
+ username = user
244
+ # Check if we have metadata for this API key
245
+ if api_key in self._api_key_metadata:
246
+ metadata = self._api_key_metadata[api_key]
247
+ user_roles = metadata.get("roles", [])
248
+ break
249
+
250
+ if not username:
251
+ return AuthResult(
252
+ is_valid=False,
253
+ status=AuthStatus.INVALID,
254
+ auth_method="api_key",
255
+ error_code=-32003,
256
+ error_message="Invalid API key",
257
+ )
258
+
259
+ # If we don't have roles from metadata, get them from permission manager
260
+ if not user_roles:
261
+ try:
262
+ user_roles = self._get_user_roles(username)
263
+ except Exception as e:
264
+ self.logger.error(
265
+ "Failed to get user roles",
266
+ extra={"username": username, "error": str(e)},
267
+ )
268
+ return AuthResult(
269
+ is_valid=False,
270
+ status=AuthStatus.FAILED,
271
+ auth_method="api_key",
272
+ error_code=-32004,
273
+ error_message="Failed to retrieve user roles",
274
+ )
275
+
276
+ # Get user permissions from permission manager
277
+ user_permissions = set()
278
+ if self.permission_manager:
279
+ try:
280
+ permissions_result = self.permission_manager.get_effective_permissions(user_roles)
281
+ # Handle both set and mock objects
282
+ if hasattr(permissions_result, '__iter__') and not isinstance(permissions_result, str):
283
+ user_permissions = set(permissions_result)
284
+ else:
285
+ user_permissions = set()
286
+ except Exception as e:
287
+ self.logger.warning(
288
+ "Failed to get user permissions",
289
+ extra={"username": username, "roles": user_roles, "error": str(e)},
290
+ )
291
+
292
+ # Create successful authentication result
293
+ auth_result = AuthResult(
294
+ is_valid=True,
295
+ status=AuthStatus.SUCCESS,
296
+ username=username,
297
+ roles=user_roles,
298
+ permissions=user_permissions,
299
+ auth_method="api_key",
300
+ auth_timestamp=datetime.now(timezone.utc),
301
+ )
302
+
303
+ self.logger.info(
304
+ "API key authentication successful",
305
+ extra={
306
+ "username": username,
307
+ "roles": user_roles,
308
+ "auth_method": "api_key",
309
+ },
310
+ )
311
+
312
+ return auth_result
313
+
314
+ except Exception as e:
315
+ self.logger.error(
316
+ "API key authentication failed",
317
+ extra={
318
+ "api_key": api_key[:8] + "..." if api_key else None,
319
+ "error": str(e),
320
+ },
321
+ )
322
+ raise AuthenticationError(f"API key authentication failed: {str(e)}")
323
+
324
+ def authenticate_jwt_token(self, token: str) -> AuthResult:
325
+ """
326
+ Authenticate user using JWT token.
327
+
328
+ This method validates a JWT token and extracts user information
329
+ and permissions from the token payload.
330
+
331
+ The method performs comprehensive JWT validation including:
332
+ - Token format and structure validation
333
+ - Signature verification using configured secret
334
+ - Token expiration and validity checks
335
+ - Payload extraction and validation
336
+ - User role and permission extraction
337
+
338
+ Args:
339
+ token (str): JWT token string to validate. Must be a valid
340
+ JWT token format with proper signature and payload.
341
+ The token should contain user information and roles.
342
+
343
+ Returns:
344
+ AuthResult: Authentication result object containing:
345
+ - is_valid (bool): True if authentication succeeded
346
+ - username (str): Authenticated username if valid
347
+ - roles (List[str]): User roles from token
348
+ - auth_method (str): Authentication method used ("jwt")
349
+ - error_code (int): Error code if authentication failed
350
+ - error_message (str): Human-readable error message
351
+
352
+ Raises:
353
+ JWTValidationError: When JWT token is invalid, expired, or
354
+ validation process fails
355
+ AuthenticationError: When authentication process fails
356
+ ValueError: When token parameter is empty or malformed
357
+
358
+ Example:
359
+ >>> auth_manager = AuthManager(config, permission_manager)
360
+ >>> result = auth_manager.authenticate_jwt_token("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...")
361
+ >>> if result.is_valid:
362
+ ... print(f"Authenticated user: {result.username}")
363
+ ... print(f"Token expires: {result.expires_at}")
364
+ >>> else:
365
+ ... print(f"JWT validation failed: {result.error_message}")
366
+
367
+ Note:
368
+ This method validates JWT tokens using the configured secret.
369
+ Token expiration is automatically checked. For production use,
370
+ consider implementing token refresh mechanisms.
371
+
372
+ See Also:
373
+ create_jwt_token: JWT token creation method
374
+ authenticate_api_key: API key authentication method
375
+ """
376
+ try:
377
+ # Validate input
378
+ if not token:
379
+ return AuthResult(
380
+ is_valid=False,
381
+ status=AuthStatus.INVALID,
382
+ auth_method="jwt",
383
+ error_code=-32001,
384
+ error_message="JWT token is required",
385
+ )
386
+
387
+ # Check token cache
388
+ if token in self._token_cache:
389
+ cached_result = self._token_cache[token]
390
+ if not self._is_token_expired(cached_result):
391
+ return cached_result
392
+ else:
393
+ # Remove expired token from cache
394
+ del self._token_cache[token]
395
+
396
+ # Decode and validate JWT token
397
+ try:
398
+ payload = jwt.decode(
399
+ token,
400
+ self._jwt_secret,
401
+ algorithms=["HS256"],
402
+ options={
403
+ "verify_signature": True,
404
+ "verify_exp": True,
405
+ "verify_iat": True,
406
+ "verify_aud": False,
407
+ },
408
+ )
409
+ except jwt.ExpiredSignatureError:
410
+ return AuthResult(
411
+ is_valid=False,
412
+ status=AuthStatus.EXPIRED,
413
+ auth_method="jwt",
414
+ error_code=-32002,
415
+ error_message="JWT token has expired",
416
+ )
417
+ except jwt.InvalidTokenError as e:
418
+ return AuthResult(
419
+ is_valid=False,
420
+ status=AuthStatus.INVALID,
421
+ auth_method="jwt",
422
+ error_code=-32003,
423
+ error_message=f"Invalid JWT token: {str(e)}",
424
+ )
425
+
426
+ # Extract user information from payload
427
+ username = payload.get("username")
428
+ if not username:
429
+ return AuthResult(
430
+ is_valid=False,
431
+ status=AuthStatus.INVALID,
432
+ auth_method="jwt",
433
+ error_code=-32004,
434
+ error_message="JWT token missing username",
435
+ )
436
+
437
+ # Extract roles from payload
438
+ roles = payload.get("roles", [])
439
+ if not isinstance(roles, list):
440
+ roles = []
441
+
442
+ # Extract expiration time
443
+ expires_at = None
444
+ if "exp" in payload:
445
+ expires_at = datetime.fromtimestamp(payload["exp"], tz=timezone.utc)
446
+
447
+ # Get user permissions from permission manager
448
+ user_permissions = set()
449
+ if self.permission_manager:
450
+ try:
451
+ permissions_result = self.permission_manager.get_effective_permissions(roles)
452
+ # Handle both set and mock objects
453
+ if hasattr(permissions_result, '__iter__') and not isinstance(permissions_result, str):
454
+ user_permissions = set(permissions_result)
455
+ else:
456
+ user_permissions = set()
457
+ except Exception as e:
458
+ self.logger.warning(
459
+ "Failed to get user permissions",
460
+ extra={"username": username, "roles": roles, "error": str(e)},
461
+ )
462
+
463
+ # Create successful authentication result
464
+ auth_result = AuthResult(
465
+ is_valid=True,
466
+ status=AuthStatus.SUCCESS,
467
+ username=username,
468
+ roles=roles,
469
+ permissions=user_permissions,
470
+ auth_method="jwt",
471
+ auth_timestamp=datetime.now(timezone.utc),
472
+ token_expiry=expires_at,
473
+ )
474
+
475
+ # Cache the result
476
+ self._token_cache[token] = auth_result
477
+
478
+ self.logger.info(
479
+ "JWT token authentication successful",
480
+ extra={"username": username, "roles": roles, "auth_method": "jwt"},
481
+ )
482
+
483
+ return auth_result
484
+
485
+ except Exception as e:
486
+ self.logger.error(
487
+ "JWT token authentication failed",
488
+ extra={"token": token[:20] + "..." if token else None, "error": str(e)},
489
+ )
490
+ raise JWTValidationError(f"JWT token authentication failed: {str(e)}")
491
+
492
+ def authenticate_certificate(self, cert_pem: str) -> AuthResult:
493
+ """
494
+ Authenticate user using X.509 certificate.
495
+
496
+ This method validates an X.509 certificate and extracts user
497
+ information and roles from certificate extensions.
498
+
499
+ The method performs comprehensive certificate validation including:
500
+ - Certificate format and structure validation
501
+ - Certificate chain validation against CA
502
+ - Certificate expiration and validity checks
503
+ - Role and permission extraction from certificate extensions
504
+ - Certificate fingerprint validation
505
+
506
+ Args:
507
+ cert_pem (str): X.509 certificate in PEM format. Must be a valid
508
+ PEM certificate with proper structure and extensions.
509
+ The certificate should contain user roles in extensions.
510
+
511
+ Returns:
512
+ AuthResult: Authentication result object containing:
513
+ - is_valid (bool): True if authentication succeeded
514
+ - username (str): Authenticated username from certificate
515
+ - roles (List[str]): User roles from certificate
516
+ - auth_method (str): Authentication method used ("certificate")
517
+ - error_code (int): Error code if authentication failed
518
+ - error_message (str): Human-readable error message
519
+
520
+ Raises:
521
+ CertificateValidationError: When certificate is invalid, expired, or
522
+ validation process fails
523
+ AuthenticationError: When authentication process fails
524
+ ValueError: When certificate parameter is empty or malformed
525
+
526
+ Example:
527
+ >>> auth_manager = AuthManager(config, permission_manager)
528
+ >>> with open("user.crt", "r") as f:
529
+ ... cert_pem = f.read()
530
+ >>> result = auth_manager.authenticate_certificate(cert_pem)
531
+ >>> if result.is_valid:
532
+ ... print(f"Authenticated user: {result.username}")
533
+ ... print(f"Certificate roles: {result.roles}")
534
+ >>> else:
535
+ ... print(f"Certificate validation failed: {result.error_message}")
536
+
537
+ Note:
538
+ This method validates X.509 certificates and extracts roles
539
+ from certificate extensions. Certificate chain validation
540
+ requires proper CA configuration.
541
+
542
+ See Also:
543
+ authenticate_api_key: API key authentication method
544
+ authenticate_jwt_token: JWT token authentication method
545
+ """
546
+ try:
547
+ # Validate input
548
+ if not cert_pem:
549
+ return AuthResult(
550
+ is_valid=False,
551
+ status=AuthStatus.INVALID,
552
+ auth_method="certificate",
553
+ error_code=-32001,
554
+ error_message="Certificate is required",
555
+ )
556
+
557
+ # Parse certificate
558
+ try:
559
+ cert = parse_certificate(cert_pem)
560
+ except Exception as e:
561
+ return AuthResult(
562
+ is_valid=False,
563
+ status=AuthStatus.INVALID,
564
+ auth_method="certificate",
565
+ error_code=-32002,
566
+ error_message=f"Invalid certificate format: {str(e)}",
567
+ )
568
+
569
+ # Check certificate expiration
570
+ now = datetime.now(timezone.utc)
571
+ if cert.not_valid_after.replace(tzinfo=timezone.utc) < now:
572
+ return AuthResult(
573
+ is_valid=False,
574
+ status=AuthStatus.EXPIRED,
575
+ auth_method="certificate",
576
+ error_code=-32003,
577
+ error_message="Certificate has expired",
578
+ )
579
+
580
+ # Extract username from certificate subject
581
+ username = self._extract_username_from_certificate(cert)
582
+ if not username:
583
+ return AuthResult(
584
+ is_valid=False,
585
+ status=AuthStatus.INVALID,
586
+ auth_method="certificate",
587
+ error_code=-32004,
588
+ error_message="Certificate missing username in subject",
589
+ )
590
+
591
+ # Extract roles from certificate
592
+ try:
593
+ roles = extract_roles_from_certificate(cert_pem)
594
+ except Exception as e:
595
+ self.logger.warning(
596
+ "Failed to extract roles from certificate",
597
+ extra={"username": username, "error": str(e)},
598
+ )
599
+ roles = []
600
+
601
+ # Validate certificate chain if CA is configured
602
+ if self.config.ca_cert_file:
603
+ try:
604
+ is_valid_chain = validate_certificate_chain(
605
+ cert_pem, self.config.ca_cert_file
606
+ )
607
+ if not is_valid_chain:
608
+ return AuthResult(
609
+ is_valid=False,
610
+ status=AuthStatus.INVALID,
611
+ auth_method="certificate",
612
+ error_code=-32005,
613
+ error_message="Certificate chain validation failed",
614
+ )
615
+ except Exception as e:
616
+ self.logger.warning(
617
+ "Certificate chain validation failed",
618
+ extra={"username": username, "error": str(e)},
619
+ )
620
+
621
+ # Create successful authentication result
622
+ auth_result = AuthResult(
623
+ is_valid=True,
624
+ status=AuthStatus.SUCCESS,
625
+ username=username,
626
+ roles=roles,
627
+ auth_method="certificate",
628
+ auth_timestamp=datetime.now(timezone.utc),
629
+ token_expiry=cert.not_valid_after.replace(tzinfo=timezone.utc),
630
+ )
631
+
632
+ self.logger.info(
633
+ "Certificate authentication successful",
634
+ extra={
635
+ "username": username,
636
+ "roles": roles,
637
+ "auth_method": "certificate",
638
+ },
639
+ )
640
+
641
+ return auth_result
642
+
643
+ except Exception as e:
644
+ self.logger.error(
645
+ "Certificate authentication failed",
646
+ extra={
647
+ "cert_pem": cert_pem[:100] + "..." if cert_pem else None,
648
+ "error": str(e),
649
+ },
650
+ )
651
+ raise CertificateValidationError(
652
+ f"Certificate authentication failed: {str(e)}"
653
+ )
654
+
655
+ def create_jwt_token(self, user_data: Dict) -> str:
656
+ """
657
+ Create JWT token for user.
658
+
659
+ This method creates a JWT token containing user information
660
+ and roles for authentication purposes.
661
+
662
+ The method creates a secure JWT token with:
663
+ - User information in payload
664
+ - Role and permission data
665
+ - Proper expiration time
666
+ - Secure signing with configured secret
667
+ - Standard JWT claims (iat, exp, sub)
668
+
669
+ Args:
670
+ user_data (Dict): User data dictionary containing:
671
+ - username (str): User username (required)
672
+ - roles (List[str]): User roles (optional)
673
+ - permissions (List[str]): User permissions (optional)
674
+ - additional_data (Dict): Additional user data (optional)
675
+ Must contain at least username field.
676
+
677
+ Returns:
678
+ str: JWT token string containing user information and claims.
679
+ The token is signed with the configured secret and includes
680
+ standard JWT claims and user-specific data.
681
+
682
+ Raises:
683
+ ValueError: When user_data is invalid or missing required fields
684
+ JWTValidationError: When JWT token creation fails
685
+ AuthenticationConfigurationError: When JWT configuration is invalid
686
+
687
+ Example:
688
+ >>> auth_manager = AuthManager(config, permission_manager)
689
+ >>> user_data = {
690
+ ... "username": "john_doe",
691
+ ... "roles": ["user", "admin"],
692
+ ... "permissions": ["read:users", "write:posts"]
693
+ ... }
694
+ >>> token = auth_manager.create_jwt_token(user_data)
695
+ >>> print(f"Created JWT token: {token}")
696
+
697
+ Note:
698
+ This method creates JWT tokens with configurable expiration.
699
+ Tokens are signed with the configured secret for security.
700
+ For production use, consider implementing token refresh mechanisms.
701
+
702
+ See Also:
703
+ authenticate_jwt_token: JWT token validation method
704
+ validate_jwt_token: JWT token validation utility
705
+ """
706
+ try:
707
+ # Validate input
708
+ if user_data is None:
709
+ raise ValueError("User data must be provided")
710
+
711
+ if not isinstance(user_data, dict):
712
+ raise ValueError("User data must be a dictionary")
713
+
714
+ if not user_data: # Check if dict is empty
715
+ raise ValueError("User data dictionary cannot be empty")
716
+
717
+ username = user_data.get("username")
718
+ if not username:
719
+ raise ValueError("Username is required in user data")
720
+
721
+ # Prepare JWT payload
722
+ now = datetime.now(timezone.utc)
723
+ payload = {
724
+ "username": username,
725
+ "roles": user_data.get("roles", []),
726
+ "permissions": user_data.get("permissions", []),
727
+ "iat": int(now.timestamp()),
728
+ "exp": int(
729
+ (now + timedelta(hours=self.config.jwt_expiry_hours)).timestamp()
730
+ ),
731
+ "sub": username,
732
+ "iss": "mcp_security_framework",
733
+ }
734
+
735
+ # Add additional user data if provided
736
+ additional_data = user_data.get("additional_data", {})
737
+ if additional_data:
738
+ payload["user_data"] = additional_data
739
+
740
+ # Create JWT token
741
+ token = jwt.encode(payload, self._jwt_secret, algorithm="HS256")
742
+
743
+ self.logger.info(
744
+ "JWT token created successfully",
745
+ extra={
746
+ "username": username,
747
+ "roles": payload["roles"],
748
+ "expires_in_hours": self.config.jwt_expiry_hours,
749
+ },
750
+ )
751
+
752
+ return token
753
+
754
+ except Exception as e:
755
+ self.logger.error(
756
+ "Failed to create JWT token",
757
+ extra={"user_data": user_data, "error": str(e)},
758
+ )
759
+ raise JWTValidationError(f"Failed to create JWT token: {str(e)}")
760
+
761
+ def validate_jwt_token(self, token: str) -> bool:
762
+ """
763
+ Validate JWT token without full authentication.
764
+
765
+ This method performs basic JWT token validation including
766
+ signature verification and expiration checks.
767
+
768
+ Args:
769
+ token (str): JWT token to validate. Must be a valid
770
+ JWT token format with proper signature.
771
+
772
+ Returns:
773
+ bool: True if token is valid, False otherwise.
774
+ Returns True when token has valid signature and
775
+ is not expired.
776
+
777
+ Example:
778
+ >>> auth_manager = AuthManager(config, permission_manager)
779
+ >>> is_valid = auth_manager.validate_jwt_token("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...")
780
+ >>> if is_valid:
781
+ ... print("Token is valid")
782
+ >>> else:
783
+ ... print("Token is invalid")
784
+ """
785
+ try:
786
+ if not token:
787
+ return False
788
+
789
+ # Decode token to check signature and expiration
790
+ jwt.decode(
791
+ token,
792
+ self._jwt_secret,
793
+ algorithms=["HS256"],
794
+ options={
795
+ "verify_signature": True,
796
+ "verify_exp": True,
797
+ "verify_aud": False,
798
+ },
799
+ )
800
+
801
+ return True
802
+
803
+ except jwt.InvalidTokenError:
804
+ return False
805
+ except Exception:
806
+ return False
807
+
808
+ def add_api_key(self, username: str, api_key: str) -> bool:
809
+ """
810
+ Add API key for user.
811
+
812
+ This method adds an API key to the authentication store
813
+ for the specified user.
814
+
815
+ Args:
816
+ username (str): Username to associate with API key
817
+ api_key (str): API key to add
818
+
819
+ Returns:
820
+ bool: True if API key was added successfully, False otherwise
821
+
822
+ Example:
823
+ >>> auth_manager = AuthManager(config, permission_manager)
824
+ >>> success = auth_manager.add_api_key("john_doe", "new_api_key_123")
825
+ >>> if success:
826
+ ... print("API key added successfully")
827
+ """
828
+ try:
829
+ if not username or not api_key:
830
+ return False
831
+
832
+ if not validate_api_key_format(api_key):
833
+ return False
834
+
835
+ self._api_keys[username] = api_key
836
+
837
+ self.logger.info("API key added for user", extra={"username": username})
838
+
839
+ return True
840
+
841
+ except Exception as e:
842
+ self.logger.error(
843
+ "Failed to add API key", extra={"username": username, "error": str(e)}
844
+ )
845
+ return False
846
+
847
+ def remove_api_key(self, username: str) -> bool:
848
+ """
849
+ Remove API key for user.
850
+
851
+ Args:
852
+ username (str): Username whose API key to remove
853
+
854
+ Returns:
855
+ bool: True if API key was removed successfully, False otherwise
856
+ """
857
+ try:
858
+ if username in self._api_keys:
859
+ del self._api_keys[username]
860
+
861
+ self.logger.info(
862
+ "API key removed for user", extra={"username": username}
863
+ )
864
+
865
+ return True
866
+
867
+ return False
868
+
869
+ except Exception as e:
870
+ self.logger.error(
871
+ "Failed to remove API key",
872
+ extra={"username": username, "error": str(e)},
873
+ )
874
+ return False
875
+
876
+ def clear_token_cache(self) -> None:
877
+ """Clear JWT token cache."""
878
+ self._token_cache.clear()
879
+ self.logger.info("JWT token cache cleared")
880
+
881
+ def clear_session_store(self) -> None:
882
+ """Clear session store."""
883
+ self._session_store.clear()
884
+ self.logger.info("Session store cleared")
885
+
886
+ def _validate_configuration(self) -> None:
887
+ """Validate authentication configuration."""
888
+ if not self._jwt_secret:
889
+ raise AuthenticationConfigurationError("JWT secret is required")
890
+
891
+ if len(self._jwt_secret) < 16:
892
+ raise AuthenticationConfigurationError(
893
+ "JWT secret must be at least 16 characters"
894
+ )
895
+
896
+ def _get_user_roles(self, username: str) -> List[str]:
897
+ """
898
+ Get user roles from configuration.
899
+
900
+ This method retrieves user roles from the authentication configuration.
901
+ It checks both the user_roles mapping and the permission manager.
902
+
903
+ Args:
904
+ username (str): Username to get roles for
905
+
906
+ Returns:
907
+ List[str]: List of user roles
908
+ """
909
+ # Check user_roles mapping first
910
+ if self.config.user_roles and username in self.config.user_roles:
911
+ return self.config.user_roles[username]
912
+
913
+ # Fallback to permission manager
914
+ if self.permission_manager:
915
+ return self.permission_manager.get_user_roles(username)
916
+
917
+ return []
918
+
919
+ def _validate_external_user(self, username: str, credentials: Dict[str, Any]) -> bool:
920
+ """
921
+ Validate user against external authentication system.
922
+
923
+ This method provides a hook for integrating with external
924
+ authentication systems (LDAP, Active Directory, etc.).
925
+
926
+ Args:
927
+ username (str): Username to validate
928
+ credentials (Dict[str, Any]): User credentials
929
+
930
+ Returns:
931
+ bool: True if user is valid in external system
932
+ """
933
+ # This is a placeholder for external authentication integration
934
+ # In a real implementation, this would connect to external auth systems
935
+ self.logger.debug(
936
+ "External user validation called",
937
+ extra={"username": username, "method": "external"}
938
+ )
939
+
940
+ # For now, return False to indicate external validation not implemented
941
+ return False
942
+
943
+ def _extract_username_from_certificate(
944
+ self, cert: x509.Certificate
945
+ ) -> Optional[str]:
946
+ """Extract username from certificate subject."""
947
+ try:
948
+ # Try to get username from Common Name
949
+ cn_attr = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
950
+ if cn_attr:
951
+ return cn_attr[0].value
952
+
953
+ # Try to get username from Subject Alternative Name
954
+ try:
955
+ san = cert.extensions.get_extension_for_oid(
956
+ x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
957
+ )
958
+ if san and san.value:
959
+ for name in san.value:
960
+ if isinstance(name, x509.DNSName):
961
+ return name.value
962
+ except x509.ExtensionNotFound:
963
+ pass
964
+
965
+ return None
966
+
967
+ except Exception:
968
+ return None
969
+
970
+ def _is_token_expired(self, auth_result: AuthResult) -> bool:
971
+ """Check if authentication result is expired."""
972
+ if not auth_result.token_expiry:
973
+ return False
974
+
975
+ return datetime.now(timezone.utc) > auth_result.token_expiry
976
+
977
+
978
+ class AuthenticationConfigurationError(Exception):
979
+ """Raised when authentication configuration is invalid."""
980
+
981
+ def __init__(self, message: str, error_code: int = -32001):
982
+ self.message = message
983
+ self.error_code = error_code
984
+ super().__init__(self.message)
985
+
986
+
987
+ class AuthenticationError(Exception):
988
+ """Raised when authentication fails."""
989
+
990
+ def __init__(self, message: str, error_code: int = -32002):
991
+ self.message = message
992
+ self.error_code = error_code
993
+ super().__init__(self.message)
994
+
995
+
996
+ class JWTValidationError(Exception):
997
+ """Raised when JWT token validation fails."""
998
+
999
+ def __init__(self, message: str, error_code: int = -32003):
1000
+ self.message = message
1001
+ self.error_code = error_code
1002
+ super().__init__(self.message)
1003
+
1004
+
1005
+ class CertificateValidationError(Exception):
1006
+ """Raised when certificate validation fails."""
1007
+
1008
+ def __init__(self, message: str, error_code: int = -32004):
1009
+ self.message = message
1010
+ self.error_code = error_code
1011
+ super().__init__(self.message)