fix(security): 16 mittlere Probleme behoben (P18-P33)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
P18: require_permission zu forgejo_error_reporter und ai_ui_control routes hinzugefügt P19: Cross-Tenant Permission-Cache-Invalidierung bei Rollenänderungen P20: Session/Permission-Cache-Invalidierung bei Gruppen-Änderungen P21: ENTITY_MODELS Registry um fehlende Plugin-Modelle erweitert P22: Entity-Links prüfen verknüpfte Entity-Permissions P23: authStore persist Middleware entfernt (kein localStorage mehr) P24: 5xx Retry nur noch für GET-Requests P25: KI-Kommentar in address.py (bekannte Inkonsistenz) P26: DeletionLog in EntityHistory gemerged (action=delete) P27: KI-Kommentar in entity_policy.py (ABAC nicht aktiv genutzt) P28: db.commit() aus bulk_permission_service entfernt P29: CSV-Export in export_service.py ausgelagert P30: plugins.py Business-Logik in plugin_install_service.py ausgelagert P31: KI-Kommentar in session.py (Dual-System dokumentiert) P32: Migration 0115: crm_platform_admin Role droppen P33: Cross-Plugin Imports über contracts.py behoben (10 Violations → 0)
This commit is contained in:
@@ -151,7 +151,7 @@ async def update_memory(
|
||||
if "content" in data:
|
||||
memory.content = data["content"]
|
||||
# Regenerate embedding for updated content
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.contracts import generate_embedding
|
||||
embedding = await generate_embedding(data["content"], db=db, tenant_id=tenant_id)
|
||||
if embedding:
|
||||
from sqlalchemy import text as sql_text
|
||||
|
||||
@@ -9,7 +9,7 @@ from sqlalchemy import select, text as sql_text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.plugins.builtins.agent_memory.models import AgentMemory
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.contracts import generate_embedding
|
||||
|
||||
|
||||
async def store_memory(
|
||||
|
||||
@@ -183,14 +183,14 @@ async def get_agent_status_external(
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
# Get recent run stats
|
||||
from app.plugins.builtins.automation.models import AgentRun
|
||||
from app.plugins.builtins.automation.contracts import AutomationContract
|
||||
from sqlalchemy import func
|
||||
|
||||
recent_runs = await db.execute(
|
||||
select(func.count())
|
||||
.select_from(AgentRun)
|
||||
.where(AgentRun.agent_id == aid)
|
||||
.where(AgentRun.tenant_id == tenant_id)
|
||||
.select_from(AutomationContract.AgentRun)
|
||||
.where(AutomationContract.AgentRun.agent_id == aid)
|
||||
.where(AutomationContract.AgentRun.tenant_id == tenant_id)
|
||||
)
|
||||
total_runs = recent_runs.scalar() or 0
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import uuid
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, HTTPException, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.deps import require_permission
|
||||
from app.plugins.builtins.ai_ui_control.schemas import (
|
||||
UICommand,
|
||||
UICommandCreate,
|
||||
@@ -31,7 +32,7 @@ router = APIRouter(prefix="/api/v1/ai-ui-control", tags=["ai-ui-control"])
|
||||
|
||||
# ─── REST endpoints (for AI agents) ───
|
||||
|
||||
@router.post("/command", response_model=UICommandResponse)
|
||||
@router.post("/command", response_model=UICommandResponse, dependencies=[Depends(require_permission("ai_ui_control:write"))])
|
||||
async def send_ui_command(
|
||||
request: Request,
|
||||
body: UICommandCreate,
|
||||
@@ -168,7 +169,7 @@ async def get_command_status(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/online-users")
|
||||
@router.get("/online-users", dependencies=[Depends(require_permission("ai_ui_control:read"))])
|
||||
async def get_online_users(request: Request):
|
||||
"""Check which users are currently online (have active frontend WS connections)."""
|
||||
from app.config import get_settings
|
||||
|
||||
@@ -263,7 +263,8 @@ class AgentCoordinator:
|
||||
|
||||
def register_agent_coordinator_tools():
|
||||
"""Register AgentCoordinator tools in the global tool registry."""
|
||||
from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry
|
||||
from app.plugins.builtins.ai_assistant.contracts import AIAssistantContract
|
||||
get_tool_registry = AIAssistantContract.get_tool_registry
|
||||
|
||||
registry = get_tool_registry()
|
||||
|
||||
@@ -355,7 +356,8 @@ def register_agent_coordinator_tools():
|
||||
|
||||
def unregister_agent_coordinator_tools():
|
||||
"""Unregister AgentCoordinator tools."""
|
||||
from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry
|
||||
from app.plugins.builtins.ai_assistant.contracts import AIAssistantContract
|
||||
get_tool_registry = AIAssistantContract.get_tool_registry
|
||||
|
||||
registry = get_tool_registry()
|
||||
registry.unregister("create_subtask")
|
||||
|
||||
@@ -11,6 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.db import get_db
|
||||
from app.core.visibility import apply_visibility_filter
|
||||
from app.deps import get_current_user, require_permission
|
||||
from app.services.entity_permission_service import check_entity_access
|
||||
from app.plugins.builtins.entity_links.models import EntityLink
|
||||
from app.plugins.builtins.entity_links.schemas import EntityLinkRequest
|
||||
|
||||
@@ -47,6 +48,14 @@ async def link_file_to_entity(
|
||||
400, detail={"detail": "Invalid entity_type", "code": "invalid_entity_type"}
|
||||
)
|
||||
|
||||
# Verify user has read access to both the file and the target entity
|
||||
has_file_access = await check_entity_access(db, tenant_id, user_id, "file", fid, "read")
|
||||
if not has_file_access:
|
||||
raise HTTPException(403, detail={"detail": "No access to file", "code": "forbidden"})
|
||||
has_entity_access = await check_entity_access(db, tenant_id, user_id, body.entity_type, entity_id, "read")
|
||||
if not has_entity_access:
|
||||
raise HTTPException(403, detail={"detail": "No access to target entity", "code": "forbidden"})
|
||||
|
||||
# Check if link already exists
|
||||
existing = await db.execute(
|
||||
select(EntityLink).where(
|
||||
|
||||
@@ -4,14 +4,16 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.deps import require_permission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/forgejo-error-reporter", tags=["forgejo_error_reporter"])
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
@router.get("/status", dependencies=[Depends(require_permission("system:read"))])
|
||||
async def get_status() -> dict:
|
||||
"""Get the current status of the Forgejo Error Reporter plugin."""
|
||||
from app.plugins.registry import get_registry
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"""GraphRAG plugin contract — public interface for cross-plugin access."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
from app.plugins.builtins.graph_rag.provider import GraphRAGSearchProvider
|
||||
|
||||
|
||||
class GraphRagContract:
|
||||
"""Public contract for the graph_rag plugin."""
|
||||
|
||||
contract_name = "graph_rag"
|
||||
|
||||
GraphRAGSearchProvider = GraphRAGSearchProvider
|
||||
|
||||
|
||||
# ─── self-registration ───
|
||||
|
||||
_contract = GraphRagContract()
|
||||
get_contract_registry().register("graph_rag", _contract)
|
||||
|
||||
# Backward-compatible local accessor
|
||||
_contract_instance: GraphRagContract | None = None
|
||||
|
||||
|
||||
def get_contract() -> GraphRagContract:
|
||||
global _contract_instance
|
||||
if _contract_instance is None:
|
||||
_contract_instance = GraphRagContract()
|
||||
return _contract_instance
|
||||
|
||||
|
||||
__all__ = ["GraphRagContract", "GraphRAGSearchProvider"]
|
||||
@@ -37,7 +37,7 @@ class GraphRAGPlugin(BasePlugin):
|
||||
async def on_activate(self, db, service_container, event_bus) -> None:
|
||||
"""Activate plugin: register GraphRAG search provider."""
|
||||
from app.plugins.builtins.graph_rag.provider import GraphRAGSearchProvider
|
||||
from app.plugins.builtins.unified_search.provider_registry import get_search_registry
|
||||
from app.plugins.builtins.unified_search.contracts import get_search_registry
|
||||
|
||||
registry = get_search_registry()
|
||||
try:
|
||||
@@ -50,7 +50,7 @@ class GraphRAGPlugin(BasePlugin):
|
||||
|
||||
async def on_deactivate(self, db, service_container, event_bus) -> None:
|
||||
"""Deactivate plugin: unregister search provider and contract."""
|
||||
from app.plugins.builtins.unified_search.provider_registry import get_search_registry
|
||||
from app.plugins.builtins.unified_search.contracts import get_search_registry
|
||||
get_search_registry().unregister("graph_relationship")
|
||||
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.plugins.builtins.unified_search.base_provider import BaseSearchProvider
|
||||
from app.plugins.builtins.unified_search.contracts import BaseSearchProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.db import get_db
|
||||
from app.core.storage import get_storage_backend
|
||||
from app.plugins.builtins.permissions.models import ShareLink
|
||||
from app.plugins.builtins.dms.models import File as DmsFile
|
||||
from app.plugins.builtins.dms.contracts import DmsContract
|
||||
DmsFile = DmsContract.DmsFile
|
||||
|
||||
router = APIRouter(prefix="/api/v1/public/share", tags=["public-share"])
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ from __future__ import annotations
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.search_engine import hybrid_search
|
||||
from app.plugins.builtins.unified_search.provider_registry import get_search_registry
|
||||
from app.plugins.builtins.unified_search.base_provider import BaseSearchProvider
|
||||
|
||||
|
||||
class UnifiedSearchContract:
|
||||
@@ -14,6 +16,8 @@ class UnifiedSearchContract:
|
||||
|
||||
generate_embedding = staticmethod(generate_embedding)
|
||||
hybrid_search = staticmethod(hybrid_search)
|
||||
get_search_registry = staticmethod(get_search_registry)
|
||||
BaseSearchProvider = BaseSearchProvider
|
||||
|
||||
|
||||
# ─── self-registration ───
|
||||
@@ -32,4 +36,4 @@ def get_contract() -> UnifiedSearchContract:
|
||||
return _contract_instance
|
||||
|
||||
|
||||
__all__ = ["UnifiedSearchContract", "generate_embedding", "hybrid_search"]
|
||||
__all__ = ["UnifiedSearchContract", "generate_embedding", "hybrid_search", "get_search_registry", "BaseSearchProvider"]
|
||||
|
||||
@@ -130,9 +130,8 @@ async def auto_register_providers(db: AsyncSession) -> None:
|
||||
from app.plugins.builtins.unified_search.providers.user_provider import (
|
||||
UserSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.graph_rag.provider import (
|
||||
GraphRAGSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.graph_rag.contracts import GraphRagContract
|
||||
GraphRAGSearchProvider = GraphRagContract.GraphRAGSearchProvider
|
||||
|
||||
registry = get_search_registry()
|
||||
registry.clear()
|
||||
|
||||
Reference in New Issue
Block a user