fix: outbox consumer_inbox idempotency logic + tenant_plugin_activation per-tenant check
This commit is contained in:
+28
-7
@@ -290,13 +290,10 @@ async def get_current_user_id(
|
||||
def require_active_plugin(plugin_name: str):
|
||||
"""FastAPI dependency factory: require that a plugin is active.
|
||||
|
||||
Returns 403 if the plugin is not active in the permission registry.
|
||||
This allows routes to be registered at app creation time while
|
||||
enforcing activation status at request time.
|
||||
Checks both global activation (permission registry) and per-tenant
|
||||
activation (tenant_plugin_activation table).
|
||||
|
||||
Note: Does NOT depend on get_current_user — individual route handlers
|
||||
already have their own auth dependencies (require_permission, etc.).
|
||||
This check only verifies plugin activation status.
|
||||
Returns 403 if the plugin is not active.
|
||||
"""
|
||||
async def _check() -> None:
|
||||
from app.core.permission_registry import get_permission_registry
|
||||
@@ -310,11 +307,35 @@ def require_active_plugin(plugin_name: str):
|
||||
"code": "plugin_inactive",
|
||||
},
|
||||
)
|
||||
# Per-tenant activation check (P1.4 fix)
|
||||
# If there's an entry in tenant_plugin_activation for this
|
||||
# tenant+plugin with is_active=false, deny access.
|
||||
# If no entry exists, default to active (backward compatible).
|
||||
from app.core.db import async_session_maker
|
||||
from sqlalchemy import text
|
||||
async with async_session_maker() as db:
|
||||
# Get tenant_id from current session context (RLS)
|
||||
result = await db.execute(
|
||||
text("""
|
||||
SELECT is_active FROM tenant_plugin_activation
|
||||
WHERE plugin_name = :name
|
||||
AND tenant_id = current_setting('app.current_tenant_id', true)::uuid
|
||||
"""),
|
||||
{"name": plugin_name},
|
||||
)
|
||||
row = result.first()
|
||||
if row is not None and not row[0]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail={
|
||||
"detail": f"Plugin '{plugin_name}' is not active for this tenant",
|
||||
"code": "plugin_inactive_tenant",
|
||||
},
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
# Fail-closed: if registry check fails, deny access (P1.2 fix)
|
||||
# Previously this was fail-open (pass) which allowed access on errors
|
||||
logger.error("Plugin activation check failed for '%s': %s", plugin_name, exc)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
||||
Reference in New Issue
Block a user