mdb-engine 0.1.6__py3-none-any.whl → 0.4.12__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 +116 -11
- mdb_engine/auth/ARCHITECTURE.md +112 -0
- mdb_engine/auth/README.md +654 -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 +265 -70
- mdb_engine/auth/config_defaults.py +5 -5
- mdb_engine/auth/config_helpers.py +19 -18
- mdb_engine/auth/cookie_utils.py +12 -16
- mdb_engine/auth/csrf.py +483 -0
- mdb_engine/auth/decorators.py +10 -16
- mdb_engine/auth/dependencies.py +69 -71
- mdb_engine/auth/helpers.py +3 -3
- mdb_engine/auth/integration.py +61 -88
- mdb_engine/auth/jwt.py +11 -15
- mdb_engine/auth/middleware.py +79 -35
- mdb_engine/auth/oso_factory.py +21 -41
- mdb_engine/auth/provider.py +270 -171
- mdb_engine/auth/rate_limiter.py +505 -0
- mdb_engine/auth/restrictions.py +21 -36
- mdb_engine/auth/session_manager.py +24 -41
- mdb_engine/auth/shared_middleware.py +977 -0
- mdb_engine/auth/shared_users.py +775 -0
- mdb_engine/auth/token_lifecycle.py +10 -12
- mdb_engine/auth/token_store.py +17 -32
- mdb_engine/auth/users.py +99 -159
- mdb_engine/auth/utils.py +236 -42
- mdb_engine/cli/commands/generate.py +546 -10
- mdb_engine/cli/commands/validate.py +3 -7
- mdb_engine/cli/utils.py +7 -7
- mdb_engine/config.py +13 -28
- 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 +31 -50
- mdb_engine/core/app_secrets.py +289 -0
- mdb_engine/core/connection.py +20 -12
- mdb_engine/core/encryption.py +222 -0
- mdb_engine/core/engine.py +2862 -115
- mdb_engine/core/index_management.py +12 -16
- mdb_engine/core/manifest.py +628 -204
- mdb_engine/core/ray_integration.py +436 -0
- mdb_engine/core/seeding.py +13 -21
- mdb_engine/core/service_initialization.py +20 -30
- mdb_engine/core/types.py +40 -43
- mdb_engine/database/README.md +140 -17
- mdb_engine/database/__init__.py +17 -6
- mdb_engine/database/abstraction.py +37 -50
- mdb_engine/database/connection.py +51 -30
- mdb_engine/database/query_validator.py +367 -0
- mdb_engine/database/resource_limiter.py +204 -0
- mdb_engine/database/scoped_wrapper.py +747 -237
- mdb_engine/dependencies.py +427 -0
- mdb_engine/di/__init__.py +34 -0
- mdb_engine/di/container.py +247 -0
- mdb_engine/di/providers.py +206 -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 +38 -155
- mdb_engine/embeddings/service.py +78 -75
- mdb_engine/exceptions.py +104 -12
- mdb_engine/indexes/README.md +30 -13
- mdb_engine/indexes/__init__.py +1 -0
- mdb_engine/indexes/helpers.py +11 -11
- mdb_engine/indexes/manager.py +59 -123
- mdb_engine/memory/README.md +95 -4
- mdb_engine/memory/__init__.py +1 -2
- mdb_engine/memory/service.py +363 -1168
- mdb_engine/observability/README.md +4 -2
- mdb_engine/observability/__init__.py +26 -9
- mdb_engine/observability/health.py +17 -17
- mdb_engine/observability/logging.py +10 -10
- mdb_engine/observability/metrics.py +40 -19
- 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 +41 -75
- mdb_engine/utils/__init__.py +3 -1
- mdb_engine/utils/mongo.py +117 -0
- mdb_engine-0.4.12.dist-info/METADATA +492 -0
- mdb_engine-0.4.12.dist-info/RECORD +97 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/WHEEL +1 -1
- 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.4.12.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/top_level.txt +0 -0
mdb_engine/auth/users.py
CHANGED
|
@@ -13,8 +13,9 @@ This module is part of MDB_ENGINE - MongoDB Engine.
|
|
|
13
13
|
import logging
|
|
14
14
|
import os
|
|
15
15
|
import uuid
|
|
16
|
+
from collections.abc import Awaitable, Callable
|
|
16
17
|
from datetime import datetime, timedelta
|
|
17
|
-
from typing import Any
|
|
18
|
+
from typing import Any
|
|
18
19
|
|
|
19
20
|
import bcrypt
|
|
20
21
|
import jwt
|
|
@@ -22,9 +23,15 @@ from fastapi import Request
|
|
|
22
23
|
from fastapi.responses import Response
|
|
23
24
|
|
|
24
25
|
try:
|
|
25
|
-
from
|
|
26
|
-
|
|
26
|
+
from bson.errors import InvalidId as BsonInvalidId
|
|
27
|
+
from pymongo.errors import (
|
|
28
|
+
ConnectionFailure,
|
|
29
|
+
OperationFailure,
|
|
30
|
+
PyMongoError,
|
|
31
|
+
ServerSelectionTimeoutError,
|
|
32
|
+
)
|
|
27
33
|
except ImportError:
|
|
34
|
+
BsonInvalidId = ValueError # Fallback if bson not available
|
|
28
35
|
ConnectionFailure = Exception
|
|
29
36
|
OperationFailure = Exception
|
|
30
37
|
ServerSelectionTimeoutError = Exception
|
|
@@ -43,9 +50,9 @@ def _is_auth_route(request_path: str) -> bool:
|
|
|
43
50
|
async def _get_app_user_config(
|
|
44
51
|
request: Request,
|
|
45
52
|
slug_id: str,
|
|
46
|
-
config:
|
|
47
|
-
get_app_config_func:
|
|
48
|
-
) ->
|
|
53
|
+
config: dict[str, Any] | None,
|
|
54
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None,
|
|
55
|
+
) -> dict[str, Any] | None:
|
|
49
56
|
"""Fetch and validate app user config."""
|
|
50
57
|
if config is None:
|
|
51
58
|
if not get_app_config_func:
|
|
@@ -66,7 +73,7 @@ async def _get_app_user_config(
|
|
|
66
73
|
return config
|
|
67
74
|
|
|
68
75
|
|
|
69
|
-
def _convert_user_id_to_objectid(user_id: Any) ->
|
|
76
|
+
def _convert_user_id_to_objectid(user_id: Any) -> tuple[Any, str | None]:
|
|
70
77
|
"""
|
|
71
78
|
Convert user_id to ObjectId if valid, otherwise keep as string.
|
|
72
79
|
|
|
@@ -90,18 +97,15 @@ def _convert_user_id_to_objectid(user_id: Any) -> Tuple[Any, Optional[str]]:
|
|
|
90
97
|
# If it's not a string or ObjectId, try to convert (for backward compatibility)
|
|
91
98
|
try:
|
|
92
99
|
return ObjectId(user_id), None
|
|
93
|
-
except (TypeError, ValueError) as e:
|
|
100
|
+
except (TypeError, ValueError, BsonInvalidId) as e:
|
|
94
101
|
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
102
|
|
|
99
103
|
return user_id, None
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
async def _validate_and_decode_session_token(
|
|
103
107
|
session_token: str, slug_id: str
|
|
104
|
-
) ->
|
|
108
|
+
) -> tuple[dict[str, Any] | None, Exception | None]:
|
|
105
109
|
"""Validate and decode session token."""
|
|
106
110
|
try:
|
|
107
111
|
from .jwt import decode_jwt_token
|
|
@@ -132,9 +136,7 @@ async def _validate_and_decode_session_token(
|
|
|
132
136
|
return None, e
|
|
133
137
|
|
|
134
138
|
|
|
135
|
-
async def _fetch_app_user_from_db(
|
|
136
|
-
db, collection_name: str, user_id: Any
|
|
137
|
-
) -> Optional[Dict[str, Any]]:
|
|
139
|
+
async def _fetch_app_user_from_db(db, collection_name: str, user_id: Any) -> dict[str, Any] | None:
|
|
138
140
|
"""Fetch user from database."""
|
|
139
141
|
# Use getattr for attribute access (works with both AppDB and ScopedMongoWrapper)
|
|
140
142
|
collection = getattr(db, collection_name)
|
|
@@ -152,12 +154,10 @@ async def get_app_user(
|
|
|
152
154
|
request: Request,
|
|
153
155
|
slug_id: str,
|
|
154
156
|
db,
|
|
155
|
-
config:
|
|
157
|
+
config: dict[str, Any] | None = None,
|
|
156
158
|
allow_demo_fallback: bool = False,
|
|
157
|
-
get_app_config_func:
|
|
158
|
-
|
|
159
|
-
] = None,
|
|
160
|
-
) -> Optional[Dict[str, Any]]:
|
|
159
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
160
|
+
) -> dict[str, Any] | None:
|
|
161
161
|
"""
|
|
162
162
|
Get app-level user from session cookie.
|
|
163
163
|
|
|
@@ -233,8 +233,8 @@ async def get_app_user(
|
|
|
233
233
|
|
|
234
234
|
|
|
235
235
|
async def _try_demo_mode(
|
|
236
|
-
request: Request, slug_id: str, db, config:
|
|
237
|
-
) ->
|
|
236
|
+
request: Request, slug_id: str, db, config: dict[str, Any]
|
|
237
|
+
) -> dict[str, Any] | None:
|
|
238
238
|
"""
|
|
239
239
|
Internal helper: Try to authenticate as demo user if demo mode is enabled.
|
|
240
240
|
|
|
@@ -279,9 +279,7 @@ async def _try_demo_mode(
|
|
|
279
279
|
f"(MONGO_URI={MONGO_URI}, DB_NAME={DB_NAME})"
|
|
280
280
|
)
|
|
281
281
|
|
|
282
|
-
demo_user = await get_or_create_demo_user(
|
|
283
|
-
db, slug_id, config, MONGO_URI, DB_NAME
|
|
284
|
-
)
|
|
282
|
+
demo_user = await get_or_create_demo_user(db, slug_id, config, MONGO_URI, DB_NAME)
|
|
285
283
|
|
|
286
284
|
if not demo_user:
|
|
287
285
|
logger.warning(
|
|
@@ -319,11 +317,9 @@ async def create_app_session(
|
|
|
319
317
|
request: Request,
|
|
320
318
|
slug_id: str,
|
|
321
319
|
user_id: str,
|
|
322
|
-
config:
|
|
323
|
-
response:
|
|
324
|
-
get_app_config_func:
|
|
325
|
-
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
326
|
-
] = None,
|
|
320
|
+
config: dict[str, Any] | None = None,
|
|
321
|
+
response: Response | None = None,
|
|
322
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
327
323
|
) -> str:
|
|
328
324
|
"""
|
|
329
325
|
Create a app-specific session token and set cookie.
|
|
@@ -387,9 +383,7 @@ async def create_app_session(
|
|
|
387
383
|
cookie_name = f"{session_cookie_name}_{slug_id}"
|
|
388
384
|
|
|
389
385
|
# Determine secure cookie setting
|
|
390
|
-
should_use_secure = (
|
|
391
|
-
request.url.scheme == "https" or os.getenv("G_NOME_ENV") == "production"
|
|
392
|
-
)
|
|
386
|
+
should_use_secure = request.url.scheme == "https" or os.getenv("G_NOME_ENV") == "production"
|
|
393
387
|
|
|
394
388
|
response.set_cookie(
|
|
395
389
|
key=cookie_name,
|
|
@@ -407,9 +401,9 @@ async def authenticate_app_user(
|
|
|
407
401
|
db,
|
|
408
402
|
email: str,
|
|
409
403
|
password: str,
|
|
410
|
-
store_id:
|
|
404
|
+
store_id: str | None = None,
|
|
411
405
|
collection_name: str = "users",
|
|
412
|
-
) ->
|
|
406
|
+
) -> dict[str, Any] | None:
|
|
413
407
|
"""
|
|
414
408
|
Authenticate a user against app-specific users collection.
|
|
415
409
|
|
|
@@ -436,12 +430,9 @@ async def authenticate_app_user(
|
|
|
436
430
|
from bson.objectid import ObjectId
|
|
437
431
|
|
|
438
432
|
query["store_id"] = ObjectId(store_id)
|
|
439
|
-
except (TypeError, ValueError) as e:
|
|
433
|
+
except (TypeError, ValueError, BsonInvalidId) as e:
|
|
440
434
|
logger.warning(f"Invalid store_id format: {store_id}: {e}")
|
|
441
435
|
return None
|
|
442
|
-
except Exception:
|
|
443
|
-
logger.exception("Unexpected error converting store_id to ObjectId")
|
|
444
|
-
raise
|
|
445
436
|
|
|
446
437
|
# Find user
|
|
447
438
|
# Use getattr to access collection (works with ScopedMongoWrapper and AppDB)
|
|
@@ -479,10 +470,12 @@ async def authenticate_app_user(
|
|
|
479
470
|
except (ValueError, TypeError):
|
|
480
471
|
logger.exception("Validation error authenticating app user")
|
|
481
472
|
return None
|
|
482
|
-
except
|
|
483
|
-
logger.exception("
|
|
484
|
-
|
|
485
|
-
|
|
473
|
+
except PyMongoError:
|
|
474
|
+
logger.exception("Database error authenticating app user")
|
|
475
|
+
return None
|
|
476
|
+
except (AttributeError, KeyError):
|
|
477
|
+
logger.exception("Attribute access error authenticating app user")
|
|
478
|
+
return None
|
|
486
479
|
|
|
487
480
|
|
|
488
481
|
async def create_app_user(
|
|
@@ -490,9 +483,9 @@ async def create_app_user(
|
|
|
490
483
|
email: str,
|
|
491
484
|
password: str,
|
|
492
485
|
role: str = "user",
|
|
493
|
-
store_id:
|
|
486
|
+
store_id: str | None = None,
|
|
494
487
|
collection_name: str = "users",
|
|
495
|
-
) ->
|
|
488
|
+
) -> dict[str, Any] | None:
|
|
496
489
|
"""
|
|
497
490
|
Create a new user in app-specific users collection.
|
|
498
491
|
|
|
@@ -513,12 +506,7 @@ async def create_app_user(
|
|
|
513
506
|
from bson.objectid import ObjectId
|
|
514
507
|
|
|
515
508
|
# 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
|
-
):
|
|
509
|
+
if not email or not isinstance(email, str) or "@" not in email or "." not in email:
|
|
522
510
|
logger.warning(f"Invalid email format: {email}")
|
|
523
511
|
return None
|
|
524
512
|
|
|
@@ -545,13 +533,9 @@ async def create_app_user(
|
|
|
545
533
|
# Always hash password (plain text support removed for security)
|
|
546
534
|
try:
|
|
547
535
|
password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
|
548
|
-
except ValueError:
|
|
536
|
+
except (ValueError, TypeError, UnicodeEncodeError):
|
|
549
537
|
logger.exception("Error encoding password for hashing")
|
|
550
538
|
return None
|
|
551
|
-
except Exception:
|
|
552
|
-
logger.exception("Unexpected error hashing password")
|
|
553
|
-
# Re-raise unexpected errors
|
|
554
|
-
raise
|
|
555
539
|
|
|
556
540
|
# Create user document
|
|
557
541
|
user_doc = {
|
|
@@ -573,24 +557,24 @@ async def create_app_user(
|
|
|
573
557
|
|
|
574
558
|
return user_doc
|
|
575
559
|
|
|
576
|
-
except (ValueError, TypeError):
|
|
560
|
+
except (ValueError, TypeError, BsonInvalidId):
|
|
577
561
|
logger.exception("Validation error creating app user")
|
|
578
562
|
return None
|
|
579
|
-
except
|
|
580
|
-
logger.exception("
|
|
581
|
-
|
|
582
|
-
|
|
563
|
+
except PyMongoError:
|
|
564
|
+
logger.exception("Database error creating app user")
|
|
565
|
+
return None
|
|
566
|
+
except (AttributeError, KeyError):
|
|
567
|
+
logger.exception("Attribute access error creating app user")
|
|
568
|
+
return None
|
|
583
569
|
|
|
584
570
|
|
|
585
571
|
async def get_or_create_anonymous_user(
|
|
586
572
|
request: Request,
|
|
587
573
|
slug_id: str,
|
|
588
574
|
db,
|
|
589
|
-
config:
|
|
590
|
-
get_app_config_func:
|
|
591
|
-
|
|
592
|
-
] = None,
|
|
593
|
-
) -> Optional[Dict[str, Any]]:
|
|
575
|
+
config: dict[str, Any] | None = None,
|
|
576
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
577
|
+
) -> dict[str, Any] | None:
|
|
594
578
|
"""
|
|
595
579
|
Get or create an anonymous user for anonymous_session strategy.
|
|
596
580
|
|
|
@@ -655,9 +639,7 @@ async def get_or_create_anonymous_user(
|
|
|
655
639
|
return user
|
|
656
640
|
|
|
657
641
|
|
|
658
|
-
async def get_platform_demo_user(
|
|
659
|
-
mongo_uri: str, db_name: str
|
|
660
|
-
) -> Optional[Dict[str, Any]]:
|
|
642
|
+
async def get_platform_demo_user(mongo_uri: str, db_name: str) -> dict[str, Any] | None:
|
|
661
643
|
"""
|
|
662
644
|
Get platform demo user information from top-level database.
|
|
663
645
|
|
|
@@ -669,8 +651,7 @@ async def get_platform_demo_user(
|
|
|
669
651
|
Dict with demo user info (email, password from config, user_id) or None if not available
|
|
670
652
|
"""
|
|
671
653
|
try:
|
|
672
|
-
from ..config import
|
|
673
|
-
DEMO_PASSWORD_DEFAULT)
|
|
654
|
+
from ..config import DEMO_EMAIL_DEFAULT, DEMO_ENABLED, DEMO_PASSWORD_DEFAULT
|
|
674
655
|
|
|
675
656
|
if not DEMO_ENABLED or not DEMO_EMAIL_DEFAULT:
|
|
676
657
|
return None
|
|
@@ -711,19 +692,15 @@ async def get_platform_demo_user(
|
|
|
711
692
|
|
|
712
693
|
async def _link_platform_demo_user(
|
|
713
694
|
db, slug_id: str, collection_name: str, mongo_uri: str, db_name: str
|
|
714
|
-
) ->
|
|
695
|
+
) -> dict[str, Any] | None:
|
|
715
696
|
"""Link platform demo user to app demo user."""
|
|
716
697
|
import datetime
|
|
717
698
|
|
|
718
699
|
try:
|
|
719
|
-
logger.debug(
|
|
720
|
-
f"ensure_demo_users_exist: Auto-linking platform demo user for '{slug_id}'"
|
|
721
|
-
)
|
|
700
|
+
logger.debug(f"ensure_demo_users_exist: Auto-linking platform demo user for '{slug_id}'")
|
|
722
701
|
platform_demo = await get_platform_demo_user(mongo_uri, db_name)
|
|
723
702
|
if not platform_demo:
|
|
724
|
-
logger.warning(
|
|
725
|
-
f"ensure_demo_users_exist: Platform demo user not found for '{slug_id}'"
|
|
726
|
-
)
|
|
703
|
+
logger.warning(f"ensure_demo_users_exist: Platform demo user not found for '{slug_id}'")
|
|
727
704
|
return None
|
|
728
705
|
|
|
729
706
|
# Check if app demo user already exists for platform demo
|
|
@@ -737,13 +714,10 @@ async def _link_platform_demo_user(
|
|
|
737
714
|
platform_password = platform_demo.get("password", "demo123")
|
|
738
715
|
password_hash = None
|
|
739
716
|
try:
|
|
740
|
-
password_hash = bcrypt.hashpw(
|
|
741
|
-
platform_password.encode("utf-8"), bcrypt.gensalt()
|
|
742
|
-
)
|
|
717
|
+
password_hash = bcrypt.hashpw(platform_password.encode("utf-8"), bcrypt.gensalt())
|
|
743
718
|
except (ValueError, TypeError, AttributeError) as e:
|
|
744
719
|
logger.error(
|
|
745
|
-
f"Error hashing password for platform demo user "
|
|
746
|
-
f"{platform_demo['email']}: {e}",
|
|
720
|
+
f"Error hashing password for platform demo user " f"{platform_demo['email']}: {e}",
|
|
747
721
|
exc_info=True,
|
|
748
722
|
)
|
|
749
723
|
return None
|
|
@@ -786,16 +760,14 @@ async def _link_platform_demo_user(
|
|
|
786
760
|
|
|
787
761
|
def _validate_demo_user_config(
|
|
788
762
|
demo_user_config: Any, slug_id: str
|
|
789
|
-
) ->
|
|
763
|
+
) -> tuple[dict[str, Any] | None, str | None]:
|
|
790
764
|
"""Validate demo user configuration."""
|
|
791
765
|
if not isinstance(demo_user_config, dict):
|
|
792
766
|
return None, f"Invalid demo_user_config entry (not a dict): {demo_user_config}"
|
|
793
767
|
|
|
794
768
|
extra_data = demo_user_config.get("extra_data", {})
|
|
795
769
|
if not isinstance(extra_data, dict):
|
|
796
|
-
logger.warning(
|
|
797
|
-
f"Invalid extra_data for demo user config (not a dict): {extra_data}"
|
|
798
|
-
)
|
|
770
|
+
logger.warning(f"Invalid extra_data for demo user config (not a dict): {extra_data}")
|
|
799
771
|
extra_data = {}
|
|
800
772
|
|
|
801
773
|
return {
|
|
@@ -809,12 +781,12 @@ def _validate_demo_user_config(
|
|
|
809
781
|
|
|
810
782
|
|
|
811
783
|
async def _resolve_demo_user_email_password(
|
|
812
|
-
email:
|
|
813
|
-
password:
|
|
814
|
-
mongo_uri:
|
|
815
|
-
db_name:
|
|
784
|
+
email: str | None,
|
|
785
|
+
password: str | None,
|
|
786
|
+
mongo_uri: str | None,
|
|
787
|
+
db_name: str | None,
|
|
816
788
|
slug_id: str,
|
|
817
|
-
) ->
|
|
789
|
+
) -> tuple[str | None, str | None]:
|
|
818
790
|
"""Resolve email and password from config or platform demo."""
|
|
819
791
|
# If email not specified, try platform demo
|
|
820
792
|
if not email:
|
|
@@ -825,14 +797,10 @@ async def _resolve_demo_user_email_password(
|
|
|
825
797
|
if not password:
|
|
826
798
|
password = platform_demo["password"]
|
|
827
799
|
else:
|
|
828
|
-
logger.warning(
|
|
829
|
-
f"No email specified and platform demo not available for {slug_id}"
|
|
830
|
-
)
|
|
800
|
+
logger.warning(f"No email specified and platform demo not available for {slug_id}")
|
|
831
801
|
return None, None
|
|
832
802
|
else:
|
|
833
|
-
logger.warning(
|
|
834
|
-
f"No email specified and cannot access platform demo for {slug_id}"
|
|
835
|
-
)
|
|
803
|
+
logger.warning(f"No email specified and cannot access platform demo for {slug_id}")
|
|
836
804
|
return None, None
|
|
837
805
|
|
|
838
806
|
# Validate email format
|
|
@@ -865,11 +833,11 @@ async def _create_demo_user_from_config(
|
|
|
865
833
|
email: str,
|
|
866
834
|
password: str,
|
|
867
835
|
role: str,
|
|
868
|
-
extra_data:
|
|
836
|
+
extra_data: dict[str, Any],
|
|
869
837
|
link_to_platform: bool,
|
|
870
|
-
mongo_uri:
|
|
871
|
-
db_name:
|
|
872
|
-
) ->
|
|
838
|
+
mongo_uri: str | None,
|
|
839
|
+
db_name: str | None,
|
|
840
|
+
) -> dict[str, Any] | None:
|
|
873
841
|
"""Create a demo user from configuration."""
|
|
874
842
|
import datetime
|
|
875
843
|
|
|
@@ -877,9 +845,7 @@ async def _create_demo_user_from_config(
|
|
|
877
845
|
try:
|
|
878
846
|
password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
|
879
847
|
except (ValueError, TypeError, AttributeError) as e:
|
|
880
|
-
logger.error(
|
|
881
|
-
f"Error hashing password for demo user {email}: {e}", exc_info=True
|
|
882
|
-
)
|
|
848
|
+
logger.error(f"Error hashing password for demo user {email}: {e}", exc_info=True)
|
|
883
849
|
return None
|
|
884
850
|
|
|
885
851
|
# Create user document
|
|
@@ -945,9 +911,7 @@ async def _create_demo_user_from_config(
|
|
|
945
911
|
|
|
946
912
|
user_doc["_id"] = result.inserted_id if not custom_id else custom_id
|
|
947
913
|
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
|
-
)
|
|
914
|
+
logger.info(f"Created demo user {email} for {slug_id} with _id={user_doc['_id']}")
|
|
951
915
|
return user_doc
|
|
952
916
|
except (
|
|
953
917
|
OperationFailure,
|
|
@@ -968,10 +932,10 @@ async def _create_demo_user_from_config(
|
|
|
968
932
|
async def ensure_demo_users_exist(
|
|
969
933
|
db,
|
|
970
934
|
slug_id: str,
|
|
971
|
-
config:
|
|
972
|
-
mongo_uri:
|
|
973
|
-
db_name:
|
|
974
|
-
) ->
|
|
935
|
+
config: dict[str, Any] | None = None,
|
|
936
|
+
mongo_uri: str | None = None,
|
|
937
|
+
db_name: str | None = None,
|
|
938
|
+
) -> list[dict[str, Any]]:
|
|
975
939
|
"""
|
|
976
940
|
Intelligently ensure demo users exist for a app based on manifest configuration.
|
|
977
941
|
|
|
@@ -1030,9 +994,7 @@ async def ensure_demo_users_exist(
|
|
|
1030
994
|
for demo_user_config in demo_users_config:
|
|
1031
995
|
try:
|
|
1032
996
|
# Validate config structure
|
|
1033
|
-
validated_config, error = _validate_demo_user_config(
|
|
1034
|
-
demo_user_config, slug_id
|
|
1035
|
-
)
|
|
997
|
+
validated_config, error = _validate_demo_user_config(demo_user_config, slug_id)
|
|
1036
998
|
if not validated_config:
|
|
1037
999
|
if error:
|
|
1038
1000
|
logger.warning(error)
|
|
@@ -1095,11 +1057,9 @@ async def get_or_create_demo_user_for_request(
|
|
|
1095
1057
|
request: Request,
|
|
1096
1058
|
slug_id: str,
|
|
1097
1059
|
db,
|
|
1098
|
-
config:
|
|
1099
|
-
get_app_config_func:
|
|
1100
|
-
|
|
1101
|
-
] = None,
|
|
1102
|
-
) -> Optional[Dict[str, Any]]:
|
|
1060
|
+
config: dict[str, Any] | None = None,
|
|
1061
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
1062
|
+
) -> dict[str, Any] | None:
|
|
1103
1063
|
"""
|
|
1104
1064
|
Get or create a demo user for the current request context.
|
|
1105
1065
|
|
|
@@ -1146,9 +1106,7 @@ async def get_or_create_demo_user_for_request(
|
|
|
1146
1106
|
# Check if app demo user exists
|
|
1147
1107
|
# Use getattr to access collection (works with ScopedMongoWrapper and AppDB)
|
|
1148
1108
|
collection = getattr(db, collection_name)
|
|
1149
|
-
app_demo = await collection.find_one(
|
|
1150
|
-
{"email": DEMO_EMAIL_DEFAULT}
|
|
1151
|
-
)
|
|
1109
|
+
app_demo = await collection.find_one({"email": DEMO_EMAIL_DEFAULT})
|
|
1152
1110
|
|
|
1153
1111
|
if app_demo:
|
|
1154
1112
|
app_demo["app_user_id"] = str(app_demo["_id"])
|
|
@@ -1158,15 +1116,11 @@ async def get_or_create_demo_user_for_request(
|
|
|
1158
1116
|
try:
|
|
1159
1117
|
from ..config import DB_NAME, MONGO_URI
|
|
1160
1118
|
|
|
1161
|
-
await ensure_demo_users_exist(
|
|
1162
|
-
db, slug_id, config, MONGO_URI, DB_NAME
|
|
1163
|
-
)
|
|
1119
|
+
await ensure_demo_users_exist(db, slug_id, config, MONGO_URI, DB_NAME)
|
|
1164
1120
|
# Use getattr to access collection (works with
|
|
1165
1121
|
# ScopedMongoWrapper and AppDB)
|
|
1166
1122
|
collection = getattr(db, collection_name)
|
|
1167
|
-
app_demo = await collection.find_one(
|
|
1168
|
-
{"email": DEMO_EMAIL_DEFAULT}
|
|
1169
|
-
)
|
|
1123
|
+
app_demo = await collection.find_one({"email": DEMO_EMAIL_DEFAULT})
|
|
1170
1124
|
if app_demo:
|
|
1171
1125
|
app_demo["app_user_id"] = str(app_demo["_id"])
|
|
1172
1126
|
return app_demo
|
|
@@ -1195,10 +1149,10 @@ async def get_or_create_demo_user_for_request(
|
|
|
1195
1149
|
async def get_or_create_demo_user(
|
|
1196
1150
|
db,
|
|
1197
1151
|
slug_id: str,
|
|
1198
|
-
config:
|
|
1199
|
-
mongo_uri:
|
|
1200
|
-
db_name:
|
|
1201
|
-
) ->
|
|
1152
|
+
config: dict[str, Any],
|
|
1153
|
+
mongo_uri: str | None = None,
|
|
1154
|
+
db_name: str | None = None,
|
|
1155
|
+
) -> dict[str, Any] | None:
|
|
1202
1156
|
"""
|
|
1203
1157
|
Get or create a demo user for an app.
|
|
1204
1158
|
|
|
@@ -1232,9 +1186,7 @@ async def get_or_create_demo_user(
|
|
|
1232
1186
|
f"db_name={'provided' if db_name else 'not provided'})"
|
|
1233
1187
|
)
|
|
1234
1188
|
|
|
1235
|
-
demo_users = await ensure_demo_users_exist(
|
|
1236
|
-
db, slug_id, config, mongo_uri, db_name
|
|
1237
|
-
)
|
|
1189
|
+
demo_users = await ensure_demo_users_exist(db, slug_id, config, mongo_uri, db_name)
|
|
1238
1190
|
|
|
1239
1191
|
if demo_users and len(demo_users) > 0:
|
|
1240
1192
|
# Return the first demo user (usually the primary one)
|
|
@@ -1299,7 +1251,7 @@ async def get_or_create_demo_user(
|
|
|
1299
1251
|
|
|
1300
1252
|
async def ensure_demo_users_for_actor(
|
|
1301
1253
|
db, slug_id: str, mongo_uri: str, db_name: str
|
|
1302
|
-
) ->
|
|
1254
|
+
) -> list[dict[str, Any]]:
|
|
1303
1255
|
"""
|
|
1304
1256
|
Convenience function for actors to ensure demo users exist.
|
|
1305
1257
|
|
|
@@ -1366,12 +1318,10 @@ async def ensure_demo_users_for_actor(
|
|
|
1366
1318
|
return []
|
|
1367
1319
|
|
|
1368
1320
|
try:
|
|
1369
|
-
with open(manifest_path
|
|
1321
|
+
with open(manifest_path) as f:
|
|
1370
1322
|
config = json.load(f)
|
|
1371
1323
|
except json.JSONDecodeError as e:
|
|
1372
|
-
logger.error(
|
|
1373
|
-
f"Invalid JSON in manifest.json for {slug_id}: {e}", exc_info=True
|
|
1374
|
-
)
|
|
1324
|
+
logger.error(f"Invalid JSON in manifest.json for {slug_id}: {e}", exc_info=True)
|
|
1375
1325
|
return []
|
|
1376
1326
|
|
|
1377
1327
|
# Ensure demo users exist
|
|
@@ -1395,17 +1345,15 @@ async def ensure_demo_users_for_actor(
|
|
|
1395
1345
|
KeyError,
|
|
1396
1346
|
PermissionError,
|
|
1397
1347
|
) as e:
|
|
1398
|
-
logger.error(
|
|
1399
|
-
f"Error ensuring demo users for actor {slug_id}: {e}", exc_info=True
|
|
1400
|
-
)
|
|
1348
|
+
logger.error(f"Error ensuring demo users for actor {slug_id}: {e}", exc_info=True)
|
|
1401
1349
|
return []
|
|
1402
1350
|
|
|
1403
1351
|
|
|
1404
1352
|
async def sync_app_user_to_casbin(
|
|
1405
|
-
user:
|
|
1353
|
+
user: dict[str, Any],
|
|
1406
1354
|
authz_provider,
|
|
1407
|
-
role:
|
|
1408
|
-
app_slug:
|
|
1355
|
+
role: str | None = None,
|
|
1356
|
+
app_slug: str | None = None,
|
|
1409
1357
|
) -> bool:
|
|
1410
1358
|
"""
|
|
1411
1359
|
Sync app-level user to Casbin by assigning a role.
|
|
@@ -1425,9 +1373,7 @@ async def sync_app_user_to_casbin(
|
|
|
1425
1373
|
try:
|
|
1426
1374
|
# Check if provider is CasbinAdapter
|
|
1427
1375
|
if not hasattr(authz_provider, "_enforcer"):
|
|
1428
|
-
logger.debug(
|
|
1429
|
-
"sync_app_user_to_casbin: Provider is not CasbinAdapter, skipping"
|
|
1430
|
-
)
|
|
1376
|
+
logger.debug("sync_app_user_to_casbin: Provider is not CasbinAdapter, skipping")
|
|
1431
1377
|
return False
|
|
1432
1378
|
|
|
1433
1379
|
enforcer = authz_provider._enforcer
|
|
@@ -1448,9 +1394,7 @@ async def sync_app_user_to_casbin(
|
|
|
1448
1394
|
# Check if role assignment already exists
|
|
1449
1395
|
existing_roles = await enforcer.get_roles_for_user(subject)
|
|
1450
1396
|
if role in existing_roles:
|
|
1451
|
-
logger.debug(
|
|
1452
|
-
f"sync_app_user_to_casbin: User {subject} already has role {role}"
|
|
1453
|
-
)
|
|
1397
|
+
logger.debug(f"sync_app_user_to_casbin: User {subject} already has role {role}")
|
|
1454
1398
|
return True
|
|
1455
1399
|
|
|
1456
1400
|
# Add grouping policy: user -> role
|
|
@@ -1479,15 +1423,11 @@ async def sync_app_user_to_casbin(
|
|
|
1479
1423
|
ConnectionError,
|
|
1480
1424
|
KeyError,
|
|
1481
1425
|
) as e:
|
|
1482
|
-
logger.error(
|
|
1483
|
-
f"sync_app_user_to_casbin: Error syncing user to Casbin: {e}", exc_info=True
|
|
1484
|
-
)
|
|
1426
|
+
logger.error(f"sync_app_user_to_casbin: Error syncing user to Casbin: {e}", exc_info=True)
|
|
1485
1427
|
return False
|
|
1486
1428
|
|
|
1487
1429
|
|
|
1488
|
-
def get_app_user_role(
|
|
1489
|
-
user: Dict[str, Any], config: Optional[Dict[str, Any]] = None
|
|
1490
|
-
) -> str:
|
|
1430
|
+
def get_app_user_role(user: dict[str, Any], config: dict[str, Any] | None = None) -> str:
|
|
1491
1431
|
"""
|
|
1492
1432
|
Determine Casbin role for app-level user.
|
|
1493
1433
|
|