mcp-proxy-adapter 4.1.1__py3-none-any.whl ā 6.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.
- mcp_proxy_adapter/__main__.py +12 -0
- mcp_proxy_adapter/api/app.py +254 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +36 -30
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +243 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
- mcp_proxy_adapter/commands/__init__.py +19 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +66 -32
- mcp_proxy_adapter/commands/builtin_commands.py +95 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +711 -354
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +159 -2
- mcp_proxy_adapter/core/app_factory.py +326 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +19 -3
- mcp_proxy_adapter/core/mtls_asgi.py +156 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/protocol_manager.py +235 -0
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +522 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +370 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/security_integration.py +277 -0
- mcp_proxy_adapter/core/server_adapter.py +345 -0
- mcp_proxy_adapter/core/server_engine.py +364 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/README.md +230 -97
- mcp_proxy_adapter/examples/README_EN.md +258 -0
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
- mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
- mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
- mcp_proxy_adapter/examples/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/admin.key +52 -0
- mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
- mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
- mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/client.crt +32 -0
- mcp_proxy_adapter/examples/certs/client.key +52 -0
- mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
- mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_user.key +52 -0
- mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
- mcp_proxy_adapter/examples/certs/readonly.key +52 -0
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/server.crt +32 -0
- mcp_proxy_adapter/examples/certs/server.key +52 -0
- mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
- mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
- mcp_proxy_adapter/examples/certs/user.crt +32 -0
- mcp_proxy_adapter/examples/certs/user.key +52 -0
- mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
- mcp_proxy_adapter/examples/commands/__init__.py +1 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
- mcp_proxy_adapter/examples/debug_request_state.py +144 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
- mcp_proxy_adapter/examples/demo_client.py +341 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
- mcp_proxy_adapter/examples/full_application/main.py +138 -0
- mcp_proxy_adapter/examples/full_application/roles.json +21 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
- mcp_proxy_adapter/examples/generate_certificates.py +121 -0
- mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
- mcp_proxy_adapter/examples/roles.json +38 -0
- mcp_proxy_adapter/examples/run_example.py +81 -0
- mcp_proxy_adapter/examples/run_security_tests.py +326 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
- mcp_proxy_adapter/examples/security_test_client.py +743 -0
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
- mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
- mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
- mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
- mcp_proxy_adapter/examples/test_examples.py +344 -0
- mcp_proxy_adapter/examples/universal_client.py +628 -0
- mcp_proxy_adapter/main.py +186 -0
- mcp_proxy_adapter/utils/config_generator.py +639 -0
- mcp_proxy_adapter/version.py +2 -1
- mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
- mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
- mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-4.1.1.dist-info ā mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter/examples/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/README.md +0 -60
- mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
- mcp_proxy_adapter/examples/basic_server/config.json +0 -35
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
- mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
- mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
- mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- mcp_proxy_adapter/examples/deployment/README.md +0 -49
- mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
- mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
- mcp_proxy_adapter/examples/deployment/config.json +0 -29
- mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
- mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
- mcp_proxy_adapter/examples/deployment/run.sh +0 -43
- mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
- mcp_proxy_adapter/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +0 -3
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
- mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
- mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
- mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
- mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
- mcp_proxy_adapter/tests/commands/__init__.py +0 -3
- mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
- mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
- mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
- mcp_proxy_adapter/tests/conftest.py +0 -131
- mcp_proxy_adapter/tests/functional/__init__.py +0 -3
- mcp_proxy_adapter/tests/functional/test_api.py +0 -253
- mcp_proxy_adapter/tests/integration/__init__.py +0 -3
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
- mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
- mcp_proxy_adapter/tests/performance/__init__.py +0 -3
- mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
- mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
- mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
- mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
- mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
- mcp_proxy_adapter/tests/test_base_command.py +0 -123
- mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
- mcp_proxy_adapter/tests/test_command_registry.py +0 -281
- mcp_proxy_adapter/tests/test_config.py +0 -127
- mcp_proxy_adapter/tests/test_utils.py +0 -65
- mcp_proxy_adapter/tests/unit/__init__.py +0 -3
- mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
- mcp_proxy_adapter/tests/unit/test_config.py +0 -217
- mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
- mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.1.dist-info ā mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info ā mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,628 @@
|
|
1
|
+
"""
|
2
|
+
Universal Client Example
|
3
|
+
|
4
|
+
This module demonstrates all possible secure connection methods to the server
|
5
|
+
using the mcp_security_framework. The client supports all authentication methods
|
6
|
+
and connection types supported by the security framework.
|
7
|
+
|
8
|
+
Author: Vasiliy Zdanovskiy
|
9
|
+
email: vasilyvz@gmail.com
|
10
|
+
"""
|
11
|
+
|
12
|
+
import asyncio
|
13
|
+
import json
|
14
|
+
import ssl
|
15
|
+
import time
|
16
|
+
from typing import Dict, Any, Optional, List, Union
|
17
|
+
from urllib.parse import urljoin
|
18
|
+
from pathlib import Path
|
19
|
+
|
20
|
+
import aiohttp
|
21
|
+
import requests
|
22
|
+
from requests.exceptions import RequestException
|
23
|
+
|
24
|
+
# Import security framework components
|
25
|
+
try:
|
26
|
+
from mcp_security_framework import SecurityManager, AuthManager, CertificateManager
|
27
|
+
from mcp_security_framework.utils import generate_api_key, create_jwt_token, validate_jwt_token
|
28
|
+
from mcp_security_framework.utils import extract_roles_from_cert, validate_certificate_chain
|
29
|
+
from mcp_security_framework.utils import create_ssl_context, validate_server_certificate
|
30
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
31
|
+
except ImportError:
|
32
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
33
|
+
print("Warning: mcp_security_framework not available. Using basic HTTP client.")
|
34
|
+
|
35
|
+
|
36
|
+
class UniversalClient:
|
37
|
+
"""
|
38
|
+
Universal client that demonstrates all possible secure connection methods.
|
39
|
+
|
40
|
+
Supports:
|
41
|
+
- HTTP/HTTPS connections
|
42
|
+
- API Key authentication
|
43
|
+
- JWT token authentication
|
44
|
+
- Certificate-based authentication
|
45
|
+
- SSL/TLS with custom certificates
|
46
|
+
- Role-based access control
|
47
|
+
- Rate limiting awareness
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(self, config: Dict[str, Any]):
|
51
|
+
"""
|
52
|
+
Initialize universal client with configuration.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
config: Client configuration with security settings
|
56
|
+
"""
|
57
|
+
self.config = config
|
58
|
+
self.base_url = config.get("server_url", "http://localhost:8000")
|
59
|
+
self.timeout = config.get("timeout", 30)
|
60
|
+
self.retry_attempts = config.get("retry_attempts", 3)
|
61
|
+
self.retry_delay = config.get("retry_delay", 1)
|
62
|
+
|
63
|
+
# Security configuration
|
64
|
+
self.security_config = config.get("security", {})
|
65
|
+
self.auth_method = self.security_config.get("auth_method", "none")
|
66
|
+
|
67
|
+
# Initialize security managers if framework is available
|
68
|
+
self.security_manager = None
|
69
|
+
self.auth_manager = None
|
70
|
+
self.cert_manager = None
|
71
|
+
|
72
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
73
|
+
self._initialize_security_managers()
|
74
|
+
|
75
|
+
# Session management
|
76
|
+
self.session: Optional[aiohttp.ClientSession] = None
|
77
|
+
self.current_token: Optional[str] = None
|
78
|
+
self.token_expiry: Optional[float] = None
|
79
|
+
|
80
|
+
print(f"Universal client initialized with auth method: {self.auth_method}")
|
81
|
+
|
82
|
+
def _initialize_security_managers(self) -> None:
|
83
|
+
"""Initialize security framework managers."""
|
84
|
+
try:
|
85
|
+
# Initialize security manager
|
86
|
+
self.security_manager = SecurityManager(self.security_config)
|
87
|
+
|
88
|
+
# Initialize auth manager
|
89
|
+
auth_config = self.security_config.get("auth", {})
|
90
|
+
self.auth_manager = AuthManager(auth_config)
|
91
|
+
|
92
|
+
# Initialize certificate manager
|
93
|
+
cert_config = self.security_config.get("certificates", {})
|
94
|
+
self.cert_manager = CertificateManager(cert_config)
|
95
|
+
|
96
|
+
print("Security framework managers initialized successfully")
|
97
|
+
except Exception as e:
|
98
|
+
print(f"Warning: Failed to initialize security managers: {e}")
|
99
|
+
|
100
|
+
async def __aenter__(self):
|
101
|
+
"""Async context manager entry."""
|
102
|
+
await self.connect()
|
103
|
+
return self
|
104
|
+
|
105
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
106
|
+
"""Async context manager exit."""
|
107
|
+
await self.disconnect()
|
108
|
+
|
109
|
+
async def connect(self) -> None:
|
110
|
+
"""Establish connection with authentication."""
|
111
|
+
print(f"Connecting to {self.base_url} with {self.auth_method} authentication...")
|
112
|
+
|
113
|
+
# Perform authentication based on method
|
114
|
+
if self.auth_method == "api_key":
|
115
|
+
await self._authenticate_api_key()
|
116
|
+
elif self.auth_method == "jwt":
|
117
|
+
await self._authenticate_jwt()
|
118
|
+
elif self.auth_method == "certificate":
|
119
|
+
await self._authenticate_certificate()
|
120
|
+
elif self.auth_method == "basic":
|
121
|
+
await self._authenticate_basic()
|
122
|
+
else:
|
123
|
+
print("No authentication required")
|
124
|
+
|
125
|
+
print("Connection established successfully")
|
126
|
+
|
127
|
+
async def disconnect(self) -> None:
|
128
|
+
"""Close connection and cleanup."""
|
129
|
+
if self.session:
|
130
|
+
await self.session.close()
|
131
|
+
self.session = None
|
132
|
+
print("Connection closed")
|
133
|
+
|
134
|
+
async def _authenticate_api_key(self) -> None:
|
135
|
+
"""Authenticate using API key."""
|
136
|
+
api_key_config = self.security_config.get("api_key", {})
|
137
|
+
api_key = api_key_config.get("key")
|
138
|
+
|
139
|
+
if not api_key:
|
140
|
+
raise ValueError("API key not provided in configuration")
|
141
|
+
|
142
|
+
# Store API key for requests
|
143
|
+
self.current_token = api_key
|
144
|
+
print(f"Authenticated with API key: {api_key[:8]}...")
|
145
|
+
|
146
|
+
async def _authenticate_jwt(self) -> None:
|
147
|
+
"""Authenticate using JWT token."""
|
148
|
+
jwt_config = self.security_config.get("jwt", {})
|
149
|
+
|
150
|
+
# Check if we have a stored token that's still valid
|
151
|
+
if self.current_token and self.token_expiry and time.time() < self.token_expiry:
|
152
|
+
print("Using existing JWT token")
|
153
|
+
return
|
154
|
+
|
155
|
+
# Get credentials for JWT
|
156
|
+
username = jwt_config.get("username")
|
157
|
+
password = jwt_config.get("password")
|
158
|
+
secret = jwt_config.get("secret")
|
159
|
+
|
160
|
+
if not all([username, password, secret]):
|
161
|
+
raise ValueError("JWT credentials not provided in configuration")
|
162
|
+
|
163
|
+
# Create JWT token
|
164
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
165
|
+
self.current_token = create_jwt_token(
|
166
|
+
username,
|
167
|
+
secret,
|
168
|
+
expiry_hours=jwt_config.get("expiry_hours", 24)
|
169
|
+
)
|
170
|
+
else:
|
171
|
+
# Simple JWT creation (for demonstration)
|
172
|
+
import jwt
|
173
|
+
payload = {
|
174
|
+
"username": username,
|
175
|
+
"exp": time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
|
176
|
+
}
|
177
|
+
self.current_token = jwt.encode(payload, secret, algorithm="HS256")
|
178
|
+
|
179
|
+
self.token_expiry = time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
|
180
|
+
print(f"Authenticated with JWT token: {self.current_token[:20]}...")
|
181
|
+
|
182
|
+
async def _authenticate_certificate(self) -> None:
|
183
|
+
"""Authenticate using client certificate."""
|
184
|
+
cert_config = self.security_config.get("certificate", {})
|
185
|
+
|
186
|
+
cert_file = cert_config.get("cert_file")
|
187
|
+
key_file = cert_config.get("key_file")
|
188
|
+
|
189
|
+
if not cert_file or not key_file:
|
190
|
+
raise ValueError("Certificate files not provided in configuration")
|
191
|
+
|
192
|
+
# Validate certificate
|
193
|
+
if SECURITY_FRAMEWORK_AVAILABLE and self.cert_manager:
|
194
|
+
try:
|
195
|
+
cert_info = self.cert_manager.validate_certificate(cert_file, key_file)
|
196
|
+
print(f"Certificate validated: {cert_info.get('subject', 'Unknown')}")
|
197
|
+
|
198
|
+
# Extract roles from certificate
|
199
|
+
roles = extract_roles_from_cert(cert_file)
|
200
|
+
if roles:
|
201
|
+
print(f"Certificate roles: {roles}")
|
202
|
+
except Exception as e:
|
203
|
+
print(f"Warning: Certificate validation failed: {e}")
|
204
|
+
|
205
|
+
print("Certificate authentication prepared")
|
206
|
+
|
207
|
+
async def _authenticate_basic(self) -> None:
|
208
|
+
"""Authenticate using basic authentication."""
|
209
|
+
basic_config = self.security_config.get("basic", {})
|
210
|
+
username = basic_config.get("username")
|
211
|
+
password = basic_config.get("password")
|
212
|
+
|
213
|
+
if not username or not password:
|
214
|
+
raise ValueError("Basic auth credentials not provided in configuration")
|
215
|
+
|
216
|
+
import base64
|
217
|
+
credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
|
218
|
+
self.current_token = f"Basic {credentials}"
|
219
|
+
print(f"Authenticated with basic auth: {username}")
|
220
|
+
|
221
|
+
def _get_auth_headers(self) -> Dict[str, str]:
|
222
|
+
"""Get authentication headers for requests."""
|
223
|
+
headers = {"Content-Type": "application/json"}
|
224
|
+
|
225
|
+
if not self.current_token:
|
226
|
+
return headers
|
227
|
+
|
228
|
+
if self.auth_method == "api_key":
|
229
|
+
api_key_config = self.security_config.get("api_key", {})
|
230
|
+
header_name = api_key_config.get("header", "X-API-Key")
|
231
|
+
headers[header_name] = self.current_token
|
232
|
+
elif self.auth_method == "jwt":
|
233
|
+
headers["Authorization"] = f"Bearer {self.current_token}"
|
234
|
+
elif self.auth_method == "basic":
|
235
|
+
headers["Authorization"] = self.current_token
|
236
|
+
|
237
|
+
return headers
|
238
|
+
|
239
|
+
def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
|
240
|
+
"""Create SSL context for secure connections."""
|
241
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
242
|
+
return None
|
243
|
+
|
244
|
+
try:
|
245
|
+
ssl_config = self.security_config.get("ssl", {})
|
246
|
+
|
247
|
+
if not ssl_config.get("enabled", False):
|
248
|
+
return None
|
249
|
+
|
250
|
+
# Create SSL context using security framework
|
251
|
+
if self.security_manager:
|
252
|
+
return self.security_manager.create_client_ssl_context()
|
253
|
+
|
254
|
+
# Fallback SSL context creation
|
255
|
+
context = ssl.create_default_context()
|
256
|
+
|
257
|
+
# Add client certificate if provided
|
258
|
+
cert_config = self.security_config.get("certificate", {})
|
259
|
+
if cert_config.get("enabled", False):
|
260
|
+
cert_file = cert_config.get("cert_file")
|
261
|
+
key_file = cert_config.get("key_file")
|
262
|
+
if cert_file and key_file:
|
263
|
+
context.load_cert_chain(cert_file, key_file)
|
264
|
+
|
265
|
+
# Add CA certificate if provided
|
266
|
+
ca_cert_file = ssl_config.get("ca_cert_file")
|
267
|
+
if ca_cert_file:
|
268
|
+
context.load_verify_locations(ca_cert_file)
|
269
|
+
|
270
|
+
# Configure verification
|
271
|
+
if ssl_config.get("check_hostname", True):
|
272
|
+
context.check_hostname = True
|
273
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
274
|
+
else:
|
275
|
+
context.check_hostname = False
|
276
|
+
context.verify_mode = ssl.CERT_NONE
|
277
|
+
|
278
|
+
return context
|
279
|
+
except Exception as e:
|
280
|
+
print(f"Warning: Failed to create SSL context: {e}")
|
281
|
+
return None
|
282
|
+
|
283
|
+
async def request(
|
284
|
+
self,
|
285
|
+
method: str,
|
286
|
+
endpoint: str,
|
287
|
+
data: Optional[Dict[str, Any]] = None,
|
288
|
+
headers: Optional[Dict[str, str]] = None
|
289
|
+
) -> Dict[str, Any]:
|
290
|
+
"""
|
291
|
+
Make authenticated request to server.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
method: HTTP method (GET, POST, etc.)
|
295
|
+
endpoint: API endpoint
|
296
|
+
data: Request data
|
297
|
+
headers: Additional headers
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
Response data
|
301
|
+
"""
|
302
|
+
url = urljoin(self.base_url, endpoint)
|
303
|
+
|
304
|
+
# Prepare headers
|
305
|
+
request_headers = self._get_auth_headers()
|
306
|
+
if headers:
|
307
|
+
request_headers.update(headers)
|
308
|
+
|
309
|
+
# Create SSL context
|
310
|
+
ssl_context = self._create_ssl_context()
|
311
|
+
|
312
|
+
# Create connector with SSL context
|
313
|
+
connector = None
|
314
|
+
if ssl_context:
|
315
|
+
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
316
|
+
|
317
|
+
try:
|
318
|
+
async with aiohttp.ClientSession(connector=connector) as session:
|
319
|
+
for attempt in range(self.retry_attempts):
|
320
|
+
try:
|
321
|
+
async with session.request(
|
322
|
+
method,
|
323
|
+
url,
|
324
|
+
json=data,
|
325
|
+
headers=request_headers,
|
326
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
327
|
+
) as response:
|
328
|
+
result = await response.json()
|
329
|
+
|
330
|
+
# Validate response if security framework available
|
331
|
+
if SECURITY_FRAMEWORK_AVAILABLE and self.security_manager:
|
332
|
+
self.security_manager.validate_server_response(dict(response.headers))
|
333
|
+
|
334
|
+
if response.status >= 400:
|
335
|
+
print(f"Request failed with status {response.status}: {result}")
|
336
|
+
return {"error": result, "status": response.status}
|
337
|
+
|
338
|
+
return result
|
339
|
+
|
340
|
+
except Exception as e:
|
341
|
+
print(f"Request attempt {attempt + 1} failed: {e}")
|
342
|
+
if attempt < self.retry_attempts - 1:
|
343
|
+
await asyncio.sleep(self.retry_delay)
|
344
|
+
else:
|
345
|
+
raise
|
346
|
+
finally:
|
347
|
+
if connector:
|
348
|
+
await connector.close()
|
349
|
+
|
350
|
+
async def get(self, endpoint: str, **kwargs) -> Dict[str, Any]:
|
351
|
+
"""Make GET request."""
|
352
|
+
return await self.request("GET", endpoint, **kwargs)
|
353
|
+
|
354
|
+
async def post(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
355
|
+
"""Make POST request."""
|
356
|
+
return await self.request("POST", endpoint, data=data, **kwargs)
|
357
|
+
|
358
|
+
async def put(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
359
|
+
"""Make PUT request."""
|
360
|
+
return await self.request("PUT", endpoint, data=data, **kwargs)
|
361
|
+
|
362
|
+
async def delete(self, endpoint: str, **kwargs) -> Dict[str, Any]:
|
363
|
+
"""Make DELETE request."""
|
364
|
+
return await self.request("DELETE", endpoint, **kwargs)
|
365
|
+
|
366
|
+
async def test_connection(self) -> bool:
|
367
|
+
"""Test connection to server."""
|
368
|
+
try:
|
369
|
+
result = await self.get("/health")
|
370
|
+
if "error" not in result:
|
371
|
+
print("ā
Connection test successful")
|
372
|
+
return True
|
373
|
+
else:
|
374
|
+
print(f"ā Connection test failed: {result}")
|
375
|
+
return False
|
376
|
+
except Exception as e:
|
377
|
+
print(f"ā Connection test failed: {e}")
|
378
|
+
return False
|
379
|
+
|
380
|
+
async def test_security_features(self) -> Dict[str, bool]:
|
381
|
+
"""Test various security features."""
|
382
|
+
results = {}
|
383
|
+
|
384
|
+
# Test basic connectivity
|
385
|
+
results["connectivity"] = await self.test_connection()
|
386
|
+
|
387
|
+
# Test authentication
|
388
|
+
if self.auth_method != "none":
|
389
|
+
try:
|
390
|
+
result = await self.get("/api/auth/status")
|
391
|
+
results["authentication"] = "error" not in result
|
392
|
+
except:
|
393
|
+
results["authentication"] = False
|
394
|
+
|
395
|
+
# Test SSL/TLS
|
396
|
+
if self.base_url.startswith("https"):
|
397
|
+
results["ssl_tls"] = True
|
398
|
+
else:
|
399
|
+
results["ssl_tls"] = False
|
400
|
+
|
401
|
+
# Test certificate validation
|
402
|
+
if self.auth_method == "certificate" and SECURITY_FRAMEWORK_AVAILABLE:
|
403
|
+
results["certificate_validation"] = True
|
404
|
+
else:
|
405
|
+
results["certificate_validation"] = False
|
406
|
+
|
407
|
+
return results
|
408
|
+
|
409
|
+
|
410
|
+
def create_client_config(
|
411
|
+
server_url: str,
|
412
|
+
auth_method: str = "none",
|
413
|
+
**kwargs
|
414
|
+
) -> Dict[str, Any]:
|
415
|
+
"""
|
416
|
+
Create client configuration for different authentication methods.
|
417
|
+
|
418
|
+
Args:
|
419
|
+
server_url: Server URL
|
420
|
+
auth_method: Authentication method (none, api_key, jwt, certificate, basic)
|
421
|
+
**kwargs: Additional configuration parameters
|
422
|
+
|
423
|
+
Returns:
|
424
|
+
Client configuration dictionary
|
425
|
+
"""
|
426
|
+
config = {
|
427
|
+
"server_url": server_url,
|
428
|
+
"timeout": 30,
|
429
|
+
"retry_attempts": 3,
|
430
|
+
"retry_delay": 1,
|
431
|
+
"security": {
|
432
|
+
"auth_method": auth_method
|
433
|
+
}
|
434
|
+
}
|
435
|
+
|
436
|
+
if auth_method == "api_key":
|
437
|
+
config["security"]["api_key"] = {
|
438
|
+
"key": kwargs.get("api_key", "your_api_key_here"),
|
439
|
+
"header": kwargs.get("header", "X-API-Key")
|
440
|
+
}
|
441
|
+
|
442
|
+
elif auth_method == "jwt":
|
443
|
+
config["security"]["jwt"] = {
|
444
|
+
"username": kwargs.get("username", "user"),
|
445
|
+
"password": kwargs.get("password", "password"),
|
446
|
+
"secret": kwargs.get("secret", "your_jwt_secret"),
|
447
|
+
"expiry_hours": kwargs.get("expiry_hours", 24)
|
448
|
+
}
|
449
|
+
|
450
|
+
elif auth_method == "certificate":
|
451
|
+
config["security"]["certificate"] = {
|
452
|
+
"enabled": True,
|
453
|
+
"cert_file": kwargs.get("cert_file", "./certs/client.crt"),
|
454
|
+
"key_file": kwargs.get("key_file", "./keys/client.key"),
|
455
|
+
"ca_cert_file": kwargs.get("ca_cert_file", "./certs/ca.crt")
|
456
|
+
}
|
457
|
+
config["security"]["ssl"] = {
|
458
|
+
"enabled": True,
|
459
|
+
"check_hostname": kwargs.get("check_hostname", True),
|
460
|
+
"ca_cert_file": kwargs.get("ca_cert_file", "./certs/ca.crt")
|
461
|
+
}
|
462
|
+
|
463
|
+
elif auth_method == "basic":
|
464
|
+
config["security"]["basic"] = {
|
465
|
+
"username": kwargs.get("username", "user"),
|
466
|
+
"password": kwargs.get("password", "password")
|
467
|
+
}
|
468
|
+
|
469
|
+
return config
|
470
|
+
|
471
|
+
|
472
|
+
async def demo_all_connection_methods():
|
473
|
+
"""Demonstrate all possible connection methods."""
|
474
|
+
print("š Universal Client Demo - All Connection Methods")
|
475
|
+
print("=" * 60)
|
476
|
+
|
477
|
+
# Test configurations for different auth methods
|
478
|
+
test_configs = [
|
479
|
+
{
|
480
|
+
"name": "No Authentication",
|
481
|
+
"config": create_client_config("http://localhost:8000", "none")
|
482
|
+
},
|
483
|
+
{
|
484
|
+
"name": "API Key Authentication",
|
485
|
+
"config": create_client_config(
|
486
|
+
"http://localhost:8000",
|
487
|
+
"api_key",
|
488
|
+
api_key="demo_api_key_123"
|
489
|
+
)
|
490
|
+
},
|
491
|
+
{
|
492
|
+
"name": "JWT Authentication",
|
493
|
+
"config": create_client_config(
|
494
|
+
"http://localhost:8000",
|
495
|
+
"jwt",
|
496
|
+
username="demo_user",
|
497
|
+
password="demo_password",
|
498
|
+
secret="demo_jwt_secret"
|
499
|
+
)
|
500
|
+
},
|
501
|
+
{
|
502
|
+
"name": "Basic Authentication",
|
503
|
+
"config": create_client_config(
|
504
|
+
"http://localhost:8000",
|
505
|
+
"basic",
|
506
|
+
username="demo_user",
|
507
|
+
password="demo_password"
|
508
|
+
)
|
509
|
+
},
|
510
|
+
{
|
511
|
+
"name": "Certificate Authentication (HTTPS)",
|
512
|
+
"config": create_client_config(
|
513
|
+
"https://localhost:8443",
|
514
|
+
"certificate",
|
515
|
+
cert_file="./certs/client.crt",
|
516
|
+
key_file="./keys/client.key",
|
517
|
+
ca_cert_file="./certs/ca.crt"
|
518
|
+
)
|
519
|
+
}
|
520
|
+
]
|
521
|
+
|
522
|
+
for test_config in test_configs:
|
523
|
+
print(f"\nš Testing: {test_config['name']}")
|
524
|
+
print("-" * 40)
|
525
|
+
|
526
|
+
try:
|
527
|
+
async with UniversalClient(test_config["config"]) as client:
|
528
|
+
# Test connection
|
529
|
+
success = await client.test_connection()
|
530
|
+
|
531
|
+
if success:
|
532
|
+
# Test security features
|
533
|
+
security_results = await client.test_security_features()
|
534
|
+
print("Security Features:")
|
535
|
+
for feature, status in security_results.items():
|
536
|
+
status_icon = "ā
" if status else "ā"
|
537
|
+
print(f" {status_icon} {feature}: {status}")
|
538
|
+
|
539
|
+
# Make a test API call
|
540
|
+
try:
|
541
|
+
result = await client.get("/api/status")
|
542
|
+
print(f"API Status: {result}")
|
543
|
+
except Exception as e:
|
544
|
+
print(f"API call failed: {e}")
|
545
|
+
else:
|
546
|
+
print("ā Connection failed")
|
547
|
+
|
548
|
+
except Exception as e:
|
549
|
+
print(f"ā Test failed: {e}")
|
550
|
+
|
551
|
+
print("\nš Demo completed!")
|
552
|
+
|
553
|
+
|
554
|
+
async def demo_specific_connection(auth_method: str, **kwargs):
|
555
|
+
"""
|
556
|
+
Demo specific connection method.
|
557
|
+
|
558
|
+
Args:
|
559
|
+
auth_method: Authentication method to test
|
560
|
+
**kwargs: Configuration parameters
|
561
|
+
"""
|
562
|
+
print(f"š Testing {auth_method} connection")
|
563
|
+
print("=" * 40)
|
564
|
+
|
565
|
+
config = create_client_config("http://localhost:8000", auth_method, **kwargs)
|
566
|
+
|
567
|
+
async with UniversalClient(config) as client:
|
568
|
+
# Test connection
|
569
|
+
success = await client.test_connection()
|
570
|
+
|
571
|
+
if success:
|
572
|
+
print("ā
Connection successful!")
|
573
|
+
|
574
|
+
# Make some API calls
|
575
|
+
try:
|
576
|
+
# Get server status
|
577
|
+
status = await client.get("/api/status")
|
578
|
+
print(f"Server Status: {status}")
|
579
|
+
|
580
|
+
# Test command execution
|
581
|
+
command_data = {
|
582
|
+
"jsonrpc": "2.0",
|
583
|
+
"method": "test_command",
|
584
|
+
"params": {"message": "Hello from universal client!"},
|
585
|
+
"id": 1
|
586
|
+
}
|
587
|
+
|
588
|
+
result = await client.post("/api/jsonrpc", command_data)
|
589
|
+
print(f"Command Result: {result}")
|
590
|
+
|
591
|
+
except Exception as e:
|
592
|
+
print(f"API calls failed: {e}")
|
593
|
+
else:
|
594
|
+
print("ā Connection failed")
|
595
|
+
|
596
|
+
|
597
|
+
if __name__ == "__main__":
|
598
|
+
import sys
|
599
|
+
|
600
|
+
if len(sys.argv) > 1:
|
601
|
+
# Test specific auth method
|
602
|
+
auth_method = sys.argv[1]
|
603
|
+
kwargs = {}
|
604
|
+
|
605
|
+
if auth_method == "api_key":
|
606
|
+
kwargs["api_key"] = "demo_key_123"
|
607
|
+
elif auth_method == "jwt":
|
608
|
+
kwargs.update({
|
609
|
+
"username": "demo_user",
|
610
|
+
"password": "demo_password",
|
611
|
+
"secret": "demo_secret"
|
612
|
+
})
|
613
|
+
elif auth_method == "certificate":
|
614
|
+
kwargs.update({
|
615
|
+
"cert_file": "./certs/client.crt",
|
616
|
+
"key_file": "./keys/client.key",
|
617
|
+
"ca_cert_file": "./certs/ca.crt"
|
618
|
+
})
|
619
|
+
elif auth_method == "basic":
|
620
|
+
kwargs.update({
|
621
|
+
"username": "demo_user",
|
622
|
+
"password": "demo_password"
|
623
|
+
})
|
624
|
+
|
625
|
+
asyncio.run(demo_specific_connection(auth_method, **kwargs))
|
626
|
+
else:
|
627
|
+
# Demo all connection methods
|
628
|
+
asyncio.run(demo_all_connection_methods())
|