mdb-engine 0.1.7__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 +71 -10
- mdb_engine/auth/ARCHITECTURE.md +112 -0
- mdb_engine/auth/README.md +125 -11
- mdb_engine/auth/__init__.py +7 -1
- mdb_engine/auth/base.py +252 -0
- mdb_engine/auth/casbin_factory.py +258 -59
- mdb_engine/auth/dependencies.py +10 -5
- mdb_engine/auth/integration.py +23 -7
- mdb_engine/auth/oso_factory.py +2 -2
- mdb_engine/auth/provider.py +263 -143
- mdb_engine/core/engine.py +307 -6
- mdb_engine/core/manifest.py +35 -15
- mdb_engine/database/README.md +28 -1
- 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 +22 -23
- mdb_engine/embeddings/dependencies.py +37 -152
- 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-0.1.7.dist-info → mdb_engine-0.2.0.dist-info}/METADATA +42 -14
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.0.dist-info}/RECORD +31 -20
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.0.dist-info}/WHEEL +0 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.0.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.7.dist-info → mdb_engine-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -16,14 +16,14 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
16
16
|
from .casbin_models import DEFAULT_RBAC_MODEL, SIMPLE_ACL_MODEL
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
-
import casbin
|
|
19
|
+
import casbin # type: ignore
|
|
20
20
|
|
|
21
21
|
from .provider import CasbinAdapter
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def get_casbin_model(model_type: str = "rbac") -> str:
|
|
26
|
+
async def get_casbin_model(model_type: str = "rbac") -> str:
|
|
27
27
|
"""
|
|
28
28
|
Get Casbin model string by type or path.
|
|
29
29
|
|
|
@@ -39,16 +39,32 @@ def get_casbin_model(model_type: str = "rbac") -> str:
|
|
|
39
39
|
return SIMPLE_ACL_MODEL
|
|
40
40
|
else:
|
|
41
41
|
# Assume it's a file path
|
|
42
|
+
# Try async file reading first, fallback to sync if not available
|
|
42
43
|
model_path = Path(model_type)
|
|
43
44
|
if model_path.exists():
|
|
44
|
-
|
|
45
|
+
try:
|
|
46
|
+
# Try async file reading (non-blocking)
|
|
47
|
+
import aiofiles
|
|
48
|
+
|
|
49
|
+
async with aiofiles.open(model_path, "r") as f:
|
|
50
|
+
content = await f.read()
|
|
51
|
+
logger.debug(f"Read model file asynchronously: {model_path}")
|
|
52
|
+
return content
|
|
53
|
+
except ImportError:
|
|
54
|
+
# Fallback to sync read if aiofiles not available
|
|
55
|
+
# This is acceptable during startup initialization
|
|
56
|
+
logger.debug(
|
|
57
|
+
"aiofiles not available, using sync file read (acceptable during startup)"
|
|
58
|
+
)
|
|
59
|
+
return model_path.read_text()
|
|
45
60
|
else:
|
|
46
61
|
logger.warning(f"Casbin model file not found: {model_type}, using default RBAC model")
|
|
47
62
|
return DEFAULT_RBAC_MODEL
|
|
48
63
|
|
|
49
64
|
|
|
50
65
|
async def create_casbin_enforcer(
|
|
51
|
-
|
|
66
|
+
mongo_uri: str,
|
|
67
|
+
db_name: str,
|
|
52
68
|
model: str = "rbac",
|
|
53
69
|
policies_collection: str = "casbin_policies",
|
|
54
70
|
default_roles: Optional[list] = None,
|
|
@@ -57,7 +73,8 @@ async def create_casbin_enforcer(
|
|
|
57
73
|
Create a Casbin AsyncEnforcer with MongoDB adapter.
|
|
58
74
|
|
|
59
75
|
Args:
|
|
60
|
-
|
|
76
|
+
mongo_uri: MongoDB connection URI string
|
|
77
|
+
db_name: MongoDB database name
|
|
61
78
|
model: Casbin model type ("rbac", "acl") or path to model file
|
|
62
79
|
policies_collection: MongoDB collection name for policies (will be app-scoped)
|
|
63
80
|
default_roles: List of default roles to create (optional)
|
|
@@ -69,30 +86,94 @@ async def create_casbin_enforcer(
|
|
|
69
86
|
ImportError: If casbin or casbin-motor-adapter is not installed
|
|
70
87
|
"""
|
|
71
88
|
try:
|
|
72
|
-
import casbin
|
|
73
|
-
from casbin_motor_adapter import
|
|
89
|
+
import casbin # type: ignore
|
|
90
|
+
from casbin_motor_adapter import Adapter # type: ignore
|
|
74
91
|
except ImportError as e:
|
|
75
92
|
raise ImportError(
|
|
76
93
|
"Casbin dependencies not installed. Install with: pip install mdb-engine[casbin]"
|
|
77
94
|
) from e
|
|
78
95
|
|
|
79
|
-
# Get model string
|
|
80
|
-
model_str = get_casbin_model(model)
|
|
96
|
+
# Get model string (async)
|
|
97
|
+
model_str = await get_casbin_model(model)
|
|
81
98
|
|
|
82
99
|
# Create MongoDB adapter
|
|
83
|
-
|
|
100
|
+
# Try to pass policies_collection if supported by the adapter
|
|
101
|
+
logger.debug(
|
|
102
|
+
f"Creating Casbin MotorAdapter with URI: {mongo_uri[:50]}..., "
|
|
103
|
+
f"db_name: {db_name}, collection: {policies_collection}"
|
|
104
|
+
)
|
|
105
|
+
try:
|
|
106
|
+
# Try passing collection name as third parameter
|
|
107
|
+
try:
|
|
108
|
+
adapter = Adapter(mongo_uri, db_name, policies_collection)
|
|
109
|
+
logger.debug(
|
|
110
|
+
f"Casbin MotorAdapter created successfully with custom "
|
|
111
|
+
f"collection '{policies_collection}'"
|
|
112
|
+
)
|
|
113
|
+
except TypeError:
|
|
114
|
+
# Fallback: adapter doesn't support collection parameter, use default
|
|
115
|
+
logger.warning(
|
|
116
|
+
"Adapter doesn't support custom collection name, " "using default collection"
|
|
117
|
+
)
|
|
118
|
+
adapter = Adapter(mongo_uri, db_name)
|
|
119
|
+
logger.debug("Casbin MotorAdapter created successfully (using default collection)")
|
|
120
|
+
except (RuntimeError, ValueError, AttributeError, TypeError, ConnectionError):
|
|
121
|
+
logger.exception("Failed to create Casbin MotorAdapter")
|
|
122
|
+
raise
|
|
84
123
|
|
|
85
124
|
# Create enforcer with model and adapter
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
# AsyncEnforcer can accept model string or Model object
|
|
126
|
+
logger.debug("Creating Casbin AsyncEnforcer with model and adapter...")
|
|
127
|
+
try:
|
|
128
|
+
logger.debug(f"Model string length: {len(model_str)} chars")
|
|
129
|
+
logger.debug(f"Adapter type: {type(adapter)}")
|
|
130
|
+
|
|
131
|
+
# Create Model object from string
|
|
132
|
+
model = casbin.Model()
|
|
133
|
+
model.load_model_from_text(model_str)
|
|
134
|
+
logger.debug(f"Model object created successfully, type: {type(model)}")
|
|
135
|
+
|
|
136
|
+
# Create enforcer with Model object and adapter
|
|
137
|
+
# AsyncEnforcer auto-loads by default (auto_load=True), so we don't need manual load
|
|
138
|
+
logger.debug("Calling casbin.AsyncEnforcer(model, adapter)...")
|
|
139
|
+
enforcer = casbin.AsyncEnforcer(model, adapter)
|
|
140
|
+
logger.debug("Enforcer created successfully")
|
|
141
|
+
|
|
142
|
+
# Check if policies were auto-loaded
|
|
143
|
+
# If auto_load is enabled (default), policies are already loaded
|
|
144
|
+
# Only manually load if auto_load was disabled
|
|
145
|
+
if not getattr(enforcer, "auto_load", True):
|
|
146
|
+
logger.debug("Auto-load disabled, manually loading policies...")
|
|
147
|
+
await enforcer.load_policy()
|
|
148
|
+
logger.debug("Policies loaded successfully")
|
|
149
|
+
else:
|
|
150
|
+
logger.debug("Policies auto-loaded by AsyncEnforcer constructor")
|
|
151
|
+
except (RuntimeError, ValueError, AttributeError, TypeError, OSError):
|
|
152
|
+
logger.exception("Failed to create or configure Casbin enforcer")
|
|
153
|
+
# Try alternative: use temp file approach
|
|
154
|
+
logger.info("Attempting alternative: using temporary model file...")
|
|
155
|
+
try:
|
|
156
|
+
import os
|
|
157
|
+
import tempfile
|
|
158
|
+
|
|
159
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".conf", delete=False) as f:
|
|
160
|
+
f.write(model_str)
|
|
161
|
+
temp_model_path = f.name
|
|
162
|
+
try:
|
|
163
|
+
enforcer = casbin.AsyncEnforcer(temp_model_path, adapter)
|
|
164
|
+
logger.info("✅ Enforcer created successfully using temp model file")
|
|
165
|
+
# Clean up temp file
|
|
166
|
+
os.unlink(temp_model_path)
|
|
167
|
+
except (RuntimeError, ValueError, AttributeError, TypeError, OSError) as e2:
|
|
168
|
+
if os.path.exists(temp_model_path):
|
|
169
|
+
os.unlink(temp_model_path)
|
|
170
|
+
logger.exception("Alternative approach also failed")
|
|
171
|
+
raise RuntimeError("Failed to create Casbin enforcer") from e2
|
|
172
|
+
except (OSError, RuntimeError) as e2:
|
|
173
|
+
logger.exception("Failed to try alternative approach")
|
|
174
|
+
raise RuntimeError("Failed to create Casbin enforcer") from e2
|
|
175
|
+
|
|
176
|
+
# Note: Removed default_roles creation - roles exist implicitly when assigned to users
|
|
96
177
|
|
|
97
178
|
logger.info(
|
|
98
179
|
f"Casbin enforcer created with model '{model}' and "
|
|
@@ -102,28 +183,8 @@ async def create_casbin_enforcer(
|
|
|
102
183
|
return enforcer
|
|
103
184
|
|
|
104
185
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Create default roles in Casbin (as grouping rules).
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
enforcer: Casbin AsyncEnforcer instance
|
|
111
|
-
roles: List of role names to create
|
|
112
|
-
"""
|
|
113
|
-
for role in roles:
|
|
114
|
-
# Create a grouping rule: role -> role (self-reference for role existence)
|
|
115
|
-
# This ensures the role exists in the system
|
|
116
|
-
# Actual user-role assignments will be added when users are created
|
|
117
|
-
try:
|
|
118
|
-
# Check if role already exists
|
|
119
|
-
existing = await enforcer.get_roles_for_user(role)
|
|
120
|
-
if not existing:
|
|
121
|
-
# Add role as a self-grouping rule to ensure it exists
|
|
122
|
-
# This is a common pattern to "register" roles
|
|
123
|
-
await enforcer.add_grouping_policy(role, role)
|
|
124
|
-
logger.debug(f"Created default Casbin role: {role}")
|
|
125
|
-
except (AttributeError, TypeError, ValueError, RuntimeError) as e:
|
|
126
|
-
logger.warning(f"Error creating default role '{role}': {e}")
|
|
186
|
+
# Removed _create_default_roles function - roles exist implicitly when assigned to users
|
|
187
|
+
# No need to "create" roles beforehand with self-referencing policies
|
|
127
188
|
|
|
128
189
|
|
|
129
190
|
async def initialize_casbin_from_manifest(
|
|
@@ -135,7 +196,7 @@ async def initialize_casbin_from_manifest(
|
|
|
135
196
|
Args:
|
|
136
197
|
engine: MongoDBEngine instance
|
|
137
198
|
app_slug: App slug identifier
|
|
138
|
-
auth_config:
|
|
199
|
+
auth_config: Full manifest config dict (contains auth.policy or auth_policy)
|
|
139
200
|
|
|
140
201
|
Returns:
|
|
141
202
|
CasbinAdapter instance if successfully created, None otherwise
|
|
@@ -143,40 +204,176 @@ async def initialize_casbin_from_manifest(
|
|
|
143
204
|
try:
|
|
144
205
|
from .provider import CasbinAdapter
|
|
145
206
|
|
|
146
|
-
|
|
207
|
+
# Support both old (auth_policy) and new (auth.policy) structures
|
|
208
|
+
auth = auth_config.get("auth", {})
|
|
209
|
+
auth_policy = auth.get("policy", {}) or auth_config.get("auth_policy", {})
|
|
147
210
|
provider = auth_policy.get("provider", "casbin")
|
|
148
211
|
|
|
149
212
|
# Only proceed if provider is casbin
|
|
150
213
|
if provider != "casbin":
|
|
214
|
+
logger.debug(f"Provider is '{provider}', not 'casbin' - skipping Casbin initialization")
|
|
151
215
|
return None
|
|
152
216
|
|
|
217
|
+
logger.info(f"Initializing Casbin provider for app '{app_slug}'...")
|
|
218
|
+
|
|
153
219
|
# Get authorization config
|
|
154
220
|
authorization = auth_policy.get("authorization", {})
|
|
155
221
|
model = authorization.get("model", "rbac")
|
|
156
222
|
policies_collection = authorization.get("policies_collection", "casbin_policies")
|
|
157
223
|
default_roles = authorization.get("default_roles", [])
|
|
224
|
+
initial_policies = authorization.get("initial_policies", [])
|
|
225
|
+
initial_roles = authorization.get("initial_roles", [])
|
|
158
226
|
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
enforcer = await create_casbin_enforcer(
|
|
164
|
-
db=db,
|
|
165
|
-
model=model,
|
|
166
|
-
policies_collection=policies_collection,
|
|
167
|
-
default_roles=default_roles,
|
|
227
|
+
# Create enforcer with MongoDB connection info from engine
|
|
228
|
+
logger.debug(
|
|
229
|
+
f"Creating Casbin enforcer with URI: {engine.mongo_uri[:50]}..., "
|
|
230
|
+
f"db: {engine.db_name}"
|
|
168
231
|
)
|
|
232
|
+
try:
|
|
233
|
+
enforcer = await create_casbin_enforcer(
|
|
234
|
+
mongo_uri=engine.mongo_uri,
|
|
235
|
+
db_name=engine.db_name,
|
|
236
|
+
model=model,
|
|
237
|
+
policies_collection=policies_collection,
|
|
238
|
+
default_roles=default_roles,
|
|
239
|
+
)
|
|
240
|
+
logger.debug("Casbin enforcer created successfully")
|
|
241
|
+
except (
|
|
242
|
+
RuntimeError,
|
|
243
|
+
ValueError,
|
|
244
|
+
AttributeError,
|
|
245
|
+
TypeError,
|
|
246
|
+
ConnectionError,
|
|
247
|
+
ImportError,
|
|
248
|
+
):
|
|
249
|
+
logger.exception(f"Failed to create Casbin enforcer for '{app_slug}'")
|
|
250
|
+
return None
|
|
169
251
|
|
|
170
252
|
# Create adapter
|
|
171
|
-
|
|
253
|
+
try:
|
|
254
|
+
adapter = CasbinAdapter(enforcer)
|
|
255
|
+
logger.info("✅ CasbinAdapter created successfully")
|
|
256
|
+
except (RuntimeError, ValueError, AttributeError, TypeError):
|
|
257
|
+
logger.exception(f"Failed to create CasbinAdapter for '{app_slug}'")
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
# Set up initial policies if configured
|
|
261
|
+
if initial_policies:
|
|
262
|
+
logger.info(f"Setting up {len(initial_policies)} initial policies...")
|
|
263
|
+
for policy in initial_policies:
|
|
264
|
+
if isinstance(policy, (list, tuple)) and len(policy) >= 3:
|
|
265
|
+
role, resource, action = policy[0], policy[1], policy[2]
|
|
266
|
+
try:
|
|
267
|
+
# Check if policy already exists
|
|
268
|
+
exists = await adapter.has_policy(role, resource, action)
|
|
269
|
+
if exists:
|
|
270
|
+
logger.debug(f" Policy already exists: {role} -> {resource}:{action}")
|
|
271
|
+
else:
|
|
272
|
+
added = await adapter.add_policy(role, resource, action)
|
|
273
|
+
if added:
|
|
274
|
+
logger.debug(f" Added policy: {role} -> {resource}:{action}")
|
|
275
|
+
else:
|
|
276
|
+
logger.warning(
|
|
277
|
+
f" Failed to add policy: {role} -> " f"{resource}:{action}"
|
|
278
|
+
)
|
|
279
|
+
except (ValueError, TypeError, RuntimeError, AttributeError) as e:
|
|
280
|
+
logger.warning(f" Failed to add policy {policy}: {e}", exc_info=True)
|
|
281
|
+
|
|
282
|
+
# Set up initial role assignments if configured
|
|
283
|
+
# Disable auto_save during bulk operations for better performance
|
|
284
|
+
if initial_roles:
|
|
285
|
+
logger.info(f"Setting up {len(initial_roles)} initial role assignments...")
|
|
286
|
+
# Temporarily disable auto_save to avoid writing on every iteration
|
|
287
|
+
original_auto_save = getattr(enforcer, "auto_save", True)
|
|
288
|
+
try:
|
|
289
|
+
enforcer.auto_save = False
|
|
290
|
+
logger.debug("Disabled auto_save for bulk role assignment")
|
|
291
|
+
except AttributeError:
|
|
292
|
+
# Some enforcer implementations don't support auto_save attribute
|
|
293
|
+
logger.debug("auto_save attribute not available, proceeding with default behavior")
|
|
294
|
+
|
|
295
|
+
for role_assignment in initial_roles:
|
|
296
|
+
if isinstance(role_assignment, dict):
|
|
297
|
+
user = role_assignment.get("user")
|
|
298
|
+
role = role_assignment.get("role")
|
|
299
|
+
if user and role:
|
|
300
|
+
try:
|
|
301
|
+
# Check if role assignment already exists
|
|
302
|
+
exists = await adapter.has_role_for_user(user, role)
|
|
303
|
+
logger.debug(
|
|
304
|
+
f" Checking role assignment: {user} -> {role}, " f"exists={exists}"
|
|
305
|
+
)
|
|
306
|
+
if exists:
|
|
307
|
+
logger.info(f" ✓ Role assignment already exists: {user} -> {role}")
|
|
308
|
+
else:
|
|
309
|
+
logger.info(f" Adding role assignment: {user} -> {role}")
|
|
310
|
+
# Use enforcer directly with add_grouping_policy
|
|
311
|
+
added = await enforcer.add_grouping_policy(user, role)
|
|
312
|
+
if added:
|
|
313
|
+
logger.info(
|
|
314
|
+
f" ✓ Successfully assigned role '{role}' "
|
|
315
|
+
f"to user '{user}'"
|
|
316
|
+
)
|
|
317
|
+
else:
|
|
318
|
+
logger.warning(
|
|
319
|
+
f" ⚠ Failed to assign role '{role}' to "
|
|
320
|
+
f"user '{user}' - add_grouping_policy returned False"
|
|
321
|
+
)
|
|
322
|
+
except (ValueError, TypeError, RuntimeError, AttributeError):
|
|
323
|
+
logger.exception(f" ✗ Exception assigning role {role_assignment}")
|
|
324
|
+
|
|
325
|
+
# Restore auto_save setting
|
|
326
|
+
if hasattr(enforcer, "auto_save"):
|
|
327
|
+
enforcer.auto_save = original_auto_save
|
|
328
|
+
logger.debug("Restored auto_save setting")
|
|
329
|
+
|
|
330
|
+
# Verify that policies and roles were set up correctly
|
|
331
|
+
if initial_policies:
|
|
332
|
+
verified = 0
|
|
333
|
+
for policy in initial_policies:
|
|
334
|
+
if isinstance(policy, (list, tuple)) and len(policy) >= 3:
|
|
335
|
+
role, resource, action = policy[0], policy[1], policy[2]
|
|
336
|
+
if await adapter.has_policy(role, resource, action):
|
|
337
|
+
verified += 1
|
|
338
|
+
logger.info(f"Verified {verified}/{len(initial_policies)} policies exist in memory")
|
|
339
|
+
|
|
340
|
+
if initial_roles:
|
|
341
|
+
verified = 0
|
|
342
|
+
for role_assignment in initial_roles:
|
|
343
|
+
if isinstance(role_assignment, dict):
|
|
344
|
+
user = role_assignment.get("user")
|
|
345
|
+
role = role_assignment.get("role")
|
|
346
|
+
if user and role and await adapter.has_role_for_user(user, role):
|
|
347
|
+
verified += 1
|
|
348
|
+
logger.info(
|
|
349
|
+
f"Verified {verified}/{len(initial_roles)} role assignments " f"exist in memory"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Save policies to persist them to database
|
|
353
|
+
# Only save if auto_save was disabled (to avoid double-saving)
|
|
354
|
+
if not getattr(enforcer, "auto_save", True):
|
|
355
|
+
saved = await adapter.save_policy()
|
|
356
|
+
if saved:
|
|
357
|
+
logger.debug("Policies saved to database successfully")
|
|
358
|
+
else:
|
|
359
|
+
logger.warning(
|
|
360
|
+
"Failed to save policies to database - they may not " "persist across restarts"
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
logger.debug(
|
|
364
|
+
"Skipping manual save_policy() - auto_save is enabled, "
|
|
365
|
+
"policies already persisted"
|
|
366
|
+
)
|
|
172
367
|
|
|
173
|
-
logger.info(f"Casbin provider initialized for app '{app_slug}'")
|
|
368
|
+
logger.info(f"✅ Casbin provider initialized for app '{app_slug}'")
|
|
369
|
+
logger.info(f"✅ CasbinAdapter ready for use - type: {type(adapter).__name__}")
|
|
174
370
|
|
|
175
371
|
return adapter
|
|
176
372
|
|
|
177
373
|
except ImportError as e:
|
|
374
|
+
# ImportError is expected if Casbin is not installed - use warning, not error
|
|
178
375
|
logger.warning(
|
|
179
|
-
f"Casbin not available for app '{app_slug}': {e}. "
|
|
376
|
+
f"❌ Casbin not available for app '{app_slug}': {e}. "
|
|
180
377
|
"Install with: pip install mdb-engine[casbin]"
|
|
181
378
|
)
|
|
182
379
|
return None
|
|
@@ -188,8 +385,10 @@ async def initialize_casbin_from_manifest(
|
|
|
188
385
|
RuntimeError,
|
|
189
386
|
KeyError,
|
|
190
387
|
) as e:
|
|
191
|
-
logger.
|
|
192
|
-
|
|
193
|
-
|
|
388
|
+
logger.exception(f"❌ Error initializing Casbin provider for app '{app_slug}': {e}")
|
|
389
|
+
# Informational message, not exception logging
|
|
390
|
+
logger.error( # noqa: TRY400
|
|
391
|
+
f"❌ Casbin provider initialization FAILED for '{app_slug}' - "
|
|
392
|
+
"check logs above for detailed error information"
|
|
194
393
|
)
|
|
195
394
|
return None
|
mdb_engine/auth/dependencies.py
CHANGED
|
@@ -41,15 +41,20 @@ def _get_secret_key() -> str:
|
|
|
41
41
|
if _SECRET_KEY_CACHE is not None:
|
|
42
42
|
return _SECRET_KEY_CACHE
|
|
43
43
|
|
|
44
|
-
secret_key =
|
|
44
|
+
secret_key = (
|
|
45
|
+
os.environ.get("FLASK_SECRET_KEY")
|
|
46
|
+
or os.environ.get("SECRET_KEY")
|
|
47
|
+
or os.environ.get("APP_SECRET_KEY")
|
|
48
|
+
)
|
|
45
49
|
|
|
46
50
|
if not secret_key:
|
|
47
51
|
raise ConfigurationError(
|
|
48
|
-
"
|
|
49
|
-
"Set
|
|
50
|
-
"
|
|
52
|
+
"SECRET_KEY environment variable is required for JWT token security. "
|
|
53
|
+
"Set FLASK_SECRET_KEY, SECRET_KEY, or APP_SECRET_KEY with a strong secret key "
|
|
54
|
+
"(minimum 32 characters, cryptographically random). "
|
|
55
|
+
"Example: export SECRET_KEY=$(python -c "
|
|
51
56
|
"'import secrets; print(secrets.token_urlsafe(32))')",
|
|
52
|
-
config_key="
|
|
57
|
+
config_key="SECRET_KEY",
|
|
53
58
|
)
|
|
54
59
|
|
|
55
60
|
if len(secret_key) < 32:
|
mdb_engine/auth/integration.py
CHANGED
|
@@ -273,14 +273,30 @@ async def _setup_demo_users(app: FastAPI, engine, slug_id: str, config: Dict[str
|
|
|
273
273
|
if role_assignment.get("user") == user_email:
|
|
274
274
|
role = role_assignment.get("role")
|
|
275
275
|
resource = role_assignment.get("resource", "documents")
|
|
276
|
+
|
|
277
|
+
# Check if provider is Casbin (uses email as subject for initial_roles)
|
|
278
|
+
is_casbin = hasattr(app.state.authz_provider, "_enforcer")
|
|
279
|
+
|
|
276
280
|
try:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
if is_casbin:
|
|
282
|
+
# For Casbin, use email as subject to match initial_roles format
|
|
283
|
+
# This ensures consistency with how initial_roles are set up
|
|
284
|
+
await app.state.authz_provider.add_role_for_user(
|
|
285
|
+
user_email, role
|
|
286
|
+
)
|
|
287
|
+
logger.info(
|
|
288
|
+
f"✅ Assigned Casbin role '{role}' "
|
|
289
|
+
f"to demo user '{user_email}' for {slug_id}"
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
# For OSO, use email, role, resource
|
|
293
|
+
await app.state.authz_provider.add_role_for_user(
|
|
294
|
+
user_email, role, resource
|
|
295
|
+
)
|
|
296
|
+
logger.info(
|
|
297
|
+
f"✅ Assigned role '{role}' on resource '{resource}' "
|
|
298
|
+
f"to demo user '{user_email}' for {slug_id}"
|
|
299
|
+
)
|
|
284
300
|
except (
|
|
285
301
|
ValueError,
|
|
286
302
|
TypeError,
|
mdb_engine/auth/oso_factory.py
CHANGED
|
@@ -46,7 +46,7 @@ async def create_oso_cloud_client(
|
|
|
46
46
|
|
|
47
47
|
# Import OSO Cloud SDK - the class is named "Oso"
|
|
48
48
|
try:
|
|
49
|
-
from oso_cloud import Oso
|
|
49
|
+
from oso_cloud import Oso # type: ignore
|
|
50
50
|
|
|
51
51
|
logger.debug("✅ Imported Oso from oso_cloud")
|
|
52
52
|
except ImportError as e:
|
|
@@ -212,7 +212,7 @@ async def initialize_oso_from_manifest(
|
|
|
212
212
|
try:
|
|
213
213
|
import asyncio
|
|
214
214
|
|
|
215
|
-
from oso_cloud import Value
|
|
215
|
+
from oso_cloud import Value # type: ignore
|
|
216
216
|
|
|
217
217
|
# Try a simple test authorization to verify connection
|
|
218
218
|
test_actor = Value("User", "test")
|