mirror of https://github.com/langgenius/dify.git
feat: Allow Editor role to use Trigger Plugin subscriptions (#29292)
This commit is contained in:
parent
ca61bb5de0
commit
97d671d9aa
|
|
@ -22,7 +22,12 @@ from services.trigger.trigger_subscription_builder_service import TriggerSubscri
|
|||
from services.trigger.trigger_subscription_operator_service import TriggerSubscriptionOperatorService
|
||||
|
||||
from .. import console_ns
|
||||
from ..wraps import account_initialization_required, is_admin_or_owner_required, setup_required
|
||||
from ..wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
is_admin_or_owner_required,
|
||||
setup_required,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -72,7 +77,7 @@ class TriggerProviderInfoApi(Resource):
|
|||
class TriggerSubscriptionListApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def get(self, provider):
|
||||
"""List all trigger subscriptions for the current tenant's provider"""
|
||||
|
|
@ -104,7 +109,7 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
|
|||
@console_ns.expect(parser)
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def post(self, provider):
|
||||
"""Add a new subscription instance for a trigger provider"""
|
||||
|
|
@ -133,6 +138,7 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
|
|||
class TriggerSubscriptionBuilderGetApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def get(self, provider, subscription_builder_id):
|
||||
"""Get a subscription instance for a trigger provider"""
|
||||
|
|
@ -155,7 +161,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
|||
@console_ns.expect(parser_api)
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def post(self, provider, subscription_builder_id):
|
||||
"""Verify a subscription instance for a trigger provider"""
|
||||
|
|
@ -200,6 +206,7 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
|||
@console_ns.expect(parser_update_api)
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def post(self, provider, subscription_builder_id):
|
||||
"""Update a subscription instance for a trigger provider"""
|
||||
|
|
@ -233,6 +240,7 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
|||
class TriggerSubscriptionBuilderLogsApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def get(self, provider, subscription_builder_id):
|
||||
"""Get the request logs for a subscription instance for a trigger provider"""
|
||||
|
|
@ -255,7 +263,7 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
|||
@console_ns.expect(parser_update_api)
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
def post(self, provider, subscription_builder_id):
|
||||
"""Build a subscription instance for a trigger provider"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,244 @@
|
|||
"""Integration tests for Trigger Provider subscription permission verification."""
|
||||
|
||||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
from controllers.console.workspace import trigger_providers as trigger_providers_api
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models import Tenant
|
||||
from models.account import Account, TenantAccountJoin, TenantAccountRole
|
||||
|
||||
|
||||
class TestTriggerProviderSubscriptionPermissions:
|
||||
"""Test permission verification for Trigger Provider subscription endpoints."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_account(self, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Create a mock Account for testing."""
|
||||
|
||||
account = Account(name="Test User", email="test@example.com")
|
||||
account.id = str(uuid.uuid4())
|
||||
account.last_active_at = naive_utc_now()
|
||||
account.created_at = naive_utc_now()
|
||||
account.updated_at = naive_utc_now()
|
||||
|
||||
# Create mock tenant
|
||||
tenant = Tenant(name="Test Tenant")
|
||||
tenant.id = str(uuid.uuid4())
|
||||
|
||||
mock_session_instance = mock.Mock()
|
||||
|
||||
mock_tenant_join = TenantAccountJoin(role=TenantAccountRole.OWNER)
|
||||
monkeypatch.setattr(mock_session_instance, "scalar", mock.Mock(return_value=mock_tenant_join))
|
||||
|
||||
mock_scalars_result = mock.Mock()
|
||||
mock_scalars_result.one.return_value = tenant
|
||||
monkeypatch.setattr(mock_session_instance, "scalars", mock.Mock(return_value=mock_scalars_result))
|
||||
|
||||
mock_session_context = mock.Mock()
|
||||
mock_session_context.__enter__.return_value = mock_session_instance
|
||||
monkeypatch.setattr("models.account.Session", lambda _, expire_on_commit: mock_session_context)
|
||||
|
||||
account.current_tenant = tenant
|
||||
account.current_tenant_id = tenant.id
|
||||
return account
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("role", "list_status", "get_status", "update_status", "create_status", "build_status", "delete_status"),
|
||||
[
|
||||
# Admin/Owner can do everything
|
||||
(TenantAccountRole.OWNER, 200, 200, 200, 200, 200, 200),
|
||||
(TenantAccountRole.ADMIN, 200, 200, 200, 200, 200, 200),
|
||||
# Editor can list, get, update (parameters), but not create, build, or delete
|
||||
(TenantAccountRole.EDITOR, 200, 200, 200, 403, 403, 403),
|
||||
# Normal user cannot do anything
|
||||
(TenantAccountRole.NORMAL, 403, 403, 403, 403, 403, 403),
|
||||
# Dataset operator cannot do anything
|
||||
(TenantAccountRole.DATASET_OPERATOR, 403, 403, 403, 403, 403, 403),
|
||||
],
|
||||
)
|
||||
def test_trigger_subscription_permissions(
|
||||
self,
|
||||
test_client: FlaskClient,
|
||||
auth_header,
|
||||
monkeypatch,
|
||||
mock_account,
|
||||
role: TenantAccountRole,
|
||||
list_status: int,
|
||||
get_status: int,
|
||||
update_status: int,
|
||||
create_status: int,
|
||||
build_status: int,
|
||||
delete_status: int,
|
||||
):
|
||||
"""Test that different roles have appropriate permissions for trigger subscription operations."""
|
||||
# Set user role
|
||||
mock_account.role = role
|
||||
|
||||
# Mock current user
|
||||
monkeypatch.setattr(trigger_providers_api, "current_user", mock_account)
|
||||
|
||||
# Mock AccountService.load_user to prevent authentication issues
|
||||
from services.account_service import AccountService
|
||||
|
||||
mock_load_user = mock.Mock(return_value=mock_account)
|
||||
monkeypatch.setattr(AccountService, "load_user", mock_load_user)
|
||||
|
||||
# Test data
|
||||
provider = "some_provider/some_trigger"
|
||||
subscription_builder_id = str(uuid.uuid4())
|
||||
subscription_id = str(uuid.uuid4())
|
||||
|
||||
# Mock service methods
|
||||
mock_list_subscriptions = mock.Mock(return_value=[])
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_provider_service.TriggerProviderService.list_trigger_provider_subscriptions",
|
||||
mock_list_subscriptions,
|
||||
)
|
||||
|
||||
mock_get_subscription_builder = mock.Mock(return_value={"id": subscription_builder_id})
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_subscription_builder_service.TriggerSubscriptionBuilderService.get_subscription_builder_by_id",
|
||||
mock_get_subscription_builder,
|
||||
)
|
||||
|
||||
mock_update_subscription_builder = mock.Mock(return_value={"id": subscription_builder_id})
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_subscription_builder_service.TriggerSubscriptionBuilderService.update_trigger_subscription_builder",
|
||||
mock_update_subscription_builder,
|
||||
)
|
||||
|
||||
mock_create_subscription_builder = mock.Mock(return_value={"id": subscription_builder_id})
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_subscription_builder_service.TriggerSubscriptionBuilderService.create_trigger_subscription_builder",
|
||||
mock_create_subscription_builder,
|
||||
)
|
||||
|
||||
mock_update_and_build_builder = mock.Mock()
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_subscription_builder_service.TriggerSubscriptionBuilderService.update_and_build_builder",
|
||||
mock_update_and_build_builder,
|
||||
)
|
||||
|
||||
mock_delete_provider = mock.Mock()
|
||||
mock_delete_plugin_trigger = mock.Mock()
|
||||
mock_db_session = mock.Mock()
|
||||
mock_db_session.commit = mock.Mock()
|
||||
|
||||
def mock_session_func(engine=None):
|
||||
return mock_session_context
|
||||
|
||||
mock_session_context = mock.Mock()
|
||||
mock_session_context.__enter__.return_value = mock_db_session
|
||||
mock_session_context.__exit__.return_value = None
|
||||
|
||||
monkeypatch.setattr("services.trigger.trigger_provider_service.Session", mock_session_func)
|
||||
monkeypatch.setattr("services.trigger.trigger_subscription_operator_service.Session", mock_session_func)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_provider_service.TriggerProviderService.delete_trigger_provider",
|
||||
mock_delete_provider,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_subscription_operator_service.TriggerSubscriptionOperatorService.delete_plugin_trigger_by_subscription",
|
||||
mock_delete_plugin_trigger,
|
||||
)
|
||||
|
||||
# Test 1: List subscriptions (should work for Editor, Admin, Owner)
|
||||
response = test_client.get(
|
||||
f"/console/api/workspaces/current/trigger-provider/{provider}/subscriptions/list",
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == list_status
|
||||
|
||||
# Test 2: Get subscription builder (should work for Editor, Admin, Owner)
|
||||
response = test_client.get(
|
||||
f"/console/api/workspaces/current/trigger-provider/{provider}/subscriptions/builder/{subscription_builder_id}",
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == get_status
|
||||
|
||||
# Test 3: Update subscription builder parameters (should work for Editor, Admin, Owner)
|
||||
response = test_client.post(
|
||||
f"/console/api/workspaces/current/trigger-provider/{provider}/subscriptions/builder/update/{subscription_builder_id}",
|
||||
headers=auth_header,
|
||||
json={"parameters": {"webhook_url": "https://example.com/webhook"}},
|
||||
)
|
||||
assert response.status_code == update_status
|
||||
|
||||
# Test 4: Create subscription builder (should only work for Admin, Owner)
|
||||
response = test_client.post(
|
||||
f"/console/api/workspaces/current/trigger-provider/{provider}/subscriptions/builder/create",
|
||||
headers=auth_header,
|
||||
json={"credential_type": "api_key"},
|
||||
)
|
||||
assert response.status_code == create_status
|
||||
|
||||
# Test 5: Build/activate subscription (should only work for Admin, Owner)
|
||||
response = test_client.post(
|
||||
f"/console/api/workspaces/current/trigger-provider/{provider}/subscriptions/builder/build/{subscription_builder_id}",
|
||||
headers=auth_header,
|
||||
json={"name": "Test Subscription"},
|
||||
)
|
||||
assert response.status_code == build_status
|
||||
|
||||
# Test 6: Delete subscription (should only work for Admin, Owner)
|
||||
response = test_client.post(
|
||||
f"/console/api/workspaces/current/trigger-provider/{subscription_id}/subscriptions/delete",
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == delete_status
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("role", "status"),
|
||||
[
|
||||
(TenantAccountRole.OWNER, 200),
|
||||
(TenantAccountRole.ADMIN, 200),
|
||||
# Editor should be able to access logs for debugging
|
||||
(TenantAccountRole.EDITOR, 200),
|
||||
(TenantAccountRole.NORMAL, 403),
|
||||
(TenantAccountRole.DATASET_OPERATOR, 403),
|
||||
],
|
||||
)
|
||||
def test_trigger_subscription_logs_permissions(
|
||||
self,
|
||||
test_client: FlaskClient,
|
||||
auth_header,
|
||||
monkeypatch,
|
||||
mock_account,
|
||||
role: TenantAccountRole,
|
||||
status: int,
|
||||
):
|
||||
"""Test that different roles have appropriate permissions for accessing subscription logs."""
|
||||
# Set user role
|
||||
mock_account.role = role
|
||||
|
||||
# Mock current user
|
||||
monkeypatch.setattr(trigger_providers_api, "current_user", mock_account)
|
||||
|
||||
# Mock AccountService.load_user to prevent authentication issues
|
||||
from services.account_service import AccountService
|
||||
|
||||
mock_load_user = mock.Mock(return_value=mock_account)
|
||||
monkeypatch.setattr(AccountService, "load_user", mock_load_user)
|
||||
|
||||
# Test data
|
||||
provider = "some_provider/some_trigger"
|
||||
subscription_builder_id = str(uuid.uuid4())
|
||||
|
||||
# Mock service method
|
||||
mock_list_logs = mock.Mock(return_value=[])
|
||||
monkeypatch.setattr(
|
||||
"services.trigger.trigger_subscription_builder_service.TriggerSubscriptionBuilderService.list_logs",
|
||||
mock_list_logs,
|
||||
)
|
||||
|
||||
# Test access to logs
|
||||
response = test_client.get(
|
||||
f"/console/api/workspaces/current/trigger-provider/{provider}/subscriptions/builder/logs/{subscription_builder_id}",
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status
|
||||
Loading…
Reference in New Issue