mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
feat: RBAC (#37107)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: fatelei <fatelei@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: 盐粒 Yanli <yanli@dify.ai> Co-authored-by: Charles Yao <chongbinyao33@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: yunlu.wen <yunlu.wen@dify.ai> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: Jingyi <jingyi.qi@dify.ai> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: Asuka Minato <i@asukaminato.eu.org> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com> Co-authored-by: gigglewang <gigglewang@dify.ai> Co-authored-by: chariri <w@chariri.moe> Co-authored-by: Evan <2869018789@qq.com> Co-authored-by: zyssyz123 <916125788@qq.com>
This commit is contained in:
co-authored by
fatelei
Copilot
autofix-ci[bot]
盐粒 Yanli
Charles Yao
Claude Opus 4.8
yunlu.wen
yyh
Jingyi
yyh
Joel
hjlarry
Asuka Minato
dependabot[bot]
Xiyuan Chen
gigglewang
chariri
Evan
zyssyz123
parent
8732d1463a
commit
33edf97f81
@@ -15,8 +15,11 @@ from pydantic import BaseModel, Field
|
||||
from controllers.common.schema import query_params_from_model, register_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
is_admin_or_owner_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user_id,
|
||||
@@ -166,6 +169,7 @@ class EndpointCollectionApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -194,6 +198,7 @@ class DeprecatedEndpointCreateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -285,6 +290,7 @@ class EndpointItemApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -304,6 +310,7 @@ class EndpointItemApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -333,6 +340,7 @@ class DeprecatedEndpointDeleteApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -363,6 +371,7 @@ class DeprecatedEndpointUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -385,6 +394,7 @@ class EndpointEnableApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@@ -412,6 +422,7 @@ class EndpointDisableApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
|
||||
@@ -32,16 +32,17 @@ from extensions.ext_redis import redis_client
|
||||
from fields.base import ResponseModel
|
||||
from fields.member_fields import AccountWithRole, AccountWithRoleList
|
||||
from libs.helper import extract_remote_ip
|
||||
from libs.login import login_required
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models.account import Account, TenantAccountJoin, TenantAccountRole
|
||||
from services.account_service import AccountService, RegisterService, TenantService
|
||||
from services.enterprise import rbac_service as enterprise_rbac_service
|
||||
from services.errors.account import AccountAlreadyInTenantError
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
|
||||
class MemberInvitePayload(BaseModel):
|
||||
emails: list[str] = Field(default_factory=list)
|
||||
role: TenantAccountRole
|
||||
role: str
|
||||
language: str | None = None
|
||||
|
||||
|
||||
@@ -107,6 +108,22 @@ def _is_role_enabled(role: TenantAccountRole | str, tenant_id: str) -> bool:
|
||||
return FeatureService.get_features(tenant_id=tenant_id, exclude_vector_space=True).dataset_operator_enabled
|
||||
|
||||
|
||||
def _serialize_member_roles(
|
||||
current_role: str | None, member_roles: list[enterprise_rbac_service.RBACRole]
|
||||
) -> list[dict[str, str]]:
|
||||
if dify_config.RBAC_ENABLED:
|
||||
return [{"id": role.id, "name": role.name} for role in member_roles]
|
||||
else:
|
||||
if current_role:
|
||||
return [{"id": current_role, "name": current_role}]
|
||||
return []
|
||||
|
||||
|
||||
def _normalize_enum_value(value: object) -> str:
|
||||
normalized = getattr(value, "value", value)
|
||||
return str(normalized) if normalized is not None else ""
|
||||
|
||||
|
||||
def _normalize_invitee_emails(emails: list[str]) -> list[str]:
|
||||
return list(dict.fromkeys(email.lower() for email in emails))
|
||||
|
||||
@@ -164,11 +181,42 @@ class MemberListApi(Resource):
|
||||
@account_initialization_required
|
||||
@console_ns.response(200, "Success", console_ns.models[AccountWithRoleList.__name__])
|
||||
@with_current_user
|
||||
def get(self, current_user: Account):
|
||||
def get(self, current_user: Account | None = None):
|
||||
if current_user is None:
|
||||
current_user, _ = current_account_with_tenant()
|
||||
if not current_user.current_tenant:
|
||||
raise ValueError("No current tenant")
|
||||
members = TenantService.get_tenant_members(current_user.current_tenant)
|
||||
member_models = TypeAdapter(list[AccountWithRole]).validate_python(members, from_attributes=True)
|
||||
if dify_config.RBAC_ENABLED:
|
||||
member_ids = [member.id for member in members]
|
||||
member_roles = enterprise_rbac_service.RBACService.MemberRoles.batch_get(
|
||||
str(current_user.current_tenant.id),
|
||||
current_user.id,
|
||||
member_ids,
|
||||
)
|
||||
roles_map = {item.account_id: item.roles for item in member_roles}
|
||||
else:
|
||||
roles_map = {}
|
||||
|
||||
serialized_members = []
|
||||
for member in members:
|
||||
current_role = _normalize_enum_value(member.current_role)
|
||||
serialized_members.append(
|
||||
{
|
||||
"id": member.id,
|
||||
"name": member.name,
|
||||
"email": member.email,
|
||||
"avatar": member.avatar,
|
||||
"last_login_at": member.last_login_at,
|
||||
"last_active_at": member.last_active_at,
|
||||
"created_at": member.created_at,
|
||||
"role": current_role,
|
||||
"roles": _serialize_member_roles(current_role, roles_map.get(member.id, [])),
|
||||
"status": _normalize_enum_value(member.status),
|
||||
}
|
||||
)
|
||||
|
||||
member_models = TypeAdapter(list[AccountWithRole]).validate_python(serialized_members)
|
||||
response = AccountWithRoleList(accounts=member_models)
|
||||
return response.model_dump(mode="json"), 200
|
||||
|
||||
@@ -190,8 +238,11 @@ class MemberInviteEmailApi(Resource):
|
||||
invitee_emails = _normalize_invitee_emails(args.emails)
|
||||
invitee_role = args.role
|
||||
interface_language = args.language
|
||||
if not TenantAccountRole.is_non_owner_role(invitee_role):
|
||||
return {"code": "invalid-role", "message": "Invalid role"}, 400
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
if not TenantAccountRole.is_valid_role(invitee_role):
|
||||
return {"code": "invalid-role", "message": "Invalid role"}, 400
|
||||
if not TenantAccountRole.is_non_owner_role(TenantAccountRole(invitee_role)):
|
||||
return {"code": "invalid-role", "message": "Invalid role"}, 400
|
||||
inviter = current_user
|
||||
if not inviter.current_tenant:
|
||||
raise ValueError("No current tenant")
|
||||
@@ -208,8 +259,9 @@ class MemberInviteEmailApi(Resource):
|
||||
|
||||
tenant_id = inviter.current_tenant.id
|
||||
with redis_client.lock(f"workspace_member_invite:{tenant_id}", timeout=60):
|
||||
new_member_count = _count_new_member_invites(tenant_id, invitee_emails)
|
||||
_check_member_invite_limits(tenant_id, new_member_count)
|
||||
if dify_config.ENTERPRISE_ENABLED is True or dify_config.BILLING_ENABLED is True:
|
||||
new_member_count = _count_new_member_invites(tenant_id, invitee_emails)
|
||||
_check_member_invite_limits(tenant_id, new_member_count)
|
||||
|
||||
for invitee_email in invitee_emails:
|
||||
try:
|
||||
|
||||
@@ -9,8 +9,11 @@ from controllers.common.fields import BinaryFileResponse, SimpleResultResponse
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
is_admin_or_owner_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -166,6 +169,7 @@ class ModelProviderCredentialApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider: str):
|
||||
@@ -191,6 +195,7 @@ class ModelProviderCredentialApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def put(self, current_tenant_id: str, provider: str):
|
||||
@@ -217,6 +222,7 @@ class ModelProviderCredentialApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def delete(self, current_tenant_id: str, provider: str):
|
||||
@@ -238,6 +244,7 @@ class ModelProviderCredentialSwitchApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider: str):
|
||||
@@ -319,6 +326,7 @@ class PreferredProviderTypeUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
|
||||
@@ -14,8 +14,11 @@ from controllers.common.schema import (
|
||||
)
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
is_admin_or_owner_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -217,6 +220,7 @@ class DefaultModelApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str):
|
||||
@@ -263,6 +267,7 @@ class ModelProviderModelApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
@@ -310,6 +315,7 @@ class ModelProviderModelApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def delete(self, tenant_id: str, provider: str):
|
||||
@@ -389,6 +395,7 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
@@ -421,6 +428,7 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def put(self, current_tenant_id: str, provider: str):
|
||||
@@ -448,6 +456,7 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def delete(self, current_tenant_id: str, provider: str):
|
||||
@@ -472,6 +481,7 @@ class ModelProviderModelCredentialSwitchApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider: str):
|
||||
@@ -498,6 +508,7 @@ class ModelProviderModelEnableApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
def patch(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
|
||||
@@ -519,6 +530,7 @@ class ModelProviderModelDisableApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
def patch(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@ from controllers.common.schema import (
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.workspace import plugin_permission_required
|
||||
from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
is_admin_or_owner_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -975,6 +978,7 @@ class PluginFetchDynamicSelectOptionsApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -1005,6 +1009,7 @@ class PluginFetchDynamicSelectOptionsWithCredentialsApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
|
||||
@@ -0,0 +1,939 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, ValidationError, field_validator
|
||||
from sqlalchemy import select
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.common.schema import register_response_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, rbac_permission_required
|
||||
from core.db.session_factory import session_factory
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models import Account
|
||||
from services.enterprise import rbac_service as svc
|
||||
|
||||
|
||||
class _RBACRoleList(svc.Paginated[svc.RBACRole]):
|
||||
pass
|
||||
|
||||
|
||||
class _RBACRoleAccountList(svc.Paginated[svc.RBACRoleAccount]):
|
||||
pass
|
||||
|
||||
|
||||
class _AccessPolicyList(svc.Paginated[svc.AccessPolicy]):
|
||||
pass
|
||||
|
||||
|
||||
class _MembersInRoleList(svc.Paginated[svc.MembersInRole]):
|
||||
pass
|
||||
|
||||
|
||||
register_response_schema_models(
|
||||
console_ns,
|
||||
svc.PermissionCatalogResponse,
|
||||
svc.RBACRole,
|
||||
_RBACRoleList,
|
||||
_RBACRoleAccountList,
|
||||
_MembersInRoleList,
|
||||
svc.AccessPolicy,
|
||||
_AccessPolicyList,
|
||||
svc.AccessPolicyBindingState,
|
||||
svc.MyPermissionsResponse,
|
||||
svc.AppAccessMatrix,
|
||||
svc.DatasetAccessMatrix,
|
||||
svc.WorkspaceAccessMatrix,
|
||||
svc.ResourceWhitelist,
|
||||
svc.ResourceUserAccessPoliciesResponse,
|
||||
svc.ReplaceUserAccessPoliciesResponse,
|
||||
svc.RoleBindingsResponse,
|
||||
svc.MemberBindingsResponse,
|
||||
svc.MemberRolesResponse,
|
||||
svc.AccessMatrixItem,
|
||||
)
|
||||
|
||||
_LEGACY_ROLE_PERMISSION_KEYS: dict[str, list[str]] = {
|
||||
# This is a compatibility projection from the pre-RBAC workspace roles into
|
||||
# the 2.0 permission matrix documented in "权限整理2.0". It intentionally
|
||||
# models the product-facing role surface for the new RBAC UI instead of the
|
||||
# legacy backend's exact hard-authorization checks.
|
||||
"owner": [
|
||||
*svc._LEGACY_WORKSPACE_OWNER_KEYS,
|
||||
*svc._LEGACY_APP_OWNER_KEYS,
|
||||
*svc._LEGACY_DATASET_OWNER_KEYS,
|
||||
],
|
||||
"admin": [
|
||||
*svc._LEGACY_WORKSPACE_ADMIN_KEYS,
|
||||
*svc._LEGACY_APP_ADMIN_KEYS,
|
||||
*svc._LEGACY_DATASET_ADMIN_KEYS,
|
||||
],
|
||||
"editor": [
|
||||
*svc._LEGACY_WORKSPACE_EDITOR_KEYS,
|
||||
*svc._LEGACY_APP_EDITOR_KEYS,
|
||||
*svc._LEGACY_DATASET_EDITOR_KEYS,
|
||||
],
|
||||
"normal": [
|
||||
*svc._LEGACY_WORKSPACE_NORMAL_KEYS,
|
||||
*svc._LEGACY_APP_NORMAL_KEYS,
|
||||
],
|
||||
"dataset_operator": [
|
||||
*svc._LEGACY_WORKSPACE_DATASET_OPERATOR_KEYS,
|
||||
*svc._LEGACY_DATASET_DATASET_OPERATOR_KEYS,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _current_ids() -> tuple[str, str]:
|
||||
"""Return ``(tenant_id, account_id)`` for the authenticated user, or
|
||||
raise a 404 when no tenant is associated with the session.
|
||||
"""
|
||||
|
||||
user, tenant_id = current_account_with_tenant()
|
||||
if not tenant_id:
|
||||
raise NotFound("Current workspace not found")
|
||||
return tenant_id, user.id
|
||||
|
||||
|
||||
def _payload(model: type[BaseModel]) -> Any:
|
||||
"""Validate the JSON body against ``model`` or raise ``ValidationError``.
|
||||
|
||||
``ValidationError`` bubbles up as HTTP 400 thanks to
|
||||
``controllers/common/helpers.py`` error handling.
|
||||
"""
|
||||
try:
|
||||
return model.model_validate(console_ns.payload or {})
|
||||
except ValidationError as exc:
|
||||
# Re-raise as-is so the upstream error handler renders a 400.
|
||||
raise exc
|
||||
|
||||
|
||||
def _dump(model: BaseModel) -> dict[str, Any]:
|
||||
return model.model_dump(mode="json")
|
||||
|
||||
|
||||
def _account_names_by_ids(account_ids: list[str]) -> dict[str, dict[str, str]]:
|
||||
ids = sorted({account_id.strip() for account_id in account_ids if account_id and account_id.strip()})
|
||||
if not ids:
|
||||
return {}
|
||||
|
||||
with session_factory.create_session() as session:
|
||||
rows = session.execute(
|
||||
select(Account.id, Account.name, Account.avatar, Account.email).where(Account.id.in_(ids))
|
||||
).all()
|
||||
|
||||
return {
|
||||
account_id: {
|
||||
"name": name or "",
|
||||
"avatar": avatar or "",
|
||||
"email": email or "",
|
||||
}
|
||||
for account_id, name, avatar, email in rows
|
||||
}
|
||||
|
||||
|
||||
def _hydrate_access_matrix_account_names(items: list[svc.AccessMatrixItem]) -> None:
|
||||
account_ids: list[str] = []
|
||||
for item in items:
|
||||
for account in item.accounts:
|
||||
account_id = account.account_id
|
||||
if account_id and not account.account_name:
|
||||
account_ids.append(account_id)
|
||||
|
||||
account_names = _account_names_by_ids(account_ids)
|
||||
if not account_names:
|
||||
return
|
||||
|
||||
for item in items:
|
||||
for account in item.accounts:
|
||||
account_id = str(account.account_id or "").strip()
|
||||
if account_id and not account.account_name:
|
||||
account.account_name = account_names.get(account_id, {}).get("name", "")
|
||||
account.avatar = account_names.get(account_id, {}).get("avatar", "")
|
||||
account.email = account_names.get(account_id, {}).get("email", "")
|
||||
|
||||
|
||||
def _hydrate_resource_user_account_names(items: list[svc.ResourceUserAccessPolicies]) -> None:
|
||||
account_names = _account_names_by_ids([item.account.account_id for item in items])
|
||||
for item in items:
|
||||
account_id = item.account.account_id
|
||||
if account_id and not item.account.account_name:
|
||||
item.account.account_name = account_names.get(account_id, {}).get("name", "")
|
||||
item.account.avatar = account_names.get(account_id, {}).get("avatar", "")
|
||||
item.account.email = account_names.get(account_id, {}).get("email", "")
|
||||
|
||||
|
||||
class _PaginationQuery(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
page_number: int | None = Field(default=None, ge=1, validation_alias=AliasChoices("page", "page_number"))
|
||||
results_per_page: int | None = Field(
|
||||
default=None, ge=1, le=99999, validation_alias=AliasChoices("limit", "results_per_page")
|
||||
)
|
||||
reverse: bool | None = None
|
||||
|
||||
def to_inner_options(self) -> svc.ListOption:
|
||||
return svc.ListOption.model_validate(self.model_dump())
|
||||
|
||||
|
||||
class _RolesListQuery(_PaginationQuery):
|
||||
include_owner: int = Field(default=0, ge=0, le=1)
|
||||
|
||||
|
||||
class CopyRoleParam(BaseModel):
|
||||
copy_member: bool = True
|
||||
|
||||
|
||||
def _pagination_options() -> svc.ListOption:
|
||||
return _PaginationQuery.model_validate(request.args.to_dict(flat=True)).to_inner_options()
|
||||
|
||||
|
||||
def _legacy_workspace_roles(
|
||||
options: svc.ListOption | None = None, *, include_owner: int = 0
|
||||
) -> svc.Paginated[svc.RBACRole]:
|
||||
"""Return the built-in legacy workspace roles in the RBAC list shape.
|
||||
|
||||
This keeps the new `/rbac/roles` endpoint compatible with the original
|
||||
Dify role model when enterprise RBAC is disabled.
|
||||
"""
|
||||
|
||||
legacy_roles = [
|
||||
svc.RBACRole(
|
||||
id=role_name,
|
||||
tenant_id="",
|
||||
type=svc.RBACRoleType.WORKSPACE.value,
|
||||
category="global_system_default",
|
||||
name=role_name,
|
||||
description="",
|
||||
is_builtin=True,
|
||||
permission_keys=list(_LEGACY_ROLE_PERMISSION_KEYS[role_name]),
|
||||
role_tag="owner" if role_name == "owner" else "",
|
||||
)
|
||||
for role_name in ("owner", "admin", "editor", "normal", "dataset_operator")
|
||||
]
|
||||
|
||||
if not include_owner:
|
||||
legacy_roles = [r for r in legacy_roles if r.name != "owner"]
|
||||
|
||||
page_number = options.page_number if options and options.page_number is not None else 1
|
||||
results_per_page = (
|
||||
options.results_per_page if options and options.results_per_page is not None else len(legacy_roles)
|
||||
)
|
||||
reverse = options.reverse if options and options.reverse is not None else False
|
||||
|
||||
ordered_roles = list(reversed(legacy_roles)) if reverse else legacy_roles
|
||||
start = max(page_number - 1, 0) * results_per_page
|
||||
end = start + results_per_page
|
||||
paged_roles = ordered_roles[start:end]
|
||||
total_count = len(legacy_roles)
|
||||
total_pages = (total_count + results_per_page - 1) // results_per_page if results_per_page > 0 else 0
|
||||
|
||||
return svc.Paginated[svc.RBACRole](
|
||||
data=paged_roles,
|
||||
pagination=svc.Pagination(
|
||||
total_count=total_count,
|
||||
per_page=results_per_page,
|
||||
current_page=page_number,
|
||||
total_pages=total_pages,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Permission catalogs.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/role-permissions/catalog")
|
||||
class RBACWorkspaceCatalogApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.PermissionCatalogResponse.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.Catalog.workspace(tenant_id, account_id))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/role-permissions/catalog/app")
|
||||
class RBACAppCatalogApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.PermissionCatalogResponse.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.Catalog.app(tenant_id, account_id))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/role-permissions/catalog/dataset")
|
||||
class RBACDatasetCatalogApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.PermissionCatalogResponse.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.Catalog.dataset(tenant_id, account_id))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Roles.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _RoleUpsertRequest(BaseModel):
|
||||
"""Accepts the payload sent by the Create/Edit Role dialog."""
|
||||
|
||||
name: str
|
||||
description: str = ""
|
||||
permission_keys: list[str] = []
|
||||
|
||||
def to_mutation(self) -> svc.RoleMutation:
|
||||
return svc.RoleMutation(
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
permission_keys=list(self.permission_keys),
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/roles")
|
||||
class RBACRolesApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[_RBACRoleList.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
query = _RolesListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
options = query.to_inner_options()
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
result = _legacy_workspace_roles(options, include_owner=query.include_owner)
|
||||
else:
|
||||
result = svc.RBACService.Roles.list(
|
||||
tenant_id, account_id, include_owner=query.include_owner, options=options
|
||||
)
|
||||
|
||||
return _dump(result)
|
||||
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(201, "Role created", console_ns.models[svc.RBACRole.__name__])
|
||||
def post(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_RoleUpsertRequest)
|
||||
role = svc.RBACService.Roles.create(tenant_id, account_id, request.to_mutation())
|
||||
return _dump(role), 201
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>")
|
||||
class RBACRoleItemApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
||||
def get(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.Roles.get(tenant_id, account_id, str(role_id)))
|
||||
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
||||
def put(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_RoleUpsertRequest)
|
||||
role = svc.RBACService.Roles.update(tenant_id, account_id, str(role_id), request.to_mutation())
|
||||
return _dump(role)
|
||||
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
||||
def delete(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
svc.RBACService.Roles.delete(tenant_id, account_id, str(role_id))
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>/copy")
|
||||
class RBACRoleCopyApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(201, "Role copied", console_ns.models[svc.RBACRole.__name__])
|
||||
def post(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(CopyRoleParam)
|
||||
role = svc.RBACService.Roles.copy(tenant_id, account_id, str(role_id), copy_member=request.copy_member)
|
||||
return _dump(role), 201
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>/members")
|
||||
class RBACRoleMembersApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[_RBACRoleAccountList.__name__])
|
||||
def get(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.Roles.members(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(role_id),
|
||||
options=_pagination_options(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Access policies (tenant-level permission sets).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _AccessPolicyCreateRequest(BaseModel):
|
||||
name: str
|
||||
resource_type: svc.RBACResourceType
|
||||
description: str = ""
|
||||
permission_keys: list[str] = []
|
||||
|
||||
|
||||
class _AccessPolicyUpdateRequest(BaseModel):
|
||||
name: str
|
||||
description: str = ""
|
||||
permission_keys: list[str] = []
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/access-policies")
|
||||
class RBACAccessPoliciesApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[_AccessPolicyList.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
# `resource_type` is exposed as a query argument so the UI can show
|
||||
# only app-scoped or only dataset-scoped permission sets.
|
||||
resource_type = request.args.get("resource_type") or None
|
||||
return _dump(
|
||||
svc.RBACService.AccessPolicies.list(
|
||||
tenant_id,
|
||||
account_id,
|
||||
resource_type=resource_type,
|
||||
options=_pagination_options(),
|
||||
)
|
||||
)
|
||||
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(201, "Policy created", console_ns.models[svc.AccessPolicy.__name__])
|
||||
def post(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_AccessPolicyCreateRequest)
|
||||
policy = svc.RBACService.AccessPolicies.create(
|
||||
tenant_id,
|
||||
account_id,
|
||||
svc.AccessPolicyCreate(
|
||||
name=request.name,
|
||||
resource_type=request.resource_type,
|
||||
description=request.description,
|
||||
permission_keys=list(request.permission_keys),
|
||||
),
|
||||
)
|
||||
return _dump(policy), 201
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/access-policies/<uuid:policy_id>")
|
||||
class RBACAccessPolicyItemApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
||||
def get(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.AccessPolicies.get(tenant_id, account_id, str(policy_id)))
|
||||
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
||||
def put(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_AccessPolicyUpdateRequest)
|
||||
policy = svc.RBACService.AccessPolicies.update(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(policy_id),
|
||||
svc.AccessPolicyUpdate(
|
||||
name=request.name,
|
||||
description=request.description,
|
||||
permission_keys=list(request.permission_keys),
|
||||
),
|
||||
)
|
||||
return _dump(policy)
|
||||
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
||||
def delete(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
svc.RBACService.AccessPolicies.delete(tenant_id, account_id, str(policy_id))
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/access-policies/<uuid:policy_id>/copy")
|
||||
class RBACAccessPolicyCopyApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(201, "Policy copied", console_ns.models[svc.AccessPolicy.__name__])
|
||||
def post(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
policy = svc.RBACService.AccessPolicies.copy(tenant_id, account_id, str(policy_id))
|
||||
return _dump(policy), 201
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/access-policy-bindings/<uuid:binding_id>/lock")
|
||||
class RBACAccessPolicyBindingLockApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicyBindingState.__name__])
|
||||
def put(self, binding_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.AccessPolicyBindings.lock(tenant_id, account_id, str(binding_id)))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/access-policy-bindings/<uuid:binding_id>/unlock")
|
||||
class RBACAccessPolicyBindingUnlockApi(Resource):
|
||||
@login_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicyBindingState.__name__])
|
||||
def put(self, binding_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.AccessPolicyBindings.unlock(tenant_id, account_id, str(binding_id)))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-app access (App Access Config).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _AccessScope(StrEnum):
|
||||
ALL = "all"
|
||||
SPECIFIC = "specific"
|
||||
ONLY_ME = "only_me"
|
||||
|
||||
|
||||
class _ResourceAccessScopeRequest(BaseModel):
|
||||
scope: _AccessScope
|
||||
|
||||
|
||||
class _ReplaceBindingsRequest(BaseModel):
|
||||
role_ids: list[str] = Field(default_factory=list)
|
||||
account_ids: list[str] = Field(default_factory=list)
|
||||
|
||||
@field_validator("role_ids", "account_ids", mode="before")
|
||||
@classmethod
|
||||
def _coerce_bindings(cls, value: Any) -> list[str]:
|
||||
if value is None:
|
||||
return []
|
||||
return value
|
||||
|
||||
|
||||
class _DeleteMemberBindingsRequest(BaseModel):
|
||||
account_ids: list[str] = Field(default_factory=list)
|
||||
|
||||
@field_validator("account_ids", mode="before")
|
||||
@classmethod
|
||||
def _coerce_account_ids(cls, value: Any) -> list[str]:
|
||||
if value is None:
|
||||
return []
|
||||
return value
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/my-permissions")
|
||||
class RBACMyPermissionsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MyPermissionsResponse.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.MyPermissions.get(
|
||||
tenant_id,
|
||||
account_id,
|
||||
app_id=request.args.get("app_id") or None,
|
||||
dataset_id=request.args.get("dataset_id") or None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/access-policy")
|
||||
class RBACAppMatrixApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AppAccessMatrix.__name__])
|
||||
def get(self, app_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
result = svc.RBACService.AppAccess.matrix(tenant_id, account_id, str(app_id))
|
||||
_hydrate_access_matrix_account_names(result.items)
|
||||
return _dump(result)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/whitelist")
|
||||
class RBACAppWhitelistApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelist.__name__])
|
||||
def get(self, app_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.AppAccess.whitelist(tenant_id, account_id, str(app_id)))
|
||||
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelist.__name__])
|
||||
def put(self, app_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_ResourceAccessScopeRequest)
|
||||
return _dump(
|
||||
svc.RBACService.AppAccess.replace_whitelist(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(app_id),
|
||||
svc.ReplaceMemberBindings(scope=request.scope.value),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/user-access-policies")
|
||||
class RBACAppUserAccessPoliciesApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceUserAccessPoliciesResponse.__name__])
|
||||
def get(self, app_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
result = svc.RBACService.AppAccess.user_access_policies(tenant_id, account_id, str(app_id))
|
||||
_hydrate_resource_user_account_names(result.data)
|
||||
return _dump(result)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/users/<uuid:target_account_id>/access-policies")
|
||||
class RBACAppUserAccessPolicyAssignmentApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ReplaceUserAccessPoliciesResponse.__name__])
|
||||
def put(self, app_id, target_account_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
payload = _payload(svc.ReplaceUserAccessPolicies)
|
||||
return _dump(
|
||||
svc.RBACService.AppAccess.replace_user_access_policies(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(app_id),
|
||||
str(target_account_id),
|
||||
payload,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/access-policies/<uuid:policy_id>/role-bindings")
|
||||
class RBACAppRoleBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
||||
def get(self, app_id, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.AppAccess.list_role_bindings(tenant_id, account_id, str(app_id), str(policy_id)))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/access-policies/<string:policy_id>/member-bindings")
|
||||
class RBACAppMemberBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||
def get(self, app_id, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.AppAccess.list_member_bindings(tenant_id, account_id, str(app_id), str(policy_id)))
|
||||
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||
def delete(self, app_id, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request_body = _payload(_DeleteMemberBindingsRequest)
|
||||
svc.RBACService.AppAccess.delete_member_bindings(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(app_id),
|
||||
str(policy_id),
|
||||
svc.DeleteMemberBindings(account_ids=request_body.account_ids),
|
||||
)
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-dataset access (Knowledge Base Access Config).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/datasets/<uuid:dataset_id>/access-policy")
|
||||
class RBACDatasetMatrixApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.DatasetAccessMatrix.__name__])
|
||||
def get(self, dataset_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
result = svc.RBACService.DatasetAccess.matrix(tenant_id, account_id, str(dataset_id))
|
||||
_hydrate_access_matrix_account_names(result.items)
|
||||
return _dump(result)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/datasets/<uuid:dataset_id>/whitelist")
|
||||
class RBACDatasetWhitelistApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelist.__name__])
|
||||
def get(self, dataset_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.DatasetAccess.whitelist(tenant_id, account_id, str(dataset_id)))
|
||||
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelist.__name__])
|
||||
def put(self, dataset_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_ResourceAccessScopeRequest)
|
||||
return _dump(
|
||||
svc.RBACService.DatasetAccess.replace_whitelist(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(dataset_id),
|
||||
svc.ReplaceMemberBindings(scope=request.scope.value),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/datasets/<uuid:dataset_id>/user-access-policies")
|
||||
class RBACDatasetUserAccessPoliciesApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceUserAccessPoliciesResponse.__name__])
|
||||
def get(self, dataset_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
result = svc.RBACService.DatasetAccess.user_access_policies(tenant_id, account_id, str(dataset_id))
|
||||
_hydrate_resource_user_account_names(result.data)
|
||||
return _dump(result)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/datasets/<uuid:dataset_id>/users/<uuid:target_account_id>/access-policies")
|
||||
class RBACDatasetUserAccessPolicyAssignmentApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.ReplaceUserAccessPoliciesResponse.__name__])
|
||||
def put(self, dataset_id, target_account_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
payload = _payload(svc.ReplaceUserAccessPolicies)
|
||||
return _dump(
|
||||
svc.RBACService.DatasetAccess.replace_user_access_policies(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(dataset_id),
|
||||
str(target_account_id),
|
||||
payload,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/datasets/<uuid:dataset_id>/access-policies/<uuid:policy_id>/role-bindings")
|
||||
class RBACDatasetRoleBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
||||
def get(self, dataset_id, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.DatasetAccess.list_role_bindings(tenant_id, account_id, str(dataset_id), str(policy_id))
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route(
|
||||
"/workspaces/current/rbac/datasets/<uuid:dataset_id>/access-policies/<string:policy_id>/member-bindings"
|
||||
)
|
||||
class RBACDatasetMemberBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||
def get(self, dataset_id, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.DatasetAccess.list_member_bindings(tenant_id, account_id, str(dataset_id), str(policy_id))
|
||||
)
|
||||
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||
def delete(self, dataset_id, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request_body = _payload(_DeleteMemberBindingsRequest)
|
||||
svc.RBACService.DatasetAccess.delete_member_bindings(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(dataset_id),
|
||||
str(policy_id),
|
||||
svc.DeleteMemberBindings(account_ids=request_body.account_ids),
|
||||
)
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Workspace-level access (Settings > Access Rules).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policy")
|
||||
class RBACWorkspaceAppMatrixApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.WorkspaceAccessMatrix.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
options = _pagination_options()
|
||||
result = svc.RBACService.WorkspaceAccess.app_matrix(tenant_id, account_id, options=options)
|
||||
_hydrate_access_matrix_account_names(result.items)
|
||||
return _dump(result)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policies/<uuid:policy_id>/role-bindings")
|
||||
class RBACWorkspaceAppRoleBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
||||
def get(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.WorkspaceAccess.list_app_role_bindings(tenant_id, account_id, str(policy_id)))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policies/<uuid:policy_id>/bindings")
|
||||
class RBACWorkspaceAppBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessMatrixItem.__name__])
|
||||
def put(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_ReplaceBindingsRequest)
|
||||
return _dump(
|
||||
svc.RBACService.WorkspaceAccess.replace_app_bindings(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(policy_id),
|
||||
svc.ReplaceBindings(role_ids=list(request.role_ids), account_ids=list(request.account_ids)),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policies/<uuid:policy_id>/member-bindings")
|
||||
class RBACWorkspaceAppMemberBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||
def get(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.WorkspaceAccess.list_app_member_bindings(tenant_id, account_id, str(policy_id)))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policy")
|
||||
class RBACWorkspaceDatasetMatrixApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.WorkspaceAccessMatrix.__name__])
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
options = _pagination_options()
|
||||
result = svc.RBACService.WorkspaceAccess.dataset_matrix(tenant_id, account_id, options=options)
|
||||
_hydrate_access_matrix_account_names(result.items)
|
||||
return _dump(result)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policies/<uuid:policy_id>/role-bindings")
|
||||
class RBACWorkspaceDatasetRoleBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
||||
def get(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.WorkspaceAccess.list_dataset_role_bindings(tenant_id, account_id, str(policy_id)))
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policies/<uuid:policy_id>/bindings")
|
||||
class RBACWorkspaceDatasetBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessMatrixItem.__name__])
|
||||
def put(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_ReplaceBindingsRequest)
|
||||
return _dump(
|
||||
svc.RBACService.WorkspaceAccess.replace_dataset_bindings(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(policy_id),
|
||||
svc.ReplaceBindings(role_ids=list(request.role_ids), account_ids=list(request.account_ids)),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policies/<uuid:policy_id>/member-bindings")
|
||||
class RBACWorkspaceDatasetMemberBindingsApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||
def get(self, policy_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.WorkspaceAccess.list_dataset_member_bindings(tenant_id, account_id, str(policy_id))
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Member ↔ role bindings (Settings > Members > Assign roles).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _ReplaceMemberRolesRequest(BaseModel):
|
||||
role_ids: list[str] = []
|
||||
|
||||
@field_validator("role_ids", mode="before")
|
||||
@classmethod
|
||||
def _coerce_role_ids(cls, value: Any) -> list[str]:
|
||||
if value is None:
|
||||
return []
|
||||
return value
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/members/<uuid:member_id>/rbac-roles")
|
||||
class RBACMemberRolesApi(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberRolesResponse.__name__])
|
||||
def get(self, member_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(svc.RBACService.MemberRoles.get(tenant_id, account_id, str(member_id)))
|
||||
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberRolesResponse.__name__])
|
||||
def put(self, member_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
request = _payload(_ReplaceMemberRolesRequest)
|
||||
return _dump(
|
||||
svc.RBACService.MemberRoles.replace(
|
||||
tenant_id,
|
||||
account_id,
|
||||
str(member_id),
|
||||
role_ids=list(request.role_ids),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>/members")
|
||||
class ListMembersByRole(Resource):
|
||||
@login_required
|
||||
@console_ns.response(200, "Success", console_ns.models[_MembersInRoleList.__name__])
|
||||
def get(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.Roles.list_members_by_role(tenant_id, role_id=role_id, options=_pagination_options())
|
||||
)
|
||||
@@ -21,8 +21,11 @@ from controllers.console.snippets.payloads import (
|
||||
UpdateSnippetPayload,
|
||||
)
|
||||
from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -151,6 +154,9 @@ class CustomizedSnippetsApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user: Account):
|
||||
@@ -213,6 +219,9 @@ class CustomizedSnippetDetailApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def patch(self, current_tenant_id: str, current_user: Account, snippet_id: str):
|
||||
@@ -257,6 +266,7 @@ class CustomizedSnippetDetailApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def delete(self, current_tenant_id: str, snippet_id: str):
|
||||
"""Delete customized snippet."""
|
||||
@@ -292,6 +302,9 @@ class CustomizedSnippetExportApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, snippet_id: str):
|
||||
"""Export snippet as DSL."""
|
||||
@@ -337,6 +350,9 @@ class CustomizedSnippetImportApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_user
|
||||
def post(self, current_user: Account):
|
||||
"""Import snippet from DSL."""
|
||||
@@ -375,6 +391,9 @@ class CustomizedSnippetImportConfirmApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_user
|
||||
def post(self, current_user: Account, import_id: str):
|
||||
"""Confirm a pending snippet import."""
|
||||
@@ -403,6 +422,9 @@ class CustomizedSnippetCheckDependenciesApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, snippet_id: str):
|
||||
"""Check dependencies for a snippet."""
|
||||
@@ -433,6 +455,9 @@ class CustomizedSnippetUseCountIncrementApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, snippet_id: str):
|
||||
"""Increment snippet use count when it is inserted into a workflow."""
|
||||
|
||||
@@ -14,9 +14,12 @@ from controllers.common.fields import BinaryFileResponse, RedirectResponse, Simp
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
enterprise_license_required,
|
||||
is_admin_or_owner_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -369,6 +372,7 @@ class ToolBuiltinProviderDeleteApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
@@ -411,6 +415,7 @@ class ToolBuiltinProviderUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -471,6 +476,7 @@ class ToolApiProviderAddApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -540,6 +546,7 @@ class ToolApiProviderUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -568,6 +575,7 @@ class ToolApiProviderDeleteApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -659,6 +667,7 @@ class ToolWorkflowProviderCreateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -686,6 +695,7 @@ class ToolWorkflowProviderUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -713,6 +723,7 @@ class ToolWorkflowProviderDeleteApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -858,6 +869,7 @@ class ToolPluginOAuthApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -959,6 +971,7 @@ class ToolBuiltinProviderSetDefaultApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider: str):
|
||||
@@ -975,6 +988,7 @@ class ToolOAuthCustomClient(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
|
||||
@@ -27,9 +27,12 @@ from services.trigger.trigger_subscription_operator_service import TriggerSubscr
|
||||
|
||||
from .. import console_ns
|
||||
from ..wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
is_admin_or_owner_required,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -143,6 +146,7 @@ class TriggerSubscriptionListApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -172,6 +176,7 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -201,6 +206,7 @@ class TriggerSubscriptionBuilderGetApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
def get(self, provider: str, subscription_builder_id: str):
|
||||
"""Get a subscription instance for a trigger provider"""
|
||||
@@ -218,6 +224,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -250,6 +257,7 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str, subscription_builder_id: str):
|
||||
@@ -282,6 +290,7 @@ class TriggerSubscriptionBuilderLogsApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
def get(self, provider: str, subscription_builder_id: str):
|
||||
"""Get the request logs for a subscription instance for a trigger provider"""
|
||||
@@ -302,6 +311,7 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@@ -336,6 +346,7 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, subscription_id: str):
|
||||
@@ -393,6 +404,7 @@ class TriggerSubscriptionDeleteApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, subscription_id: str):
|
||||
@@ -581,6 +593,7 @@ class TriggerOAuthClientManageApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, provider: str):
|
||||
@@ -626,6 +639,7 @@ class TriggerOAuthClientManageApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
@@ -650,6 +664,7 @@ class TriggerOAuthClientManageApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@is_admin_or_owner_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||
@with_current_tenant_id
|
||||
@@ -678,6 +693,7 @@ class TriggerSubscriptionVerifyApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
|
||||
Reference in New Issue
Block a user