embed-client 2.0.0.0__py3-none-any.whl → 3.1.0.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.
embed_client/auth.py ADDED
@@ -0,0 +1,487 @@
1
+ """
2
+ Authentication system for embed-client.
3
+
4
+ This module provides comprehensive authentication management for the embed-client,
5
+ supporting all security modes and authentication methods. It integrates with
6
+ mcp_security_framework when available and provides fallback implementations.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ import base64
13
+ import logging
14
+ import time
15
+ from typing import Any, Dict, List, Optional, Union
16
+ from urllib.parse import urlparse
17
+
18
+ # Try to import mcp_security_framework components
19
+ try:
20
+ from mcp_security_framework.core.auth_manager import AuthManager
21
+ from mcp_security_framework.core.permission_manager import PermissionManager
22
+ from mcp_security_framework.schemas.config import AuthConfig, PermissionConfig
23
+ from mcp_security_framework.schemas.models import AuthResult, AuthStatus
24
+ from mcp_security_framework.utils.crypto_utils import (
25
+ create_jwt_token,
26
+ verify_jwt_token,
27
+ generate_secure_token,
28
+ hash_password,
29
+ verify_password
30
+ )
31
+ SECURITY_FRAMEWORK_AVAILABLE = True
32
+ except ImportError:
33
+ SECURITY_FRAMEWORK_AVAILABLE = False
34
+ print("Warning: mcp_security_framework not available. Using fallback authentication.")
35
+
36
+ # Fallback JWT implementation
37
+ try:
38
+ import jwt
39
+ JWT_AVAILABLE = True
40
+ except ImportError:
41
+ JWT_AVAILABLE = False
42
+ print("Warning: PyJWT not available. JWT authentication will be disabled.")
43
+
44
+
45
+ class AuthenticationError(Exception):
46
+ """Raised when authentication fails."""
47
+
48
+ def __init__(self, message: str, error_code: int = 401):
49
+ self.message = message
50
+ self.error_code = error_code
51
+ super().__init__(self.message)
52
+
53
+
54
+ class AuthResult:
55
+ """Authentication result container."""
56
+
57
+ def __init__(self, success: bool, user_id: Optional[str] = None,
58
+ roles: Optional[List[str]] = None, error: Optional[str] = None):
59
+ self.success = success
60
+ self.user_id = user_id
61
+ self.roles = roles or []
62
+ self.error = error
63
+
64
+
65
+ class ClientAuthManager:
66
+ """
67
+ Client Authentication Manager.
68
+
69
+ This class provides authentication management for the embed-client,
70
+ supporting multiple authentication methods with integration to
71
+ mcp_security_framework when available.
72
+ """
73
+
74
+ def __init__(self, config: Dict[str, Any]):
75
+ """
76
+ Initialize authentication manager.
77
+
78
+ Args:
79
+ config: Authentication configuration dictionary
80
+ """
81
+ self.config = config
82
+ self.logger = logging.getLogger(__name__)
83
+
84
+ # Initialize security framework components if available
85
+ self.auth_manager = None
86
+ self.permission_manager = None
87
+
88
+ if SECURITY_FRAMEWORK_AVAILABLE:
89
+ self._initialize_security_framework()
90
+ else:
91
+ self.logger.warning("mcp_security_framework not available, using fallback authentication")
92
+
93
+ def _initialize_security_framework(self) -> None:
94
+ """Initialize mcp_security_framework components."""
95
+ try:
96
+ # Create auth config
97
+ auth_config = AuthConfig(
98
+ enabled=self.config.get("auth", {}).get("enabled", True),
99
+ methods=self.config.get("auth", {}).get("methods", ["api_key"]),
100
+ api_keys=self.config.get("auth", {}).get("api_keys", {}),
101
+ jwt_secret=self.config.get("auth", {}).get("jwt", {}).get("secret", ""),
102
+ jwt_algorithm="HS256",
103
+ jwt_expiry_hours=self.config.get("auth", {}).get("jwt", {}).get("expiry_hours", 24)
104
+ )
105
+
106
+ # Create permission config
107
+ permission_config = PermissionConfig(
108
+ enabled=self.config.get("security", {}).get("roles_enabled", False),
109
+ roles_file=self.config.get("security", {}).get("roles_file")
110
+ )
111
+
112
+ # Initialize managers
113
+ self.permission_manager = PermissionManager(permission_config)
114
+ self.auth_manager = AuthManager(auth_config, self.permission_manager)
115
+
116
+ self.logger.info("Security framework initialized successfully")
117
+
118
+ except Exception as e:
119
+ self.logger.warning(f"Failed to initialize security framework: {e}")
120
+ self.auth_manager = None
121
+ self.permission_manager = None
122
+
123
+ def authenticate_api_key(self, api_key: str, header_name: str = "X-API-Key") -> AuthResult:
124
+ """
125
+ Authenticate using API key.
126
+
127
+ Args:
128
+ api_key: API key to authenticate
129
+ header_name: Header name for API key
130
+
131
+ Returns:
132
+ AuthResult with authentication status
133
+ """
134
+ try:
135
+ if self.auth_manager:
136
+ # Use security framework
137
+ result = self.auth_manager.authenticate_api_key(api_key)
138
+ if result.status == AuthStatus.SUCCESS:
139
+ return AuthResult(
140
+ success=True,
141
+ user_id=result.user_id,
142
+ roles=result.roles
143
+ )
144
+ else:
145
+ return AuthResult(success=False, error=result.error_message)
146
+ else:
147
+ # Fallback implementation
148
+ return self._authenticate_api_key_fallback(api_key)
149
+
150
+ except Exception as e:
151
+ self.logger.error(f"API key authentication failed: {e}")
152
+ return AuthResult(success=False, error=str(e))
153
+
154
+ def _authenticate_api_key_fallback(self, api_key: str) -> AuthResult:
155
+ """Fallback API key authentication."""
156
+ api_keys = self.config.get("auth", {}).get("api_keys", {})
157
+
158
+ # Check if API key exists
159
+ for user_id, stored_key in api_keys.items():
160
+ if stored_key == api_key:
161
+ return AuthResult(success=True, user_id=user_id)
162
+
163
+ return AuthResult(success=False, error="Invalid API key")
164
+
165
+ def authenticate_jwt(self, token: str) -> AuthResult:
166
+ """
167
+ Authenticate using JWT token.
168
+
169
+ Args:
170
+ token: JWT token to authenticate
171
+
172
+ Returns:
173
+ AuthResult with authentication status
174
+ """
175
+ try:
176
+ if self.auth_manager:
177
+ # Use security framework
178
+ result = self.auth_manager.validate_jwt_token(token)
179
+ if result.status == AuthStatus.SUCCESS:
180
+ return AuthResult(
181
+ success=True,
182
+ user_id=result.user_id,
183
+ roles=result.roles
184
+ )
185
+ else:
186
+ return AuthResult(success=False, error=result.error_message)
187
+ else:
188
+ # Fallback implementation
189
+ return self._authenticate_jwt_fallback(token)
190
+
191
+ except Exception as e:
192
+ self.logger.error(f"JWT authentication failed: {e}")
193
+ return AuthResult(success=False, error=str(e))
194
+
195
+ def _authenticate_jwt_fallback(self, token: str) -> AuthResult:
196
+ """Fallback JWT authentication."""
197
+ if not JWT_AVAILABLE:
198
+ return AuthResult(success=False, error="JWT not available")
199
+
200
+ try:
201
+ jwt_config = self.config.get("auth", {}).get("jwt", {})
202
+ secret = jwt_config.get("secret", "")
203
+
204
+ if not secret:
205
+ return AuthResult(success=False, error="JWT secret not configured")
206
+
207
+ # Decode and verify token
208
+ payload = jwt.decode(token, secret, algorithms=["HS256"])
209
+
210
+ # Check expiration
211
+ if "exp" in payload and payload["exp"] < time.time():
212
+ return AuthResult(success=False, error="Token expired")
213
+
214
+ return AuthResult(
215
+ success=True,
216
+ user_id=payload.get("sub", payload.get("user_id")),
217
+ roles=payload.get("roles", [])
218
+ )
219
+
220
+ except jwt.ExpiredSignatureError:
221
+ return AuthResult(success=False, error="Token expired")
222
+ except jwt.InvalidTokenError as e:
223
+ return AuthResult(success=False, error=f"Invalid token: {e}")
224
+
225
+ def authenticate_basic(self, username: str, password: str) -> AuthResult:
226
+ """
227
+ Authenticate using basic authentication.
228
+
229
+ Args:
230
+ username: Username
231
+ password: Password
232
+
233
+ Returns:
234
+ AuthResult with authentication status
235
+ """
236
+ try:
237
+ if self.auth_manager:
238
+ # Use security framework
239
+ result = self.auth_manager.authenticate_basic(username, password)
240
+ if result.status == AuthStatus.SUCCESS:
241
+ return AuthResult(
242
+ success=True,
243
+ user_id=result.user_id,
244
+ roles=result.roles
245
+ )
246
+ else:
247
+ return AuthResult(success=False, error=result.error_message)
248
+ else:
249
+ # Fallback implementation
250
+ return self._authenticate_basic_fallback(username, password)
251
+
252
+ except Exception as e:
253
+ self.logger.error(f"Basic authentication failed: {e}")
254
+ return AuthResult(success=False, error=str(e))
255
+
256
+ def _authenticate_basic_fallback(self, username: str, password: str) -> AuthResult:
257
+ """Fallback basic authentication."""
258
+ basic_config = self.config.get("auth", {}).get("basic", {})
259
+ stored_username = basic_config.get("username")
260
+ stored_password = basic_config.get("password")
261
+
262
+ if username == stored_username and password == stored_password:
263
+ return AuthResult(success=True, user_id=username)
264
+
265
+ return AuthResult(success=False, error="Invalid credentials")
266
+
267
+ def authenticate_certificate(self, cert_file: str, key_file: str) -> AuthResult:
268
+ """
269
+ Authenticate using client certificate.
270
+
271
+ Args:
272
+ cert_file: Path to client certificate file
273
+ key_file: Path to client private key file
274
+
275
+ Returns:
276
+ AuthResult with authentication status
277
+ """
278
+ try:
279
+ if self.auth_manager:
280
+ # Use security framework
281
+ result = self.auth_manager.authenticate_certificate(cert_file, key_file)
282
+ if result.status == AuthStatus.SUCCESS:
283
+ return AuthResult(
284
+ success=True,
285
+ user_id=result.user_id,
286
+ roles=result.roles
287
+ )
288
+ else:
289
+ return AuthResult(success=False, error=result.error_message)
290
+ else:
291
+ # Fallback implementation
292
+ return self._authenticate_certificate_fallback(cert_file, key_file)
293
+
294
+ except Exception as e:
295
+ self.logger.error(f"Certificate authentication failed: {e}")
296
+ return AuthResult(success=False, error=str(e))
297
+
298
+ def _authenticate_certificate_fallback(self, cert_file: str, key_file: str) -> AuthResult:
299
+ """Fallback certificate authentication."""
300
+ try:
301
+ # Basic file existence check
302
+ import os
303
+ if not os.path.exists(cert_file):
304
+ return AuthResult(success=False, error="Certificate file not found")
305
+ if not os.path.exists(key_file):
306
+ return AuthResult(success=False, error="Key file not found")
307
+
308
+ # For fallback, we just verify files exist
309
+ # In a real implementation, you would validate the certificate
310
+ return AuthResult(success=True, user_id="certificate_user")
311
+
312
+ except Exception as e:
313
+ return AuthResult(success=False, error=f"Certificate validation failed: {e}")
314
+
315
+ def create_jwt_token(self, user_id: str, roles: Optional[List[str]] = None,
316
+ expiry_hours: Optional[int] = None) -> str:
317
+ """
318
+ Create JWT token for user.
319
+
320
+ Args:
321
+ user_id: User identifier
322
+ roles: List of user roles
323
+ expiry_hours: Token expiry in hours
324
+
325
+ Returns:
326
+ JWT token string
327
+ """
328
+ try:
329
+ if self.auth_manager:
330
+ # Use security framework
331
+ return self.auth_manager.create_jwt_token(user_id, roles, expiry_hours)
332
+ else:
333
+ # Fallback implementation
334
+ return self._create_jwt_token_fallback(user_id, roles, expiry_hours)
335
+
336
+ except Exception as e:
337
+ self.logger.error(f"JWT token creation failed: {e}")
338
+ raise AuthenticationError(f"Failed to create JWT token: {e}")
339
+
340
+ def _create_jwt_token_fallback(self, user_id: str, roles: Optional[List[str]] = None,
341
+ expiry_hours: Optional[int] = None) -> str:
342
+ """Fallback JWT token creation."""
343
+ if not JWT_AVAILABLE:
344
+ raise AuthenticationError("JWT not available")
345
+
346
+ jwt_config = self.config.get("auth", {}).get("jwt", {})
347
+ secret = jwt_config.get("secret", "")
348
+ expiry = expiry_hours or jwt_config.get("expiry_hours", 24)
349
+
350
+ if not secret:
351
+ raise AuthenticationError("JWT secret not configured")
352
+
353
+ payload = {
354
+ "sub": user_id,
355
+ "user_id": user_id,
356
+ "roles": roles or [],
357
+ "exp": time.time() + (expiry * 3600),
358
+ "iat": time.time()
359
+ }
360
+
361
+ return jwt.encode(payload, secret, algorithm="HS256")
362
+
363
+ def get_auth_headers(self, auth_method: str, **kwargs) -> Dict[str, str]:
364
+ """
365
+ Get authentication headers for requests.
366
+
367
+ Args:
368
+ auth_method: Authentication method
369
+ **kwargs: Additional authentication parameters
370
+
371
+ Returns:
372
+ Dictionary of headers
373
+ """
374
+ headers = {}
375
+
376
+ if auth_method == "api_key":
377
+ api_key = kwargs.get("api_key")
378
+ header_name = kwargs.get("header", "X-API-Key")
379
+ if api_key:
380
+ headers[header_name] = api_key
381
+
382
+ elif auth_method == "jwt":
383
+ token = kwargs.get("token")
384
+ if token:
385
+ headers["Authorization"] = f"Bearer {token}"
386
+
387
+ elif auth_method == "basic":
388
+ username = kwargs.get("username")
389
+ password = kwargs.get("password")
390
+ if username and password:
391
+ credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
392
+ headers["Authorization"] = f"Basic {credentials}"
393
+
394
+ elif auth_method == "certificate":
395
+ # Certificate authentication is handled at SSL level
396
+ pass
397
+
398
+ return headers
399
+
400
+ def validate_auth_config(self) -> List[str]:
401
+ """
402
+ Validate authentication configuration.
403
+
404
+ Returns:
405
+ List of validation errors
406
+ """
407
+ errors = []
408
+ auth_config = self.config.get("auth", {})
409
+ auth_method = auth_config.get("method", "none")
410
+
411
+ if auth_method == "api_key":
412
+ api_keys = auth_config.get("api_keys", {})
413
+ if not api_keys:
414
+ errors.append("API keys not configured for api_key authentication")
415
+
416
+ elif auth_method == "jwt":
417
+ jwt_config = auth_config.get("jwt", {})
418
+ if not jwt_config.get("secret"):
419
+ errors.append("JWT secret not configured")
420
+ if not jwt_config.get("username"):
421
+ errors.append("JWT username not configured")
422
+ if not jwt_config.get("password"):
423
+ errors.append("JWT password not configured")
424
+
425
+ elif auth_method == "certificate":
426
+ cert_config = auth_config.get("certificate", {})
427
+ if not cert_config.get("cert_file"):
428
+ errors.append("Certificate file not configured")
429
+ if not cert_config.get("key_file"):
430
+ errors.append("Key file not configured")
431
+
432
+ elif auth_method == "basic":
433
+ basic_config = auth_config.get("basic", {})
434
+ if not basic_config.get("username"):
435
+ errors.append("Basic auth username not configured")
436
+ if not basic_config.get("password"):
437
+ errors.append("Basic auth password not configured")
438
+
439
+ return errors
440
+
441
+ def is_auth_enabled(self) -> bool:
442
+ """Check if authentication is enabled."""
443
+ return self.config.get("auth", {}).get("method", "none") != "none"
444
+
445
+ def get_auth_method(self) -> str:
446
+ """Get current authentication method."""
447
+ return self.config.get("auth", {}).get("method", "none")
448
+
449
+ def get_supported_methods(self) -> List[str]:
450
+ """Get list of supported authentication methods."""
451
+ if SECURITY_FRAMEWORK_AVAILABLE:
452
+ return ["api_key", "jwt", "certificate", "basic"]
453
+ else:
454
+ methods = ["api_key", "basic"]
455
+ if JWT_AVAILABLE:
456
+ methods.append("jwt")
457
+ return methods
458
+
459
+
460
+ def create_auth_manager(config: Dict[str, Any]) -> ClientAuthManager:
461
+ """
462
+ Create authentication manager from configuration.
463
+
464
+ Args:
465
+ config: Configuration dictionary
466
+
467
+ Returns:
468
+ ClientAuthManager instance
469
+ """
470
+ return ClientAuthManager(config)
471
+
472
+
473
+ def create_auth_headers(auth_method: str, **kwargs) -> Dict[str, str]:
474
+ """
475
+ Create authentication headers for requests.
476
+
477
+ Args:
478
+ auth_method: Authentication method
479
+ **kwargs: Authentication parameters
480
+
481
+ Returns:
482
+ Dictionary of headers
483
+ """
484
+ # Create temporary auth manager for header generation
485
+ temp_config = {"auth": {"method": auth_method}}
486
+ auth_manager = ClientAuthManager(temp_config)
487
+ return auth_manager.get_auth_headers(auth_method, **kwargs)