mdb-engine 0.1.6__py3-none-any.whl → 0.2.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.
- mdb_engine/__init__.py +104 -11
- mdb_engine/auth/ARCHITECTURE.md +112 -0
- mdb_engine/auth/README.md +648 -11
- mdb_engine/auth/__init__.py +136 -29
- mdb_engine/auth/audit.py +592 -0
- mdb_engine/auth/base.py +252 -0
- mdb_engine/auth/casbin_factory.py +264 -69
- mdb_engine/auth/config_helpers.py +7 -6
- mdb_engine/auth/cookie_utils.py +3 -7
- mdb_engine/auth/csrf.py +373 -0
- mdb_engine/auth/decorators.py +3 -10
- mdb_engine/auth/dependencies.py +47 -50
- mdb_engine/auth/helpers.py +3 -3
- mdb_engine/auth/integration.py +53 -80
- mdb_engine/auth/jwt.py +2 -6
- mdb_engine/auth/middleware.py +77 -34
- mdb_engine/auth/oso_factory.py +18 -38
- mdb_engine/auth/provider.py +270 -171
- mdb_engine/auth/rate_limiter.py +504 -0
- mdb_engine/auth/restrictions.py +8 -24
- mdb_engine/auth/session_manager.py +14 -29
- mdb_engine/auth/shared_middleware.py +600 -0
- mdb_engine/auth/shared_users.py +759 -0
- mdb_engine/auth/token_store.py +14 -28
- mdb_engine/auth/users.py +54 -113
- mdb_engine/auth/utils.py +213 -15
- mdb_engine/cli/commands/generate.py +545 -9
- mdb_engine/cli/commands/validate.py +3 -7
- mdb_engine/cli/utils.py +3 -3
- mdb_engine/config.py +7 -21
- mdb_engine/constants.py +65 -0
- mdb_engine/core/README.md +117 -6
- mdb_engine/core/__init__.py +39 -7
- mdb_engine/core/app_registration.py +22 -41
- mdb_engine/core/app_secrets.py +290 -0
- mdb_engine/core/connection.py +18 -9
- mdb_engine/core/encryption.py +223 -0
- mdb_engine/core/engine.py +1057 -93
- mdb_engine/core/index_management.py +12 -16
- mdb_engine/core/manifest.py +459 -150
- mdb_engine/core/ray_integration.py +435 -0
- mdb_engine/core/seeding.py +10 -18
- mdb_engine/core/service_initialization.py +12 -23
- mdb_engine/core/types.py +2 -5
- mdb_engine/database/README.md +140 -17
- mdb_engine/database/__init__.py +17 -6
- mdb_engine/database/abstraction.py +25 -37
- mdb_engine/database/connection.py +11 -18
- mdb_engine/database/query_validator.py +367 -0
- mdb_engine/database/resource_limiter.py +204 -0
- mdb_engine/database/scoped_wrapper.py +713 -196
- mdb_engine/dependencies.py +426 -0
- mdb_engine/di/__init__.py +34 -0
- mdb_engine/di/container.py +248 -0
- mdb_engine/di/providers.py +205 -0
- mdb_engine/di/scopes.py +139 -0
- mdb_engine/embeddings/README.md +54 -24
- mdb_engine/embeddings/__init__.py +31 -24
- mdb_engine/embeddings/dependencies.py +37 -154
- mdb_engine/embeddings/service.py +11 -25
- mdb_engine/exceptions.py +92 -0
- mdb_engine/indexes/README.md +30 -13
- mdb_engine/indexes/__init__.py +1 -0
- mdb_engine/indexes/helpers.py +1 -1
- mdb_engine/indexes/manager.py +50 -114
- mdb_engine/memory/README.md +2 -2
- mdb_engine/memory/__init__.py +1 -2
- mdb_engine/memory/service.py +30 -87
- mdb_engine/observability/README.md +4 -2
- mdb_engine/observability/__init__.py +26 -9
- mdb_engine/observability/health.py +8 -9
- mdb_engine/observability/metrics.py +32 -12
- mdb_engine/repositories/__init__.py +34 -0
- mdb_engine/repositories/base.py +325 -0
- mdb_engine/repositories/mongo.py +233 -0
- mdb_engine/repositories/unit_of_work.py +166 -0
- mdb_engine/routing/README.md +1 -1
- mdb_engine/routing/__init__.py +1 -3
- mdb_engine/routing/websockets.py +25 -60
- mdb_engine-0.2.0.dist-info/METADATA +313 -0
- mdb_engine-0.2.0.dist-info/RECORD +96 -0
- mdb_engine-0.1.6.dist-info/METADATA +0 -213
- mdb_engine-0.1.6.dist-info/RECORD +0 -75
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/WHEEL +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/top_level.txt +0 -0
mdb_engine/auth/token_store.py
CHANGED
|
@@ -11,8 +11,11 @@ from datetime import datetime, timedelta
|
|
|
11
11
|
from typing import Optional
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
|
-
from pymongo.errors import (
|
|
15
|
-
|
|
14
|
+
from pymongo.errors import (
|
|
15
|
+
ConnectionFailure,
|
|
16
|
+
OperationFailure,
|
|
17
|
+
ServerSelectionTimeoutError,
|
|
18
|
+
)
|
|
16
19
|
except ImportError:
|
|
17
20
|
ConnectionFailure = Exception
|
|
18
21
|
OperationFailure = Exception
|
|
@@ -54,9 +57,7 @@ class TokenBlacklist:
|
|
|
54
57
|
|
|
55
58
|
try:
|
|
56
59
|
# Create unique index on jti
|
|
57
|
-
await self.collection.create_index(
|
|
58
|
-
"jti", unique=True, name="jti_unique_idx"
|
|
59
|
-
)
|
|
60
|
+
await self.collection.create_index("jti", unique=True, name="jti_unique_idx")
|
|
60
61
|
|
|
61
62
|
# Create TTL index on expires_at (auto-delete expired entries)
|
|
62
63
|
await self.collection.create_index(
|
|
@@ -110,9 +111,7 @@ class TokenBlacklist:
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
# Use upsert to handle duplicate jti gracefully
|
|
113
|
-
await self.collection.update_one(
|
|
114
|
-
{"jti": jti}, {"$set": blacklist_entry}, upsert=True
|
|
115
|
-
)
|
|
114
|
+
await self.collection.update_one({"jti": jti}, {"$set": blacklist_entry}, upsert=True)
|
|
116
115
|
|
|
117
116
|
logger.debug(f"Token {jti} added to blacklist (reason: {reason})")
|
|
118
117
|
return True
|
|
@@ -158,15 +157,11 @@ class TokenBlacklist:
|
|
|
158
157
|
ValueError,
|
|
159
158
|
TypeError,
|
|
160
159
|
) as e:
|
|
161
|
-
logger.error(
|
|
162
|
-
f"Error checking token revocation status for {jti}: {e}", exc_info=True
|
|
163
|
-
)
|
|
160
|
+
logger.error(f"Error checking token revocation status for {jti}: {e}", exc_info=True)
|
|
164
161
|
# On error, assume not revoked (fail open for availability)
|
|
165
162
|
return False
|
|
166
163
|
|
|
167
|
-
async def revoke_all_user_tokens(
|
|
168
|
-
self, user_id: str, reason: Optional[str] = None
|
|
169
|
-
) -> int:
|
|
164
|
+
async def revoke_all_user_tokens(self, user_id: str, reason: Optional[str] = None) -> int:
|
|
170
165
|
"""
|
|
171
166
|
Revoke all tokens for a specific user.
|
|
172
167
|
|
|
@@ -189,8 +184,7 @@ class TokenBlacklist:
|
|
|
189
184
|
"user_id": user_id,
|
|
190
185
|
"revoked_at": datetime.utcnow(),
|
|
191
186
|
"reason": reason or "logout_all",
|
|
192
|
-
"expires_at": datetime.utcnow()
|
|
193
|
-
+ timedelta(days=30), # Keep for 30 days
|
|
187
|
+
"expires_at": datetime.utcnow() + timedelta(days=30), # Keep for 30 days
|
|
194
188
|
}
|
|
195
189
|
|
|
196
190
|
# Use upsert to update or create marker
|
|
@@ -213,9 +207,7 @@ class TokenBlacklist:
|
|
|
213
207
|
ValueError,
|
|
214
208
|
TypeError,
|
|
215
209
|
) as e:
|
|
216
|
-
logger.error(
|
|
217
|
-
f"Error revoking all tokens for user {user_id}: {e}", exc_info=True
|
|
218
|
-
)
|
|
210
|
+
logger.error(f"Error revoking all tokens for user {user_id}: {e}", exc_info=True)
|
|
219
211
|
return 0
|
|
220
212
|
|
|
221
213
|
async def is_user_revoked(self, user_id: str) -> bool:
|
|
@@ -231,9 +223,7 @@ class TokenBlacklist:
|
|
|
231
223
|
try:
|
|
232
224
|
await self.ensure_indexes()
|
|
233
225
|
|
|
234
|
-
marker = await self.collection.find_one(
|
|
235
|
-
{"user_id": user_id, "type": "user_revocation"}
|
|
236
|
-
)
|
|
226
|
+
marker = await self.collection.find_one({"user_id": user_id, "type": "user_revocation"})
|
|
237
227
|
|
|
238
228
|
if marker:
|
|
239
229
|
# Check if marker hasn't expired
|
|
@@ -269,9 +259,7 @@ class TokenBlacklist:
|
|
|
269
259
|
Number of entries deleted
|
|
270
260
|
"""
|
|
271
261
|
try:
|
|
272
|
-
result = await self.collection.delete_many(
|
|
273
|
-
{"expires_at": {"$lt": datetime.utcnow()}}
|
|
274
|
-
)
|
|
262
|
+
result = await self.collection.delete_many({"expires_at": {"$lt": datetime.utcnow()}})
|
|
275
263
|
deleted_count = result.deleted_count
|
|
276
264
|
if deleted_count > 0:
|
|
277
265
|
logger.info(f"Cleared {deleted_count} expired blacklist entries")
|
|
@@ -283,7 +271,5 @@ class TokenBlacklist:
|
|
|
283
271
|
ValueError,
|
|
284
272
|
TypeError,
|
|
285
273
|
) as e:
|
|
286
|
-
logger.error(
|
|
287
|
-
f"Error clearing expired blacklist entries: {e}", exc_info=True
|
|
288
|
-
)
|
|
274
|
+
logger.error(f"Error clearing expired blacklist entries: {e}", exc_info=True)
|
|
289
275
|
return 0
|
mdb_engine/auth/users.py
CHANGED
|
@@ -22,9 +22,15 @@ from fastapi import Request
|
|
|
22
22
|
from fastapi.responses import Response
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
|
-
from
|
|
26
|
-
|
|
25
|
+
from bson.errors import InvalidId as BsonInvalidId
|
|
26
|
+
from pymongo.errors import (
|
|
27
|
+
ConnectionFailure,
|
|
28
|
+
OperationFailure,
|
|
29
|
+
PyMongoError,
|
|
30
|
+
ServerSelectionTimeoutError,
|
|
31
|
+
)
|
|
27
32
|
except ImportError:
|
|
33
|
+
BsonInvalidId = ValueError # Fallback if bson not available
|
|
28
34
|
ConnectionFailure = Exception
|
|
29
35
|
OperationFailure = Exception
|
|
30
36
|
ServerSelectionTimeoutError = Exception
|
|
@@ -90,11 +96,8 @@ def _convert_user_id_to_objectid(user_id: Any) -> Tuple[Any, Optional[str]]:
|
|
|
90
96
|
# If it's not a string or ObjectId, try to convert (for backward compatibility)
|
|
91
97
|
try:
|
|
92
98
|
return ObjectId(user_id), None
|
|
93
|
-
except (TypeError, ValueError) as e:
|
|
99
|
+
except (TypeError, ValueError, BsonInvalidId) as e:
|
|
94
100
|
return None, f"Invalid user_id format: {user_id}: {e}"
|
|
95
|
-
except Exception:
|
|
96
|
-
logger.exception("Unexpected error converting user_id to ObjectId")
|
|
97
|
-
raise
|
|
98
101
|
|
|
99
102
|
return user_id, None
|
|
100
103
|
|
|
@@ -154,9 +157,7 @@ async def get_app_user(
|
|
|
154
157
|
db,
|
|
155
158
|
config: Optional[Dict[str, Any]] = None,
|
|
156
159
|
allow_demo_fallback: bool = False,
|
|
157
|
-
get_app_config_func: Optional[
|
|
158
|
-
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
159
|
-
] = None,
|
|
160
|
+
get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
|
|
160
161
|
) -> Optional[Dict[str, Any]]:
|
|
161
162
|
"""
|
|
162
163
|
Get app-level user from session cookie.
|
|
@@ -279,9 +280,7 @@ async def _try_demo_mode(
|
|
|
279
280
|
f"(MONGO_URI={MONGO_URI}, DB_NAME={DB_NAME})"
|
|
280
281
|
)
|
|
281
282
|
|
|
282
|
-
demo_user = await get_or_create_demo_user(
|
|
283
|
-
db, slug_id, config, MONGO_URI, DB_NAME
|
|
284
|
-
)
|
|
283
|
+
demo_user = await get_or_create_demo_user(db, slug_id, config, MONGO_URI, DB_NAME)
|
|
285
284
|
|
|
286
285
|
if not demo_user:
|
|
287
286
|
logger.warning(
|
|
@@ -321,9 +320,7 @@ async def create_app_session(
|
|
|
321
320
|
user_id: str,
|
|
322
321
|
config: Optional[Dict[str, Any]] = None,
|
|
323
322
|
response: Optional[Response] = None,
|
|
324
|
-
get_app_config_func: Optional[
|
|
325
|
-
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
326
|
-
] = None,
|
|
323
|
+
get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
|
|
327
324
|
) -> str:
|
|
328
325
|
"""
|
|
329
326
|
Create a app-specific session token and set cookie.
|
|
@@ -387,9 +384,7 @@ async def create_app_session(
|
|
|
387
384
|
cookie_name = f"{session_cookie_name}_{slug_id}"
|
|
388
385
|
|
|
389
386
|
# Determine secure cookie setting
|
|
390
|
-
should_use_secure = (
|
|
391
|
-
request.url.scheme == "https" or os.getenv("G_NOME_ENV") == "production"
|
|
392
|
-
)
|
|
387
|
+
should_use_secure = request.url.scheme == "https" or os.getenv("G_NOME_ENV") == "production"
|
|
393
388
|
|
|
394
389
|
response.set_cookie(
|
|
395
390
|
key=cookie_name,
|
|
@@ -436,12 +431,9 @@ async def authenticate_app_user(
|
|
|
436
431
|
from bson.objectid import ObjectId
|
|
437
432
|
|
|
438
433
|
query["store_id"] = ObjectId(store_id)
|
|
439
|
-
except (TypeError, ValueError) as e:
|
|
434
|
+
except (TypeError, ValueError, BsonInvalidId) as e:
|
|
440
435
|
logger.warning(f"Invalid store_id format: {store_id}: {e}")
|
|
441
436
|
return None
|
|
442
|
-
except Exception:
|
|
443
|
-
logger.exception("Unexpected error converting store_id to ObjectId")
|
|
444
|
-
raise
|
|
445
437
|
|
|
446
438
|
# Find user
|
|
447
439
|
# Use getattr to access collection (works with ScopedMongoWrapper and AppDB)
|
|
@@ -479,10 +471,12 @@ async def authenticate_app_user(
|
|
|
479
471
|
except (ValueError, TypeError):
|
|
480
472
|
logger.exception("Validation error authenticating app user")
|
|
481
473
|
return None
|
|
482
|
-
except
|
|
483
|
-
logger.exception("
|
|
484
|
-
|
|
485
|
-
|
|
474
|
+
except PyMongoError:
|
|
475
|
+
logger.exception("Database error authenticating app user")
|
|
476
|
+
return None
|
|
477
|
+
except (AttributeError, KeyError):
|
|
478
|
+
logger.exception("Attribute access error authenticating app user")
|
|
479
|
+
return None
|
|
486
480
|
|
|
487
481
|
|
|
488
482
|
async def create_app_user(
|
|
@@ -513,12 +507,7 @@ async def create_app_user(
|
|
|
513
507
|
from bson.objectid import ObjectId
|
|
514
508
|
|
|
515
509
|
# Validate email format
|
|
516
|
-
if (
|
|
517
|
-
not email
|
|
518
|
-
or not isinstance(email, str)
|
|
519
|
-
or "@" not in email
|
|
520
|
-
or "." not in email
|
|
521
|
-
):
|
|
510
|
+
if not email or not isinstance(email, str) or "@" not in email or "." not in email:
|
|
522
511
|
logger.warning(f"Invalid email format: {email}")
|
|
523
512
|
return None
|
|
524
513
|
|
|
@@ -545,13 +534,9 @@ async def create_app_user(
|
|
|
545
534
|
# Always hash password (plain text support removed for security)
|
|
546
535
|
try:
|
|
547
536
|
password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
|
548
|
-
except ValueError:
|
|
537
|
+
except (ValueError, TypeError, UnicodeEncodeError):
|
|
549
538
|
logger.exception("Error encoding password for hashing")
|
|
550
539
|
return None
|
|
551
|
-
except Exception:
|
|
552
|
-
logger.exception("Unexpected error hashing password")
|
|
553
|
-
# Re-raise unexpected errors
|
|
554
|
-
raise
|
|
555
540
|
|
|
556
541
|
# Create user document
|
|
557
542
|
user_doc = {
|
|
@@ -573,13 +558,15 @@ async def create_app_user(
|
|
|
573
558
|
|
|
574
559
|
return user_doc
|
|
575
560
|
|
|
576
|
-
except (ValueError, TypeError):
|
|
561
|
+
except (ValueError, TypeError, BsonInvalidId):
|
|
577
562
|
logger.exception("Validation error creating app user")
|
|
578
563
|
return None
|
|
579
|
-
except
|
|
580
|
-
logger.exception("
|
|
581
|
-
|
|
582
|
-
|
|
564
|
+
except PyMongoError:
|
|
565
|
+
logger.exception("Database error creating app user")
|
|
566
|
+
return None
|
|
567
|
+
except (AttributeError, KeyError):
|
|
568
|
+
logger.exception("Attribute access error creating app user")
|
|
569
|
+
return None
|
|
583
570
|
|
|
584
571
|
|
|
585
572
|
async def get_or_create_anonymous_user(
|
|
@@ -587,9 +574,7 @@ async def get_or_create_anonymous_user(
|
|
|
587
574
|
slug_id: str,
|
|
588
575
|
db,
|
|
589
576
|
config: Optional[Dict[str, Any]] = None,
|
|
590
|
-
get_app_config_func: Optional[
|
|
591
|
-
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
592
|
-
] = None,
|
|
577
|
+
get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
|
|
593
578
|
) -> Optional[Dict[str, Any]]:
|
|
594
579
|
"""
|
|
595
580
|
Get or create an anonymous user for anonymous_session strategy.
|
|
@@ -655,9 +640,7 @@ async def get_or_create_anonymous_user(
|
|
|
655
640
|
return user
|
|
656
641
|
|
|
657
642
|
|
|
658
|
-
async def get_platform_demo_user(
|
|
659
|
-
mongo_uri: str, db_name: str
|
|
660
|
-
) -> Optional[Dict[str, Any]]:
|
|
643
|
+
async def get_platform_demo_user(mongo_uri: str, db_name: str) -> Optional[Dict[str, Any]]:
|
|
661
644
|
"""
|
|
662
645
|
Get platform demo user information from top-level database.
|
|
663
646
|
|
|
@@ -669,8 +652,7 @@ async def get_platform_demo_user(
|
|
|
669
652
|
Dict with demo user info (email, password from config, user_id) or None if not available
|
|
670
653
|
"""
|
|
671
654
|
try:
|
|
672
|
-
from ..config import
|
|
673
|
-
DEMO_PASSWORD_DEFAULT)
|
|
655
|
+
from ..config import DEMO_EMAIL_DEFAULT, DEMO_ENABLED, DEMO_PASSWORD_DEFAULT
|
|
674
656
|
|
|
675
657
|
if not DEMO_ENABLED or not DEMO_EMAIL_DEFAULT:
|
|
676
658
|
return None
|
|
@@ -716,14 +698,10 @@ async def _link_platform_demo_user(
|
|
|
716
698
|
import datetime
|
|
717
699
|
|
|
718
700
|
try:
|
|
719
|
-
logger.debug(
|
|
720
|
-
f"ensure_demo_users_exist: Auto-linking platform demo user for '{slug_id}'"
|
|
721
|
-
)
|
|
701
|
+
logger.debug(f"ensure_demo_users_exist: Auto-linking platform demo user for '{slug_id}'")
|
|
722
702
|
platform_demo = await get_platform_demo_user(mongo_uri, db_name)
|
|
723
703
|
if not platform_demo:
|
|
724
|
-
logger.warning(
|
|
725
|
-
f"ensure_demo_users_exist: Platform demo user not found for '{slug_id}'"
|
|
726
|
-
)
|
|
704
|
+
logger.warning(f"ensure_demo_users_exist: Platform demo user not found for '{slug_id}'")
|
|
727
705
|
return None
|
|
728
706
|
|
|
729
707
|
# Check if app demo user already exists for platform demo
|
|
@@ -737,13 +715,10 @@ async def _link_platform_demo_user(
|
|
|
737
715
|
platform_password = platform_demo.get("password", "demo123")
|
|
738
716
|
password_hash = None
|
|
739
717
|
try:
|
|
740
|
-
password_hash = bcrypt.hashpw(
|
|
741
|
-
platform_password.encode("utf-8"), bcrypt.gensalt()
|
|
742
|
-
)
|
|
718
|
+
password_hash = bcrypt.hashpw(platform_password.encode("utf-8"), bcrypt.gensalt())
|
|
743
719
|
except (ValueError, TypeError, AttributeError) as e:
|
|
744
720
|
logger.error(
|
|
745
|
-
f"Error hashing password for platform demo user "
|
|
746
|
-
f"{platform_demo['email']}: {e}",
|
|
721
|
+
f"Error hashing password for platform demo user " f"{platform_demo['email']}: {e}",
|
|
747
722
|
exc_info=True,
|
|
748
723
|
)
|
|
749
724
|
return None
|
|
@@ -793,9 +768,7 @@ def _validate_demo_user_config(
|
|
|
793
768
|
|
|
794
769
|
extra_data = demo_user_config.get("extra_data", {})
|
|
795
770
|
if not isinstance(extra_data, dict):
|
|
796
|
-
logger.warning(
|
|
797
|
-
f"Invalid extra_data for demo user config (not a dict): {extra_data}"
|
|
798
|
-
)
|
|
771
|
+
logger.warning(f"Invalid extra_data for demo user config (not a dict): {extra_data}")
|
|
799
772
|
extra_data = {}
|
|
800
773
|
|
|
801
774
|
return {
|
|
@@ -825,14 +798,10 @@ async def _resolve_demo_user_email_password(
|
|
|
825
798
|
if not password:
|
|
826
799
|
password = platform_demo["password"]
|
|
827
800
|
else:
|
|
828
|
-
logger.warning(
|
|
829
|
-
f"No email specified and platform demo not available for {slug_id}"
|
|
830
|
-
)
|
|
801
|
+
logger.warning(f"No email specified and platform demo not available for {slug_id}")
|
|
831
802
|
return None, None
|
|
832
803
|
else:
|
|
833
|
-
logger.warning(
|
|
834
|
-
f"No email specified and cannot access platform demo for {slug_id}"
|
|
835
|
-
)
|
|
804
|
+
logger.warning(f"No email specified and cannot access platform demo for {slug_id}")
|
|
836
805
|
return None, None
|
|
837
806
|
|
|
838
807
|
# Validate email format
|
|
@@ -877,9 +846,7 @@ async def _create_demo_user_from_config(
|
|
|
877
846
|
try:
|
|
878
847
|
password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
|
879
848
|
except (ValueError, TypeError, AttributeError) as e:
|
|
880
|
-
logger.error(
|
|
881
|
-
f"Error hashing password for demo user {email}: {e}", exc_info=True
|
|
882
|
-
)
|
|
849
|
+
logger.error(f"Error hashing password for demo user {email}: {e}", exc_info=True)
|
|
883
850
|
return None
|
|
884
851
|
|
|
885
852
|
# Create user document
|
|
@@ -945,9 +912,7 @@ async def _create_demo_user_from_config(
|
|
|
945
912
|
|
|
946
913
|
user_doc["_id"] = result.inserted_id if not custom_id else custom_id
|
|
947
914
|
user_doc["app_user_id"] = str(user_doc["_id"])
|
|
948
|
-
logger.info(
|
|
949
|
-
f"Created demo user {email} for {slug_id} with _id={user_doc['_id']}"
|
|
950
|
-
)
|
|
915
|
+
logger.info(f"Created demo user {email} for {slug_id} with _id={user_doc['_id']}")
|
|
951
916
|
return user_doc
|
|
952
917
|
except (
|
|
953
918
|
OperationFailure,
|
|
@@ -1030,9 +995,7 @@ async def ensure_demo_users_exist(
|
|
|
1030
995
|
for demo_user_config in demo_users_config:
|
|
1031
996
|
try:
|
|
1032
997
|
# Validate config structure
|
|
1033
|
-
validated_config, error = _validate_demo_user_config(
|
|
1034
|
-
demo_user_config, slug_id
|
|
1035
|
-
)
|
|
998
|
+
validated_config, error = _validate_demo_user_config(demo_user_config, slug_id)
|
|
1036
999
|
if not validated_config:
|
|
1037
1000
|
if error:
|
|
1038
1001
|
logger.warning(error)
|
|
@@ -1096,9 +1059,7 @@ async def get_or_create_demo_user_for_request(
|
|
|
1096
1059
|
slug_id: str,
|
|
1097
1060
|
db,
|
|
1098
1061
|
config: Optional[Dict[str, Any]] = None,
|
|
1099
|
-
get_app_config_func: Optional[
|
|
1100
|
-
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
1101
|
-
] = None,
|
|
1062
|
+
get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
|
|
1102
1063
|
) -> Optional[Dict[str, Any]]:
|
|
1103
1064
|
"""
|
|
1104
1065
|
Get or create a demo user for the current request context.
|
|
@@ -1146,9 +1107,7 @@ async def get_or_create_demo_user_for_request(
|
|
|
1146
1107
|
# Check if app demo user exists
|
|
1147
1108
|
# Use getattr to access collection (works with ScopedMongoWrapper and AppDB)
|
|
1148
1109
|
collection = getattr(db, collection_name)
|
|
1149
|
-
app_demo = await collection.find_one(
|
|
1150
|
-
{"email": DEMO_EMAIL_DEFAULT}
|
|
1151
|
-
)
|
|
1110
|
+
app_demo = await collection.find_one({"email": DEMO_EMAIL_DEFAULT})
|
|
1152
1111
|
|
|
1153
1112
|
if app_demo:
|
|
1154
1113
|
app_demo["app_user_id"] = str(app_demo["_id"])
|
|
@@ -1158,15 +1117,11 @@ async def get_or_create_demo_user_for_request(
|
|
|
1158
1117
|
try:
|
|
1159
1118
|
from ..config import DB_NAME, MONGO_URI
|
|
1160
1119
|
|
|
1161
|
-
await ensure_demo_users_exist(
|
|
1162
|
-
db, slug_id, config, MONGO_URI, DB_NAME
|
|
1163
|
-
)
|
|
1120
|
+
await ensure_demo_users_exist(db, slug_id, config, MONGO_URI, DB_NAME)
|
|
1164
1121
|
# Use getattr to access collection (works with
|
|
1165
1122
|
# ScopedMongoWrapper and AppDB)
|
|
1166
1123
|
collection = getattr(db, collection_name)
|
|
1167
|
-
app_demo = await collection.find_one(
|
|
1168
|
-
{"email": DEMO_EMAIL_DEFAULT}
|
|
1169
|
-
)
|
|
1124
|
+
app_demo = await collection.find_one({"email": DEMO_EMAIL_DEFAULT})
|
|
1170
1125
|
if app_demo:
|
|
1171
1126
|
app_demo["app_user_id"] = str(app_demo["_id"])
|
|
1172
1127
|
return app_demo
|
|
@@ -1232,9 +1187,7 @@ async def get_or_create_demo_user(
|
|
|
1232
1187
|
f"db_name={'provided' if db_name else 'not provided'})"
|
|
1233
1188
|
)
|
|
1234
1189
|
|
|
1235
|
-
demo_users = await ensure_demo_users_exist(
|
|
1236
|
-
db, slug_id, config, mongo_uri, db_name
|
|
1237
|
-
)
|
|
1190
|
+
demo_users = await ensure_demo_users_exist(db, slug_id, config, mongo_uri, db_name)
|
|
1238
1191
|
|
|
1239
1192
|
if demo_users and len(demo_users) > 0:
|
|
1240
1193
|
# Return the first demo user (usually the primary one)
|
|
@@ -1366,12 +1319,10 @@ async def ensure_demo_users_for_actor(
|
|
|
1366
1319
|
return []
|
|
1367
1320
|
|
|
1368
1321
|
try:
|
|
1369
|
-
with open(manifest_path
|
|
1322
|
+
with open(manifest_path) as f:
|
|
1370
1323
|
config = json.load(f)
|
|
1371
1324
|
except json.JSONDecodeError as e:
|
|
1372
|
-
logger.error(
|
|
1373
|
-
f"Invalid JSON in manifest.json for {slug_id}: {e}", exc_info=True
|
|
1374
|
-
)
|
|
1325
|
+
logger.error(f"Invalid JSON in manifest.json for {slug_id}: {e}", exc_info=True)
|
|
1375
1326
|
return []
|
|
1376
1327
|
|
|
1377
1328
|
# Ensure demo users exist
|
|
@@ -1395,9 +1346,7 @@ async def ensure_demo_users_for_actor(
|
|
|
1395
1346
|
KeyError,
|
|
1396
1347
|
PermissionError,
|
|
1397
1348
|
) as e:
|
|
1398
|
-
logger.error(
|
|
1399
|
-
f"Error ensuring demo users for actor {slug_id}: {e}", exc_info=True
|
|
1400
|
-
)
|
|
1349
|
+
logger.error(f"Error ensuring demo users for actor {slug_id}: {e}", exc_info=True)
|
|
1401
1350
|
return []
|
|
1402
1351
|
|
|
1403
1352
|
|
|
@@ -1425,9 +1374,7 @@ async def sync_app_user_to_casbin(
|
|
|
1425
1374
|
try:
|
|
1426
1375
|
# Check if provider is CasbinAdapter
|
|
1427
1376
|
if not hasattr(authz_provider, "_enforcer"):
|
|
1428
|
-
logger.debug(
|
|
1429
|
-
"sync_app_user_to_casbin: Provider is not CasbinAdapter, skipping"
|
|
1430
|
-
)
|
|
1377
|
+
logger.debug("sync_app_user_to_casbin: Provider is not CasbinAdapter, skipping")
|
|
1431
1378
|
return False
|
|
1432
1379
|
|
|
1433
1380
|
enforcer = authz_provider._enforcer
|
|
@@ -1448,9 +1395,7 @@ async def sync_app_user_to_casbin(
|
|
|
1448
1395
|
# Check if role assignment already exists
|
|
1449
1396
|
existing_roles = await enforcer.get_roles_for_user(subject)
|
|
1450
1397
|
if role in existing_roles:
|
|
1451
|
-
logger.debug(
|
|
1452
|
-
f"sync_app_user_to_casbin: User {subject} already has role {role}"
|
|
1453
|
-
)
|
|
1398
|
+
logger.debug(f"sync_app_user_to_casbin: User {subject} already has role {role}")
|
|
1454
1399
|
return True
|
|
1455
1400
|
|
|
1456
1401
|
# Add grouping policy: user -> role
|
|
@@ -1479,15 +1424,11 @@ async def sync_app_user_to_casbin(
|
|
|
1479
1424
|
ConnectionError,
|
|
1480
1425
|
KeyError,
|
|
1481
1426
|
) as e:
|
|
1482
|
-
logger.error(
|
|
1483
|
-
f"sync_app_user_to_casbin: Error syncing user to Casbin: {e}", exc_info=True
|
|
1484
|
-
)
|
|
1427
|
+
logger.error(f"sync_app_user_to_casbin: Error syncing user to Casbin: {e}", exc_info=True)
|
|
1485
1428
|
return False
|
|
1486
1429
|
|
|
1487
1430
|
|
|
1488
|
-
def get_app_user_role(
|
|
1489
|
-
user: Dict[str, Any], config: Optional[Dict[str, Any]] = None
|
|
1490
|
-
) -> str:
|
|
1431
|
+
def get_app_user_role(user: Dict[str, Any], config: Optional[Dict[str, Any]] = None) -> str:
|
|
1491
1432
|
"""
|
|
1492
1433
|
Determine Casbin role for app-level user.
|
|
1493
1434
|
|