mdb-engine 0.1.6__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/README.md +144 -0
- mdb_engine/__init__.py +37 -0
- mdb_engine/auth/README.md +631 -0
- mdb_engine/auth/__init__.py +128 -0
- mdb_engine/auth/casbin_factory.py +199 -0
- mdb_engine/auth/casbin_models.py +46 -0
- mdb_engine/auth/config_defaults.py +71 -0
- mdb_engine/auth/config_helpers.py +213 -0
- mdb_engine/auth/cookie_utils.py +158 -0
- mdb_engine/auth/decorators.py +350 -0
- mdb_engine/auth/dependencies.py +747 -0
- mdb_engine/auth/helpers.py +64 -0
- mdb_engine/auth/integration.py +578 -0
- mdb_engine/auth/jwt.py +225 -0
- mdb_engine/auth/middleware.py +241 -0
- mdb_engine/auth/oso_factory.py +323 -0
- mdb_engine/auth/provider.py +570 -0
- mdb_engine/auth/restrictions.py +271 -0
- mdb_engine/auth/session_manager.py +477 -0
- mdb_engine/auth/token_lifecycle.py +213 -0
- mdb_engine/auth/token_store.py +289 -0
- mdb_engine/auth/users.py +1516 -0
- mdb_engine/auth/utils.py +614 -0
- mdb_engine/cli/__init__.py +13 -0
- mdb_engine/cli/commands/__init__.py +7 -0
- mdb_engine/cli/commands/generate.py +105 -0
- mdb_engine/cli/commands/migrate.py +83 -0
- mdb_engine/cli/commands/show.py +70 -0
- mdb_engine/cli/commands/validate.py +63 -0
- mdb_engine/cli/main.py +41 -0
- mdb_engine/cli/utils.py +92 -0
- mdb_engine/config.py +217 -0
- mdb_engine/constants.py +160 -0
- mdb_engine/core/README.md +542 -0
- mdb_engine/core/__init__.py +42 -0
- mdb_engine/core/app_registration.py +392 -0
- mdb_engine/core/connection.py +243 -0
- mdb_engine/core/engine.py +749 -0
- mdb_engine/core/index_management.py +162 -0
- mdb_engine/core/manifest.py +2793 -0
- mdb_engine/core/seeding.py +179 -0
- mdb_engine/core/service_initialization.py +355 -0
- mdb_engine/core/types.py +413 -0
- mdb_engine/database/README.md +522 -0
- mdb_engine/database/__init__.py +31 -0
- mdb_engine/database/abstraction.py +635 -0
- mdb_engine/database/connection.py +387 -0
- mdb_engine/database/scoped_wrapper.py +1721 -0
- mdb_engine/embeddings/README.md +184 -0
- mdb_engine/embeddings/__init__.py +62 -0
- mdb_engine/embeddings/dependencies.py +193 -0
- mdb_engine/embeddings/service.py +759 -0
- mdb_engine/exceptions.py +167 -0
- mdb_engine/indexes/README.md +651 -0
- mdb_engine/indexes/__init__.py +21 -0
- mdb_engine/indexes/helpers.py +145 -0
- mdb_engine/indexes/manager.py +895 -0
- mdb_engine/memory/README.md +451 -0
- mdb_engine/memory/__init__.py +30 -0
- mdb_engine/memory/service.py +1285 -0
- mdb_engine/observability/README.md +515 -0
- mdb_engine/observability/__init__.py +42 -0
- mdb_engine/observability/health.py +296 -0
- mdb_engine/observability/logging.py +161 -0
- mdb_engine/observability/metrics.py +297 -0
- mdb_engine/routing/README.md +462 -0
- mdb_engine/routing/__init__.py +73 -0
- mdb_engine/routing/websockets.py +813 -0
- mdb_engine/utils/__init__.py +7 -0
- mdb_engine-0.1.6.dist-info/METADATA +213 -0
- mdb_engine-0.1.6.dist-info/RECORD +75 -0
- mdb_engine-0.1.6.dist-info/WHEEL +5 -0
- mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
- mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
- mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication Helper Functions
|
|
3
|
+
|
|
4
|
+
Provides helper functions for initializing token management components.
|
|
5
|
+
|
|
6
|
+
This module is part of MDB_ENGINE - MongoDB Engine.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def initialize_token_management(app, db):
|
|
15
|
+
"""
|
|
16
|
+
Initialize token management components (blacklist and session manager) on app startup.
|
|
17
|
+
|
|
18
|
+
This function should be called in the app's lifespan startup event.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
app: FastAPI application instance
|
|
22
|
+
db: MongoDB database instance (Motor AsyncIOMotorDatabase)
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
from mdb_engine.auth.helpers import initialize_token_management
|
|
26
|
+
from mdb_engine.auth import TokenBlacklist, SessionManager
|
|
27
|
+
|
|
28
|
+
@app.on_event("startup")
|
|
29
|
+
async def startup():
|
|
30
|
+
# Get database from engine
|
|
31
|
+
db = engine.get_database()
|
|
32
|
+
|
|
33
|
+
# Initialize token management
|
|
34
|
+
await initialize_token_management(app, db)
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
from .session_manager import SessionManager
|
|
38
|
+
from .token_store import TokenBlacklist
|
|
39
|
+
|
|
40
|
+
# Initialize token blacklist
|
|
41
|
+
blacklist = TokenBlacklist(db)
|
|
42
|
+
await blacklist.ensure_indexes()
|
|
43
|
+
app.state.token_blacklist = blacklist
|
|
44
|
+
logger.info("Token blacklist initialized")
|
|
45
|
+
|
|
46
|
+
# Initialize session manager
|
|
47
|
+
session_mgr = SessionManager(db)
|
|
48
|
+
await session_mgr.ensure_indexes()
|
|
49
|
+
app.state.session_manager = session_mgr
|
|
50
|
+
logger.info("Session manager initialized")
|
|
51
|
+
|
|
52
|
+
except (
|
|
53
|
+
ImportError,
|
|
54
|
+
AttributeError,
|
|
55
|
+
TypeError,
|
|
56
|
+
ValueError,
|
|
57
|
+
RuntimeError,
|
|
58
|
+
KeyError,
|
|
59
|
+
) as e:
|
|
60
|
+
logger.error(f"Error initializing token management: {e}", exc_info=True)
|
|
61
|
+
# Don't raise - allow app to start without token management (backward compatibility)
|
|
62
|
+
logger.warning(
|
|
63
|
+
"Token management not available - continuing without enhanced token features"
|
|
64
|
+
)
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication Integration Helpers
|
|
3
|
+
|
|
4
|
+
Helpers for integrating authentication features from manifest configuration.
|
|
5
|
+
|
|
6
|
+
This module is part of MDB_ENGINE - MongoDB Engine.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from fastapi import FastAPI
|
|
14
|
+
|
|
15
|
+
from .config_defaults import (CORS_DEFAULTS, OBSERVABILITY_DEFAULTS,
|
|
16
|
+
SECURITY_CONFIG_DEFAULTS,
|
|
17
|
+
TOKEN_MANAGEMENT_DEFAULTS)
|
|
18
|
+
from .config_helpers import merge_config_with_defaults
|
|
19
|
+
from .helpers import initialize_token_management
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Cache for auth configs
|
|
24
|
+
_auth_config_cache: Dict[str, Dict[str, Any]] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _has_cors_middleware(app: FastAPI) -> bool:
|
|
28
|
+
"""
|
|
29
|
+
Check if CORS middleware is already added to the FastAPI app.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
app: FastAPI application instance
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
True if CORS middleware exists, False otherwise
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
39
|
+
|
|
40
|
+
# Check if CORS middleware is in the middleware stack
|
|
41
|
+
# FastAPI stores middleware in app.user_middleware list
|
|
42
|
+
for middleware in app.user_middleware:
|
|
43
|
+
# Middleware is stored as (middleware_class, options) tuple
|
|
44
|
+
if len(middleware) >= 1:
|
|
45
|
+
middleware_cls = middleware[0]
|
|
46
|
+
# Check if it's CORSMiddleware or a subclass
|
|
47
|
+
if middleware_cls == CORSMiddleware or (
|
|
48
|
+
hasattr(middleware_cls, "__name__")
|
|
49
|
+
and "CORS" in middleware_cls.__name__
|
|
50
|
+
):
|
|
51
|
+
return True
|
|
52
|
+
return False
|
|
53
|
+
except (AttributeError, TypeError, ValueError) as e:
|
|
54
|
+
logger.debug(f"Error checking for CORS middleware: {e}")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def invalidate_auth_config_cache(slug_id: Optional[str] = None) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Invalidate auth config cache for a specific app or all apps.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
slug_id: App slug identifier. If None, invalidates entire cache.
|
|
64
|
+
"""
|
|
65
|
+
if slug_id:
|
|
66
|
+
_auth_config_cache.pop(slug_id, None)
|
|
67
|
+
logger.debug(f"Invalidated auth config cache for {slug_id}")
|
|
68
|
+
else:
|
|
69
|
+
_auth_config_cache.clear()
|
|
70
|
+
logger.debug("Invalidated entire auth config cache")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def get_auth_config(slug_id: str, engine) -> Dict[str, Any]:
|
|
74
|
+
"""
|
|
75
|
+
Retrieve authentication configuration from manifest.
|
|
76
|
+
|
|
77
|
+
Caches results for performance.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
slug_id: App slug identifier
|
|
81
|
+
engine: MongoDBEngine instance
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary with token_management and auth (containing policy and users) configs
|
|
85
|
+
"""
|
|
86
|
+
# Check cache first
|
|
87
|
+
if slug_id in _auth_config_cache:
|
|
88
|
+
return _auth_config_cache[slug_id]
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Get manifest
|
|
92
|
+
manifest = await engine.get_manifest(slug_id)
|
|
93
|
+
if not manifest:
|
|
94
|
+
logger.warning(f"Manifest not found for {slug_id}")
|
|
95
|
+
return {}
|
|
96
|
+
|
|
97
|
+
# Extract auth configs - support both old and new format for backward compatibility
|
|
98
|
+
auth_config = manifest.get("auth", {})
|
|
99
|
+
|
|
100
|
+
# Migrate old format if present
|
|
101
|
+
if "auth_policy" in manifest or "sub_auth" in manifest:
|
|
102
|
+
if "policy" not in auth_config and "auth_policy" in manifest:
|
|
103
|
+
auth_config["policy"] = manifest.get("auth_policy", {})
|
|
104
|
+
if "users" not in auth_config and "sub_auth" in manifest:
|
|
105
|
+
auth_config["users"] = manifest.get("sub_auth", {})
|
|
106
|
+
|
|
107
|
+
config = {
|
|
108
|
+
"token_management": manifest.get("token_management", {}),
|
|
109
|
+
"auth": auth_config,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Cache it
|
|
113
|
+
_auth_config_cache[slug_id] = config
|
|
114
|
+
|
|
115
|
+
return config
|
|
116
|
+
except (AttributeError, TypeError, ValueError, KeyError, RuntimeError) as e:
|
|
117
|
+
logger.error(f"Error getting auth config for {slug_id}: {e}", exc_info=True)
|
|
118
|
+
return {}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def _setup_authorization_provider(
|
|
122
|
+
app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Set up authorization provider (Casbin/OSO/custom) from manifest."""
|
|
125
|
+
auth = config.get("auth", {})
|
|
126
|
+
auth_policy = auth.get("policy", {})
|
|
127
|
+
provider = auth_policy.get("provider", "casbin" if auth_policy else None)
|
|
128
|
+
|
|
129
|
+
if provider == "casbin" or (provider is None and auth_policy):
|
|
130
|
+
# Auto-create Casbin provider
|
|
131
|
+
try:
|
|
132
|
+
from .casbin_factory import initialize_casbin_from_manifest
|
|
133
|
+
|
|
134
|
+
authz_provider = await initialize_casbin_from_manifest(
|
|
135
|
+
engine, slug_id, config
|
|
136
|
+
)
|
|
137
|
+
if authz_provider:
|
|
138
|
+
app.state.authz_provider = authz_provider
|
|
139
|
+
logger.info(
|
|
140
|
+
f"Authorization provider (Casbin) auto-created for {slug_id}"
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
logger.debug(
|
|
144
|
+
f"Casbin provider not created for {slug_id} (may not be installed)"
|
|
145
|
+
)
|
|
146
|
+
except (
|
|
147
|
+
ImportError,
|
|
148
|
+
AttributeError,
|
|
149
|
+
TypeError,
|
|
150
|
+
ValueError,
|
|
151
|
+
RuntimeError,
|
|
152
|
+
KeyError,
|
|
153
|
+
) as e:
|
|
154
|
+
logger.warning(f"Could not auto-create Casbin provider for {slug_id}: {e}")
|
|
155
|
+
elif provider == "oso":
|
|
156
|
+
# Auto-create OSO Cloud provider
|
|
157
|
+
try:
|
|
158
|
+
from .oso_factory import initialize_oso_from_manifest
|
|
159
|
+
|
|
160
|
+
authz_provider = await initialize_oso_from_manifest(engine, slug_id, config)
|
|
161
|
+
if authz_provider:
|
|
162
|
+
app.state.authz_provider = authz_provider
|
|
163
|
+
logger.info(
|
|
164
|
+
f"✅ Authorization provider (OSO Cloud) auto-created for {slug_id}"
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
logger.error(
|
|
168
|
+
f"❌ OSO Cloud provider not created for {slug_id}. "
|
|
169
|
+
f"Check logs above for details. "
|
|
170
|
+
f"OSO_AUTH={'SET' if os.getenv('OSO_AUTH') else 'NOT SET'}, "
|
|
171
|
+
f"OSO_URL={os.getenv('OSO_URL', 'NOT SET')}"
|
|
172
|
+
)
|
|
173
|
+
except (
|
|
174
|
+
ImportError,
|
|
175
|
+
AttributeError,
|
|
176
|
+
TypeError,
|
|
177
|
+
ValueError,
|
|
178
|
+
RuntimeError,
|
|
179
|
+
ConnectionError,
|
|
180
|
+
KeyError,
|
|
181
|
+
) as e:
|
|
182
|
+
logger.error(
|
|
183
|
+
f"❌ Could not auto-create OSO Cloud provider for {slug_id}: {e}",
|
|
184
|
+
exc_info=True,
|
|
185
|
+
)
|
|
186
|
+
elif provider == "custom":
|
|
187
|
+
logger.info(f"Custom provider specified for {slug_id} - manual setup required")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def _setup_demo_users(
|
|
191
|
+
app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
|
|
192
|
+
) -> list:
|
|
193
|
+
"""Set up demo users and link with OSO roles if applicable."""
|
|
194
|
+
auth = config.get("auth", {})
|
|
195
|
+
users_config = auth.get("users", {})
|
|
196
|
+
demo_users = []
|
|
197
|
+
|
|
198
|
+
if not users_config.get("enabled", False):
|
|
199
|
+
return demo_users
|
|
200
|
+
|
|
201
|
+
seed_strategy = users_config.get("demo_user_seed_strategy", "auto")
|
|
202
|
+
demo_users_config = users_config.get("demo_users", [])
|
|
203
|
+
|
|
204
|
+
# Only auto-create if strategy is explicitly "auto" or default (when not specified)
|
|
205
|
+
if seed_strategy == "auto":
|
|
206
|
+
# Check if demo_users is explicitly configured or using defaults
|
|
207
|
+
has_explicit_demo_users = len(demo_users_config) > 0
|
|
208
|
+
auto_link_platform = users_config.get("auto_link_platform_demo", True)
|
|
209
|
+
|
|
210
|
+
if has_explicit_demo_users or auto_link_platform:
|
|
211
|
+
try:
|
|
212
|
+
from .users import ensure_demo_users_exist
|
|
213
|
+
|
|
214
|
+
db = engine.get_scoped_db(slug_id)
|
|
215
|
+
|
|
216
|
+
logger.info(
|
|
217
|
+
f"Auto-creating demo users for {slug_id} "
|
|
218
|
+
f"(strategy: {seed_strategy}, "
|
|
219
|
+
f"explicit_users: {has_explicit_demo_users}, "
|
|
220
|
+
f"auto_link_platform: {auto_link_platform})"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
demo_users = await ensure_demo_users_exist(
|
|
224
|
+
db=db,
|
|
225
|
+
slug_id=slug_id,
|
|
226
|
+
config=config,
|
|
227
|
+
mongo_uri=engine.mongo_uri,
|
|
228
|
+
db_name=engine.db_name,
|
|
229
|
+
)
|
|
230
|
+
if demo_users:
|
|
231
|
+
logger.info(
|
|
232
|
+
f"✅ Created/verified {len(demo_users)} demo user(s) for {slug_id}: "
|
|
233
|
+
f"{', '.join([u.get('email', 'unknown') for u in demo_users])}"
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
logger.debug(
|
|
237
|
+
f"No demo users created for {slug_id} "
|
|
238
|
+
f"(may already exist or config disabled)"
|
|
239
|
+
)
|
|
240
|
+
except (
|
|
241
|
+
ValueError,
|
|
242
|
+
TypeError,
|
|
243
|
+
AttributeError,
|
|
244
|
+
RuntimeError,
|
|
245
|
+
ConnectionError,
|
|
246
|
+
KeyError,
|
|
247
|
+
) as e:
|
|
248
|
+
logger.warning(
|
|
249
|
+
f"Could not create demo users for {slug_id}: {e}",
|
|
250
|
+
exc_info=True,
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
logger.debug(
|
|
254
|
+
f"Skipping demo user creation for {slug_id}: "
|
|
255
|
+
f"demo_user_seed_strategy is 'auto' but no demo_users configured "
|
|
256
|
+
f"and auto_link_platform_demo is disabled"
|
|
257
|
+
)
|
|
258
|
+
elif seed_strategy == "disabled":
|
|
259
|
+
logger.debug(
|
|
260
|
+
f"Demo user creation disabled for {slug_id} (demo_user_seed_strategy: disabled)"
|
|
261
|
+
)
|
|
262
|
+
elif seed_strategy == "manual":
|
|
263
|
+
logger.debug(
|
|
264
|
+
f"Demo user creation set to manual for {slug_id} - skipping auto-creation"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Link demo users with OSO initial_roles if auth provider is OSO
|
|
268
|
+
if hasattr(app.state, "authz_provider") and demo_users:
|
|
269
|
+
try:
|
|
270
|
+
auth = config.get("auth", {})
|
|
271
|
+
auth_policy = auth.get("policy", {})
|
|
272
|
+
authorization = auth_policy.get("authorization", {})
|
|
273
|
+
initial_roles = authorization.get("initial_roles", [])
|
|
274
|
+
|
|
275
|
+
if initial_roles:
|
|
276
|
+
# Match demo users by email to initial_roles entries
|
|
277
|
+
for demo_user in demo_users:
|
|
278
|
+
user_email = demo_user.get("email")
|
|
279
|
+
if not user_email:
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
for role_assignment in initial_roles:
|
|
283
|
+
if role_assignment.get("user") == user_email:
|
|
284
|
+
role = role_assignment.get("role")
|
|
285
|
+
resource = role_assignment.get("resource", "documents")
|
|
286
|
+
try:
|
|
287
|
+
await app.state.authz_provider.add_role_for_user(
|
|
288
|
+
user_email, role, resource
|
|
289
|
+
)
|
|
290
|
+
logger.info(
|
|
291
|
+
f"✅ Assigned role '{role}' on resource '{resource}' "
|
|
292
|
+
f"to demo user '{user_email}' for {slug_id}"
|
|
293
|
+
)
|
|
294
|
+
except (
|
|
295
|
+
ValueError,
|
|
296
|
+
TypeError,
|
|
297
|
+
AttributeError,
|
|
298
|
+
RuntimeError,
|
|
299
|
+
ConnectionError,
|
|
300
|
+
) as e:
|
|
301
|
+
logger.warning(
|
|
302
|
+
f"Failed to assign role '{role}' to user '{user_email}' "
|
|
303
|
+
f"for {slug_id}: {e}"
|
|
304
|
+
)
|
|
305
|
+
except (
|
|
306
|
+
ValueError,
|
|
307
|
+
TypeError,
|
|
308
|
+
AttributeError,
|
|
309
|
+
RuntimeError,
|
|
310
|
+
ConnectionError,
|
|
311
|
+
KeyError,
|
|
312
|
+
) as e:
|
|
313
|
+
logger.warning(
|
|
314
|
+
f"Could not link demo users with OSO roles for {slug_id}: {e}",
|
|
315
|
+
exc_info=True,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
return demo_users
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
async def _setup_token_management(
|
|
322
|
+
app: FastAPI, engine, slug_id: str, token_management: Dict[str, Any]
|
|
323
|
+
) -> None:
|
|
324
|
+
"""Initialize token management (blacklist and session manager)."""
|
|
325
|
+
if token_management.get("auto_setup", True):
|
|
326
|
+
try:
|
|
327
|
+
db = engine.get_database()
|
|
328
|
+
await initialize_token_management(app, db)
|
|
329
|
+
|
|
330
|
+
# Configure session fingerprinting if session manager exists
|
|
331
|
+
session_mgr = getattr(app.state, "session_manager", None)
|
|
332
|
+
if session_mgr:
|
|
333
|
+
fingerprinting_config = app.state.security_config.get(
|
|
334
|
+
"session_fingerprinting", {}
|
|
335
|
+
)
|
|
336
|
+
session_mgr.configure_fingerprinting(
|
|
337
|
+
enabled=fingerprinting_config.get("enabled", True),
|
|
338
|
+
strict=fingerprinting_config.get("strict_mode", False),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
logger.info(f"Token management initialized for {slug_id}")
|
|
342
|
+
except (
|
|
343
|
+
ImportError,
|
|
344
|
+
AttributeError,
|
|
345
|
+
TypeError,
|
|
346
|
+
ValueError,
|
|
347
|
+
RuntimeError,
|
|
348
|
+
KeyError,
|
|
349
|
+
) as e:
|
|
350
|
+
logger.warning(f"Could not initialize token management for {slug_id}: {e}")
|
|
351
|
+
# Continue without token management (backward compatibility)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
async def _setup_security_middleware(
|
|
355
|
+
app: FastAPI, slug_id: str, security_config: Dict[str, Any]
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Set up security middleware (if not already added)."""
|
|
358
|
+
if security_config.get("csrf_protection", True) or security_config.get(
|
|
359
|
+
"require_https", False
|
|
360
|
+
):
|
|
361
|
+
try:
|
|
362
|
+
from .middleware import SecurityMiddleware
|
|
363
|
+
|
|
364
|
+
# Try to add middleware - FastAPI will raise RuntimeError if app has started
|
|
365
|
+
# FastAPI with lifespan might have initialized middleware stack already
|
|
366
|
+
# Try to add middleware and catch the error if it fails
|
|
367
|
+
try:
|
|
368
|
+
app.add_middleware(
|
|
369
|
+
SecurityMiddleware,
|
|
370
|
+
require_https=security_config.get("require_https", False),
|
|
371
|
+
csrf_protection=security_config.get("csrf_protection", True),
|
|
372
|
+
security_headers=True,
|
|
373
|
+
)
|
|
374
|
+
logger.info(f"Security middleware added for {slug_id}")
|
|
375
|
+
except (RuntimeError, ValueError) as e:
|
|
376
|
+
error_msg = str(e).lower()
|
|
377
|
+
if "cannot add middleware" in error_msg or "middleware" in error_msg:
|
|
378
|
+
# App has already started - this is expected with lifespan context managers
|
|
379
|
+
# The middleware is optional for security, so we just log
|
|
380
|
+
# a debug message
|
|
381
|
+
logger.debug(
|
|
382
|
+
f"Security middleware not added for {slug_id} - "
|
|
383
|
+
f"app middleware stack already initialized. "
|
|
384
|
+
f"This is normal when using lifespan context managers."
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
logger.warning(
|
|
388
|
+
f"Could not set up security middleware for {slug_id}: {e}"
|
|
389
|
+
)
|
|
390
|
+
except (AttributeError, TypeError, ValueError, RuntimeError, ImportError) as e:
|
|
391
|
+
logger.warning(f"Could not set up security middleware for {slug_id}: {e}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
async def _setup_cors_and_observability(
|
|
395
|
+
app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
|
|
396
|
+
) -> None:
|
|
397
|
+
"""Set up CORS and observability configs and middleware."""
|
|
398
|
+
# Get manifest data first if available
|
|
399
|
+
manifest_data = None
|
|
400
|
+
if hasattr(engine, "get_manifest"):
|
|
401
|
+
try:
|
|
402
|
+
manifest_data = await engine.get_manifest(slug_id)
|
|
403
|
+
except (AttributeError, TypeError, ValueError, RuntimeError, KeyError) as e:
|
|
404
|
+
logger.warning(f"Could not retrieve manifest for {slug_id}: {e}")
|
|
405
|
+
manifest_data = None
|
|
406
|
+
|
|
407
|
+
# Extract and store CORS config
|
|
408
|
+
cors_config = manifest_data.get("cors", {}) if manifest_data else {}
|
|
409
|
+
app.state.cors_config = merge_config_with_defaults(cors_config, CORS_DEFAULTS)
|
|
410
|
+
|
|
411
|
+
# Extract and store observability config
|
|
412
|
+
observability_config = (
|
|
413
|
+
manifest_data.get("observability", {}) if manifest_data else {}
|
|
414
|
+
)
|
|
415
|
+
app.state.observability_config = merge_config_with_defaults(
|
|
416
|
+
observability_config, OBSERVABILITY_DEFAULTS
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Set up CORS middleware if enabled
|
|
420
|
+
if app.state.cors_config.get("enabled", False):
|
|
421
|
+
try:
|
|
422
|
+
# Check if CORS middleware already exists to avoid duplication
|
|
423
|
+
if _has_cors_middleware(app):
|
|
424
|
+
logger.debug(
|
|
425
|
+
f"CORS middleware already exists for {slug_id}, skipping addition"
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
429
|
+
|
|
430
|
+
if not hasattr(app.state, "_started"):
|
|
431
|
+
try:
|
|
432
|
+
app.add_middleware(
|
|
433
|
+
CORSMiddleware,
|
|
434
|
+
allow_origins=app.state.cors_config.get(
|
|
435
|
+
"allow_origins", ["*"]
|
|
436
|
+
),
|
|
437
|
+
allow_credentials=app.state.cors_config.get(
|
|
438
|
+
"allow_credentials", False
|
|
439
|
+
),
|
|
440
|
+
allow_methods=app.state.cors_config.get(
|
|
441
|
+
"allow_methods",
|
|
442
|
+
["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
443
|
+
),
|
|
444
|
+
allow_headers=app.state.cors_config.get(
|
|
445
|
+
"allow_headers", ["*"]
|
|
446
|
+
),
|
|
447
|
+
expose_headers=app.state.cors_config.get(
|
|
448
|
+
"expose_headers", []
|
|
449
|
+
),
|
|
450
|
+
max_age=app.state.cors_config.get("max_age", 3600),
|
|
451
|
+
)
|
|
452
|
+
logger.info(f"CORS middleware added for {slug_id}")
|
|
453
|
+
except (RuntimeError, ValueError) as e:
|
|
454
|
+
error_msg = str(e).lower()
|
|
455
|
+
if (
|
|
456
|
+
"cannot add middleware" in error_msg
|
|
457
|
+
or "middleware" in error_msg
|
|
458
|
+
):
|
|
459
|
+
logger.debug(
|
|
460
|
+
f"CORS middleware not added for {slug_id} - "
|
|
461
|
+
f"app middleware stack already initialized. "
|
|
462
|
+
f"This is normal when using lifespan context managers."
|
|
463
|
+
)
|
|
464
|
+
else:
|
|
465
|
+
logger.warning(
|
|
466
|
+
f"Could not set up CORS middleware for {slug_id}: {e}"
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
logger.warning(
|
|
470
|
+
f"CORS middleware not added for {slug_id} - app already started"
|
|
471
|
+
)
|
|
472
|
+
except (AttributeError, TypeError, ValueError, RuntimeError, ImportError) as e:
|
|
473
|
+
logger.warning(f"Could not set up CORS middleware for {slug_id}: {e}")
|
|
474
|
+
|
|
475
|
+
# Add stale session cleanup middleware if auth.users is enabled
|
|
476
|
+
auth = config.get("auth", {})
|
|
477
|
+
users_config = auth.get("users", {})
|
|
478
|
+
if users_config.get("enabled", False):
|
|
479
|
+
try:
|
|
480
|
+
from .middleware import StaleSessionMiddleware
|
|
481
|
+
|
|
482
|
+
try:
|
|
483
|
+
app.add_middleware(
|
|
484
|
+
StaleSessionMiddleware, slug_id=slug_id, engine=engine
|
|
485
|
+
)
|
|
486
|
+
logger.info(f"Stale session cleanup middleware added for {slug_id}")
|
|
487
|
+
except (RuntimeError, ValueError) as e:
|
|
488
|
+
error_msg = str(e).lower()
|
|
489
|
+
if "cannot add middleware" in error_msg or "middleware" in error_msg:
|
|
490
|
+
logger.debug(
|
|
491
|
+
f"Stale session middleware not added for {slug_id} - "
|
|
492
|
+
f"app middleware stack already initialized. "
|
|
493
|
+
f"This is normal when using lifespan context managers."
|
|
494
|
+
)
|
|
495
|
+
else:
|
|
496
|
+
logger.warning(
|
|
497
|
+
f"Could not set up stale session middleware for {slug_id}: {e}"
|
|
498
|
+
)
|
|
499
|
+
except (AttributeError, TypeError, ValueError, RuntimeError, ImportError) as e:
|
|
500
|
+
logger.warning(
|
|
501
|
+
f"Could not set up stale session middleware for {slug_id}: {e}"
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
async def setup_auth_from_manifest(app: FastAPI, engine, slug_id: str) -> bool:
|
|
506
|
+
"""
|
|
507
|
+
Set up authentication features from manifest configuration.
|
|
508
|
+
|
|
509
|
+
This function:
|
|
510
|
+
1. Reads auth.policy and token_management config from manifest
|
|
511
|
+
2. Auto-creates authorization provider (default: Casbin) if configured
|
|
512
|
+
3. Initializes TokenBlacklist and SessionManager if enabled
|
|
513
|
+
4. Sets up security middleware if configured
|
|
514
|
+
5. Links auth.users to authorization if enabled
|
|
515
|
+
6. Stores config in app.state for easy access
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
app: FastAPI application instance
|
|
519
|
+
engine: MongoDBEngine instance
|
|
520
|
+
slug_id: App slug identifier
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
True if setup was successful, False otherwise
|
|
524
|
+
"""
|
|
525
|
+
try:
|
|
526
|
+
# Get auth config
|
|
527
|
+
config = await get_auth_config(slug_id, engine)
|
|
528
|
+
token_management = config.get("token_management", {})
|
|
529
|
+
|
|
530
|
+
# Set up authorization provider
|
|
531
|
+
await _setup_authorization_provider(app, engine, slug_id, config)
|
|
532
|
+
|
|
533
|
+
# Set up demo users
|
|
534
|
+
await _setup_demo_users(app, engine, slug_id, config)
|
|
535
|
+
|
|
536
|
+
# Check if token management is enabled
|
|
537
|
+
if not token_management.get("enabled", True):
|
|
538
|
+
logger.info(f"Token management disabled for {slug_id}")
|
|
539
|
+
return False
|
|
540
|
+
|
|
541
|
+
# Store config in app state for easy access
|
|
542
|
+
merged_token_config = merge_config_with_defaults(
|
|
543
|
+
token_management, TOKEN_MANAGEMENT_DEFAULTS
|
|
544
|
+
)
|
|
545
|
+
app.state.token_management_config = merged_token_config
|
|
546
|
+
app.state.auth_config = config
|
|
547
|
+
|
|
548
|
+
# Extract and store security policy config with defaults merged
|
|
549
|
+
security_config = token_management.get("security", {})
|
|
550
|
+
app.state.security_config = merge_config_with_defaults(
|
|
551
|
+
security_config, SECURITY_CONFIG_DEFAULTS
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
# Initialize token management
|
|
555
|
+
await _setup_token_management(app, engine, slug_id, token_management)
|
|
556
|
+
|
|
557
|
+
# Set up security middleware
|
|
558
|
+
await _setup_security_middleware(app, slug_id, security_config)
|
|
559
|
+
|
|
560
|
+
# Set up CORS and observability
|
|
561
|
+
await _setup_cors_and_observability(app, engine, slug_id, config)
|
|
562
|
+
|
|
563
|
+
logger.info(f"Auth setup completed for {slug_id}")
|
|
564
|
+
return True
|
|
565
|
+
|
|
566
|
+
except (
|
|
567
|
+
ValueError,
|
|
568
|
+
TypeError,
|
|
569
|
+
AttributeError,
|
|
570
|
+
RuntimeError,
|
|
571
|
+
ImportError,
|
|
572
|
+
KeyError,
|
|
573
|
+
ConnectionError,
|
|
574
|
+
) as e:
|
|
575
|
+
logger.error(
|
|
576
|
+
f"Error setting up auth from manifest for {slug_id}: {e}", exc_info=True
|
|
577
|
+
)
|
|
578
|
+
return False
|