cloudbrain-server 1.1.0__py3-none-any.whl → 2.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.
- cloudbrain_server/ai_brain_state_schema.sql +144 -0
- cloudbrain_server/db_config.py +123 -0
- cloudbrain_server/init_database.py +5 -5
- cloudbrain_server/start_server.py +1894 -69
- cloudbrain_server/token_manager.py +717 -0
- {cloudbrain_server-1.1.0.dist-info → cloudbrain_server-2.0.0.dist-info}/METADATA +2 -2
- cloudbrain_server-2.0.0.dist-info/RECORD +14 -0
- cloudbrain_server-1.1.0.dist-info/RECORD +0 -11
- {cloudbrain_server-1.1.0.dist-info → cloudbrain_server-2.0.0.dist-info}/WHEEL +0 -0
- {cloudbrain_server-1.1.0.dist-info → cloudbrain_server-2.0.0.dist-info}/entry_points.txt +0 -0
- {cloudbrain_server-1.1.0.dist-info → cloudbrain_server-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CloudBrain Token Management Tool
|
|
4
|
+
Generate and manage authentication tokens for AI agents
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sqlite3
|
|
8
|
+
import secrets
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, Dict, List
|
|
14
|
+
from db_config import get_db_connection, is_postgres, is_sqlite
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TokenManager:
|
|
18
|
+
"""Manage authentication tokens for CloudBrain"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, db_path: str = 'ai_db/cloudbrain.db'):
|
|
21
|
+
self.db_path = db_path
|
|
22
|
+
self._ensure_tables_exist()
|
|
23
|
+
|
|
24
|
+
def _get_connection(self):
|
|
25
|
+
"""Get database connection"""
|
|
26
|
+
conn = get_db_connection()
|
|
27
|
+
if is_sqlite():
|
|
28
|
+
conn.row_factory = sqlite3.Row
|
|
29
|
+
return conn
|
|
30
|
+
|
|
31
|
+
def _ensure_tables_exist(self):
|
|
32
|
+
"""Ensure authorization tables exist"""
|
|
33
|
+
# Skip schema execution for PostgreSQL - tables are already created
|
|
34
|
+
if is_postgres():
|
|
35
|
+
conn = self._get_connection()
|
|
36
|
+
cursor = conn.cursor()
|
|
37
|
+
|
|
38
|
+
# Read and execute PostgreSQL schema
|
|
39
|
+
schema_path = Path(__file__).parent / 'server_authorization_schema_postgres.sql'
|
|
40
|
+
if schema_path.exists():
|
|
41
|
+
with open(schema_path, 'r') as f:
|
|
42
|
+
schema_sql = f.read()
|
|
43
|
+
|
|
44
|
+
for statement in schema_sql.split(';'):
|
|
45
|
+
statement = statement.strip()
|
|
46
|
+
if statement and not statement.startswith('--'):
|
|
47
|
+
try:
|
|
48
|
+
cursor.execute(statement)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
if 'already exists' not in str(e) and 'duplicate' not in str(e).lower():
|
|
51
|
+
print(f"⚠️ Error executing schema: {e}")
|
|
52
|
+
|
|
53
|
+
conn.commit()
|
|
54
|
+
conn.close()
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
conn = self._get_connection()
|
|
58
|
+
cursor = conn.cursor()
|
|
59
|
+
|
|
60
|
+
# Read and execute schema
|
|
61
|
+
schema_path = Path(__file__).parent / 'server_authorization_schema.sql'
|
|
62
|
+
if schema_path.exists():
|
|
63
|
+
with open(schema_path, 'r') as f:
|
|
64
|
+
schema_sql = f.read()
|
|
65
|
+
|
|
66
|
+
for statement in schema_sql.split(';'):
|
|
67
|
+
statement = statement.strip()
|
|
68
|
+
if statement and not statement.startswith('--'):
|
|
69
|
+
try:
|
|
70
|
+
cursor.execute(statement)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
if 'already exists' not in str(e):
|
|
73
|
+
print(f"⚠️ Error executing schema: {e}")
|
|
74
|
+
|
|
75
|
+
conn.commit()
|
|
76
|
+
conn.close()
|
|
77
|
+
|
|
78
|
+
def generate_token(self, ai_id: int, expires_days: int = 30, description: str = "") -> Dict:
|
|
79
|
+
"""
|
|
80
|
+
Generate a new authentication token for an AI
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
ai_id: AI ID to generate token for
|
|
84
|
+
expires_days: Number of days until token expires (default: 30)
|
|
85
|
+
description: Optional description for the token
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dictionary with token info or error
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
# Check if AI exists
|
|
92
|
+
conn = self._get_connection()
|
|
93
|
+
cursor = conn.cursor()
|
|
94
|
+
|
|
95
|
+
if is_sqlite():
|
|
96
|
+
cursor.execute("SELECT id, name FROM ai_profiles WHERE id = ?", (ai_id,))
|
|
97
|
+
else:
|
|
98
|
+
cursor.execute("SELECT id, name FROM ai_profiles WHERE id = %s", (ai_id,))
|
|
99
|
+
ai_profile = cursor.fetchone()
|
|
100
|
+
|
|
101
|
+
if not ai_profile:
|
|
102
|
+
conn.close()
|
|
103
|
+
return {
|
|
104
|
+
'success': False,
|
|
105
|
+
'error': f'AI {ai_id} not found'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Generate secure random token
|
|
109
|
+
token = secrets.token_urlsafe(32)
|
|
110
|
+
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
|
111
|
+
token_prefix = f"sk_live_{token[:8]}"
|
|
112
|
+
|
|
113
|
+
# Calculate expiration
|
|
114
|
+
expires_at = datetime.now() + timedelta(days=expires_days)
|
|
115
|
+
|
|
116
|
+
# Deactivate old tokens for this AI
|
|
117
|
+
if is_sqlite():
|
|
118
|
+
cursor.execute(
|
|
119
|
+
"UPDATE ai_auth_tokens SET is_active = 0 WHERE ai_id = ?",
|
|
120
|
+
(ai_id,)
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
cursor.execute(
|
|
124
|
+
"UPDATE ai_auth_tokens SET is_active = FALSE WHERE ai_id = %s",
|
|
125
|
+
(ai_id,)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Insert new token
|
|
129
|
+
if is_sqlite():
|
|
130
|
+
cursor.execute("""
|
|
131
|
+
INSERT INTO ai_auth_tokens (ai_id, token_hash, token_prefix, expires_at, description)
|
|
132
|
+
VALUES (?, ?, ?, ?, ?)
|
|
133
|
+
""", (ai_id, token_hash, token_prefix, expires_at.isoformat(), description))
|
|
134
|
+
else:
|
|
135
|
+
cursor.execute("""
|
|
136
|
+
INSERT INTO ai_auth_tokens (ai_id, token_hash, token_prefix, expires_at, description)
|
|
137
|
+
VALUES (%s, %s, %s, %s, %s)
|
|
138
|
+
""", (ai_id, token_hash, token_prefix, expires_at.isoformat(), description))
|
|
139
|
+
|
|
140
|
+
conn.commit()
|
|
141
|
+
conn.close()
|
|
142
|
+
|
|
143
|
+
print(f"✅ Token generated for AI {ai_id} ({ai_profile['name']})")
|
|
144
|
+
print(f" Token: {token}")
|
|
145
|
+
print(f" Prefix: {token_prefix}")
|
|
146
|
+
print(f" Expires: {expires_at.isoformat()}")
|
|
147
|
+
print(f" Description: {description or 'No description'}")
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
'success': True,
|
|
151
|
+
'token': token,
|
|
152
|
+
'token_prefix': token_prefix,
|
|
153
|
+
'token_hash': token_hash,
|
|
154
|
+
'ai_id': ai_id,
|
|
155
|
+
'ai_name': ai_profile['name'],
|
|
156
|
+
'expires_at': expires_at.isoformat(),
|
|
157
|
+
'description': description
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"❌ Error generating token: {e}")
|
|
162
|
+
return {
|
|
163
|
+
'success': False,
|
|
164
|
+
'error': str(e)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def validate_token(self, token: str) -> Dict:
|
|
168
|
+
"""
|
|
169
|
+
Validate an authentication token
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
token: Token to validate
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Dictionary with validation result
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
|
179
|
+
|
|
180
|
+
conn = self._get_connection()
|
|
181
|
+
cursor = conn.cursor()
|
|
182
|
+
|
|
183
|
+
if is_sqlite():
|
|
184
|
+
cursor.execute("""
|
|
185
|
+
SELECT t.*, p.name as ai_name, p.expertise
|
|
186
|
+
FROM ai_auth_tokens t
|
|
187
|
+
JOIN ai_profiles p ON t.ai_id = p.id
|
|
188
|
+
WHERE t.token_hash = ? AND t.is_active = 1
|
|
189
|
+
""", (token_hash,))
|
|
190
|
+
else:
|
|
191
|
+
cursor.execute("""
|
|
192
|
+
SELECT t.*, p.name as ai_name, p.expertise
|
|
193
|
+
FROM ai_auth_tokens t
|
|
194
|
+
JOIN ai_profiles p ON t.ai_id = p.id
|
|
195
|
+
WHERE t.token_hash = %s AND t.is_active = TRUE
|
|
196
|
+
""", (token_hash,))
|
|
197
|
+
|
|
198
|
+
token_record = cursor.fetchone()
|
|
199
|
+
|
|
200
|
+
if not token_record:
|
|
201
|
+
conn.close()
|
|
202
|
+
return {
|
|
203
|
+
'valid': False,
|
|
204
|
+
'error': 'Invalid or expired token'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Check expiration
|
|
208
|
+
expires_at = datetime.fromisoformat(token_record['expires_at'])
|
|
209
|
+
if datetime.now() > expires_at:
|
|
210
|
+
conn.close()
|
|
211
|
+
return {
|
|
212
|
+
'valid': False,
|
|
213
|
+
'error': 'Token has expired'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Update last used timestamp
|
|
217
|
+
if is_sqlite():
|
|
218
|
+
cursor.execute(
|
|
219
|
+
"UPDATE ai_auth_tokens SET last_used_at = ? WHERE id = ?",
|
|
220
|
+
(datetime.now().isoformat(), token_record['id'])
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
cursor.execute(
|
|
224
|
+
"UPDATE ai_auth_tokens SET last_used_at = %s WHERE id = %s",
|
|
225
|
+
(datetime.now().isoformat(), token_record['id'])
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
conn.commit()
|
|
229
|
+
conn.close()
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
'valid': True,
|
|
233
|
+
'ai_id': token_record['ai_id'],
|
|
234
|
+
'ai_name': token_record['ai_name'],
|
|
235
|
+
'expertise': token_record['expertise'],
|
|
236
|
+
'token_prefix': token_record['token_prefix'],
|
|
237
|
+
'expires_at': token_record['expires_at']
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"❌ Error validating token: {e}")
|
|
242
|
+
return {
|
|
243
|
+
'valid': False,
|
|
244
|
+
'error': str(e)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
def revoke_token(self, token_prefix: str) -> Dict:
|
|
248
|
+
"""
|
|
249
|
+
Revoke an authentication token
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
token_prefix: Token prefix to revoke (e.g., "sk_live_abc12345")
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Dictionary with revocation result
|
|
256
|
+
"""
|
|
257
|
+
try:
|
|
258
|
+
conn = self._get_connection()
|
|
259
|
+
cursor = conn.cursor()
|
|
260
|
+
|
|
261
|
+
if is_sqlite():
|
|
262
|
+
cursor.execute("""
|
|
263
|
+
UPDATE ai_auth_tokens
|
|
264
|
+
SET is_active = 0
|
|
265
|
+
WHERE token_prefix = ?
|
|
266
|
+
""", (token_prefix,))
|
|
267
|
+
else:
|
|
268
|
+
cursor.execute("""
|
|
269
|
+
UPDATE ai_auth_tokens
|
|
270
|
+
SET is_active = FALSE
|
|
271
|
+
WHERE token_prefix = %s
|
|
272
|
+
""", (token_prefix,))
|
|
273
|
+
|
|
274
|
+
rows_affected = cursor.rowcount
|
|
275
|
+
conn.commit()
|
|
276
|
+
conn.close()
|
|
277
|
+
|
|
278
|
+
if rows_affected > 0:
|
|
279
|
+
print(f"✅ Token {token_prefix} revoked")
|
|
280
|
+
return {
|
|
281
|
+
'success': True,
|
|
282
|
+
'revoked': True
|
|
283
|
+
}
|
|
284
|
+
else:
|
|
285
|
+
print(f"⚠️ Token {token_prefix} not found")
|
|
286
|
+
return {
|
|
287
|
+
'success': False,
|
|
288
|
+
'error': 'Token not found'
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
print(f"❌ Error revoking token: {e}")
|
|
293
|
+
return {
|
|
294
|
+
'success': False,
|
|
295
|
+
'error': str(e)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
def list_tokens(self, ai_id: Optional[int] = None) -> List[Dict]:
|
|
299
|
+
"""
|
|
300
|
+
List all tokens or tokens for a specific AI
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
ai_id: Optional AI ID to filter by
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of token information
|
|
307
|
+
"""
|
|
308
|
+
try:
|
|
309
|
+
conn = self._get_connection()
|
|
310
|
+
cursor = conn.cursor()
|
|
311
|
+
|
|
312
|
+
if ai_id:
|
|
313
|
+
if is_sqlite():
|
|
314
|
+
cursor.execute("""
|
|
315
|
+
SELECT t.*, p.name as ai_name
|
|
316
|
+
FROM ai_auth_tokens t
|
|
317
|
+
JOIN ai_profiles p ON t.ai_id = p.id
|
|
318
|
+
WHERE t.ai_id = ?
|
|
319
|
+
ORDER BY t.created_at DESC
|
|
320
|
+
""", (ai_id,))
|
|
321
|
+
else:
|
|
322
|
+
cursor.execute("""
|
|
323
|
+
SELECT t.*, p.name as ai_name
|
|
324
|
+
FROM ai_auth_tokens t
|
|
325
|
+
JOIN ai_profiles p ON t.ai_id = p.id
|
|
326
|
+
WHERE t.ai_id = %s
|
|
327
|
+
ORDER BY t.created_at DESC
|
|
328
|
+
""", (ai_id,))
|
|
329
|
+
else:
|
|
330
|
+
cursor.execute("""
|
|
331
|
+
SELECT t.*, p.name as ai_name
|
|
332
|
+
FROM ai_auth_tokens t
|
|
333
|
+
JOIN ai_profiles p ON t.ai_id = p.id
|
|
334
|
+
ORDER BY t.created_at DESC
|
|
335
|
+
""")
|
|
336
|
+
|
|
337
|
+
tokens = [dict(row) for row in cursor.fetchall()]
|
|
338
|
+
conn.close()
|
|
339
|
+
|
|
340
|
+
return tokens
|
|
341
|
+
|
|
342
|
+
except Exception as e:
|
|
343
|
+
print(f"❌ Error listing tokens: {e}")
|
|
344
|
+
return []
|
|
345
|
+
|
|
346
|
+
def grant_project_permission(self, ai_id: int, project: str, role: str = 'member', granted_by: int = None) -> Dict:
|
|
347
|
+
"""
|
|
348
|
+
Grant project permission to an AI
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
ai_id: AI ID to grant permission to
|
|
352
|
+
project: Project name
|
|
353
|
+
role: Role (admin, member, viewer, contributor)
|
|
354
|
+
granted_by: AI ID of admin granting permission
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Dictionary with grant result
|
|
358
|
+
"""
|
|
359
|
+
try:
|
|
360
|
+
conn = self._get_connection()
|
|
361
|
+
cursor = conn.cursor()
|
|
362
|
+
|
|
363
|
+
# Check if AI exists
|
|
364
|
+
if is_sqlite():
|
|
365
|
+
cursor.execute("SELECT name FROM ai_profiles WHERE id = ?", (ai_id,))
|
|
366
|
+
else:
|
|
367
|
+
cursor.execute("SELECT name FROM ai_profiles WHERE id = %s", (ai_id,))
|
|
368
|
+
ai_profile = cursor.fetchone()
|
|
369
|
+
|
|
370
|
+
if not ai_profile:
|
|
371
|
+
conn.close()
|
|
372
|
+
return {
|
|
373
|
+
'success': False,
|
|
374
|
+
'error': f'AI {ai_id} not found'
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
# Check if permission already exists
|
|
378
|
+
if is_sqlite():
|
|
379
|
+
cursor.execute("""
|
|
380
|
+
SELECT id FROM ai_project_permissions
|
|
381
|
+
WHERE ai_id = ? AND project = ?
|
|
382
|
+
""", (ai_id, project))
|
|
383
|
+
else:
|
|
384
|
+
cursor.execute("""
|
|
385
|
+
SELECT id FROM ai_project_permissions
|
|
386
|
+
WHERE ai_id = %s AND project = %s
|
|
387
|
+
""", (ai_id, project))
|
|
388
|
+
|
|
389
|
+
existing = cursor.fetchone()
|
|
390
|
+
|
|
391
|
+
if existing:
|
|
392
|
+
# Update existing permission
|
|
393
|
+
if is_sqlite():
|
|
394
|
+
cursor.execute("""
|
|
395
|
+
UPDATE ai_project_permissions
|
|
396
|
+
SET role = ?, granted_by = ?
|
|
397
|
+
WHERE ai_id = ? AND project = ?
|
|
398
|
+
""", (role, granted_by, ai_id, project))
|
|
399
|
+
else:
|
|
400
|
+
cursor.execute("""
|
|
401
|
+
UPDATE ai_project_permissions
|
|
402
|
+
SET role = %s, granted_by = %s
|
|
403
|
+
WHERE ai_id = %s AND project = %s
|
|
404
|
+
""", (role, granted_by, ai_id, project))
|
|
405
|
+
print(f"✅ Updated permission for AI {ai_id} on project {project}: {role}")
|
|
406
|
+
else:
|
|
407
|
+
# Insert new permission
|
|
408
|
+
if is_sqlite():
|
|
409
|
+
cursor.execute("""
|
|
410
|
+
INSERT INTO ai_project_permissions (ai_id, project, role, granted_by)
|
|
411
|
+
VALUES (?, ?, ?, ?)
|
|
412
|
+
""", (ai_id, project, role, granted_by))
|
|
413
|
+
else:
|
|
414
|
+
cursor.execute("""
|
|
415
|
+
INSERT INTO ai_project_permissions (ai_id, project, role, granted_by)
|
|
416
|
+
VALUES (%s, %s, %s, %s)
|
|
417
|
+
""", (ai_id, project, role, granted_by))
|
|
418
|
+
print(f"✅ Granted permission for AI {ai_id} on project {project}: {role}")
|
|
419
|
+
|
|
420
|
+
conn.commit()
|
|
421
|
+
conn.close()
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
'success': True,
|
|
425
|
+
'ai_id': ai_id,
|
|
426
|
+
'project': project,
|
|
427
|
+
'role': role
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
except Exception as e:
|
|
431
|
+
print(f"❌ Error granting permission: {e}")
|
|
432
|
+
return {
|
|
433
|
+
'success': False,
|
|
434
|
+
'error': str(e)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
def revoke_project_permission(self, ai_id: int, project: str) -> Dict:
|
|
438
|
+
"""
|
|
439
|
+
Revoke project permission from an AI
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
ai_id: AI ID to revoke permission from
|
|
443
|
+
project: Project name
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Dictionary with revocation result
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
conn = self._get_connection()
|
|
450
|
+
cursor = conn.cursor()
|
|
451
|
+
|
|
452
|
+
if is_sqlite():
|
|
453
|
+
cursor.execute("""
|
|
454
|
+
DELETE FROM ai_project_permissions
|
|
455
|
+
WHERE ai_id = ? AND project = ?
|
|
456
|
+
""", (ai_id, project))
|
|
457
|
+
else:
|
|
458
|
+
cursor.execute("""
|
|
459
|
+
DELETE FROM ai_project_permissions
|
|
460
|
+
WHERE ai_id = %s AND project = %s
|
|
461
|
+
""", (ai_id, project))
|
|
462
|
+
|
|
463
|
+
rows_affected = cursor.rowcount
|
|
464
|
+
conn.commit()
|
|
465
|
+
conn.close()
|
|
466
|
+
|
|
467
|
+
if rows_affected > 0:
|
|
468
|
+
print(f"✅ Revoked permission for AI {ai_id} on project {project}")
|
|
469
|
+
return {
|
|
470
|
+
'success': True,
|
|
471
|
+
'revoked': True
|
|
472
|
+
}
|
|
473
|
+
else:
|
|
474
|
+
print(f"⚠️ No permission found for AI {ai_id} on project {project}")
|
|
475
|
+
return {
|
|
476
|
+
'success': False,
|
|
477
|
+
'error': 'Permission not found'
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
print(f"❌ Error revoking permission: {e}")
|
|
482
|
+
return {
|
|
483
|
+
'success': False,
|
|
484
|
+
'error': str(e)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
def list_permissions(self, ai_id: Optional[int] = None, project: Optional[str] = None) -> List[Dict]:
|
|
488
|
+
"""
|
|
489
|
+
List project permissions
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
ai_id: Optional AI ID to filter by
|
|
493
|
+
project: Optional project name to filter by
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
List of permission information
|
|
497
|
+
"""
|
|
498
|
+
try:
|
|
499
|
+
conn = self._get_connection()
|
|
500
|
+
cursor = conn.cursor()
|
|
501
|
+
|
|
502
|
+
if ai_id and project:
|
|
503
|
+
if is_sqlite():
|
|
504
|
+
cursor.execute("""
|
|
505
|
+
SELECT pp.*, p.name as ai_name
|
|
506
|
+
FROM ai_project_permissions pp
|
|
507
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
508
|
+
WHERE pp.ai_id = ? AND pp.project = ?
|
|
509
|
+
ORDER BY pp.created_at DESC
|
|
510
|
+
""", (ai_id, project))
|
|
511
|
+
else:
|
|
512
|
+
cursor.execute("""
|
|
513
|
+
SELECT pp.*, p.name as ai_name
|
|
514
|
+
FROM ai_project_permissions pp
|
|
515
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
516
|
+
WHERE pp.ai_id = %s AND pp.project = %s
|
|
517
|
+
ORDER BY pp.created_at DESC
|
|
518
|
+
""", (ai_id, project))
|
|
519
|
+
elif ai_id:
|
|
520
|
+
if is_sqlite():
|
|
521
|
+
cursor.execute("""
|
|
522
|
+
SELECT pp.*, p.name as ai_name
|
|
523
|
+
FROM ai_project_permissions pp
|
|
524
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
525
|
+
WHERE pp.ai_id = ?
|
|
526
|
+
ORDER BY pp.created_at DESC
|
|
527
|
+
""", (ai_id,))
|
|
528
|
+
else:
|
|
529
|
+
cursor.execute("""
|
|
530
|
+
SELECT pp.*, p.name as ai_name
|
|
531
|
+
FROM ai_project_permissions pp
|
|
532
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
533
|
+
WHERE pp.ai_id = %s
|
|
534
|
+
ORDER BY pp.created_at DESC
|
|
535
|
+
""", (ai_id,))
|
|
536
|
+
elif project:
|
|
537
|
+
if is_sqlite():
|
|
538
|
+
cursor.execute("""
|
|
539
|
+
SELECT pp.*, p.name as ai_name
|
|
540
|
+
FROM ai_project_permissions pp
|
|
541
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
542
|
+
WHERE pp.project = ?
|
|
543
|
+
ORDER BY pp.created_at DESC
|
|
544
|
+
""", (project,))
|
|
545
|
+
else:
|
|
546
|
+
cursor.execute("""
|
|
547
|
+
SELECT pp.*, p.name as ai_name
|
|
548
|
+
FROM ai_project_permissions pp
|
|
549
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
550
|
+
WHERE pp.project = %s
|
|
551
|
+
ORDER BY pp.created_at DESC
|
|
552
|
+
""", (project,))
|
|
553
|
+
else:
|
|
554
|
+
cursor.execute("""
|
|
555
|
+
SELECT pp.*, p.name as ai_name
|
|
556
|
+
FROM ai_project_permissions pp
|
|
557
|
+
JOIN ai_profiles p ON pp.ai_id = p.id
|
|
558
|
+
ORDER BY pp.created_at DESC
|
|
559
|
+
""")
|
|
560
|
+
|
|
561
|
+
permissions = [dict(row) for row in cursor.fetchall()]
|
|
562
|
+
conn.close()
|
|
563
|
+
|
|
564
|
+
return permissions
|
|
565
|
+
|
|
566
|
+
except Exception as e:
|
|
567
|
+
print(f"❌ Error listing permissions: {e}")
|
|
568
|
+
return []
|
|
569
|
+
|
|
570
|
+
def check_project_permission(self, ai_id: int, project: str) -> Dict:
|
|
571
|
+
"""
|
|
572
|
+
Check if an AI has permission for a project
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
ai_id: AI ID to check
|
|
576
|
+
project: Project name to check
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Dictionary with permission status and role
|
|
580
|
+
"""
|
|
581
|
+
try:
|
|
582
|
+
conn = self._get_connection()
|
|
583
|
+
cursor = conn.cursor()
|
|
584
|
+
|
|
585
|
+
if is_sqlite():
|
|
586
|
+
cursor.execute("""
|
|
587
|
+
SELECT role, created_at
|
|
588
|
+
FROM ai_project_permissions
|
|
589
|
+
WHERE ai_id = ? AND project = ?
|
|
590
|
+
""", (ai_id, project))
|
|
591
|
+
else:
|
|
592
|
+
cursor.execute("""
|
|
593
|
+
SELECT role, created_at
|
|
594
|
+
FROM ai_project_permissions
|
|
595
|
+
WHERE ai_id = %s AND project = %s
|
|
596
|
+
""", (ai_id, project))
|
|
597
|
+
|
|
598
|
+
permission = cursor.fetchone()
|
|
599
|
+
conn.close()
|
|
600
|
+
|
|
601
|
+
if permission:
|
|
602
|
+
return {
|
|
603
|
+
'has_permission': True,
|
|
604
|
+
'role': permission['role'],
|
|
605
|
+
'granted_at': permission['created_at']
|
|
606
|
+
}
|
|
607
|
+
else:
|
|
608
|
+
return {
|
|
609
|
+
'has_permission': False,
|
|
610
|
+
'role': None
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
except Exception as e:
|
|
614
|
+
print(f"❌ Error checking project permission: {e}")
|
|
615
|
+
return {
|
|
616
|
+
'has_permission': False,
|
|
617
|
+
'role': None,
|
|
618
|
+
'error': str(e)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
def log_authentication(self, ai_id: int, project: Optional[str] = None,
|
|
622
|
+
success: bool = True, details: str = "") -> Dict:
|
|
623
|
+
"""
|
|
624
|
+
Log authentication attempt to audit table
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
ai_id: AI ID attempting authentication
|
|
628
|
+
project: Project name (optional)
|
|
629
|
+
success: Whether authentication succeeded
|
|
630
|
+
details: Additional details about the attempt
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
Dictionary with log result
|
|
634
|
+
"""
|
|
635
|
+
try:
|
|
636
|
+
conn = self._get_connection()
|
|
637
|
+
cursor = conn.cursor()
|
|
638
|
+
|
|
639
|
+
# Get AI name for logging
|
|
640
|
+
cursor.execute("SELECT name FROM ai_profiles WHERE id = ?", (ai_id,))
|
|
641
|
+
ai_profile = cursor.fetchone()
|
|
642
|
+
ai_name = ai_profile['name'] if ai_profile else 'Unknown'
|
|
643
|
+
|
|
644
|
+
if is_sqlite():
|
|
645
|
+
cursor.execute("""
|
|
646
|
+
INSERT INTO ai_auth_audit (ai_id, ai_name, project, success, details, created_at)
|
|
647
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))
|
|
648
|
+
""", (ai_id, ai_name, project, 1 if success else 0, details))
|
|
649
|
+
else:
|
|
650
|
+
cursor.execute("""
|
|
651
|
+
INSERT INTO ai_auth_audit (ai_id, ai_name, project, success, details, created_at)
|
|
652
|
+
VALUES (%s, %s, %s, %s, %s, CURRENT_TIMESTAMP)
|
|
653
|
+
""", (ai_id, ai_name, project, success, details))
|
|
654
|
+
|
|
655
|
+
conn.commit()
|
|
656
|
+
conn.close()
|
|
657
|
+
|
|
658
|
+
return {
|
|
659
|
+
'success': True,
|
|
660
|
+
'ai_id': ai_id,
|
|
661
|
+
'project': project,
|
|
662
|
+
'success': success
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
except Exception as e:
|
|
666
|
+
print(f"❌ Error logging authentication [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]: {e}")
|
|
667
|
+
return {
|
|
668
|
+
'success': False,
|
|
669
|
+
'error': str(e)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def main():
|
|
674
|
+
"""Example usage of token manager"""
|
|
675
|
+
manager = TokenManager()
|
|
676
|
+
|
|
677
|
+
print("=" * 70)
|
|
678
|
+
print("🔐 CloudBrain Token Manager")
|
|
679
|
+
print("=" * 70)
|
|
680
|
+
print()
|
|
681
|
+
|
|
682
|
+
# Example: Generate token for AI 19 (GLM-4.7)
|
|
683
|
+
print("📝 Example 1: Generate token for AI 19")
|
|
684
|
+
result = manager.generate_token(
|
|
685
|
+
ai_id=19,
|
|
686
|
+
expires_days=30,
|
|
687
|
+
description="CloudBrain development token"
|
|
688
|
+
)
|
|
689
|
+
print()
|
|
690
|
+
|
|
691
|
+
# Example: Validate token
|
|
692
|
+
if result['success']:
|
|
693
|
+
print("📝 Example 2: Validate token")
|
|
694
|
+
validation = manager.validate_token(result['token'])
|
|
695
|
+
print(f" Valid: {validation['valid']}")
|
|
696
|
+
print()
|
|
697
|
+
|
|
698
|
+
# Example: Grant project permission
|
|
699
|
+
print("📝 Example 3: Grant project permission")
|
|
700
|
+
manager.grant_project_permission(
|
|
701
|
+
ai_id=19,
|
|
702
|
+
project='cloudbrain',
|
|
703
|
+
role='admin',
|
|
704
|
+
granted_by=1
|
|
705
|
+
)
|
|
706
|
+
print()
|
|
707
|
+
|
|
708
|
+
# Example: List all tokens
|
|
709
|
+
print("📝 Example 4: List all tokens")
|
|
710
|
+
tokens = manager.list_tokens()
|
|
711
|
+
for token in tokens[:5]:
|
|
712
|
+
print(f" {token['ai_name']}: {token['token_prefix']} (expires: {token['expires_at']})")
|
|
713
|
+
print()
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
if __name__ == "__main__":
|
|
717
|
+
main()
|