diff --git a/api/controllers/openapi/app_dsl.py b/api/controllers/openapi/app_dsl.py index 8a8c62f28ca..a42a77dc35f 100644 --- a/api/controllers/openapi/app_dsl.py +++ b/api/controllers/openapi/app_dsl.py @@ -5,6 +5,7 @@ from typing import cast from flask_restx import Resource from sqlalchemy.orm import Session +from controllers.common.wraps import RBACPermission, RBACResourceScope, rbac_permission_required from controllers.openapi import openapi_ns from controllers.openapi._contract import accepts, returns from controllers.openapi._models import AppDslExportQuery, AppDslExportResponse, AppDslImportPayload @@ -33,6 +34,7 @@ class AppDslImportApi(Resource): Returns 400 when the import failed due to invalid DSL or a business error. """ + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL, resource_required=False) @auth_router.guard_workspace( scope=Scope.WORKSPACE_WRITE, allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}), @@ -121,6 +123,7 @@ class AppDslExportApi(Resource): receive a 403; enable the API in the console first if needed. """ + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL) @auth_router.guard( scope=Scope.APPS_READ, allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}), @@ -151,6 +154,7 @@ class AppDslCheckDependenciesApi(Resource): dependencies are satisfied. """ + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL) @auth_router.guard( scope=Scope.APPS_READ, allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}), diff --git a/api/controllers/openapi/app_run.py b/api/controllers/openapi/app_run.py index 76ddd166596..4f09631fce4 100644 --- a/api/controllers/openapi/app_run.py +++ b/api/controllers/openapi/app_run.py @@ -19,6 +19,7 @@ from werkzeug.exceptions import ( import services from controllers.common.fields import EventStreamResponse +from controllers.common.wraps import RBACPermission, RBACResourceScope, rbac_permission_required from controllers.openapi import openapi_ns from controllers.openapi._audit import emit_app_run from controllers.openapi._contract import accepts, returns @@ -136,6 +137,7 @@ _DISPATCH: dict[AppMode, Callable[[App, Any, AppRunRequest], Any]] = { @openapi_ns.route("/apps//run") class AppRunApi(Resource): + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN) @auth_router.guard(scope=Scope.APPS_RUN) @openapi_ns.response(200, "Run result (SSE stream)", openapi_ns.models[EventStreamResponse.__name__]) @accepts(body=AppRunRequest) @@ -167,6 +169,7 @@ class AppRunApi(Resource): @openapi_ns.route("/apps//tasks//stop") class AppRunTaskStopApi(Resource): + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN) @auth_router.guard(scope=Scope.APPS_RUN) @returns(200, TaskStopResponse, description="Task stopped") def post(self, app_id: str, task_id: str, *, auth_data: AuthData): diff --git a/api/controllers/openapi/apps.py b/api/controllers/openapi/apps.py index 84b1610d5f5..10b7fb0d5e1 100644 --- a/api/controllers/openapi/apps.py +++ b/api/controllers/openapi/apps.py @@ -21,6 +21,7 @@ from controllers.openapi._models import ( AppListRow, TagItem, ) +from controllers.common.wraps import RBACPermission, RBACResourceScope, rbac_permission_required from controllers.openapi.auth.composition import auth_router from controllers.openapi.auth.data import AuthData from controllers.service_api.app.error import AppUnavailableError @@ -86,6 +87,7 @@ def parameters_payload(app: App) -> dict: @openapi_ns.route("/apps//describe") class AppDescribeApi(AppReadResource): + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT) @auth_router.guard(scope=Scope.APPS_READ, allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT})) @returns(200, AppDescribeResponse, description="App description") @accepts(query=AppDescribeQuery) @@ -136,6 +138,7 @@ class AppDescribeApi(AppReadResource): @openapi_ns.route("/apps") class AppListApi(Resource): + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT, resource_required=False) @auth_router.guard_workspace(scope=Scope.APPS_READ, allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT})) @returns(200, AppListResponse, description="App list") @accepts(query=AppListQuery) diff --git a/api/controllers/openapi/human_input_form.py b/api/controllers/openapi/human_input_form.py index 995315150cc..6f04d111c52 100644 --- a/api/controllers/openapi/human_input_form.py +++ b/api/controllers/openapi/human_input_form.py @@ -16,6 +16,7 @@ from werkzeug.exceptions import BadRequest, NotFound from controllers.common.human_input import HumanInputFormSubmitPayload, stringify_form_default_values from controllers.common.schema import register_schema_models +from controllers.common.wraps import RBACPermission, RBACResourceScope, rbac_permission_required from controllers.openapi import openapi_ns from controllers.openapi._contract import accepts, returns from controllers.openapi._models import FormSubmitResponse, HumanInputFormDefinitionResponse @@ -57,6 +58,7 @@ def _ensure_form_is_allowed_for_openapi(form) -> None: @openapi_ns.route("/apps//form/human_input/") class OpenApiWorkflowHumanInputFormApi(Resource): + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN) @openapi_ns.response(200, "Form definition", openapi_ns.models[HumanInputFormDefinitionResponse.__name__]) @auth_router.guard(scope=Scope.APPS_RUN) def get(self, app_id: str, form_token: str, *, auth_data: AuthData): @@ -71,6 +73,7 @@ class OpenApiWorkflowHumanInputFormApi(Resource): service.ensure_form_active(form) return _jsonify_form_definition(form) + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN) @auth_router.guard(scope=Scope.APPS_RUN) @returns(200, FormSubmitResponse, description="Form submitted") @accepts(body=HumanInputFormSubmitPayload) diff --git a/api/controllers/openapi/workflow_events.py b/api/controllers/openapi/workflow_events.py index 61ebb3012dc..bbb9889cff4 100644 --- a/api/controllers/openapi/workflow_events.py +++ b/api/controllers/openapi/workflow_events.py @@ -19,6 +19,7 @@ from werkzeug.exceptions import NotFound, UnprocessableEntity from controllers.common.fields import EventStreamResponse from controllers.common.schema import query_params_from_model +from controllers.common.wraps import RBACPermission, RBACResourceScope, rbac_permission_required from controllers.openapi import openapi_ns from controllers.openapi.auth.composition import auth_router from controllers.openapi.auth.data import AuthData @@ -44,6 +45,7 @@ class WorkflowEventsQuery(BaseModel): @openapi_ns.route("/apps//tasks//events") class OpenApiWorkflowEventsApi(Resource): + @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN) @openapi_ns.doc(params=query_params_from_model(WorkflowEventsQuery)) @openapi_ns.response(200, "SSE event stream", openapi_ns.models[EventStreamResponse.__name__]) @auth_router.guard(scope=Scope.APPS_RUN)