diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py index 413cc53cc50..4338c6ca704 100644 --- a/api/services/app_dsl_service.py +++ b/api/services/app_dsl_service.py @@ -14,6 +14,7 @@ from packaging.version import parse as parse_version from pydantic import BaseModel, Field from sqlalchemy import select from sqlalchemy.orm import Session +from werkzeug.exceptions import Forbidden from configs import dify_config from constants.dsl_version import CURRENT_APP_DSL_VERSION @@ -37,6 +38,7 @@ from graphon.nodes.question_classifier.entities import QuestionClassifierNodeDat from graphon.nodes.tool.entities import ToolNodeData from libs.datetime_utils import naive_utc_now from models import Account, App, AppMode +from models.account import TenantAccountRole from models.model import AppModelConfig, AppModelConfigDict, IconType, load_annotation_reply_config from models.workflow import Workflow from services.agent.dsl_service import AgentDslService, AgentPackage @@ -395,6 +397,12 @@ class AppDslService: leaked_dependencies=leaked_dependencies, ) + @staticmethod + def assert_secret_export_allowed(*, include_secret: bool, current_role: "TenantAccountRole | None") -> None: + """Only workspace managers (owner/admin) may export secrets in a DSL.""" + if include_secret and not TenantAccountRole.is_privileged_role(current_role): + raise Forbidden("Only workspace managers can export secrets.") + def _create_or_update_app( self, *, diff --git a/api/tests/unit_tests/services/test_app_dsl_service_secret_export.py b/api/tests/unit_tests/services/test_app_dsl_service_secret_export.py new file mode 100644 index 00000000000..db03b167ad9 --- /dev/null +++ b/api/tests/unit_tests/services/test_app_dsl_service_secret_export.py @@ -0,0 +1,25 @@ +import pytest +from werkzeug.exceptions import Forbidden + +from models.account import TenantAccountRole +from services.app_dsl_service import AppDslService + + +def test_non_secret_export_allowed_for_any_role() -> None: + # Must not raise for a normal member exporting without secrets. + AppDslService.assert_secret_export_allowed(include_secret=False, current_role=TenantAccountRole.NORMAL) + + +def test_secret_export_allowed_for_privileged_roles() -> None: + AppDslService.assert_secret_export_allowed(include_secret=True, current_role=TenantAccountRole.OWNER) + AppDslService.assert_secret_export_allowed(include_secret=True, current_role=TenantAccountRole.ADMIN) + + +def test_secret_export_forbidden_for_non_privileged_role() -> None: + with pytest.raises(Forbidden): + AppDslService.assert_secret_export_allowed(include_secret=True, current_role=TenantAccountRole.NORMAL) + + +def test_secret_export_forbidden_for_none_role() -> None: + with pytest.raises(Forbidden): + AppDslService.assert_secret_export_allowed(include_secret=True, current_role=None)