genxai-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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
genxai/security/auth.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Authentication and API key management for GenXAI."""
|
|
2
|
+
|
|
3
|
+
import secrets
|
|
4
|
+
import hashlib
|
|
5
|
+
import time
|
|
6
|
+
from typing import Optional, List, Dict, Any
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
import sqlite3
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class APIKey:
|
|
15
|
+
"""API key model."""
|
|
16
|
+
key_id: str
|
|
17
|
+
user_id: str
|
|
18
|
+
name: str
|
|
19
|
+
key_hash: str
|
|
20
|
+
created_at: datetime
|
|
21
|
+
last_used: Optional[datetime] = None
|
|
22
|
+
expires_at: Optional[datetime] = None
|
|
23
|
+
is_active: bool = True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class APIKeyManager:
|
|
27
|
+
"""Manage API keys for GenXAI services."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, db_path: str = "genxai_keys.db"):
|
|
30
|
+
"""Initialize API key manager.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
db_path: Path to SQLite database
|
|
34
|
+
"""
|
|
35
|
+
self.db_path = db_path
|
|
36
|
+
self._init_db()
|
|
37
|
+
|
|
38
|
+
def _init_db(self):
|
|
39
|
+
"""Initialize database schema."""
|
|
40
|
+
conn = sqlite3.connect(self.db_path)
|
|
41
|
+
cursor = conn.cursor()
|
|
42
|
+
|
|
43
|
+
cursor.execute("""
|
|
44
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
45
|
+
key_id TEXT PRIMARY KEY,
|
|
46
|
+
user_id TEXT NOT NULL,
|
|
47
|
+
name TEXT NOT NULL,
|
|
48
|
+
key_hash TEXT NOT NULL,
|
|
49
|
+
created_at TIMESTAMP NOT NULL,
|
|
50
|
+
last_used TIMESTAMP,
|
|
51
|
+
expires_at TIMESTAMP,
|
|
52
|
+
is_active BOOLEAN NOT NULL DEFAULT 1
|
|
53
|
+
)
|
|
54
|
+
""")
|
|
55
|
+
|
|
56
|
+
cursor.execute("""
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_user_id ON api_keys(user_id)
|
|
58
|
+
""")
|
|
59
|
+
|
|
60
|
+
cursor.execute("""
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_key_hash ON api_keys(key_hash)
|
|
62
|
+
""")
|
|
63
|
+
|
|
64
|
+
conn.commit()
|
|
65
|
+
conn.close()
|
|
66
|
+
|
|
67
|
+
def generate_key(
|
|
68
|
+
self,
|
|
69
|
+
user_id: str,
|
|
70
|
+
name: str,
|
|
71
|
+
expires_in_days: Optional[int] = None
|
|
72
|
+
) -> str:
|
|
73
|
+
"""Generate new API key.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
user_id: User ID
|
|
77
|
+
name: Key name/description
|
|
78
|
+
expires_in_days: Optional expiration in days
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Generated API key
|
|
82
|
+
"""
|
|
83
|
+
# Generate random key
|
|
84
|
+
random_part = secrets.token_urlsafe(32)
|
|
85
|
+
env = os.getenv("GENXAI_ENV", "dev")
|
|
86
|
+
api_key = f"genxai_{env}_{random_part}"
|
|
87
|
+
|
|
88
|
+
# Hash the key for storage
|
|
89
|
+
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
|
|
90
|
+
|
|
91
|
+
# Generate key ID
|
|
92
|
+
key_id = secrets.token_urlsafe(16)
|
|
93
|
+
|
|
94
|
+
# Calculate expiration
|
|
95
|
+
expires_at = None
|
|
96
|
+
if expires_in_days:
|
|
97
|
+
expires_at = datetime.utcnow() + timedelta(days=expires_in_days)
|
|
98
|
+
|
|
99
|
+
# Store in database
|
|
100
|
+
conn = sqlite3.connect(self.db_path)
|
|
101
|
+
cursor = conn.cursor()
|
|
102
|
+
|
|
103
|
+
cursor.execute("""
|
|
104
|
+
INSERT INTO api_keys (key_id, user_id, name, key_hash, created_at, expires_at)
|
|
105
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
106
|
+
""", (key_id, user_id, name, key_hash, datetime.utcnow(), expires_at))
|
|
107
|
+
|
|
108
|
+
conn.commit()
|
|
109
|
+
conn.close()
|
|
110
|
+
|
|
111
|
+
return api_key
|
|
112
|
+
|
|
113
|
+
def validate_key(self, api_key: str) -> Optional[APIKey]:
|
|
114
|
+
"""Validate API key.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
api_key: API key to validate
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
APIKey object if valid, None otherwise
|
|
121
|
+
"""
|
|
122
|
+
# Hash the provided key
|
|
123
|
+
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
|
|
124
|
+
|
|
125
|
+
# Look up in database
|
|
126
|
+
conn = sqlite3.connect(self.db_path)
|
|
127
|
+
cursor = conn.cursor()
|
|
128
|
+
|
|
129
|
+
cursor.execute("""
|
|
130
|
+
SELECT key_id, user_id, name, key_hash, created_at, last_used, expires_at, is_active
|
|
131
|
+
FROM api_keys
|
|
132
|
+
WHERE key_hash = ? AND is_active = 1
|
|
133
|
+
""", (key_hash,))
|
|
134
|
+
|
|
135
|
+
row = cursor.fetchone()
|
|
136
|
+
|
|
137
|
+
if not row:
|
|
138
|
+
conn.close()
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
# Parse row
|
|
142
|
+
key_id, user_id, name, key_hash, created_at, last_used, expires_at, is_active = row
|
|
143
|
+
|
|
144
|
+
# Check expiration
|
|
145
|
+
if expires_at:
|
|
146
|
+
expires_dt = datetime.fromisoformat(expires_at)
|
|
147
|
+
if datetime.utcnow() > expires_dt:
|
|
148
|
+
conn.close()
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# Update last used
|
|
152
|
+
cursor.execute("""
|
|
153
|
+
UPDATE api_keys SET last_used = ? WHERE key_id = ?
|
|
154
|
+
""", (datetime.utcnow(), key_id))
|
|
155
|
+
|
|
156
|
+
conn.commit()
|
|
157
|
+
conn.close()
|
|
158
|
+
|
|
159
|
+
return APIKey(
|
|
160
|
+
key_id=key_id,
|
|
161
|
+
user_id=user_id,
|
|
162
|
+
name=name,
|
|
163
|
+
key_hash=key_hash,
|
|
164
|
+
created_at=datetime.fromisoformat(created_at),
|
|
165
|
+
last_used=datetime.fromisoformat(last_used) if last_used else None,
|
|
166
|
+
expires_at=datetime.fromisoformat(expires_at) if expires_at else None,
|
|
167
|
+
is_active=bool(is_active)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def revoke_key(self, key_id: str) -> bool:
|
|
171
|
+
"""Revoke API key.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
key_id: Key ID to revoke
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
True if revoked, False otherwise
|
|
178
|
+
"""
|
|
179
|
+
conn = sqlite3.connect(self.db_path)
|
|
180
|
+
cursor = conn.cursor()
|
|
181
|
+
|
|
182
|
+
cursor.execute("""
|
|
183
|
+
UPDATE api_keys SET is_active = 0 WHERE key_id = ?
|
|
184
|
+
""", (key_id,))
|
|
185
|
+
|
|
186
|
+
rows_affected = cursor.rowcount
|
|
187
|
+
conn.commit()
|
|
188
|
+
conn.close()
|
|
189
|
+
|
|
190
|
+
return rows_affected > 0
|
|
191
|
+
|
|
192
|
+
def list_keys(self, user_id: str) -> List[APIKey]:
|
|
193
|
+
"""List all keys for user.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
user_id: User ID
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of API keys
|
|
200
|
+
"""
|
|
201
|
+
conn = sqlite3.connect(self.db_path)
|
|
202
|
+
cursor = conn.cursor()
|
|
203
|
+
|
|
204
|
+
cursor.execute("""
|
|
205
|
+
SELECT key_id, user_id, name, key_hash, created_at, last_used, expires_at, is_active
|
|
206
|
+
FROM api_keys
|
|
207
|
+
WHERE user_id = ?
|
|
208
|
+
ORDER BY created_at DESC
|
|
209
|
+
""", (user_id,))
|
|
210
|
+
|
|
211
|
+
keys = []
|
|
212
|
+
for row in cursor.fetchall():
|
|
213
|
+
key_id, user_id, name, key_hash, created_at, last_used, expires_at, is_active = row
|
|
214
|
+
keys.append(APIKey(
|
|
215
|
+
key_id=key_id,
|
|
216
|
+
user_id=user_id,
|
|
217
|
+
name=name,
|
|
218
|
+
key_hash=key_hash,
|
|
219
|
+
created_at=datetime.fromisoformat(created_at),
|
|
220
|
+
last_used=datetime.fromisoformat(last_used) if last_used else None,
|
|
221
|
+
expires_at=datetime.fromisoformat(expires_at) if expires_at else None,
|
|
222
|
+
is_active=bool(is_active)
|
|
223
|
+
))
|
|
224
|
+
|
|
225
|
+
conn.close()
|
|
226
|
+
return keys
|
|
227
|
+
|
|
228
|
+
def rotate_key(self, key_id: str) -> str:
|
|
229
|
+
"""Rotate API key.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
key_id: Key ID to rotate
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
New API key
|
|
236
|
+
"""
|
|
237
|
+
# Get existing key
|
|
238
|
+
conn = sqlite3.connect(self.db_path)
|
|
239
|
+
cursor = conn.cursor()
|
|
240
|
+
|
|
241
|
+
cursor.execute("""
|
|
242
|
+
SELECT user_id, name, expires_at FROM api_keys WHERE key_id = ?
|
|
243
|
+
""", (key_id,))
|
|
244
|
+
|
|
245
|
+
row = cursor.fetchone()
|
|
246
|
+
conn.close()
|
|
247
|
+
|
|
248
|
+
if not row:
|
|
249
|
+
raise ValueError(f"Key not found: {key_id}")
|
|
250
|
+
|
|
251
|
+
user_id, name, expires_at = row
|
|
252
|
+
|
|
253
|
+
# Revoke old key
|
|
254
|
+
self.revoke_key(key_id)
|
|
255
|
+
|
|
256
|
+
# Generate new key
|
|
257
|
+
expires_in_days = None
|
|
258
|
+
if expires_at:
|
|
259
|
+
expires_dt = datetime.fromisoformat(expires_at)
|
|
260
|
+
expires_in_days = (expires_dt - datetime.utcnow()).days
|
|
261
|
+
|
|
262
|
+
return self.generate_key(user_id, name, expires_in_days)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# Global API key manager
|
|
266
|
+
_api_key_manager = None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def get_api_key_manager() -> APIKeyManager:
|
|
270
|
+
"""Get global API key manager.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
APIKeyManager instance
|
|
274
|
+
"""
|
|
275
|
+
global _api_key_manager
|
|
276
|
+
|
|
277
|
+
if _api_key_manager is None:
|
|
278
|
+
db_path = os.getenv("GENXAI_API_KEY_DB", "genxai_keys.db")
|
|
279
|
+
_api_key_manager = APIKeyManager(db_path)
|
|
280
|
+
|
|
281
|
+
return _api_key_manager
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def require_api_key(func):
|
|
285
|
+
"""Decorator to require API key authentication.
|
|
286
|
+
|
|
287
|
+
Usage:
|
|
288
|
+
@require_api_key
|
|
289
|
+
async def my_endpoint():
|
|
290
|
+
pass
|
|
291
|
+
"""
|
|
292
|
+
from functools import wraps
|
|
293
|
+
|
|
294
|
+
@wraps(func)
|
|
295
|
+
async def wrapper(*args, **kwargs):
|
|
296
|
+
# Get API key from request headers
|
|
297
|
+
# This is a placeholder - actual implementation depends on web framework
|
|
298
|
+
api_key = kwargs.get("api_key") or os.getenv("GENXAI_API_KEY")
|
|
299
|
+
|
|
300
|
+
if not api_key:
|
|
301
|
+
raise ValueError("API key required")
|
|
302
|
+
|
|
303
|
+
# Validate key
|
|
304
|
+
manager = get_api_key_manager()
|
|
305
|
+
key_obj = manager.validate_key(api_key)
|
|
306
|
+
|
|
307
|
+
if not key_obj:
|
|
308
|
+
raise ValueError("Invalid API key")
|
|
309
|
+
|
|
310
|
+
# Add user_id to kwargs
|
|
311
|
+
kwargs["user_id"] = key_obj.user_id
|
|
312
|
+
|
|
313
|
+
return await func(*args, **kwargs)
|
|
314
|
+
|
|
315
|
+
return wrapper
|