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