mirror of
https://github.com/langgenius/dify.git
synced 2026-09-07 18:36:02 +08:00
feat(rbac): agent RBAC/ACL scenes and resource-locator check bundles (#41768)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
60e983c493
commit
86134ea99e
@ -23,6 +23,7 @@ from .plugin import (
|
|||||||
transform_datasource_credentials,
|
transform_datasource_credentials,
|
||||||
)
|
)
|
||||||
from .rbac import (
|
from .rbac import (
|
||||||
|
migrate_agent_permissions_to_rbac,
|
||||||
migrate_dataset_permissions_to_rbac,
|
migrate_dataset_permissions_to_rbac,
|
||||||
migrate_member_roles_to_rbac,
|
migrate_member_roles_to_rbac,
|
||||||
migrate_only_me_resource_whitelist_scopes_to_automatic_include,
|
migrate_only_me_resource_whitelist_scopes_to_automatic_include,
|
||||||
@ -81,6 +82,7 @@ __all__ = [
|
|||||||
"install_plugins",
|
"install_plugins",
|
||||||
"install_rag_pipeline_plugins",
|
"install_rag_pipeline_plugins",
|
||||||
"legacy_model_types",
|
"legacy_model_types",
|
||||||
|
"migrate_agent_permissions_to_rbac",
|
||||||
"migrate_annotation_vector_database",
|
"migrate_annotation_vector_database",
|
||||||
"migrate_data_for_plugin",
|
"migrate_data_for_plugin",
|
||||||
"migrate_dataset_permissions_to_rbac",
|
"migrate_dataset_permissions_to_rbac",
|
||||||
|
|||||||
@ -3,6 +3,8 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@ -11,12 +13,34 @@ from sqlalchemy.orm import Session
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from core.db.session_factory import session_factory
|
from core.db.session_factory import session_factory
|
||||||
from core.rbac import RBACResourceWhitelistScope
|
from core.rbac import RBACResourceWhitelistScope
|
||||||
from models import App, Dataset, DatasetPermission, DatasetPermissionEnum, TenantAccountJoin, TenantAccountRole
|
from models import (
|
||||||
from services.enterprise.rbac_service import ListOption, RBACService, ReplaceMemberBindings, ReplaceUserAccessPolicies
|
Agent,
|
||||||
|
AgentStatus,
|
||||||
|
App,
|
||||||
|
Dataset,
|
||||||
|
DatasetPermission,
|
||||||
|
DatasetPermissionEnum,
|
||||||
|
Tenant,
|
||||||
|
TenantAccountJoin,
|
||||||
|
TenantAccountRole,
|
||||||
|
)
|
||||||
|
from services.enterprise.rbac_service import (
|
||||||
|
LegacyAgentRoleMigration,
|
||||||
|
ListOption,
|
||||||
|
RBACResourceType,
|
||||||
|
RBACService,
|
||||||
|
ReplaceMemberBindings,
|
||||||
|
ReplaceUserAccessPolicies,
|
||||||
|
)
|
||||||
|
|
||||||
_RBAC_DEFAULT_ACCESS_POLICY_ID = "default"
|
_RBAC_DEFAULT_ACCESS_POLICY_ID = "default"
|
||||||
_RBAC_RESOURCE_ACCESS_POLICY_BATCH_SIZE = 500
|
_RBAC_RESOURCE_ACCESS_POLICY_BATCH_SIZE = 500
|
||||||
|
|
||||||
|
_AGENT_MIGRATION_STATE_LABEL = {
|
||||||
|
False: "would change",
|
||||||
|
True: "changed",
|
||||||
|
}
|
||||||
|
|
||||||
_LEGACY_ROLE_TO_BUILTIN_TAG = {
|
_LEGACY_ROLE_TO_BUILTIN_TAG = {
|
||||||
TenantAccountRole.OWNER.value: "owner",
|
TenantAccountRole.OWNER.value: "owner",
|
||||||
TenantAccountRole.ADMIN.value: "admin",
|
TenantAccountRole.ADMIN.value: "admin",
|
||||||
@ -67,6 +91,88 @@ def _resolve_builtin_role_id(tenant_id: str, operator_account_id: str, legacy_ro
|
|||||||
return _resolve_builtin_role_ids(tenant_id, operator_account_id)[legacy_role]
|
return _resolve_builtin_role_ids(tenant_id, operator_account_id)[legacy_role]
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_tenant_ids(tenant_id: str | None, *, batch_size: int) -> Iterator[str]:
|
||||||
|
if tenant_id:
|
||||||
|
yield tenant_id
|
||||||
|
return
|
||||||
|
last_id: str | None = None
|
||||||
|
while True:
|
||||||
|
with session_factory.create_session() as session:
|
||||||
|
stmt = select(Tenant.id).order_by(Tenant.id.asc()).limit(batch_size)
|
||||||
|
if last_id is not None:
|
||||||
|
stmt = stmt.where(Tenant.id > last_id)
|
||||||
|
rows = session.execute(stmt).scalars().all()
|
||||||
|
if not rows:
|
||||||
|
return
|
||||||
|
for row in rows:
|
||||||
|
yield str(row)
|
||||||
|
last_id = str(rows[-1])
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_agent_migration_event(payload: dict[str, object]) -> None:
|
||||||
|
click.echo(json.dumps(payload, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _AgentMigrationEventKind:
|
||||||
|
event_stem: str
|
||||||
|
include_tenant_id: bool = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skipped(self) -> str:
|
||||||
|
return f"{self.event_stem}_skipped"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def failed(self) -> str:
|
||||||
|
return f"{self.event_stem}_failed"
|
||||||
|
|
||||||
|
def outcome(self, *, apply: bool) -> str:
|
||||||
|
return f"{self.event_stem}_applied" if apply else f"{self.event_stem}_proposed_change"
|
||||||
|
|
||||||
|
|
||||||
|
_AGENT_ROLE_MIGRATION_EVENT_KIND = _AgentMigrationEventKind(event_stem="agent_manage_role_migration")
|
||||||
|
_AGENT_ROLE_TEMPLATE_MIGRATION_EVENT_KIND = _AgentMigrationEventKind(
|
||||||
|
event_stem="agent_manage_role_template_migration", include_tenant_id=False
|
||||||
|
)
|
||||||
|
_AGENT_ACCESS_BOOTSTRAP_EVENT_KIND = _AgentMigrationEventKind(event_stem="agent_access_bootstrap")
|
||||||
|
|
||||||
|
_AGENT_BACKING_APP_SPECIFIC_WHITELIST_EVENT = "agent_backing_app_has_specific_whitelist"
|
||||||
|
_AGENT_ACCESS_BOOTSTRAP_MEMBER_SOURCE = "workspace_members"
|
||||||
|
|
||||||
|
|
||||||
|
class _AgentAccessBootstrapReason(StrEnum):
|
||||||
|
ALREADY_INITIALIZED = "already_initialized"
|
||||||
|
MISSING_WHITELIST_SCOPE = "missing_whitelist_scope"
|
||||||
|
NO_CREATOR = "no_creator"
|
||||||
|
|
||||||
|
|
||||||
|
def _agent_manage_role_event(
|
||||||
|
tenant_id: str | None,
|
||||||
|
entry: LegacyAgentRoleMigration,
|
||||||
|
*,
|
||||||
|
apply: bool,
|
||||||
|
kind: _AgentMigrationEventKind,
|
||||||
|
) -> dict[str, object]:
|
||||||
|
base: dict[str, object] = {
|
||||||
|
"dry_run": not apply,
|
||||||
|
"role_id": entry.role_id,
|
||||||
|
"role_name": entry.role_name,
|
||||||
|
}
|
||||||
|
if kind.include_tenant_id:
|
||||||
|
base["tenant_id"] = tenant_id
|
||||||
|
if entry.skipped:
|
||||||
|
return {**base, "event": kind.skipped, "reason": entry.skipped}
|
||||||
|
return {
|
||||||
|
**base,
|
||||||
|
"event": kind.outcome(apply=apply),
|
||||||
|
"after": {
|
||||||
|
"added_keys": entry.added_keys,
|
||||||
|
"removed_keys": entry.removed_keys,
|
||||||
|
"bound_policies": entry.bound_policies,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _iter_tenant_member_batches(
|
def _iter_tenant_member_batches(
|
||||||
tenant_id: str | None,
|
tenant_id: str | None,
|
||||||
*,
|
*,
|
||||||
@ -1005,3 +1111,302 @@ def migrate_dataset_permissions_to_rbac(
|
|||||||
fg="green",
|
fg="green",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_AgentRow = tuple[str, str | None, str | None]
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_agent_row_batches(tenant_id: str, batch_size: int) -> Iterator[list[_AgentRow]]:
|
||||||
|
last_agent_id: str | None = None
|
||||||
|
while True:
|
||||||
|
with session_factory.create_session() as session:
|
||||||
|
stmt = (
|
||||||
|
select(Agent.id, Agent.created_by, Agent.backing_app_id)
|
||||||
|
.where(Agent.tenant_id == tenant_id, Agent.status != AgentStatus.ARCHIVED)
|
||||||
|
.order_by(Agent.id.asc())
|
||||||
|
.limit(batch_size)
|
||||||
|
)
|
||||||
|
if last_agent_id:
|
||||||
|
stmt = stmt.where(Agent.id > last_agent_id)
|
||||||
|
|
||||||
|
rows = list(session.execute(stmt).all())
|
||||||
|
if not rows:
|
||||||
|
return
|
||||||
|
|
||||||
|
yield [
|
||||||
|
(
|
||||||
|
str(row.id),
|
||||||
|
str(row.created_by) if row.created_by else None,
|
||||||
|
str(row.backing_app_id) if row.backing_app_id else None,
|
||||||
|
)
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
last_agent_id = str(rows[-1].id)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _AgentAccessBootstrapCounts:
|
||||||
|
changed: int = 0
|
||||||
|
already_initialized: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _AgentAccessBootstrapOptions:
|
||||||
|
tenant_id: str
|
||||||
|
agent_id: str
|
||||||
|
creator_account_id: str | None
|
||||||
|
backing_app_id: str | None
|
||||||
|
operator_account_id: str
|
||||||
|
member_batch_size: int
|
||||||
|
apply: bool
|
||||||
|
initialized: bool
|
||||||
|
|
||||||
|
|
||||||
|
def _report_backing_app_specific_whitelist(options: _AgentAccessBootstrapOptions) -> None:
|
||||||
|
if not options.backing_app_id:
|
||||||
|
return
|
||||||
|
app_config = _resource_legacy_whitelist_config(
|
||||||
|
"app",
|
||||||
|
tenant_id=options.tenant_id,
|
||||||
|
operator_account_id=options.operator_account_id,
|
||||||
|
resource_id=options.backing_app_id,
|
||||||
|
)
|
||||||
|
if _normalize_rbac_whitelist_scope(app_config.rbac_whitelist_scope) is not RBACResourceWhitelistScope.SPECIFIC:
|
||||||
|
return
|
||||||
|
_emit_agent_migration_event(
|
||||||
|
{
|
||||||
|
"event": _AGENT_BACKING_APP_SPECIFIC_WHITELIST_EVENT,
|
||||||
|
"dry_run": not options.apply,
|
||||||
|
"tenant_id": options.tenant_id,
|
||||||
|
"agent_id": options.agent_id,
|
||||||
|
"app_id": options.backing_app_id,
|
||||||
|
"backing_app_account_ids": sorted(set(app_config.account_ids)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_agent_access_rows(options: _AgentAccessBootstrapOptions) -> None:
|
||||||
|
for batch in _workspace_member_account_id_batches(options.tenant_id, options.member_batch_size):
|
||||||
|
RBACService.AgentAccess.replace_user_access_policies(
|
||||||
|
tenant_id=options.tenant_id,
|
||||||
|
account_id=options.operator_account_id,
|
||||||
|
agent_id=options.agent_id,
|
||||||
|
target_account_id=None,
|
||||||
|
payload=ReplaceUserAccessPolicies(
|
||||||
|
access_policy_ids=[_RBAC_DEFAULT_ACCESS_POLICY_ID],
|
||||||
|
account_ids=batch,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if options.creator_account_id:
|
||||||
|
RBACService.AccessPolicies.sync_creator_access_policy_member_bindings(
|
||||||
|
tenant_id=options.tenant_id,
|
||||||
|
account_id=options.creator_account_id,
|
||||||
|
resource_type=RBACResourceType.AGENT,
|
||||||
|
resource_id=options.agent_id,
|
||||||
|
)
|
||||||
|
RBACService.AgentAccess.replace_whitelist(
|
||||||
|
tenant_id=options.tenant_id,
|
||||||
|
account_id=options.operator_account_id,
|
||||||
|
agent_id=options.agent_id,
|
||||||
|
payload=ReplaceMemberBindings(automatic_include_workspace_members=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_agent_access_bootstrap_skipped(
|
||||||
|
options: _AgentAccessBootstrapOptions,
|
||||||
|
reason: _AgentAccessBootstrapReason,
|
||||||
|
) -> None:
|
||||||
|
_emit_agent_migration_event(
|
||||||
|
{
|
||||||
|
"event": _AGENT_ACCESS_BOOTSTRAP_EVENT_KIND.skipped,
|
||||||
|
"reason": reason.value,
|
||||||
|
"dry_run": not options.apply,
|
||||||
|
"tenant_id": options.tenant_id,
|
||||||
|
"agent_id": options.agent_id,
|
||||||
|
"operator_account_id": options.operator_account_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _agent_access_bootstrap_failure(
|
||||||
|
options: _AgentAccessBootstrapOptions,
|
||||||
|
exc: Exception,
|
||||||
|
) -> click.ClickException:
|
||||||
|
_emit_agent_migration_event(
|
||||||
|
{
|
||||||
|
"event": _AGENT_ACCESS_BOOTSTRAP_EVENT_KIND.failed,
|
||||||
|
"tenant_id": options.tenant_id,
|
||||||
|
"agent_id": options.agent_id,
|
||||||
|
"error": str(exc),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return click.ClickException(f"tenant {options.tenant_id} agent {options.agent_id}: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def _bootstrap_agent_access(options: _AgentAccessBootstrapOptions, counts: _AgentAccessBootstrapCounts) -> None:
|
||||||
|
if options.initialized:
|
||||||
|
counts.already_initialized += 1
|
||||||
|
_emit_agent_access_bootstrap_skipped(options, _AgentAccessBootstrapReason.ALREADY_INITIALIZED)
|
||||||
|
return
|
||||||
|
|
||||||
|
config = RBACService.AgentAccess.legacy_whitelist_config(
|
||||||
|
tenant_id=options.tenant_id,
|
||||||
|
account_id=options.operator_account_id,
|
||||||
|
agent_id=options.agent_id,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
scope = _normalize_rbac_whitelist_scope(config.rbac_whitelist_scope)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise _agent_access_bootstrap_failure(options, exc) from exc
|
||||||
|
if scope is None:
|
||||||
|
_emit_agent_access_bootstrap_skipped(options, _AgentAccessBootstrapReason.MISSING_WHITELIST_SCOPE)
|
||||||
|
return
|
||||||
|
|
||||||
|
account_ids = sorted(set(config.account_ids))
|
||||||
|
_report_backing_app_specific_whitelist(options)
|
||||||
|
|
||||||
|
if options.apply:
|
||||||
|
try:
|
||||||
|
_write_agent_access_rows(options)
|
||||||
|
except Exception as exc:
|
||||||
|
raise _agent_access_bootstrap_failure(options, exc) from exc
|
||||||
|
counts.changed += 1
|
||||||
|
|
||||||
|
event: dict[str, object] = {
|
||||||
|
"event": _AGENT_ACCESS_BOOTSTRAP_EVENT_KIND.outcome(apply=options.apply),
|
||||||
|
"dry_run": not options.apply,
|
||||||
|
"tenant_id": options.tenant_id,
|
||||||
|
"agent_id": options.agent_id,
|
||||||
|
"operator_account_id": options.operator_account_id,
|
||||||
|
"before": {"rbac_whitelist_scope": scope.value, "whitelist_account_ids": account_ids},
|
||||||
|
"after": {
|
||||||
|
"automatic_include_workspace_members": True,
|
||||||
|
"default_policy_member_source": _AGENT_ACCESS_BOOTSTRAP_MEMBER_SOURCE,
|
||||||
|
"creator_access_policy_synced": bool(options.creator_account_id),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if not options.creator_account_id:
|
||||||
|
event["reason"] = _AgentAccessBootstrapReason.NO_CREATOR.value
|
||||||
|
_emit_agent_migration_event(event)
|
||||||
|
|
||||||
|
|
||||||
|
def _bootstrap_tenant_agent_access(
|
||||||
|
tenant_id: str,
|
||||||
|
*,
|
||||||
|
agent_batch_size: int,
|
||||||
|
member_batch_size: int,
|
||||||
|
apply: bool,
|
||||||
|
counts: _AgentAccessBootstrapCounts,
|
||||||
|
) -> None:
|
||||||
|
owner_account_id: str | None = None
|
||||||
|
for rows in _iter_agent_row_batches(tenant_id, agent_batch_size):
|
||||||
|
initialized_agent_ids = set(
|
||||||
|
RBACService.Migrations.list_configured_agent_ids(tenant_id, [agent_id for agent_id, _, _ in rows])
|
||||||
|
)
|
||||||
|
for agent_id, creator_account_id, backing_app_id in rows:
|
||||||
|
if creator_account_id:
|
||||||
|
operator_account_id = creator_account_id
|
||||||
|
else:
|
||||||
|
if owner_account_id is None:
|
||||||
|
with session_factory.create_session() as session:
|
||||||
|
owner_account_id = _owner_account_id(tenant_id, session=session)
|
||||||
|
operator_account_id = owner_account_id
|
||||||
|
_bootstrap_agent_access(
|
||||||
|
_AgentAccessBootstrapOptions(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
creator_account_id=creator_account_id,
|
||||||
|
backing_app_id=backing_app_id,
|
||||||
|
operator_account_id=operator_account_id,
|
||||||
|
member_batch_size=member_batch_size,
|
||||||
|
apply=apply,
|
||||||
|
initialized=agent_id in initialized_agent_ids,
|
||||||
|
),
|
||||||
|
counts,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(
|
||||||
|
"rbac-migrate-agent-permissions",
|
||||||
|
help=(
|
||||||
|
"Upgrade step for agent RBAC. Phase 1 asks the RBAC service to replace agent.manage on every "
|
||||||
|
"custom role with agent.create plus the agent.full_access binding. Phase 2 bootstraps the access "
|
||||||
|
"rows pre-existing agents never got, so they stay visible to workspace members. Agents that "
|
||||||
|
"already have a whitelist scope row are skipped. Dry run by default."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@click.option("--tenant-id", help="Only migrate a single workspace.")
|
||||||
|
@click.option(
|
||||||
|
"--batch-size",
|
||||||
|
default=500,
|
||||||
|
show_default=True,
|
||||||
|
type=click.IntRange(min=1),
|
||||||
|
help="Tenants fetched per database batch.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--agent-batch-size",
|
||||||
|
default=500,
|
||||||
|
show_default=True,
|
||||||
|
type=click.IntRange(min=1),
|
||||||
|
help="Agents fetched per database batch.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--member-batch-size",
|
||||||
|
default=_RBAC_RESOURCE_ACCESS_POLICY_BATCH_SIZE,
|
||||||
|
show_default=True,
|
||||||
|
type=click.IntRange(min=1),
|
||||||
|
help="Workspace members written per default-policy call when bootstrapping an agent.",
|
||||||
|
)
|
||||||
|
@click.option("--apply", is_flag=True, default=False, help="Write changes. Without it nothing is written.")
|
||||||
|
def migrate_agent_permissions_to_rbac(
|
||||||
|
tenant_id: str | None,
|
||||||
|
batch_size: int,
|
||||||
|
agent_batch_size: int,
|
||||||
|
member_batch_size: int,
|
||||||
|
apply: bool,
|
||||||
|
) -> None:
|
||||||
|
click.echo(click.style("Starting agent RBAC migration: custom roles holding agent.manage.", fg="green"))
|
||||||
|
tenant_count = 0
|
||||||
|
role_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
seen_template_ids: set[str] = set()
|
||||||
|
template_count = 0
|
||||||
|
agent_counts = _AgentAccessBootstrapCounts()
|
||||||
|
for workspace_id in _iter_tenant_ids(tenant_id, batch_size=batch_size):
|
||||||
|
tenant_count += 1
|
||||||
|
try:
|
||||||
|
report = RBACService.Migrations.migrate_agent_manage_roles(workspace_id, apply=apply)
|
||||||
|
except Exception as exc:
|
||||||
|
raise click.ClickException(f"tenant {workspace_id}: {exc}") from exc
|
||||||
|
for entry in report.roles:
|
||||||
|
role_count += 1
|
||||||
|
if entry.skipped:
|
||||||
|
skipped_count += 1
|
||||||
|
_emit_agent_migration_event(
|
||||||
|
_agent_manage_role_event(workspace_id, entry, apply=apply, kind=_AGENT_ROLE_MIGRATION_EVENT_KIND)
|
||||||
|
)
|
||||||
|
for template in report.role_templates:
|
||||||
|
if template.role_id in seen_template_ids:
|
||||||
|
continue
|
||||||
|
seen_template_ids.add(template.role_id)
|
||||||
|
template_count += 1
|
||||||
|
_emit_agent_migration_event(
|
||||||
|
_agent_manage_role_event(None, template, apply=apply, kind=_AGENT_ROLE_TEMPLATE_MIGRATION_EVENT_KIND)
|
||||||
|
)
|
||||||
|
_bootstrap_tenant_agent_access(
|
||||||
|
workspace_id,
|
||||||
|
agent_batch_size=agent_batch_size,
|
||||||
|
member_batch_size=member_batch_size,
|
||||||
|
apply=apply,
|
||||||
|
counts=agent_counts,
|
||||||
|
)
|
||||||
|
role_state = _AGENT_MIGRATION_STATE_LABEL[apply]
|
||||||
|
click.echo(
|
||||||
|
f"{tenant_count} tenant(s), {role_count} role(s) {role_state}, {skipped_count} skipped, "
|
||||||
|
f"{template_count} template(s) {role_state}, "
|
||||||
|
f"{agent_counts.changed} agent(s) {role_state}, "
|
||||||
|
f"{agent_counts.already_initialized} already initialised"
|
||||||
|
)
|
||||||
|
if not apply:
|
||||||
|
click.echo(click.style("Dry run: no changes written. Re-run with --apply.", fg="yellow"))
|
||||||
|
|||||||
29
api/controllers/common/rbac/__init__.py
Normal file
29
api/controllers/common/rbac/__init__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from core.rbac import RBACPermission, RBACResourceScope
|
||||||
|
|
||||||
|
from .checks import RBAC_CHECKS_ATTR, RBACCheck, enforce_rbac_checks
|
||||||
|
from .locators import (
|
||||||
|
AgentBehindApp,
|
||||||
|
AgentId,
|
||||||
|
DatasetByPipeline,
|
||||||
|
DatasetId,
|
||||||
|
PlainApp,
|
||||||
|
ResourceIdentity,
|
||||||
|
ResourceLocator,
|
||||||
|
Workspace,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"RBAC_CHECKS_ATTR",
|
||||||
|
"AgentBehindApp",
|
||||||
|
"AgentId",
|
||||||
|
"DatasetByPipeline",
|
||||||
|
"DatasetId",
|
||||||
|
"PlainApp",
|
||||||
|
"RBACCheck",
|
||||||
|
"RBACPermission",
|
||||||
|
"RBACResourceScope",
|
||||||
|
"ResourceIdentity",
|
||||||
|
"ResourceLocator",
|
||||||
|
"Workspace",
|
||||||
|
"enforce_rbac_checks",
|
||||||
|
]
|
||||||
65
api/controllers/common/rbac/checks.py
Normal file
65
api/controllers/common/rbac/checks.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from collections.abc import Mapping, Sequence
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
|
from configs import dify_config
|
||||||
|
from core.rbac import RBACPermission, RBACResourceScope
|
||||||
|
from services.enterprise.rbac_service import RBACService
|
||||||
|
|
||||||
|
from .locators import ResourceLocator, Workspace
|
||||||
|
|
||||||
|
__all__ = ["RBAC_CHECKS_ATTR", "RBACCheck", "enforce_rbac_checks"]
|
||||||
|
|
||||||
|
RBAC_CHECKS_ATTR = "rbac_checks"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RBACCheck:
|
||||||
|
scene: RBACPermission
|
||||||
|
locator: ResourceLocator
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if isinstance(self.locator, Workspace) or self.scene.scope is RBACResourceScope.WORKSPACE:
|
||||||
|
return
|
||||||
|
if self.locator.scope is not self.scene.scope:
|
||||||
|
raise ValueError(
|
||||||
|
f"{self.scene} is a {self.scene.scope} scene but {self.locator!r} locates {self.locator.scope}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def enforce_rbac_checks(
|
||||||
|
*,
|
||||||
|
tenant_id: str,
|
||||||
|
account_id: str,
|
||||||
|
checks: Sequence[RBACCheck],
|
||||||
|
path_args: Mapping[str, object] | None = None,
|
||||||
|
) -> None:
|
||||||
|
if not dify_config.RBAC_ENABLED:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
view_args = request.view_args or {}
|
||||||
|
except RuntimeError:
|
||||||
|
view_args = {}
|
||||||
|
merged: dict[str, object] = {**view_args, **(path_args or {})}
|
||||||
|
applicable = [
|
||||||
|
(check, identity) for check in checks if (identity := check.locator.locate(tenant_id, merged)) is not None
|
||||||
|
]
|
||||||
|
if not applicable:
|
||||||
|
raise NotFound()
|
||||||
|
for check, identity in applicable:
|
||||||
|
is_workspace = identity.scope is RBACResourceScope.WORKSPACE
|
||||||
|
owner = None if is_workspace else check.locator.owner_id(tenant_id, identity)
|
||||||
|
if owner is not None and owner == account_id:
|
||||||
|
return
|
||||||
|
allowed = RBACService.CheckAccess.check(
|
||||||
|
tenant_id,
|
||||||
|
account_id,
|
||||||
|
scene=check.scene,
|
||||||
|
resource_type=None if is_workspace else identity.scope,
|
||||||
|
resource_id=None if is_workspace else identity.id,
|
||||||
|
)
|
||||||
|
if allowed:
|
||||||
|
return
|
||||||
|
raise Forbidden()
|
||||||
144
api/controllers/common/rbac/locators.py
Normal file
144
api/controllers/common/rbac/locators.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
from collections.abc import Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Protocol, override, runtime_checkable
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from core.rbac import RBACResourceScope
|
||||||
|
from services.rbac_resource_service import RBACResourceService
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from models.agent import Agent
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AgentBehindApp",
|
||||||
|
"AgentId",
|
||||||
|
"DatasetByPipeline",
|
||||||
|
"DatasetId",
|
||||||
|
"PlainApp",
|
||||||
|
"ResourceIdentity",
|
||||||
|
"ResourceLocator",
|
||||||
|
"Workspace",
|
||||||
|
"agent_binding",
|
||||||
|
]
|
||||||
|
|
||||||
|
_AGENT_BINDING_CACHE_KEY = "_rbac_agent_bindings"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ResourceIdentity:
|
||||||
|
scope: RBACResourceScope
|
||||||
|
id: str
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class ResourceLocator(Protocol):
|
||||||
|
scope: RBACResourceScope
|
||||||
|
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None: ...
|
||||||
|
|
||||||
|
def owner_id(self, tenant_id: str, identity: ResourceIdentity) -> str | None: ...
|
||||||
|
|
||||||
|
|
||||||
|
def _required(path_args: Mapping[str, object], param: str) -> str:
|
||||||
|
value = path_args.get(param)
|
||||||
|
if not value:
|
||||||
|
raise ValueError(f"Missing {param} in request path")
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def agent_binding(tenant_id: str, app_id: str) -> "Agent | None":
|
||||||
|
cache: dict[tuple[str, str], Agent | None] = g.setdefault(_AGENT_BINDING_CACHE_KEY, {})
|
||||||
|
key = (tenant_id, app_id)
|
||||||
|
if key not in cache:
|
||||||
|
cache[key] = RBACResourceService.get_app_agent_binding(tenant_id, app_id)
|
||||||
|
return cache[key]
|
||||||
|
|
||||||
|
|
||||||
|
class Workspace:
|
||||||
|
scope = RBACResourceScope.WORKSPACE
|
||||||
|
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None:
|
||||||
|
return ResourceIdentity(self.scope, "")
|
||||||
|
|
||||||
|
def owner_id(self, tenant_id: str, identity: ResourceIdentity) -> str | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Workspace()"
|
||||||
|
|
||||||
|
|
||||||
|
class _ParamLocator:
|
||||||
|
scope: RBACResourceScope
|
||||||
|
default_param: str
|
||||||
|
|
||||||
|
def __init__(self, param: str | None = None):
|
||||||
|
self.param = param or self.default_param
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{type(self).__name__}({self.param!r})"
|
||||||
|
|
||||||
|
|
||||||
|
class AgentId(_ParamLocator):
|
||||||
|
scope = RBACResourceScope.AGENT
|
||||||
|
default_param = "agent_id"
|
||||||
|
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None:
|
||||||
|
return ResourceIdentity(self.scope, _required(path_args, self.param))
|
||||||
|
|
||||||
|
def owner_id(self, tenant_id: str, identity: ResourceIdentity) -> str | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class PlainApp(_ParamLocator):
|
||||||
|
scope = RBACResourceScope.APP
|
||||||
|
default_param = "app_id"
|
||||||
|
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None:
|
||||||
|
app_id = _required(path_args, self.param)
|
||||||
|
if agent_binding(tenant_id, app_id) is not None:
|
||||||
|
return None
|
||||||
|
return ResourceIdentity(self.scope, app_id)
|
||||||
|
|
||||||
|
def owner_id(self, tenant_id: str, identity: ResourceIdentity) -> str | None:
|
||||||
|
return RBACResourceService.get_app_maintainer(tenant_id, identity.id)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentBehindApp(_ParamLocator):
|
||||||
|
scope = RBACResourceScope.AGENT
|
||||||
|
default_param = "app_id"
|
||||||
|
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None:
|
||||||
|
from models.agent import AgentScope
|
||||||
|
|
||||||
|
binding = agent_binding(tenant_id, _required(path_args, self.param))
|
||||||
|
if binding is None or binding.scope == AgentScope.WORKFLOW_ONLY:
|
||||||
|
return None
|
||||||
|
return ResourceIdentity(self.scope, str(binding.id))
|
||||||
|
|
||||||
|
def owner_id(self, tenant_id: str, identity: ResourceIdentity) -> str | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DatasetId(_ParamLocator):
|
||||||
|
scope = RBACResourceScope.DATASET
|
||||||
|
default_param = "dataset_id"
|
||||||
|
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None:
|
||||||
|
return ResourceIdentity(self.scope, _required(path_args, self.param))
|
||||||
|
|
||||||
|
def owner_id(self, tenant_id: str, identity: ResourceIdentity) -> str | None:
|
||||||
|
return RBACResourceService.get_dataset_maintainer(tenant_id, identity.id)
|
||||||
|
|
||||||
|
|
||||||
|
class DatasetByPipeline(DatasetId):
|
||||||
|
default_param = "pipeline_id"
|
||||||
|
|
||||||
|
@override
|
||||||
|
def locate(self, tenant_id: str, path_args: Mapping[str, object]) -> ResourceIdentity | None:
|
||||||
|
pipeline_id = _required(path_args, self.param)
|
||||||
|
dataset_id = RBACResourceService.get_dataset_id_by_pipeline(tenant_id, pipeline_id)
|
||||||
|
if dataset_id is None:
|
||||||
|
raise NotFound("Dataset not found for pipeline")
|
||||||
|
return ResourceIdentity(self.scope, dataset_id)
|
||||||
@ -1,181 +1,32 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
from controllers.common.rbac import RBAC_CHECKS_ATTR, RBACCheck, enforce_rbac_checks
|
||||||
from extensions.ext_database import db
|
|
||||||
from libs.login import current_account_with_tenant
|
from libs.login import current_account_with_tenant
|
||||||
from models.dataset import Dataset
|
|
||||||
from models.model import App
|
|
||||||
from services.agent.roster_service import AgentRosterService
|
|
||||||
from services.enterprise.rbac_service import RBACService
|
|
||||||
|
|
||||||
__all__ = ["RBACPermission", "RBACResourceScope", "enforce_rbac_access", "rbac_permission_required"]
|
__all__ = ["rbac_permission_required"]
|
||||||
|
|
||||||
|
|
||||||
def enforce_rbac_access(
|
def rbac_permission_required[**P, R](*checks: RBACCheck) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
||||||
*,
|
if not checks:
|
||||||
tenant_id: str,
|
raise ValueError("rbac_permission_required requires at least one RBACCheck")
|
||||||
account_id: str,
|
|
||||||
resource_type: RBACResourceScope,
|
|
||||||
scene: RBACPermission,
|
|
||||||
resource_required: bool = True,
|
|
||||||
path_args: dict[str, object] | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Enforce enterprise RBAC for an explicit account/tenant pair.
|
|
||||||
|
|
||||||
This is the flask-login-independent core of the RBAC gate so it can run
|
|
||||||
inside request-handling layers that resolve the caller themselves (e.g. the
|
|
||||||
openapi auth pipeline, which has the account on ``AuthData`` before
|
|
||||||
flask-login is mounted).
|
|
||||||
|
|
||||||
No-op when ``RBAC_ENABLED`` is ``False``. For resource-scoped checks the
|
|
||||||
resource ID is taken from ``path_args`` merged with ``request.view_args``;
|
|
||||||
resource ownership short-circuits the check. Raises ``Forbidden`` when
|
|
||||||
access is denied. For workspace-level checks pass ``resource_required=False``
|
|
||||||
so the RBAC request omits ``resource_id``.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tenant_id: The tenant the access is evaluated against.
|
|
||||||
account_id: The account requesting access.
|
|
||||||
resource_type: The :class:`RBACResourceScope` member (app/dataset/workspace).
|
|
||||||
scene: The :class:`RBACPermission` permission point, e.g. ``RBACPermission.APP_DELETE``.
|
|
||||||
resource_required: Whether a concrete resource ID is required.
|
|
||||||
path_args: Extra path arguments to merge with ``request.view_args``.
|
|
||||||
"""
|
|
||||||
if not dify_config.RBAC_ENABLED:
|
|
||||||
return
|
|
||||||
|
|
||||||
check_resource_type = None if resource_type == RBACResourceScope.WORKSPACE else resource_type
|
|
||||||
resource_id = None
|
|
||||||
if resource_required and check_resource_type:
|
|
||||||
resource_id = _extract_resource_id(resource_type, tenant_id, path_args)
|
|
||||||
if _is_resource_owned_by_current_user(tenant_id, account_id, resource_type, resource_id):
|
|
||||||
return
|
|
||||||
allowed = RBACService.CheckAccess.check(
|
|
||||||
tenant_id,
|
|
||||||
account_id,
|
|
||||||
scene=scene,
|
|
||||||
resource_type=check_resource_type,
|
|
||||||
resource_id=resource_id,
|
|
||||||
)
|
|
||||||
if not allowed:
|
|
||||||
raise Forbidden()
|
|
||||||
|
|
||||||
|
|
||||||
def rbac_permission_required[**P, R](
|
|
||||||
resource_type: RBACResourceScope,
|
|
||||||
scene: RBACPermission,
|
|
||||||
*,
|
|
||||||
resource_required: bool = True,
|
|
||||||
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
||||||
"""Check enterprise RBAC permissions for the current flask-login user.
|
|
||||||
|
|
||||||
When ``RBAC_ENABLED`` is ``False`` the decorator is a no-op and the
|
|
||||||
request passes through unchanged. When enabled it resolves the current
|
|
||||||
account/tenant and delegates to :func:`enforce_rbac_access`, raising
|
|
||||||
``Forbidden`` if access is denied.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
resource_type: The :class:`RBACResourceScope` member (app/dataset/workspace).
|
|
||||||
scene: The :class:`RBACPermission` permission point, e.g. ``RBACPermission.APP_DELETE``.
|
|
||||||
resource_required: Whether a concrete resource ID is required.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(view: Callable[P, R]) -> Callable[P, R]:
|
def decorator(view: Callable[P, R]) -> Callable[P, R]:
|
||||||
@wraps(view)
|
@wraps(view, updated=())
|
||||||
def decorated(*args: P.args, **kwargs: P.kwargs) -> R:
|
def decorated(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
if not dify_config.RBAC_ENABLED:
|
if not dify_config.RBAC_ENABLED:
|
||||||
return view(*args, **kwargs)
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
current_user, current_tenant_id = current_account_with_tenant()
|
current_user, current_tenant_id = current_account_with_tenant()
|
||||||
enforce_rbac_access(
|
enforce_rbac_checks(
|
||||||
tenant_id=current_tenant_id,
|
tenant_id=current_tenant_id,
|
||||||
account_id=current_user.id,
|
account_id=current_user.id,
|
||||||
resource_type=resource_type,
|
checks=checks,
|
||||||
scene=scene,
|
|
||||||
resource_required=resource_required,
|
|
||||||
path_args=kwargs,
|
path_args=kwargs,
|
||||||
)
|
)
|
||||||
return view(*args, **kwargs)
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
|
setattr(decorated, RBAC_CHECKS_ATTR, checks)
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def _is_resource_owned_by_current_user(
|
|
||||||
tenant_id: str, account_id: str, resource_type: RBACResourceScope, resource_id: str
|
|
||||||
) -> bool:
|
|
||||||
if resource_type == RBACResourceScope.APP:
|
|
||||||
maintainer = db.session.scalar(
|
|
||||||
select(App.maintainer).where(
|
|
||||||
App.id == resource_id,
|
|
||||||
App.tenant_id == tenant_id,
|
|
||||||
App.status == "normal",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return maintainer == account_id
|
|
||||||
|
|
||||||
if resource_type == RBACResourceScope.DATASET:
|
|
||||||
maintainer = db.session.scalar(
|
|
||||||
select(Dataset.maintainer).where(
|
|
||||||
Dataset.id == resource_id,
|
|
||||||
Dataset.tenant_id == tenant_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return maintainer == account_id
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_resource_id(
|
|
||||||
resource_type: RBACResourceScope, tenant_id: str, path_args: dict[str, object] | None = None
|
|
||||||
) -> str:
|
|
||||||
"""Extract the resource ID from matched path arguments.
|
|
||||||
|
|
||||||
Some legacy route classes use neutral names such as ``resource_id`` for
|
|
||||||
app/dataset resources, and Agent routes carry ``agent_id``, which is
|
|
||||||
resolved to the App backing that Agent.
|
|
||||||
Dataset endpoints behind a rag-pipeline route contain ``pipeline_id``
|
|
||||||
instead of ``dataset_id``. In that case we look up the associated
|
|
||||||
``Dataset`` row via ``Dataset.pipeline_id``.
|
|
||||||
"""
|
|
||||||
from flask import request
|
|
||||||
|
|
||||||
view_args = request.view_args or {}
|
|
||||||
matched_args = {**view_args, **(path_args or {})}
|
|
||||||
|
|
||||||
if resource_type == RBACResourceScope.APP:
|
|
||||||
app_id = matched_args.get("app_id")
|
|
||||||
if app_id:
|
|
||||||
return str(app_id) # pyrefly: ignore[unnecessary-type-conversion]
|
|
||||||
|
|
||||||
agent_id = matched_args.get("agent_id")
|
|
||||||
if agent_id:
|
|
||||||
authz_app_id = AgentRosterService(db.session).peek_authz_app_id(tenant_id=tenant_id, agent_id=str(agent_id))
|
|
||||||
return authz_app_id or str(agent_id)
|
|
||||||
|
|
||||||
resource_id = matched_args.get("resource_id")
|
|
||||||
if resource_id:
|
|
||||||
return str(resource_id) # pyrefly: ignore[unnecessary-type-conversion]
|
|
||||||
raise ValueError("Missing app_id in request path")
|
|
||||||
|
|
||||||
if resource_type == RBACResourceScope.DATASET:
|
|
||||||
dataset_id = matched_args.get("dataset_id") or matched_args.get("resource_id")
|
|
||||||
if dataset_id:
|
|
||||||
return str(dataset_id)
|
|
||||||
|
|
||||||
pipeline_id = matched_args.get("pipeline_id")
|
|
||||||
if pipeline_id:
|
|
||||||
dataset = db.session.scalar(
|
|
||||||
select(Dataset).where(Dataset.pipeline_id == str(pipeline_id), Dataset.tenant_id == tenant_id)
|
|
||||||
)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found for pipeline")
|
|
||||||
return str(dataset.id) # pyrefly: ignore[unnecessary-type-conversion]
|
|
||||||
raise ValueError("Missing dataset_id or pipeline_id in request path")
|
|
||||||
raise ValueError(f"Unknown resource_type: {resource_type}")
|
|
||||||
|
|||||||
@ -4,13 +4,13 @@ from flask_restx import Resource
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from controllers.common.rbac import AgentId, PlainApp, RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app, get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -94,7 +94,7 @@ class WorkflowAgentComposerApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -134,7 +134,7 @@ class WorkflowAgentComposerCopyFromRosterApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -246,8 +246,8 @@ class WorkflowAgentComposerSaveToRosterApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_CREATE, Workspace()))
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -323,9 +323,7 @@ class SnippetAgentComposerApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -362,9 +360,7 @@ class SnippetAgentComposerCopyFromRosterApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -476,10 +472,8 @@ class SnippetAgentComposerSaveToRosterApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_CREATE, Workspace()))
|
||||||
)
|
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -512,7 +506,7 @@ class AgentComposerApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
||||||
@ -527,7 +521,7 @@ class AgentComposerApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -554,6 +548,7 @@ class AgentComposerValidateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(ComposerSavePayload)
|
@model_validate(ComposerSavePayload)
|
||||||
@ -575,6 +570,7 @@ class AgentComposerCandidatesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from pydantic import AliasChoices, BaseModel, Field, field_validator
|
|||||||
from sqlalchemy import func, or_, select
|
from sqlalchemy import func, or_, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from controllers.common.rbac import AgentId, RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -32,10 +33,8 @@ from controllers.console.app.app import (
|
|||||||
from controllers.console.app.app import (
|
from controllers.console.app.app import (
|
||||||
UpdateAppPayload as GenericUpdateAppPayload,
|
UpdateAppPayload as GenericUpdateAppPayload,
|
||||||
)
|
)
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app
|
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
enterprise_license_required,
|
enterprise_license_required,
|
||||||
@ -608,7 +607,7 @@ class AgentAppListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -668,7 +667,7 @@ class AgentAppListApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_CREATE, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -694,7 +693,7 @@ class AgentAppApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@enterprise_license_required
|
@enterprise_license_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -711,7 +710,7 @@ class AgentAppApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -744,7 +743,7 @@ class AgentAppApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_DELETE, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
def delete(self, session: Session, tenant_id: str, agent_id: UUID):
|
def delete(self, session: Session, tenant_id: str, agent_id: UUID):
|
||||||
@ -765,6 +764,7 @@ class AgentDebugConversationRefreshApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -790,7 +790,7 @@ class AgentPublishApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_RELEASE_AND_VERSION, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -820,7 +820,7 @@ class AgentBuildDraftCheckoutApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -850,6 +850,7 @@ class AgentBuildDraftApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -867,6 +868,7 @@ class AgentBuildDraftApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -892,6 +894,7 @@ class AgentBuildDraftApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -911,7 +914,7 @@ class AgentBuildDraftApplyApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -934,7 +937,8 @@ class AgentAppCopyApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_CREATE, Workspace()))
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -967,7 +971,7 @@ class AgentApiAccessApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_ACCESS_POINT_VIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
||||||
@ -984,7 +988,7 @@ class AgentApiStatusApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(AgentApiStatusPayload)
|
@model_validate(AgentApiStatusPayload)
|
||||||
@ -1002,7 +1006,7 @@ class AgentApiKeyListApi(BaseApiKeyListResource):
|
|||||||
token_prefix = "app-"
|
token_prefix = "app-"
|
||||||
|
|
||||||
@console_ns.response(200, "Agent service API keys", console_ns.models[ApiKeyList.__name__])
|
@console_ns.response(200, "Agent service API keys", console_ns.models[ApiKeyList.__name__])
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_ACCESS_POINT_VIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -1014,7 +1018,7 @@ class AgentApiKeyListApi(BaseApiKeyListResource):
|
|||||||
@console_ns.response(400, "Maximum keys exceeded")
|
@console_ns.response(400, "Maximum keys exceeded")
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentId()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, tenant_id: str, agent_id: UUID) -> tuple[dict[str, object], int]:
|
def post(self, session: Session, tenant_id: str, agent_id: UUID) -> tuple[dict[str, object], int]:
|
||||||
app_model = _resolve_agent_app_model(session, tenant_id=tenant_id, agent_id=agent_id)
|
app_model = _resolve_agent_app_model(session, tenant_id=tenant_id, agent_id=agent_id)
|
||||||
@ -1033,7 +1037,7 @@ class AgentApiKeyApi(BaseApiKeyResource):
|
|||||||
@console_ns.response(204, "Agent service API key deleted")
|
@console_ns.response(204, "Agent service API key deleted")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
@ -1055,6 +1059,7 @@ class AgentInviteOptionsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@model_validate(AgentInviteOptionsQuery)
|
@model_validate(AgentInviteOptionsQuery)
|
||||||
@ -1078,7 +1083,7 @@ class AgentLogsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_LOG_MANAGE, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -1117,7 +1122,7 @@ class AgentLogMessagesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_LOG_MANAGE, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -1156,7 +1161,7 @@ class AgentLogSourcesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_LOG_MANAGE, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -1177,7 +1182,7 @@ class AgentStatisticsSummaryApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_MONITOR, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -1210,7 +1215,7 @@ class AgentRosterVersionsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_RELEASE_AND_VERSION, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
||||||
@ -1226,7 +1231,7 @@ class AgentRosterVersionDetailApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_RELEASE_AND_VERSION, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, tenant_id: str, agent_id: UUID, version_id: UUID):
|
def get(self, session: Session, tenant_id: str, agent_id: UUID, version_id: UUID):
|
||||||
@ -1247,7 +1252,7 @@ class AgentRosterVersionRestoreApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.AGENT_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_RELEASE_AND_VERSION, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
|
|||||||
@ -11,9 +11,9 @@ from sqlalchemy.orm import Session
|
|||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.common.rbac import AgentBehindApp, DatasetId, PlainApp, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models
|
from controllers.common.schema import register_response_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app
|
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.helper import dump_response, to_timestamp
|
from libs.helper import dump_response, to_timestamp
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
@ -27,7 +27,6 @@ from services.app_service import AppService
|
|||||||
from . import console_ns
|
from . import console_ns
|
||||||
from .wraps import (
|
from .wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
rbac_permission_required,
|
rbac_permission_required,
|
||||||
@ -242,8 +241,10 @@ class AppApiKeyListResource(BaseApiKeyListResource):
|
|||||||
@console_ns.response(200, "API keys retrieved successfully", console_ns.models[ApiKeyList.__name__])
|
@console_ns.response(200, "API keys retrieved successfully", console_ns.models[ApiKeyList.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp("resource_id")),
|
||||||
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_VIEW, AgentBehindApp("resource_id")),
|
||||||
|
)
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, resource_id: UUID) -> dict[str, object]:
|
def get(self, session: Session, current_tenant_id: str, resource_id: UUID) -> dict[str, object]:
|
||||||
"""Get all API keys for an app"""
|
"""Get all API keys for an app"""
|
||||||
@ -259,8 +260,10 @@ class AppApiKeyListResource(BaseApiKeyListResource):
|
|||||||
@console_ns.response(400, "Maximum keys exceeded")
|
@console_ns.response(400, "Maximum keys exceeded")
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp("resource_id")),
|
||||||
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentBehindApp("resource_id")),
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_tenant_id: str, resource_id: UUID) -> tuple[dict[str, object], int]:
|
def post(self, session: Session, current_tenant_id: str, resource_id: UUID) -> tuple[dict[str, object], int]:
|
||||||
"""Create a new API key for an app"""
|
"""Create a new API key for an app"""
|
||||||
@ -283,8 +286,10 @@ class AppApiKeyResource(BaseApiKeyResource):
|
|||||||
@console_ns.response(204, "API key deleted successfully")
|
@console_ns.response(204, "API key deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp("resource_id")),
|
||||||
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentBehindApp("resource_id")),
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
@ -321,7 +326,7 @@ class DatasetApiKeyListResource(BaseApiKeyListResource):
|
|||||||
@console_ns.response(200, "API keys retrieved successfully", console_ns.models[ApiKeyList.__name__])
|
@console_ns.response(200, "API keys retrieved successfully", console_ns.models[ApiKeyList.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_API_KEY_MANAGE, DatasetId("resource_id")))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, resource_id: UUID) -> dict[str, object]:
|
def get(self, session: Session, current_tenant_id: str, resource_id: UUID) -> dict[str, object]:
|
||||||
"""Get all API keys for a dataset"""
|
"""Get all API keys for a dataset"""
|
||||||
@ -337,7 +342,7 @@ class DatasetApiKeyListResource(BaseApiKeyListResource):
|
|||||||
@console_ns.response(400, "Maximum keys exceeded")
|
@console_ns.response(400, "Maximum keys exceeded")
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_API_KEY_MANAGE, DatasetId("resource_id")))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_tenant_id: str, resource_id: UUID) -> tuple[dict[str, object], int]:
|
def post(self, session: Session, current_tenant_id: str, resource_id: UUID) -> tuple[dict[str, object], int]:
|
||||||
"""Create a new API key for a dataset"""
|
"""Create a new API key for a dataset"""
|
||||||
@ -360,7 +365,7 @@ class DatasetApiKeyResource(BaseApiKeyResource):
|
|||||||
@console_ns.response(204, "API key deleted successfully")
|
@console_ns.response(204, "API key deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_API_KEY_MANAGE, DatasetId("resource_id")))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -4,13 +4,13 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
rbac_permission_required,
|
rbac_permission_required,
|
||||||
@ -84,7 +84,7 @@ class AgentLogApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model(mode=[AppMode.AGENT_CHAT])
|
@get_app_model(mode=[AppMode.AGENT_CHAT])
|
||||||
@model_validate(AgentLogQuery)
|
@model_validate(AgentLogQuery)
|
||||||
|
|||||||
@ -11,11 +11,18 @@ from flask_restx import Resource
|
|||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from controllers.common.rbac import AgentId, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models
|
from controllers.common.schema import register_response_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
from controllers.console.wraps import (
|
||||||
|
RBACPermission,
|
||||||
|
account_initialization_required,
|
||||||
|
rbac_permission_required,
|
||||||
|
setup_required,
|
||||||
|
with_current_tenant_id,
|
||||||
|
)
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from services.agent.roster_service import AgentRosterService
|
from services.agent.roster_service import AgentRosterService
|
||||||
@ -55,6 +62,7 @@ class AgentAppReferencingWorkflowsResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
def get(self, session: Session, tenant_id: str, agent_id: UUID):
|
||||||
|
|||||||
@ -16,13 +16,13 @@ from pydantic import BaseModel, Field
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import AgentId, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
|
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -83,7 +83,7 @@ class AgentAppFeatureConfigResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from clients.agent_backend.errors import backend_error_detail, backend_reported_failure
|
from clients.agent_backend.errors import backend_error_detail, backend_reported_failure
|
||||||
|
from controllers.common.rbac import AgentId, PlainApp, RBACCheck
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -23,10 +24,9 @@ from controllers.common.schema import (
|
|||||||
)
|
)
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.error import AppNotFoundError
|
from controllers.console.app.error import AppNotFoundError
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app, get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
rbac_permission_required,
|
rbac_permission_required,
|
||||||
@ -153,7 +153,7 @@ class AgentAppSandboxInfoResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def get(self, current_user: Account, tenant_id: str, agent_id: UUID):
|
def get(self, current_user: Account, tenant_id: str, agent_id: UUID):
|
||||||
@ -183,7 +183,7 @@ class AgentAppSandboxListResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def get(self, current_user: Account, tenant_id: str, agent_id: UUID):
|
def get(self, current_user: Account, tenant_id: str, agent_id: UUID):
|
||||||
@ -214,7 +214,7 @@ class AgentAppSandboxReadResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def get(self, current_user: Account, tenant_id: str, agent_id: UUID):
|
def get(self, current_user: Account, tenant_id: str, agent_id: UUID):
|
||||||
@ -245,7 +245,7 @@ class AgentAppSandboxDownloadResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@model_validate(AgentSandboxDownloadPayload)
|
@model_validate(AgentSandboxDownloadPayload)
|
||||||
@ -289,7 +289,7 @@ class WorkflowAgentSandboxListResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, app_model: App, workflow_run_id: UUID, node_id: str):
|
def get(self, tenant_id: str, app_model: App, workflow_run_id: UUID, node_id: str):
|
||||||
@ -327,7 +327,7 @@ class WorkflowAgentSandboxReadResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, app_model: App, workflow_run_id: UUID, node_id: str):
|
def get(self, tenant_id: str, app_model: App, workflow_run_id: UUID, node_id: str):
|
||||||
@ -358,7 +358,7 @@ class WorkflowAgentSandboxDownloadResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(WorkflowAgentSandboxDownloadPayload)
|
@model_validate(WorkflowAgentSandboxDownloadPayload)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from controllers.common.rbac import AgentId, PlainApp, RBACCheck
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -27,7 +28,6 @@ from controllers.console.agent.app_helpers import resolve_agent_runtime_app_mode
|
|||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -659,6 +659,7 @@ class AgentConfigManifestByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -704,6 +705,7 @@ class AgentConfigSkillUploadByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -733,7 +735,7 @@ class AgentConfigSkillUploadApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
@ -751,6 +753,7 @@ class AgentConfigSkillsByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -789,6 +792,7 @@ class AgentConfigFilesByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -808,6 +812,7 @@ class AgentConfigFilesByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -853,7 +858,7 @@ class AgentConfigFilesApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
@ -877,6 +882,7 @@ class AgentConfigSkillInspectByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -928,6 +934,7 @@ class AgentConfigSkillFilePreviewByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -988,6 +995,7 @@ class AgentConfigSkillDownloadByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1037,6 +1045,7 @@ class AgentConfigSkillFileDownloadByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1106,6 +1115,7 @@ class AgentConfigSkillFileDownloadContentByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1158,6 +1168,7 @@ class AgentConfigSkillByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1182,7 +1193,7 @@ class AgentConfigSkillApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
@ -1205,6 +1216,7 @@ class AgentConfigFilePreviewByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1250,6 +1262,7 @@ class AgentConfigFileDownloadByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_PREVIEW, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1295,6 +1308,7 @@ class AgentConfigFileByAgentApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_EDIT, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1319,7 +1333,7 @@ class AgentConfigFileApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
|
|||||||
@ -9,12 +9,12 @@ from sqlalchemy.orm import Session
|
|||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.common.errors import NoFileUploadedError, TooManyFilesError
|
from controllers.common.errors import NoFileUploadedError, TooManyFilesError
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
annotation_import_concurrency_limit,
|
annotation_import_concurrency_limit,
|
||||||
annotation_import_rate_limit,
|
annotation_import_rate_limit,
|
||||||
@ -180,7 +180,7 @@ class AnnotationReplyActionApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("annotation")
|
@cloud_edition_billing_resource_check("annotation")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@model_validate(AnnotationReplyPayload)
|
@model_validate(AnnotationReplyPayload)
|
||||||
def post(self, req_data: AnnotationReplyPayload, app_id: UUID, action: Literal["enable", "disable"]):
|
def post(self, req_data: AnnotationReplyPayload, app_id: UUID, action: Literal["enable", "disable"]):
|
||||||
match action:
|
match action:
|
||||||
@ -211,7 +211,7 @@ class AppAnnotationSettingDetailApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, app_id: UUID):
|
def get(self, session: Session, app_id: UUID):
|
||||||
result = AppAnnotationService.get_app_annotation_setting_by_app_id(str(app_id), session)
|
result = AppAnnotationService.get_app_annotation_setting_by_app_id(str(app_id), session)
|
||||||
@ -230,7 +230,7 @@ class AppAnnotationSettingUpdateApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(AnnotationSettingUpdatePayload)
|
@model_validate(AnnotationSettingUpdatePayload)
|
||||||
def post(
|
def post(
|
||||||
@ -263,7 +263,7 @@ class AnnotationReplyActionStatusApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("annotation")
|
@cloud_edition_billing_resource_check("annotation")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, app_id: UUID, job_id: UUID, action: str):
|
def get(self, app_id: UUID, job_id: UUID, action: str):
|
||||||
job_id_str = str(job_id)
|
job_id_str = str(job_id)
|
||||||
app_annotation_job_key = f"{action}_app_annotation_job_{job_id_str}"
|
app_annotation_job_key = f"{action}_app_annotation_job_{job_id_str}"
|
||||||
@ -294,7 +294,7 @@ class AnnotationApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@model_validate(AnnotationListQuery)
|
@model_validate(AnnotationListQuery)
|
||||||
def get(self, req_data: AnnotationListQuery, session: Session, app_id: UUID):
|
def get(self, req_data: AnnotationListQuery, session: Session, app_id: UUID):
|
||||||
@ -321,7 +321,7 @@ class AnnotationApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("annotation")
|
@cloud_edition_billing_resource_check("annotation")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(CreateAnnotationPayload)
|
@model_validate(CreateAnnotationPayload)
|
||||||
def post(self, req_data: CreateAnnotationPayload, session: Session, app_id: UUID):
|
def post(self, req_data: CreateAnnotationPayload, session: Session, app_id: UUID):
|
||||||
@ -341,7 +341,7 @@ class AnnotationApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp()))
|
||||||
@console_ns.response(204, "Annotations deleted successfully")
|
@console_ns.response(204, "Annotations deleted successfully")
|
||||||
@with_session
|
@with_session
|
||||||
def delete(self, session: Session, app_id: UUID):
|
def delete(self, session: Session, app_id: UUID):
|
||||||
@ -382,7 +382,7 @@ class AnnotationExportApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, app_id: UUID):
|
def get(self, session: Session, app_id: UUID):
|
||||||
annotation_list = AppAnnotationService.export_annotation_list_by_app_id(str(app_id), session)
|
annotation_list = AppAnnotationService.export_annotation_list_by_app_id(str(app_id), session)
|
||||||
@ -411,7 +411,7 @@ class AnnotationUpdateDeleteApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("annotation")
|
@cloud_edition_billing_resource_check("annotation")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(UpdateAnnotationPayload)
|
@model_validate(UpdateAnnotationPayload)
|
||||||
def post(self, req_data: UpdateAnnotationPayload, session: Session, app_id: UUID, annotation_id: UUID):
|
def post(self, req_data: UpdateAnnotationPayload, session: Session, app_id: UUID, annotation_id: UUID):
|
||||||
@ -429,7 +429,7 @@ class AnnotationUpdateDeleteApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@console_ns.response(204, "Annotation deleted successfully")
|
@console_ns.response(204, "Annotation deleted successfully")
|
||||||
@with_session
|
@with_session
|
||||||
def delete(self, session: Session, app_id: UUID, annotation_id: UUID):
|
def delete(self, session: Session, app_id: UUID, annotation_id: UUID):
|
||||||
@ -458,7 +458,7 @@ class AnnotationBatchImportApi(Resource):
|
|||||||
@annotation_import_rate_limit
|
@annotation_import_rate_limit
|
||||||
@annotation_import_concurrency_limit
|
@annotation_import_concurrency_limit
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, app_id: UUID):
|
def post(self, session: Session, app_id: UUID):
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -513,7 +513,7 @@ class AnnotationBatchImportStatusApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("annotation")
|
@cloud_edition_billing_resource_check("annotation")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, app_id: UUID, job_id: UUID):
|
def get(self, app_id: UUID, job_id: UUID):
|
||||||
indexing_cache_key = f"app_annotation_batch_import_{str(job_id)}"
|
indexing_cache_key = f"app_annotation_batch_import_{str(job_id)}"
|
||||||
cache_result = redis_client.get(indexing_cache_key)
|
cache_result = redis_client.get(indexing_cache_key)
|
||||||
@ -546,7 +546,7 @@ class AnnotationHitHistoryListApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, app_id: UUID, annotation_id: UUID):
|
def get(self, session: Session, app_id: UUID, annotation_id: UUID):
|
||||||
page = request.args.get("page", default=1, type=int)
|
page = request.args.get("page", default=1, type=int)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.app_access import resolve_app_access_filter
|
from controllers.common.app_access import resolve_app_access_filter
|
||||||
from controllers.common.fields import RedirectUrlResponse, SimpleResultResponse
|
from controllers.common.fields import RedirectUrlResponse, SimpleResultResponse
|
||||||
|
from controllers.common.rbac import AgentBehindApp, PlainApp, RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -21,11 +22,10 @@ from controllers.common.schema import (
|
|||||||
register_schema_models,
|
register_schema_models,
|
||||||
)
|
)
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app, get_app_model, with_session
|
from controllers.console.app.wraps import get_app_model, with_session
|
||||||
from controllers.console.workspace.models import LoadBalancingPayload
|
from controllers.console.workspace.models import LoadBalancingPayload
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
cloud_edition_billing_resource_check,
|
cloud_edition_billing_resource_check,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
@ -691,7 +691,7 @@ class AppListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, Workspace()))
|
||||||
@cloud_edition_billing_resource_check("apps")
|
@cloud_edition_billing_resource_check("apps")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@ -868,7 +868,7 @@ class AppApi(Resource):
|
|||||||
@enterprise_license_required
|
@enterprise_license_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, app_model: App):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, app_model: App):
|
||||||
@ -907,8 +907,9 @@ class AppApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_EDIT, PlainApp()), RBACCheck(RBACPermission.AGENT_EDIT, AgentBehindApp())
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
@model_validate(UpdateAppPayload)
|
@model_validate(UpdateAppPayload)
|
||||||
@ -942,8 +943,9 @@ class AppApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_DELETE)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_DELETE, PlainApp()), RBACCheck(RBACPermission.AGENT_DELETE, AgentBehindApp())
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def delete(self, session: Session, app_model: App):
|
def delete(self, session: Session, app_model: App):
|
||||||
@ -967,8 +969,7 @@ class AppCopyApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp()))
|
||||||
@agent_manage_required_for_agent_app
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
@ -1045,8 +1046,10 @@ class AppExportApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, PlainApp()),
|
||||||
|
RBACCheck(RBACPermission.AGENT_IMPORT_EXPORT_DSL, AgentBehindApp()),
|
||||||
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(AppExportQuery)
|
@model_validate(AppExportQuery)
|
||||||
def get(self, req_data: AppExportQuery, app_model: App):
|
def get(self, req_data: AppExportQuery, app_model: App):
|
||||||
@ -1070,8 +1073,10 @@ class AppPublishToCreatorsPlatformApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, PlainApp()),
|
||||||
|
RBACCheck(RBACPermission.AGENT_RELEASE_AND_VERSION, AgentBehindApp()),
|
||||||
|
)
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
def post(self, current_user_id: str, app_model: App):
|
def post(self, current_user_id: str, app_model: App):
|
||||||
@ -1111,8 +1116,9 @@ class AppNameApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_EDIT, PlainApp()), RBACCheck(RBACPermission.AGENT_EDIT, AgentBehindApp())
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
@model_validate(AppNamePayload)
|
@model_validate(AppNamePayload)
|
||||||
@ -1139,8 +1145,9 @@ class AppIconApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_EDIT, PlainApp()), RBACCheck(RBACPermission.AGENT_EDIT, AgentBehindApp())
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
@model_validate(AppIconPayload)
|
@model_validate(AppIconPayload)
|
||||||
@ -1173,7 +1180,10 @@ class AppSiteStatus(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(
|
||||||
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp()),
|
||||||
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentBehindApp()),
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
@model_validate(AppSiteStatusPayload)
|
@model_validate(AppSiteStatusPayload)
|
||||||
@ -1200,8 +1210,10 @@ class AppApiStatus(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(
|
||||||
@agent_manage_required_for_agent_app
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp()),
|
||||||
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentBehindApp()),
|
||||||
|
)
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=None)
|
@get_app_model(mode=None)
|
||||||
@model_validate(AppApiStatusPayload)
|
@model_validate(AppApiStatusPayload)
|
||||||
@ -1230,7 +1242,7 @@ class AppTraceApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_session
|
@with_session
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def get(self, session: Session, app_model: App):
|
def get(self, session: Session, app_model: App):
|
||||||
"""Get app trace"""
|
"""Get app trace"""
|
||||||
@ -1252,7 +1264,7 @@ class AppTraceApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TRACING_CONFIG)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(AppTracePayload)
|
@model_validate(AppTracePayload)
|
||||||
def post(self, req_data: AppTracePayload, app_model: App):
|
def post(self, req_data: AppTracePayload, app_model: App):
|
||||||
|
|||||||
@ -4,11 +4,11 @@ from sqlalchemy.orm import Session
|
|||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck, Workspace
|
||||||
from controllers.common.schema import register_enum_models, register_schema_models
|
from controllers.common.schema import register_enum_models, register_schema_models
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
cloud_edition_billing_resource_check,
|
cloud_edition_billing_resource_check,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
@ -82,7 +82,7 @@ class AppImportApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("apps")
|
@cloud_edition_billing_resource_check("apps")
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@model_validate(AppImportPayload)
|
@model_validate(AppImportPayload)
|
||||||
def post(self, req_data: AppImportPayload, current_user: Account | None = None):
|
def post(self, req_data: AppImportPayload, current_user: Account | None = None):
|
||||||
@ -149,7 +149,7 @@ class AppImportConfirmApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def post(self, current_user: Account | None = None, import_id: str = ""):
|
def post(self, current_user: Account | None = None, import_id: str = ""):
|
||||||
current_user = current_user if current_user is not None else _current_user_and_tenant_id(None)[0]
|
current_user = current_user if current_user is not None else _current_user_and_tenant_id(None)[0]
|
||||||
@ -208,7 +208,7 @@ class AppImportCheckDependenciesApi(Resource):
|
|||||||
@get_app_model
|
@get_app_model
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
with Session(db.engine, expire_on_commit=False) as session:
|
with Session(db.engine, expire_on_commit=False) as session:
|
||||||
import_service = AppDslService(session)
|
import_service = AppDslService(session)
|
||||||
|
|||||||
@ -9,8 +9,8 @@ from werkzeug.datastructures import FileStorage
|
|||||||
from werkzeug.exceptions import HTTPException, InternalServerError
|
from werkzeug.exceptions import HTTPException, InternalServerError
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck, enforce_rbac_checks
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.wraps import enforce_rbac_access
|
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
|
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
@ -28,7 +28,6 @@ from controllers.console.app.error import (
|
|||||||
from controllers.console.app.wraps import get_app_model, with_session
|
from controllers.console.app.wraps import get_app_model, with_session
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -242,11 +241,10 @@ class AgentChatMessageAudioApi(Resource):
|
|||||||
agent_id=agent_id,
|
agent_id=agent_id,
|
||||||
)
|
)
|
||||||
# Agent routes expose Agent ids, while APP RBAC is keyed by the resolved runtime App id.
|
# Agent routes expose Agent ids, while APP RBAC is keyed by the resolved runtime App id.
|
||||||
enforce_rbac_access(
|
enforce_rbac_checks(
|
||||||
tenant_id=current_tenant_id,
|
tenant_id=current_tenant_id,
|
||||||
account_id=current_user.id,
|
account_id=current_user.id,
|
||||||
resource_type=RBACResourceScope.APP,
|
checks=[RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp())],
|
||||||
scene=RBACPermission.APP_TEST_AND_RUN,
|
|
||||||
path_args={"app_id": app_model.id},
|
path_args={"app_id": app_model.id},
|
||||||
)
|
)
|
||||||
agent_soul = AgentComposerService.load_agent_soul_for_debug(
|
agent_soul = AgentComposerService.load_agent_soul_for_debug(
|
||||||
@ -338,7 +336,7 @@ class TextModesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(TextToSpeechVoiceQuery)
|
@model_validate(TextToSpeechVoiceQuery)
|
||||||
def get(self, req_data: TextToSpeechVoiceQuery, app_model: App):
|
def get(self, req_data: TextToSpeechVoiceQuery, app_model: App):
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
|
|||||||
|
|
||||||
import services
|
import services
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import AgentId, PlainApp, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
|
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
|
||||||
@ -24,10 +25,9 @@ from controllers.console.app.error import (
|
|||||||
ProviderNotInitializeError,
|
ProviderNotInitializeError,
|
||||||
ProviderQuotaExceededError,
|
ProviderQuotaExceededError,
|
||||||
)
|
)
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app, get_app_model, with_session
|
from controllers.console.app.wraps import get_app_model, with_session
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -159,7 +159,7 @@ class CompletionMessageApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
@model_validate(CompletionMessagePayload)
|
@model_validate(CompletionMessagePayload)
|
||||||
@ -241,7 +241,7 @@ class ChatMessageApi(Resource):
|
|||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.AGENT])
|
||||||
def post(self, session: Session, current_tenant_id: str, current_user: Account, app_model: App):
|
def post(self, session: Session, current_tenant_id: str, current_user: Account, app_model: App):
|
||||||
@ -263,7 +263,7 @@ class AgentChatMessageApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -292,7 +292,7 @@ class AgentBuildChatFinalizeApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -333,6 +333,7 @@ class AgentChatMessageStopApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
|
|||||||
@ -9,13 +9,13 @@ from sqlalchemy import func, or_
|
|||||||
from sqlalchemy.orm import Session, selectinload
|
from sqlalchemy.orm import Session, selectinload
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -106,7 +106,7 @@ class CompletionConversationApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
@model_validate(CompletionConversationQuery)
|
@model_validate(CompletionConversationQuery)
|
||||||
@ -187,7 +187,7 @@ class CompletionConversationDetailApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
def get(self, session: Session, current_user: Account, app_model: App, conversation_id: UUID):
|
def get(self, session: Session, current_user: Account, app_model: App, conversation_id: UUID):
|
||||||
@ -209,7 +209,7 @@ class CompletionConversationDetailApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
@ -236,7 +236,7 @@ class ChatConversationApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||||
@model_validate(ChatConversationQuery)
|
@model_validate(ChatConversationQuery)
|
||||||
@ -356,7 +356,7 @@ class ChatConversationDetailApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||||
def get(self, session: Session, current_user: Account, app_model: App, conversation_id: UUID):
|
def get(self, session: Session, current_user: Account, app_model: App, conversation_id: UUID):
|
||||||
@ -378,7 +378,7 @@ class ChatConversationDetailApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||||
|
|||||||
@ -8,12 +8,12 @@ from pydantic import BaseModel, Field, field_validator
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
rbac_permission_required,
|
rbac_permission_required,
|
||||||
@ -99,7 +99,7 @@ class ConversationVariablesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp()))
|
||||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||||
@model_validate(ConversationVariablesQuery)
|
@model_validate(ConversationVariablesQuery)
|
||||||
def get(self, req_data: ConversationVariablesQuery, app_model: App):
|
def get(self, req_data: ConversationVariablesQuery, app_model: App):
|
||||||
|
|||||||
@ -7,12 +7,12 @@ from pydantic import BaseModel, Field, field_validator
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import register_schema_models
|
from controllers.common.schema import register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -87,7 +87,7 @@ class AppMCPServerController(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@setup_required
|
@setup_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
server = db.session.scalar(select(AppMCPServer).where(AppMCPServer.app_id == app_model.id).limit(1))
|
server = db.session.scalar(select(AppMCPServer).where(AppMCPServer.app_id == app_model.id).limit(1))
|
||||||
@ -107,7 +107,7 @@ class AppMCPServerController(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@setup_required
|
@setup_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(MCPServerCreatePayload)
|
@model_validate(MCPServerCreatePayload)
|
||||||
@ -142,7 +142,7 @@ class AppMCPServerController(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(MCPServerUpdatePayload)
|
@model_validate(MCPServerUpdatePayload)
|
||||||
def put(self, req_data: MCPServerUpdatePayload, app_model: App):
|
def put(self, req_data: MCPServerUpdatePayload, app_model: App):
|
||||||
@ -190,7 +190,7 @@ class AppMCPServerRefreshController(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def post(self, current_tenant_id: str, app_model: App):
|
def post(self, current_tenant_id: str, app_model: App):
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from werkzeug.exceptions import InternalServerError, NotFound
|
|||||||
|
|
||||||
from controllers.common.controller_schemas import MessageFeedbackPayload as _MessageFeedbackPayloadBase
|
from controllers.common.controller_schemas import MessageFeedbackPayload as _MessageFeedbackPayloadBase
|
||||||
from controllers.common.fields import SimpleResultResponse, TextFileResponse
|
from controllers.common.fields import SimpleResultResponse, TextFileResponse
|
||||||
|
from controllers.common.rbac import AgentId, PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
@ -21,11 +22,10 @@ from controllers.console.app.error import (
|
|||||||
ProviderNotInitializeError,
|
ProviderNotInitializeError,
|
||||||
ProviderQuotaExceededError,
|
ProviderQuotaExceededError,
|
||||||
)
|
)
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app, get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.explore.error import AppSuggestedQuestionsAfterAnswerDisabledError
|
from controllers.console.explore.error import AppSuggestedQuestionsAfterAnswerDisabledError
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -155,7 +155,7 @@ class ChatMessageListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||||
def get(self, session: Session, current_user: Account, app_model: App):
|
def get(self, session: Session, current_user: Account, app_model: App):
|
||||||
@ -174,7 +174,7 @@ class AgentChatMessageListApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@setup_required
|
@setup_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@agent_manage_required_for_agent_app(scene=RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -217,6 +217,7 @@ class AgentMessageFeedbackApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -242,7 +243,7 @@ class MessageAnnotationCountApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
count = db.session.scalar(
|
count = db.session.scalar(
|
||||||
@ -267,7 +268,7 @@ class MessageSuggestedQuestionApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||||
def get(self, session: Session, current_user: Account, app_model: App, message_id: UUID):
|
def get(self, session: Session, current_user: Account, app_model: App, message_id: UUID):
|
||||||
@ -290,6 +291,7 @@ class AgentMessageSuggestedQuestionApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -319,7 +321,7 @@ class MessageFeedbackExportApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(FeedbackExportQuery)
|
@model_validate(FeedbackExportQuery)
|
||||||
def get(self, req_data: FeedbackExportQuery, app_model: App):
|
def get(self, req_data: FeedbackExportQuery, app_model: App):
|
||||||
@ -358,7 +360,7 @@ class MessageApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def get(self, session: Session, app_model: App, message_id: UUID):
|
def get(self, session: Session, app_model: App, message_id: UUID):
|
||||||
@ -375,6 +377,7 @@ class AgentMessageApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@rbac_permission_required(RBACCheck(RBACPermission.AGENT_TEST_AND_RUN, AgentId()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, agent_id: UUID, message_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, agent_id: UUID, message_id: UUID):
|
||||||
|
|||||||
@ -7,13 +7,13 @@ from pydantic import BaseModel, Field
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
rbac_permission_required,
|
rbac_permission_required,
|
||||||
@ -90,7 +90,7 @@ class ModelConfigResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from uuid import UUID
|
|||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
@ -18,7 +19,6 @@ from controllers.console.app.error import (
|
|||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
model_validate,
|
model_validate,
|
||||||
)
|
)
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
@ -106,8 +106,7 @@ class TraceAppConfigApi(Resource):
|
|||||||
@console_ns.response(404, "Application not found")
|
@console_ns.response(404, "Application not found")
|
||||||
@console_ns.response(500, "Tracing configuration processing failed")
|
@console_ns.response(500, "Tracing configuration processing failed")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_TRACING_CONFIG,
|
|
||||||
)
|
)
|
||||||
@model_validate(TraceProviderQuery)
|
@model_validate(TraceProviderQuery)
|
||||||
def get(
|
def get(
|
||||||
@ -155,8 +154,7 @@ class TraceAppConfigApi(Resource):
|
|||||||
@console_ns.response(500, "Tracing configuration processing failed")
|
@console_ns.response(500, "Tracing configuration processing failed")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
|
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_TRACING_CONFIG,
|
|
||||||
)
|
)
|
||||||
@model_validate(TraceConfigPayload)
|
@model_validate(TraceConfigPayload)
|
||||||
def post(
|
def post(
|
||||||
@ -205,8 +203,7 @@ class TraceAppConfigApi(Resource):
|
|||||||
@console_ns.response(500, "Tracing configuration processing failed")
|
@console_ns.response(500, "Tracing configuration processing failed")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
|
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_TRACING_CONFIG,
|
|
||||||
)
|
)
|
||||||
@model_validate(TraceConfigPayload)
|
@model_validate(TraceConfigPayload)
|
||||||
def patch(
|
def patch(
|
||||||
@ -251,8 +248,7 @@ class TraceAppConfigApi(Resource):
|
|||||||
@console_ns.response(500, "Tracing configuration processing failed")
|
@console_ns.response(500, "Tracing configuration processing failed")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
|
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_TRACING_CONFIG,
|
|
||||||
)
|
)
|
||||||
@model_validate(TraceProviderQuery)
|
@model_validate(TraceProviderQuery)
|
||||||
def delete(
|
def delete(
|
||||||
|
|||||||
@ -5,13 +5,13 @@ from pydantic import BaseModel, Field, field_validator
|
|||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from constants.languages import supported_language
|
from constants.languages import supported_language
|
||||||
|
from controllers.common.rbac import AgentBehindApp, PlainApp, RBACCheck
|
||||||
from controllers.common.schema import register_schema_models
|
from controllers.common.schema import register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.error import AppNotFoundError
|
from controllers.console.app.error import AppNotFoundError
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
model_validate,
|
model_validate,
|
||||||
)
|
)
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
@ -105,9 +105,10 @@ class AppSite(Resource):
|
|||||||
@console_ns.response(404, "App not found")
|
@console_ns.response(404, "App not found")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_APP_SITE_EDIT_ROLES,
|
allowed_roles=_APP_SITE_EDIT_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[
|
||||||
rbac_permission=RBACPermission.APP_RELEASE_AND_VERSION,
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp()),
|
||||||
agent_manage_fallback=True,
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentBehindApp()),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
@model_validate(AppSiteUpdatePayload)
|
@model_validate(AppSiteUpdatePayload)
|
||||||
def post(
|
def post(
|
||||||
@ -136,9 +137,10 @@ class AppSiteAccessTokenReset(Resource):
|
|||||||
@console_ns.response(404, "App or site not found")
|
@console_ns.response(404, "App or site not found")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_APP_SITE_TOKEN_RESET_ROLES,
|
allowed_roles=_APP_SITE_TOKEN_RESET_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[
|
||||||
rbac_permission=RBACPermission.APP_RELEASE_AND_VERSION,
|
RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp()),
|
||||||
agent_manage_fallback=True,
|
RBACCheck(RBACPermission.AGENT_ACCESS_POINT_MANAGE, AgentBehindApp()),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def post(self, request_context: RequestContext, app_id: UUID):
|
def post(self, request_context: RequestContext, app_id: UUID):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -5,11 +5,12 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate
|
from controllers.console.wraps import RBACPermission, model_validate
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.datetime_utils import parse_time_range
|
from libs.datetime_utils import parse_time_range
|
||||||
@ -158,8 +159,7 @@ class DailyMessageStatistic(Resource):
|
|||||||
console_ns.models[DailyMessageStatisticResponse.__name__],
|
console_ns.models[DailyMessageStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -186,8 +186,7 @@ class DailyConversationStatistic(Resource):
|
|||||||
console_ns.models[DailyConversationStatisticResponse.__name__],
|
console_ns.models[DailyConversationStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -214,8 +213,7 @@ class DailyTerminalsStatistic(Resource):
|
|||||||
console_ns.models[DailyTerminalStatisticResponse.__name__],
|
console_ns.models[DailyTerminalStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -242,8 +240,7 @@ class DailyTokenCostStatistic(Resource):
|
|||||||
console_ns.models[DailyTokenCostStatisticResponse.__name__],
|
console_ns.models[DailyTokenCostStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -270,8 +267,7 @@ class AverageSessionInteractionStatistic(Resource):
|
|||||||
console_ns.models[AverageSessionInteractionStatisticResponse.__name__],
|
console_ns.models[AverageSessionInteractionStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -298,8 +294,7 @@ class UserSatisfactionRateStatistic(Resource):
|
|||||||
console_ns.models[UserSatisfactionRateStatisticResponse.__name__],
|
console_ns.models[UserSatisfactionRateStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -326,8 +321,7 @@ class AverageResponseTimeStatistic(Resource):
|
|||||||
console_ns.models[AverageResponseTimeStatisticResponse.__name__],
|
console_ns.models[AverageResponseTimeStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
@ -354,8 +348,7 @@ class TokensPerSecondStatistic(Resource):
|
|||||||
console_ns.models[TokensPerSecondStatisticResponse.__name__],
|
console_ns.models[TokensPerSecondStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(StatisticTimeRangeQuery)
|
@model_validate(StatisticTimeRangeQuery)
|
||||||
|
|||||||
@ -25,6 +25,7 @@ from controllers.common.app_access import resolve_app_access_filter
|
|||||||
from controllers.common.controller_schemas import DefaultBlockConfigQuery, WorkflowListQuery, WorkflowUpdatePayload
|
from controllers.common.controller_schemas import DefaultBlockConfigQuery, WorkflowListQuery, WorkflowUpdatePayload
|
||||||
from controllers.common.errors import InvalidArgumentError
|
from controllers.common.errors import InvalidArgumentError
|
||||||
from controllers.common.fields import GeneratedAppResponse, NewAppResponse, SimpleResultResponse
|
from controllers.common.fields import GeneratedAppResponse, NewAppResponse, SimpleResultResponse
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
register_response_schema_model,
|
register_response_schema_model,
|
||||||
@ -41,7 +42,6 @@ from controllers.console.app.permission_keys import get_app_permission_keys
|
|||||||
from controllers.console.app.wraps import get_app_model, with_session
|
from controllers.console.app.wraps import get_app_model, with_session
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -582,7 +582,7 @@ class DraftWorkflowApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
"""
|
"""
|
||||||
@ -625,7 +625,7 @@ class DraftWorkflowApi(Resource):
|
|||||||
@console_ns.response(403, "Permission denied")
|
@console_ns.response(403, "Permission denied")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def post(self, current_user: Account, app_model: App):
|
def post(self, current_user: Account, app_model: App):
|
||||||
"""
|
"""
|
||||||
Sync draft workflow
|
Sync draft workflow
|
||||||
@ -704,7 +704,7 @@ class AdvancedChatDraftWorkflowRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -760,7 +760,7 @@ class AdvancedChatDraftRunIterationNodeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -809,7 +809,7 @@ class WorkflowDraftRunIterationNodeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -854,7 +854,7 @@ class AdvancedChatDraftRunLoopNodeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -902,7 +902,7 @@ class WorkflowDraftRunLoopNodeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -982,7 +982,7 @@ class AdvancedChatDraftHumanInputFormPreviewApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@model_validate(HumanInputFormPreviewPayload)
|
@model_validate(HumanInputFormPreviewPayload)
|
||||||
def post(self, args: HumanInputFormPreviewPayload, current_user: Account, app_model: App, node_id: str):
|
def post(self, args: HumanInputFormPreviewPayload, current_user: Account, app_model: App, node_id: str):
|
||||||
"""
|
"""
|
||||||
@ -1015,7 +1015,7 @@ class AdvancedChatDraftHumanInputFormRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -1050,7 +1050,7 @@ class WorkflowDraftHumanInputFormPreviewApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@model_validate(HumanInputFormPreviewPayload)
|
@model_validate(HumanInputFormPreviewPayload)
|
||||||
def post(self, args: HumanInputFormPreviewPayload, current_user: Account, app_model: App, node_id: str):
|
def post(self, args: HumanInputFormPreviewPayload, current_user: Account, app_model: App, node_id: str):
|
||||||
"""
|
"""
|
||||||
@ -1083,7 +1083,7 @@ class WorkflowDraftHumanInputFormRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -1118,7 +1118,7 @@ class WorkflowDraftHumanInputDeliveryTestApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@model_validate(HumanInputDeliveryTestPayload)
|
@model_validate(HumanInputDeliveryTestPayload)
|
||||||
def post(self, args: HumanInputDeliveryTestPayload, current_user: Account, app_model: App, node_id: str):
|
def post(self, args: HumanInputDeliveryTestPayload, current_user: Account, app_model: App, node_id: str):
|
||||||
"""
|
"""
|
||||||
@ -1151,7 +1151,7 @@ class DraftWorkflowRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -1194,7 +1194,7 @@ class WorkflowTaskStopApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def post(self, app_model: App, task_id: str):
|
def post(self, app_model: App, task_id: str):
|
||||||
"""
|
"""
|
||||||
@ -1226,7 +1226,7 @@ class DraftWorkflowNodeRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -1278,7 +1278,7 @@ class PublishedWorkflowApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
"""
|
"""
|
||||||
@ -1300,7 +1300,7 @@ class PublishedWorkflowApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -1349,7 +1349,7 @@ class DefaultBlockConfigsApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
"""
|
"""
|
||||||
@ -1376,7 +1376,7 @@ class DefaultBlockConfigApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@model_validate(DefaultBlockConfigQuery)
|
@model_validate(DefaultBlockConfigQuery)
|
||||||
def get(self, args: DefaultBlockConfigQuery, app_model: App, block_type: str):
|
def get(self, args: DefaultBlockConfigQuery, app_model: App, block_type: str):
|
||||||
@ -1416,7 +1416,7 @@ class ConvertToWorkflowApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@model_validate(ConvertToWorkflowPayload)
|
@model_validate(ConvertToWorkflowPayload)
|
||||||
def post(self, payload: ConvertToWorkflowPayload, current_tenant_id: str, current_user: Account, app_model: App):
|
def post(self, payload: ConvertToWorkflowPayload, current_tenant_id: str, current_user: Account, app_model: App):
|
||||||
"""
|
"""
|
||||||
@ -1458,7 +1458,7 @@ class WorkflowFeaturesApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@model_validate(WorkflowFeaturesPayload)
|
@model_validate(WorkflowFeaturesPayload)
|
||||||
def post(self, args: WorkflowFeaturesPayload, current_user: Account, app_model: App):
|
def post(self, args: WorkflowFeaturesPayload, current_user: Account, app_model: App):
|
||||||
features = args.features.model_dump(mode="json", exclude_unset=True)
|
features = args.features.model_dump(mode="json", exclude_unset=True)
|
||||||
@ -1485,7 +1485,7 @@ class PublishedAllWorkflowApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -1538,7 +1538,7 @@ class DraftWorkflowRestoreApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_RELEASE_AND_VERSION, PlainApp()))
|
||||||
def post(self, current_user: Account, app_model: App, workflow_id: str):
|
def post(self, current_user: Account, app_model: App, workflow_id: str):
|
||||||
workflow_service = WorkflowService()
|
workflow_service = WorkflowService()
|
||||||
|
|
||||||
@ -1578,7 +1578,7 @@ class WorkflowByIdApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@model_validate(WorkflowUpdatePayload)
|
@model_validate(WorkflowUpdatePayload)
|
||||||
def patch(self, args: WorkflowUpdatePayload, current_user: Account, app_model: App, workflow_id: str):
|
def patch(self, args: WorkflowUpdatePayload, current_user: Account, app_model: App, workflow_id: str):
|
||||||
"""
|
"""
|
||||||
@ -1620,7 +1620,7 @@ class WorkflowByIdApi(Resource):
|
|||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@console_ns.response(204, "Workflow deleted successfully")
|
@console_ns.response(204, "Workflow deleted successfully")
|
||||||
def delete(self, current_user: Account, app_model: App, workflow_id: str):
|
def delete(self, current_user: Account, app_model: App, workflow_id: str):
|
||||||
"""
|
"""
|
||||||
@ -1666,7 +1666,7 @@ class DraftWorkflowNodeLastRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, node_id: str):
|
def get(self, app_model: App, node_id: str):
|
||||||
srv = WorkflowService()
|
srv = WorkflowService()
|
||||||
@ -1711,7 +1711,7 @@ class DraftWorkflowTriggerRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -1782,7 +1782,7 @@ class DraftWorkflowTriggerNodeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@ -1865,7 +1865,7 @@ class DraftWorkflowTriggerRunAllApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()))
|
||||||
@with_session
|
@with_session
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@model_validate(DraftWorkflowTriggerRunAllPayload)
|
@model_validate(DraftWorkflowTriggerRunAllPayload)
|
||||||
|
|||||||
@ -5,15 +5,12 @@ from dateutil.parser import isoparse
|
|||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import RBACPermission, model_validate
|
||||||
RBACPermission,
|
|
||||||
RBACResourceScope,
|
|
||||||
model_validate,
|
|
||||||
)
|
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from fields.end_user_fields import SimpleEndUser
|
from fields.end_user_fields import SimpleEndUser
|
||||||
@ -133,8 +130,7 @@ class WorkflowAppLogApi(Resource):
|
|||||||
console_ns.models[WorkflowAppLogPaginationResponse.__name__],
|
console_ns.models[WorkflowAppLogPaginationResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_LOG_AND_ANNOTATION, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_LOG_AND_ANNOTATION,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@model_validate(WorkflowAppLogQuery)
|
@model_validate(WorkflowAppLogQuery)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
|
|
||||||
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
@ -19,7 +20,6 @@ from controllers.console.app.error import (
|
|||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -337,7 +337,7 @@ def _api_prerequisite[T, **P, R](
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
@ -359,7 +359,7 @@ class WorkflowVariableCollectionApi(Resource):
|
|||||||
)
|
)
|
||||||
@_api_prerequisite
|
@_api_prerequisite
|
||||||
@marshal_with(workflow_draft_variable_list_without_value_model)
|
@marshal_with(workflow_draft_variable_list_without_value_model)
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@model_validate(WorkflowDraftVariableListQuery)
|
@model_validate(WorkflowDraftVariableListQuery)
|
||||||
def get(self, req_data: WorkflowDraftVariableListQuery, current_user: Account, app_model: App):
|
def get(self, req_data: WorkflowDraftVariableListQuery, current_user: Account, app_model: App):
|
||||||
"""
|
"""
|
||||||
@ -424,7 +424,7 @@ class NodeVariableCollectionApi(Resource):
|
|||||||
@console_ns.response(200, "Node variables retrieved successfully", workflow_draft_variable_list_model)
|
@console_ns.response(200, "Node variables retrieved successfully", workflow_draft_variable_list_model)
|
||||||
@_api_prerequisite
|
@_api_prerequisite
|
||||||
@marshal_with(workflow_draft_variable_list_model)
|
@marshal_with(workflow_draft_variable_list_model)
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, current_user: Account, app_model: App, node_id: str):
|
def get(self, current_user: Account, app_model: App, node_id: str):
|
||||||
validate_node_id(node_id)
|
validate_node_id(node_id)
|
||||||
with sessionmaker(bind=db.engine, expire_on_commit=False).begin() as session:
|
with sessionmaker(bind=db.engine, expire_on_commit=False).begin() as session:
|
||||||
@ -459,7 +459,7 @@ class VariableApi(Resource):
|
|||||||
@console_ns.response(404, "Variable not found")
|
@console_ns.response(404, "Variable not found")
|
||||||
@_api_prerequisite
|
@_api_prerequisite
|
||||||
@marshal_with(workflow_draft_variable_model)
|
@marshal_with(workflow_draft_variable_model)
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, current_user: Account, app_model: App, variable_id: UUID):
|
def get(self, current_user: Account, app_model: App, variable_id: UUID):
|
||||||
draft_var_srv = WorkflowDraftVariableService(
|
draft_var_srv = WorkflowDraftVariableService(
|
||||||
session=db.session(),
|
session=db.session(),
|
||||||
@ -638,7 +638,7 @@ class ConversationVariableCollectionApi(Resource):
|
|||||||
@console_ns.response(404, "Draft workflow not found")
|
@console_ns.response(404, "Draft workflow not found")
|
||||||
@_api_prerequisite
|
@_api_prerequisite
|
||||||
@marshal_with(workflow_draft_variable_list_model)
|
@marshal_with(workflow_draft_variable_list_model)
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, current_user: Account, app_model: App):
|
def get(self, current_user: Account, app_model: App):
|
||||||
# NOTE(QuantumGhost): Prefill conversation variables into the draft variables table
|
# NOTE(QuantumGhost): Prefill conversation variables into the draft variables table
|
||||||
# so their IDs can be returned to the caller.
|
# so their IDs can be returned to the caller.
|
||||||
@ -664,7 +664,7 @@ class ConversationVariableCollectionApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||||
@model_validate(ConversationVariableUpdatePayload)
|
@model_validate(ConversationVariableUpdatePayload)
|
||||||
@ -697,7 +697,7 @@ class SystemVariableCollectionApi(Resource):
|
|||||||
@console_ns.response(200, "System variables retrieved successfully", workflow_draft_variable_list_model)
|
@console_ns.response(200, "System variables retrieved successfully", workflow_draft_variable_list_model)
|
||||||
@_api_prerequisite
|
@_api_prerequisite
|
||||||
@marshal_with(workflow_draft_variable_list_model)
|
@marshal_with(workflow_draft_variable_list_model)
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, current_user: Account, app_model: App):
|
def get(self, current_user: Account, app_model: App):
|
||||||
return _get_variable_list(app_model, SYSTEM_VARIABLE_NODE_ID, current_user.id)
|
return _get_variable_list(app_model, SYSTEM_VARIABLE_NODE_ID, current_user.id)
|
||||||
|
|
||||||
@ -714,7 +714,7 @@ class EnvironmentVariableCollectionApi(Resource):
|
|||||||
)
|
)
|
||||||
@console_ns.response(404, "Draft workflow not found")
|
@console_ns.response(404, "Draft workflow not found")
|
||||||
@_api_prerequisite
|
@_api_prerequisite
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
def get(self, _current_user: Account, app_model: App):
|
def get(self, _current_user: Account, app_model: App):
|
||||||
"""
|
"""
|
||||||
Get draft workflow
|
Get draft workflow
|
||||||
@ -759,7 +759,7 @@ class EnvironmentVariableCollectionApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@model_validate(EnvironmentVariableUpdatePayload)
|
@model_validate(EnvironmentVariableUpdatePayload)
|
||||||
|
|||||||
@ -31,12 +31,12 @@ from flask import Response
|
|||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
|
|
||||||
from controllers.common.fields import EventStreamResponse
|
from controllers.common.fields import EventStreamResponse
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models
|
from controllers.common.schema import register_response_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
rbac_permission_required,
|
rbac_permission_required,
|
||||||
setup_required,
|
setup_required,
|
||||||
@ -157,7 +157,7 @@ class WorkflowDraftRunNodeOutputsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID):
|
def get(self, app_model: App, run_id: UUID):
|
||||||
return _serve_snapshot(app_model, run_id)
|
return _serve_snapshot(app_model, run_id)
|
||||||
@ -181,7 +181,7 @@ class WorkflowDraftRunNodeOutputDetailApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID, node_id: str):
|
def get(self, app_model: App, run_id: UUID, node_id: str):
|
||||||
return _serve_node_detail(app_model, run_id, node_id)
|
return _serve_node_detail(app_model, run_id, node_id)
|
||||||
@ -208,7 +208,7 @@ class WorkflowDraftRunNodeOutputPreviewApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID, node_id: str, output_name: str):
|
def get(self, app_model: App, run_id: UUID, node_id: str, output_name: str):
|
||||||
return _serve_output_preview(app_model, run_id, node_id, output_name)
|
return _serve_output_preview(app_model, run_id, node_id, output_name)
|
||||||
@ -353,7 +353,7 @@ class WorkflowDraftRunNodeOutputEventsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID):
|
def get(self, app_model: App, run_id: UUID):
|
||||||
return Response(
|
return Response(
|
||||||
@ -384,7 +384,7 @@ class WorkflowPublishedRunNodeOutputsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID):
|
def get(self, app_model: App, run_id: UUID):
|
||||||
return _serve_snapshot(app_model, run_id)
|
return _serve_snapshot(app_model, run_id)
|
||||||
@ -408,7 +408,7 @@ class WorkflowPublishedRunNodeOutputDetailApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID, node_id: str):
|
def get(self, app_model: App, run_id: UUID, node_id: str):
|
||||||
return _serve_node_detail(app_model, run_id, node_id)
|
return _serve_node_detail(app_model, run_id, node_id)
|
||||||
@ -436,7 +436,7 @@ class WorkflowPublishedRunNodeOutputPreviewApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID, node_id: str, output_name: str):
|
def get(self, app_model: App, run_id: UUID, node_id: str, output_name: str):
|
||||||
return _serve_output_preview(app_model, run_id, node_id, output_name)
|
return _serve_output_preview(app_model, run_id, node_id, output_name)
|
||||||
@ -458,7 +458,7 @@ class WorkflowPublishedRunNodeOutputEventsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, app_model: App, run_id: UUID):
|
def get(self, app_model: App, run_id: UUID):
|
||||||
return Response(
|
return Response(
|
||||||
|
|||||||
@ -6,13 +6,13 @@ from pydantic import BaseModel, Field, field_validator
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.errors import NotFoundError
|
from controllers.common.errors import NotFoundError
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
model_validate,
|
model_validate,
|
||||||
)
|
)
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
@ -143,8 +143,7 @@ class AdvancedChatAppWorkflowRunListApi(Resource):
|
|||||||
console_ns.models[AdvancedChatWorkflowRunPaginationResponse.__name__],
|
console_ns.models[AdvancedChatWorkflowRunPaginationResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||||
@model_validate(WorkflowRunListQuery)
|
@model_validate(WorkflowRunListQuery)
|
||||||
@ -174,8 +173,7 @@ class AdvancedChatAppWorkflowRunCountApi(Resource):
|
|||||||
console_ns.models[WorkflowRunCountResponse.__name__],
|
console_ns.models[WorkflowRunCountResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||||
@model_validate(WorkflowRunCountQuery)
|
@model_validate(WorkflowRunCountQuery)
|
||||||
@ -206,8 +204,7 @@ class WorkflowRunListApi(Resource):
|
|||||||
console_ns.models[WorkflowRunPaginationResponse.__name__],
|
console_ns.models[WorkflowRunPaginationResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@model_validate(WorkflowRunListQuery)
|
@model_validate(WorkflowRunListQuery)
|
||||||
@ -237,8 +234,7 @@ class WorkflowRunCountApi(Resource):
|
|||||||
console_ns.models[WorkflowRunCountResponse.__name__],
|
console_ns.models[WorkflowRunCountResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
@model_validate(WorkflowRunCountQuery)
|
@model_validate(WorkflowRunCountQuery)
|
||||||
@ -269,8 +265,7 @@ class WorkflowRunDetailApi(Resource):
|
|||||||
)
|
)
|
||||||
@console_ns.response(404, "Workflow run not found")
|
@console_ns.response(404, "Workflow run not found")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, request_context: RequestContext, app_model: App, run_id: UUID):
|
def get(self, request_context: RequestContext, app_model: App, run_id: UUID):
|
||||||
@ -300,8 +295,7 @@ class WorkflowRunNodeExecutionListApi(Resource):
|
|||||||
)
|
)
|
||||||
@console_ns.response(404, "Workflow run not found")
|
@console_ns.response(404, "Workflow run not found")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
def get(self, request_context: RequestContext, app_model: App, run_id: UUID):
|
def get(self, request_context: RequestContext, app_model: App, run_id: UUID):
|
||||||
|
|||||||
@ -4,13 +4,13 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
model_validate,
|
model_validate,
|
||||||
)
|
)
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
@ -106,8 +106,7 @@ class WorkflowDailyRunsStatistic(Resource):
|
|||||||
console_ns.models[WorkflowDailyRunsStatisticResponse.__name__],
|
console_ns.models[WorkflowDailyRunsStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(WorkflowStatisticQuery)
|
@model_validate(WorkflowStatisticQuery)
|
||||||
@ -136,8 +135,7 @@ class WorkflowDailyTerminalsStatistic(Resource):
|
|||||||
console_ns.models[WorkflowDailyTerminalsStatisticResponse.__name__],
|
console_ns.models[WorkflowDailyTerminalsStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(WorkflowStatisticQuery)
|
@model_validate(WorkflowStatisticQuery)
|
||||||
@ -166,8 +164,7 @@ class WorkflowDailyTokenCostStatistic(Resource):
|
|||||||
console_ns.models[WorkflowDailyTokenCostStatisticResponse.__name__],
|
console_ns.models[WorkflowDailyTokenCostStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model
|
@get_app_model
|
||||||
@model_validate(WorkflowStatisticQuery)
|
@model_validate(WorkflowStatisticQuery)
|
||||||
@ -196,8 +193,7 @@ class WorkflowAverageAppInteractionStatistic(Resource):
|
|||||||
console_ns.models[WorkflowAverageAppInteractionStatisticResponse.__name__],
|
console_ns.models[WorkflowAverageAppInteractionStatisticResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.APP,
|
rbac_checks=[RBACCheck(RBACPermission.APP_MONITOR, PlainApp())],
|
||||||
rbac_permission=RBACPermission.APP_MONITOR,
|
|
||||||
)
|
)
|
||||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||||
@model_validate(WorkflowStatisticQuery)
|
@model_validate(WorkflowStatisticQuery)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_schema_models
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
@ -21,7 +22,6 @@ from .. import console_ns
|
|||||||
from ..app.wraps import get_app_model
|
from ..app.wraps import get_app_model
|
||||||
from ..wraps import (
|
from ..wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -100,7 +100,7 @@ class WebhookTriggerApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@console_ns.response(200, "Success", console_ns.models[WebhookTriggerResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[WebhookTriggerResponse.__name__])
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=AppMode.WORKFLOW)
|
@get_app_model(mode=AppMode.WORKFLOW)
|
||||||
@model_validate(Parser)
|
@model_validate(Parser)
|
||||||
def get(self, req_data: Parser, app_model: App):
|
def get(self, req_data: Parser, app_model: App):
|
||||||
@ -134,7 +134,7 @@ class AppTriggersApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@console_ns.response(200, "Success", console_ns.models[WorkflowTriggerListResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[WorkflowTriggerListResponse.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||||
@get_app_model(mode=AppMode.WORKFLOW)
|
@get_app_model(mode=AppMode.WORKFLOW)
|
||||||
def get(self, current_tenant_id: str, app_model: App):
|
def get(self, current_tenant_id: str, app_model: App):
|
||||||
"""Get app triggers list"""
|
"""Get app triggers list"""
|
||||||
@ -171,7 +171,7 @@ class AppTriggerEnableApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_EDIT, PlainApp()))
|
||||||
@console_ns.response(200, "Success", console_ns.models[WorkflowTriggerResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[WorkflowTriggerResponse.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@get_app_model(mode=AppMode.WORKFLOW)
|
@get_app_model(mode=AppMode.WORKFLOW)
|
||||||
|
|||||||
@ -11,35 +11,37 @@ from functools import wraps
|
|||||||
from typing import cast, overload
|
from typing import cast, overload
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session, scoped_session
|
||||||
from werkzeug.exceptions import Forbidden
|
|
||||||
|
|
||||||
from configs import dify_config
|
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope, _extract_resource_id, enforce_rbac_access
|
|
||||||
from controllers.console.app.error import AppNotFoundError
|
from controllers.console.app.error import AppNotFoundError
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.login import current_account_with_tenant
|
from libs.login import current_account_with_tenant
|
||||||
from models import App, AppMode
|
from models import App, AppMode
|
||||||
from models.agent import Agent, AgentScope
|
from models.agent import AgentScope
|
||||||
from services.app_service import AppService
|
from services.app_service import AppService
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"agent_manage_required_for_agent_app",
|
|
||||||
"enforce_agent_manage_or_app_scene",
|
|
||||||
"get_app_model",
|
"get_app_model",
|
||||||
"get_previewable_app_model",
|
"get_previewable_app_model",
|
||||||
"with_session",
|
"with_session",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_hidden_backing_app(app_model: App, session: Session | scoped_session) -> bool:
|
||||||
|
binding = app_model.agent_app_binding_with_session(session=session, include_archived=True)
|
||||||
|
return binding is not None and binding.scope == AgentScope.WORKFLOW_ONLY
|
||||||
|
|
||||||
|
|
||||||
def _load_app_model(session: Session, app_id: str) -> App | None:
|
def _load_app_model(session: Session, app_id: str) -> App | None:
|
||||||
"""Load the tenant-scoped app row with the request session owned by `with_session`."""
|
"""Load the tenant-scoped app row with the request session owned by `with_session`."""
|
||||||
_, current_tenant_id = current_account_with_tenant()
|
_, current_tenant_id = current_account_with_tenant()
|
||||||
app_model = session.scalar(
|
app_model = session.scalar(
|
||||||
select(App).where(App.id == app_id, App.tenant_id == current_tenant_id, App.status == "normal").limit(1)
|
select(App).where(App.id == app_id, App.tenant_id == current_tenant_id, App.status == "normal").limit(1)
|
||||||
)
|
)
|
||||||
|
if app_model is not None and _is_hidden_backing_app(app_model, session):
|
||||||
|
return None
|
||||||
return app_model
|
return app_model
|
||||||
|
|
||||||
|
|
||||||
@ -49,6 +51,8 @@ def _load_app_model_from_scoped_session(app_id: str) -> App | None:
|
|||||||
app_model = db.session.scalar(
|
app_model = db.session.scalar(
|
||||||
select(App).where(App.id == app_id, App.tenant_id == current_tenant_id, App.status == "normal").limit(1)
|
select(App).where(App.id == app_id, App.tenant_id == current_tenant_id, App.status == "normal").limit(1)
|
||||||
)
|
)
|
||||||
|
if app_model is not None and _is_hidden_backing_app(app_model, db.session):
|
||||||
|
return None
|
||||||
return app_model
|
return app_model
|
||||||
|
|
||||||
|
|
||||||
@ -59,118 +63,6 @@ def _load_previewable_app_model(session: Session, app_id: str) -> App | None:
|
|||||||
return AppService.get_normal_app_by_id(app_id, session)
|
return AppService.get_normal_app_by_id(app_id, session)
|
||||||
|
|
||||||
|
|
||||||
def _agent_app_binding(app_id: str) -> Agent | None:
|
|
||||||
app_model = _load_app_model_from_scoped_session(app_id)
|
|
||||||
if app_model is None:
|
|
||||||
return None
|
|
||||||
return app_model.agent_app_binding_with_session(session=db.session(), include_archived=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _reject_hidden_agent_backing_app(path_args: dict[str, object]) -> None:
|
|
||||||
raw_app_id = path_args.get("app_id") or path_args.get("resource_id")
|
|
||||||
if raw_app_id is None:
|
|
||||||
return
|
|
||||||
binding = _agent_app_binding(str(raw_app_id))
|
|
||||||
if binding is not None and binding.scope == AgentScope.WORKFLOW_ONLY:
|
|
||||||
raise AppNotFoundError()
|
|
||||||
|
|
||||||
|
|
||||||
def enforce_agent_manage_or_app_scene(
|
|
||||||
*,
|
|
||||||
tenant_id: str,
|
|
||||||
account_id: str,
|
|
||||||
scene: RBACPermission,
|
|
||||||
path_args: dict[str, object],
|
|
||||||
) -> None:
|
|
||||||
# Must run before the RBAC_ENABLED check below: a hidden workflow-only
|
|
||||||
# backing App has to stay unreachable regardless of RBAC_ENABLED.
|
|
||||||
_reject_hidden_agent_backing_app(path_args)
|
|
||||||
|
|
||||||
if not dify_config.RBAC_ENABLED:
|
|
||||||
return
|
|
||||||
|
|
||||||
binding = _agent_app_binding(_extract_resource_id(RBACResourceScope.APP, tenant_id, path_args))
|
|
||||||
|
|
||||||
if binding is not None:
|
|
||||||
if binding.scope == AgentScope.WORKFLOW_ONLY:
|
|
||||||
raise AppNotFoundError()
|
|
||||||
try:
|
|
||||||
enforce_rbac_access(
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
account_id=account_id,
|
|
||||||
resource_type=RBACResourceScope.WORKSPACE,
|
|
||||||
scene=RBACPermission.AGENT_MANAGE,
|
|
||||||
resource_required=False,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except Forbidden:
|
|
||||||
pass # not an agent.manage holder — fall through to the normal scene check
|
|
||||||
|
|
||||||
enforce_rbac_access(
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
account_id=account_id,
|
|
||||||
resource_type=RBACResourceScope.APP,
|
|
||||||
scene=scene,
|
|
||||||
path_args=path_args,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def agent_manage_required_for_agent_app[**P, R](view: Callable[P, R]) -> Callable[P, R]: ...
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def agent_manage_required_for_agent_app[**P, R](
|
|
||||||
view: None = None, *, scene: RBACPermission | None = None
|
|
||||||
) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
|
|
||||||
|
|
||||||
|
|
||||||
def agent_manage_required_for_agent_app[**P, R](
|
|
||||||
view: Callable[P, R] | None = None, *, scene: RBACPermission | None = None
|
|
||||||
) -> Callable[P, R] | Callable[[Callable[P, R]], Callable[P, R]]:
|
|
||||||
# Must sit above get_app_model in the decorator stack — get_app_model
|
|
||||||
# deletes app_id from kwargs, and this decorator needs it.
|
|
||||||
# TODO: this is a workaround, remove this after ACL for agent app is available
|
|
||||||
def decorator(view_func: Callable[P, R]) -> Callable[P, R]:
|
|
||||||
@wraps(view_func)
|
|
||||||
def decorated(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
||||||
if scene is not None:
|
|
||||||
if not dify_config.RBAC_ENABLED:
|
|
||||||
_reject_hidden_agent_backing_app(kwargs)
|
|
||||||
return view_func(*args, **kwargs)
|
|
||||||
current_user, current_tenant_id = current_account_with_tenant()
|
|
||||||
enforce_agent_manage_or_app_scene(
|
|
||||||
tenant_id=current_tenant_id,
|
|
||||||
account_id=current_user.id,
|
|
||||||
scene=scene,
|
|
||||||
path_args=kwargs,
|
|
||||||
)
|
|
||||||
return view_func(*args, **kwargs)
|
|
||||||
|
|
||||||
raw_app_id = kwargs.get("app_id") or kwargs.get("resource_id")
|
|
||||||
if raw_app_id is not None:
|
|
||||||
binding = _agent_app_binding(str(raw_app_id))
|
|
||||||
if binding is not None:
|
|
||||||
if binding.scope == AgentScope.WORKFLOW_ONLY:
|
|
||||||
raise AppNotFoundError()
|
|
||||||
if dify_config.RBAC_ENABLED:
|
|
||||||
current_user, current_tenant_id = current_account_with_tenant()
|
|
||||||
enforce_rbac_access(
|
|
||||||
tenant_id=current_tenant_id,
|
|
||||||
account_id=current_user.id,
|
|
||||||
resource_type=RBACResourceScope.WORKSPACE,
|
|
||||||
scene=RBACPermission.AGENT_MANAGE,
|
|
||||||
resource_required=False,
|
|
||||||
)
|
|
||||||
return view_func(*args, **kwargs)
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
if view is None:
|
|
||||||
return decorator
|
|
||||||
return decorator(view)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_injected_session(args: tuple[object, ...]) -> Session | None:
|
def _get_injected_session(args: tuple[object, ...]) -> Session | None:
|
||||||
"""Return the request session inserted by `with_session`, if this handler has been migrated."""
|
"""Return the request session inserted by `with_session`, if this handler has been migrated."""
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.auth.error import (
|
from controllers.console.auth.error import (
|
||||||
@ -14,7 +15,7 @@ from controllers.console.auth.error import (
|
|||||||
InvalidDataSourceApiKeyAuthCredentialsRequestError,
|
InvalidDataSourceApiKeyAuthCredentialsRequestError,
|
||||||
)
|
)
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate
|
from controllers.console.wraps import RBACPermission, model_validate
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.helper import dump_response
|
from libs.helper import dump_response
|
||||||
@ -104,9 +105,7 @@ class ApiKeyAuthDataSourceBinding(Resource):
|
|||||||
@console_ns.expect(console_ns.models[ApiKeyAuthBindingPayload.__name__])
|
@console_ns.expect(console_ns.models[ApiKeyAuthBindingPayload.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_ADMIN_OR_OWNER_ROLES,
|
allowed_roles=_ADMIN_OR_OWNER_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.CREDENTIAL_CREATE, Workspace())],
|
||||||
rbac_permission=RBACPermission.CREDENTIAL_CREATE,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@model_validate(ApiKeyAuthBindingPayload)
|
@model_validate(ApiKeyAuthBindingPayload)
|
||||||
def post(self, req_data: ApiKeyAuthBindingPayload, request_context: RequestContext):
|
def post(self, req_data: ApiKeyAuthBindingPayload, request_context: RequestContext):
|
||||||
@ -138,9 +137,7 @@ class ApiKeyAuthDataSourceBindingDelete(Resource):
|
|||||||
@console_ns.response(HTTPStatus.NO_CONTENT, "Binding deleted successfully")
|
@console_ns.response(HTTPStatus.NO_CONTENT, "Binding deleted successfully")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_ADMIN_OR_OWNER_ROLES,
|
allowed_roles=_ADMIN_OR_OWNER_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace())],
|
||||||
rbac_permission=RBACPermission.CREDENTIAL_MANAGE,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
def delete(self, request_context: RequestContext, binding_id: UUID):
|
def delete(self, request_context: RequestContext, binding_id: UUID):
|
||||||
application_services().data_source_api_key_auth.delete_binding(request_context, str(binding_id))
|
application_services().data_source_api_key_auth.delete_binding(request_context, str(binding_id))
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, ValidationError
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import RedirectResponse
|
from controllers.common.fields import RedirectResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -17,7 +18,7 @@ from controllers.common.schema import (
|
|||||||
register_schema_models,
|
register_schema_models,
|
||||||
)
|
)
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
from core.rbac import RBACPermission
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.helper import dump_response
|
from libs.helper import dump_response
|
||||||
@ -111,9 +112,7 @@ class OAuthDataSource(Resource):
|
|||||||
@console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required")
|
@console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
allowed_roles=_ADMIN_OR_OWNER_ROLES,
|
allowed_roles=_ADMIN_OR_OWNER_ROLES,
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace())],
|
||||||
rbac_permission=RBACPermission.CREDENTIAL_MANAGE,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
def get(self, request_context: RequestContext, provider: str):
|
def get(self, request_context: RequestContext, provider: str):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from sqlalchemy.orm import Session
|
|||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse, TextContentResponse
|
from controllers.common.fields import SimpleResultResponse, TextContentResponse
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from core.datasource.entities.datasource_entities import DatasourceProviderType, OnlineDocumentPagesMessage
|
from core.datasource.entities.datasource_entities import DatasourceProviderType, OnlineDocumentPagesMessage
|
||||||
@ -34,7 +35,6 @@ from tasks.document_indexing_sync_task import document_indexing_sync_task
|
|||||||
from .. import console_ns
|
from .. import console_ns
|
||||||
from ..wraps import (
|
from ..wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -140,7 +140,7 @@ class DataSourceApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@console_ns.response(200, "Success", console_ns.models[DataSourceIntegrateListResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[DataSourceIntegrateListResponse.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -191,7 +191,7 @@ class DataSourceApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -420,7 +420,7 @@ class DataSourceNotionDatasetSyncApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, dataset_id: UUID) -> tuple[dict[str, str], int]:
|
def get(self, session: Session, dataset_id: UUID) -> tuple[dict[str, str], int]:
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -440,7 +440,7 @@ class DataSourceNotionDocumentSyncApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, dataset_id: UUID, document_id: UUID) -> tuple[dict[str, str], int]:
|
def get(self, session: Session, dataset_id: UUID, document_id: UUID) -> tuple[dict[str, str], int]:
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from werkzeug.exceptions import Forbidden, NotFound
|
|||||||
import services
|
import services
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import ApiBaseUrlResponse, SimpleResultResponse, UsageCheckResponse
|
from controllers.common.fields import ApiBaseUrlResponse, SimpleResultResponse, UsageCheckResponse
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
@ -21,7 +22,6 @@ from controllers.console.app.error import ProviderNotInitializeError
|
|||||||
from controllers.console.datasets.error import DatasetInUseError, DatasetNameDuplicateError, IndexingEstimateError
|
from controllers.console.datasets.error import DatasetInUseError, DatasetNameDuplicateError, IndexingEstimateError
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
cloud_edition_billing_rate_limit_check,
|
cloud_edition_billing_rate_limit_check,
|
||||||
enterprise_license_required,
|
enterprise_license_required,
|
||||||
@ -589,9 +589,7 @@ class DatasetListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, Workspace()))
|
||||||
RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT, resource_required=False
|
|
||||||
)
|
|
||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -662,7 +660,7 @@ class DatasetApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -731,7 +729,7 @@ class DatasetApi(Resource):
|
|||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(DatasetUpdatePayload)
|
@model_validate(DatasetUpdatePayload)
|
||||||
def patch(
|
def patch(
|
||||||
@ -798,7 +796,7 @@ class DatasetApi(Resource):
|
|||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@console_ns.response(204, "Dataset deleted successfully")
|
@console_ns.response(204, "Dataset deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(self, session: Session, current_user: Account, dataset_id: UUID):
|
def delete(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -831,7 +829,7 @@ class DatasetUseCheckApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
||||||
@ -853,7 +851,7 @@ class DatasetQueryApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -1009,7 +1007,7 @@ class DatasetRelatedAppListApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -1048,7 +1046,7 @@ class DatasetIndexingStatusApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
||||||
@ -1112,7 +1110,7 @@ class DatasetApiKeyApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_API_KEY_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
@ -1130,7 +1128,7 @@ class DatasetApiKeyApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_API_KEY_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1191,7 +1189,7 @@ class DatasetApiDeleteApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_API_KEY_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -1228,7 +1226,7 @@ class DatasetEnableApiApi(Resource):
|
|||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, status: str):
|
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, status: str):
|
||||||
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
||||||
@ -1303,7 +1301,7 @@ class DatasetErrorDocs(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
||||||
@ -1330,7 +1328,7 @@ class DatasetPermissionUserListApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -1363,7 +1361,7 @@ class DatasetAutoDisableLogApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
dataset = _get_accessible_dataset(dataset_id, current_tenant_id, current_user, session)
|
||||||
|
|||||||
@ -19,10 +19,11 @@ import services
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.controller_schemas import DocumentBatchDownloadZipPayload
|
from controllers.common.controller_schemas import DocumentBatchDownloadZipPayload
|
||||||
from controllers.common.fields import SimpleResultMessageResponse, SimpleResultResponse, UrlResponse
|
from controllers.common.fields import SimpleResultMessageResponse, SimpleResultResponse, UrlResponse
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate, rbac_permission_required
|
from controllers.console.wraps import RBACPermission, model_validate, rbac_permission_required
|
||||||
from core.entities.knowledge_entities import IndexingEstimate
|
from core.entities.knowledge_entities import IndexingEstimate
|
||||||
from core.errors.error import (
|
from core.errors.error import (
|
||||||
LLMBadRequestError,
|
LLMBadRequestError,
|
||||||
@ -405,7 +406,7 @@ class DatasetDocumentListApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -534,7 +535,7 @@ class DatasetDocumentListApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[KnowledgeConfig.__name__])
|
@console_ns.expect(console_ns.models[KnowledgeConfig.__name__])
|
||||||
@console_ns.response(200, "Documents created successfully", console_ns.models[DatasetAndDocumentResponse.__name__])
|
@console_ns.response(200, "Documents created successfully", console_ns.models[DatasetAndDocumentResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_user: Account, dataset_id: UUID):
|
def post(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -585,7 +586,7 @@ class DatasetDocumentListApi(Resource):
|
|||||||
@console_ns.response(204, "Documents deleted successfully")
|
@console_ns.response(204, "Documents deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
@ -719,7 +720,7 @@ class DocumentIndexingEstimateApi(DocumentResource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -811,7 +812,7 @@ class DocumentBatchIndexingEstimateApi(DocumentResource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, batch: str):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, batch: str):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -933,7 +934,7 @@ class DocumentBatchIndexingStatusApi(DocumentResource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_user: Account, dataset_id: UUID, batch: str):
|
def get(self, session: Session, current_user: Account, dataset_id: UUID, batch: str):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -993,7 +994,7 @@ class DocumentIndexingStatusApi(DocumentResource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -1059,7 +1060,7 @@ class DocumentApi(DocumentResource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -1133,7 +1134,7 @@ class DocumentApi(DocumentResource):
|
|||||||
@console_ns.response(204, "Document deleted successfully")
|
@console_ns.response(204, "Document deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID
|
self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID
|
||||||
@ -1169,7 +1170,7 @@ class DocumentDownloadApi(DocumentResource):
|
|||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_DOCUMENT_DOWNLOAD)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_DOCUMENT_DOWNLOAD, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(
|
def get(
|
||||||
self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID
|
self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID
|
||||||
@ -1193,7 +1194,7 @@ class DocumentBatchDownloadZipApi(DocumentResource):
|
|||||||
@console_ns.expect(console_ns.models[DocumentBatchDownloadZipPayload.__name__])
|
@console_ns.expect(console_ns.models[DocumentBatchDownloadZipPayload.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
"""Stream a ZIP archive containing the requested uploaded documents."""
|
"""Stream a ZIP archive containing the requested uploaded documents."""
|
||||||
@ -1245,7 +1246,7 @@ class DocumentProcessingApi(DocumentResource):
|
|||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def patch(
|
def patch(
|
||||||
self,
|
self,
|
||||||
@ -1302,7 +1303,7 @@ class DocumentMetadataApi(DocumentResource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(DocumentMetadataUpdatePayload)
|
@model_validate(DocumentMetadataUpdatePayload)
|
||||||
def put(
|
def put(
|
||||||
@ -1361,7 +1362,7 @@ class DocumentStatusApi(DocumentResource):
|
|||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def patch(
|
def patch(
|
||||||
self,
|
self,
|
||||||
@ -1407,7 +1408,7 @@ class DocumentPauseApi(DocumentResource):
|
|||||||
@console_ns.response(204, "Document paused successfully")
|
@console_ns.response(204, "Document paused successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def patch(
|
def patch(
|
||||||
self,
|
self,
|
||||||
@ -1447,7 +1448,7 @@ class DocumentRecoverApi(DocumentResource):
|
|||||||
@console_ns.response(204, "Document resumed successfully")
|
@console_ns.response(204, "Document resumed successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def patch(
|
def patch(
|
||||||
self,
|
self,
|
||||||
@ -1487,7 +1488,7 @@ class DocumentRetryApi(DocumentResource):
|
|||||||
@console_ns.response(204, "Documents retry started successfully")
|
@console_ns.response(204, "Documents retry started successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(DocumentRetryPayload)
|
@model_validate(DocumentRetryPayload)
|
||||||
def post(
|
def post(
|
||||||
@ -1552,7 +1553,7 @@ class DocumentRenameApi(DocumentResource):
|
|||||||
@console_ns.response(200, "Document renamed successfully", console_ns.models[DocumentResponse.__name__])
|
@console_ns.response(200, "Document renamed successfully", console_ns.models[DocumentResponse.__name__])
|
||||||
@console_ns.expect(console_ns.models[DocumentRenamePayload.__name__])
|
@console_ns.expect(console_ns.models[DocumentRenamePayload.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(DocumentRenamePayload)
|
@model_validate(DocumentRenamePayload)
|
||||||
def post(
|
def post(
|
||||||
@ -1587,7 +1588,7 @@ class WebsiteDocumentSyncApi(DocumentResource):
|
|||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def get(
|
def get(
|
||||||
self,
|
self,
|
||||||
@ -1629,7 +1630,7 @@ class DocumentPipelineExecutionLogApi(DocumentResource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(
|
def get(
|
||||||
self,
|
self,
|
||||||
@ -1681,7 +1682,7 @@ class DocumentGenerateSummaryApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(GenerateSummaryPayload)
|
@model_validate(GenerateSummaryPayload)
|
||||||
def post(self, req_data: GenerateSummaryPayload, session: Session, current_user: Account, dataset_id: UUID):
|
def post(self, req_data: GenerateSummaryPayload, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
@ -1782,7 +1783,7 @@ class DocumentSummaryStatusApi(DocumentResource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_user: Account, dataset_id: UUID, document_id: UUID):
|
def get(self, session: Session, current_user: Account, dataset_id: UUID, document_id: UUID):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import services
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.controller_schemas import ChildChunkCreatePayload, ChildChunkUpdatePayload
|
from controllers.common.controller_schemas import ChildChunkCreatePayload, ChildChunkUpdatePayload
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -31,7 +32,6 @@ from controllers.console.datasets.error import (
|
|||||||
)
|
)
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
cloud_edition_billing_knowledge_limit_check,
|
cloud_edition_billing_knowledge_limit_check,
|
||||||
cloud_edition_billing_rate_limit_check,
|
cloud_edition_billing_rate_limit_check,
|
||||||
@ -191,7 +191,7 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID, document_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -302,7 +302,7 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
@console_ns.doc(params=query_params_from_model(SegmentIdListQuery))
|
@console_ns.doc(params=query_params_from_model(SegmentIdListQuery))
|
||||||
@console_ns.response(204, "Segments deleted successfully")
|
@console_ns.response(204, "Segments deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(self, session: Session, current_user: Account, dataset_id: UUID, document_id: UUID):
|
def delete(self, session: Session, current_user: Account, dataset_id: UUID, document_id: UUID):
|
||||||
# check dataset
|
# check dataset
|
||||||
@ -342,7 +342,7 @@ class DatasetDocumentSegmentApi(Resource):
|
|||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def patch(
|
def patch(
|
||||||
self,
|
self,
|
||||||
@ -413,7 +413,7 @@ class DatasetDocumentSegmentAddApi(Resource):
|
|||||||
@console_ns.response(200, "Segment created successfully", console_ns.models[SegmentDetailResponse.__name__])
|
@console_ns.response(200, "Segment created successfully", console_ns.models[SegmentDetailResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(SegmentCreatePayload)
|
@model_validate(SegmentCreatePayload)
|
||||||
def post(
|
def post(
|
||||||
@ -485,7 +485,7 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
@console_ns.response(200, "Segment updated successfully", console_ns.models[SegmentDetailResponse.__name__])
|
@console_ns.response(200, "Segment updated successfully", console_ns.models[SegmentDetailResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(SegmentUpdatePayload)
|
@model_validate(SegmentUpdatePayload)
|
||||||
def patch(
|
def patch(
|
||||||
@ -566,7 +566,7 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
@console_ns.response(204, "Segment deleted successfully")
|
@console_ns.response(204, "Segment deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
@ -617,7 +617,7 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[BatchImportPayload.__name__])
|
@console_ns.expect(console_ns.models[BatchImportPayload.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(BatchImportPayload)
|
@model_validate(BatchImportPayload)
|
||||||
def post(
|
def post(
|
||||||
@ -676,7 +676,7 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
def get(self, job_id=None, dataset_id: UUID | None = None, document_id: UUID | None = None):
|
def get(self, job_id=None, dataset_id: UUID | None = None, document_id: UUID | None = None):
|
||||||
if job_id is None:
|
if job_id is None:
|
||||||
raise NotFound("The job does not exist.")
|
raise NotFound("The job does not exist.")
|
||||||
@ -703,7 +703,7 @@ class ChildChunkAddApi(Resource):
|
|||||||
@console_ns.response(200, "Child chunk created successfully", console_ns.models[ChildChunkDetailResponse.__name__])
|
@console_ns.response(200, "Child chunk created successfully", console_ns.models[ChildChunkDetailResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(ChildChunkCreatePayload)
|
@model_validate(ChildChunkCreatePayload)
|
||||||
def post(
|
def post(
|
||||||
@ -764,7 +764,7 @@ class ChildChunkAddApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, dataset_id: UUID, document_id: UUID, segment_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, dataset_id: UUID, document_id: UUID, segment_id: UUID):
|
||||||
# check dataset
|
# check dataset
|
||||||
@ -819,7 +819,7 @@ class ChildChunkAddApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[ChildChunkBatchUpdatePayload.__name__])
|
@console_ns.expect(console_ns.models[ChildChunkBatchUpdatePayload.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(ChildChunkBatchUpdatePayload)
|
@model_validate(ChildChunkBatchUpdatePayload)
|
||||||
def patch(
|
def patch(
|
||||||
@ -873,7 +873,7 @@ class ChildChunkUpdateApi(Resource):
|
|||||||
@console_ns.response(204, "Child chunk deleted successfully")
|
@console_ns.response(204, "Child chunk deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
@ -926,7 +926,7 @@ class ChildChunkUpdateApi(Resource):
|
|||||||
@console_ns.response(200, "Child chunk updated successfully", console_ns.models[ChildChunkDetailResponse.__name__])
|
@console_ns.response(200, "Child chunk updated successfully", console_ns.models[ChildChunkDetailResponse.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(ChildChunkUpdatePayload)
|
@model_validate(ChildChunkUpdatePayload)
|
||||||
def patch(
|
def patch(
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
|||||||
|
|
||||||
import services
|
import services
|
||||||
from controllers.common.fields import UsageCountResponse
|
from controllers.common.fields import UsageCountResponse
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
register_response_schema_models,
|
register_response_schema_models,
|
||||||
@ -22,7 +23,6 @@ from controllers.console import console_ns
|
|||||||
from controllers.console.datasets.error import DatasetNameDuplicateError
|
from controllers.console.datasets.error import DatasetNameDuplicateError
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -350,9 +350,7 @@ class ExternalDatasetCreateApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EXTERNAL_CONNECT, Workspace()))
|
||||||
RBACResourceScope.DATASET, RBACPermission.DATASET_EXTERNAL_CONNECT, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_session
|
@with_session
|
||||||
@ -411,7 +409,7 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_PIPELINE_TEST)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_PIPELINE_TEST, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(ExternalHitTestingPayload)
|
@model_validate(ExternalHitTestingPayload)
|
||||||
def post(self, req_data: ExternalHitTestingPayload, session: Session, current_user: Account, dataset_id: UUID):
|
def post(self, req_data: ExternalHitTestingPayload, session: Session, current_user: Account, dataset_id: UUID):
|
||||||
|
|||||||
@ -5,9 +5,10 @@ from uuid import UUID
|
|||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.console.app.wraps import with_session
|
from controllers.console.app.wraps import with_session
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, rbac_permission_required
|
from controllers.console.wraps import RBACPermission, rbac_permission_required
|
||||||
from fields.hit_testing_fields import HitTestingResponse
|
from fields.hit_testing_fields import HitTestingResponse
|
||||||
from libs.helper import dump_response
|
from libs.helper import dump_response
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
@ -46,7 +47,7 @@ class HitTestingApi(Resource, DatasetsHitTestingBase):
|
|||||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_PIPELINE_TEST)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_PIPELINE_TEST, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(
|
def post(
|
||||||
self, session: Session, current_user: Account, current_tenant_id: str, dataset_id: UUID
|
self, session: Session, current_user: Account, current_tenant_id: str, dataset_id: UUID
|
||||||
|
|||||||
@ -8,12 +8,12 @@ from werkzeug.exceptions import Forbidden, NotFound
|
|||||||
import services
|
import services
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.controller_schemas import MetadataUpdatePayload
|
from controllers.common.controller_schemas import MetadataUpdatePayload
|
||||||
|
from controllers.common.rbac import DatasetId, RBACCheck
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
enterprise_license_required,
|
enterprise_license_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -61,7 +61,7 @@ class DatasetMetadataCreateApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[MetadataArgs.__name__])
|
@console_ns.expect(console_ns.models[MetadataArgs.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(MetadataArgs)
|
@model_validate(MetadataArgs)
|
||||||
def post(
|
def post(
|
||||||
@ -92,7 +92,7 @@ class DatasetMetadataCreateApi(Resource):
|
|||||||
)
|
)
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, DatasetId()))
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def get(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -118,7 +118,7 @@ class DatasetMetadataApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[MetadataUpdatePayload.__name__])
|
@console_ns.expect(console_ns.models[MetadataUpdatePayload.__name__])
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(MetadataUpdatePayload)
|
@model_validate(MetadataUpdatePayload)
|
||||||
def patch(
|
def patch(
|
||||||
@ -149,7 +149,7 @@ class DatasetMetadataApi(Resource):
|
|||||||
@console_ns.response(204, "Metadata deleted successfully")
|
@console_ns.response(204, "Metadata deleted successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
@ -195,7 +195,7 @@ class DatasetMetadataBuiltInFieldActionApi(Resource):
|
|||||||
@enterprise_license_required
|
@enterprise_license_required
|
||||||
@console_ns.response(204, "Action completed successfully")
|
@console_ns.response(204, "Action completed successfully")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_user: Account, dataset_id: UUID, action: Literal["enable", "disable"]):
|
def post(self, session: Session, current_user: Account, dataset_id: UUID, action: Literal["enable", "disable"]):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
@ -227,7 +227,7 @@ class DocumentMetadataEditApi(Resource):
|
|||||||
@console_ns.response(404, "Dataset, document, or metadata not found")
|
@console_ns.response(404, "Dataset, document, or metadata not found")
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(MetadataOperationData)
|
@model_validate(MetadataOperationData)
|
||||||
def post(
|
def post(
|
||||||
|
|||||||
@ -7,11 +7,11 @@ from werkzeug.exceptions import Forbidden, NotFound
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -167,7 +167,7 @@ class DatasourcePluginOAuthAuthorizationUrl(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, current_tenant_id: str, current_user: Account, provider_id: str):
|
def get(self, current_tenant_id: str, current_user: Account, provider_id: str):
|
||||||
@ -310,7 +310,7 @@ class DatasourceAuth(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_CREATE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(DatasourceCredentialPayload)
|
@model_validate(DatasourceCredentialPayload)
|
||||||
def post(self, req_data: DatasourceCredentialPayload, current_tenant_id: str, provider_id: str):
|
def post(self, req_data: DatasourceCredentialPayload, current_tenant_id: str, provider_id: str):
|
||||||
@ -337,7 +337,7 @@ class DatasourceAuth(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, current_tenant_id: str, user: Account, provider_id: str):
|
def get(self, current_tenant_id: str, user: Account, provider_id: str):
|
||||||
@ -362,7 +362,7 @@ class DatasourceAuthDeleteApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(DatasourceCredentialDeletePayload)
|
@model_validate(DatasourceCredentialDeletePayload)
|
||||||
def post(self, req_data: DatasourceCredentialDeletePayload, current_tenant_id: str, provider_id: str):
|
def post(self, req_data: DatasourceCredentialDeletePayload, current_tenant_id: str, provider_id: str):
|
||||||
@ -391,7 +391,7 @@ class DatasourceAuthUpdateApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(DatasourceCredentialUpdatePayload)
|
@model_validate(DatasourceCredentialUpdatePayload)
|
||||||
def post(self, req_data: DatasourceCredentialUpdatePayload, current_tenant_id: str, provider_id: str):
|
def post(self, req_data: DatasourceCredentialUpdatePayload, current_tenant_id: str, provider_id: str):
|
||||||
@ -459,7 +459,7 @@ class DatasourceAuthOauthCustomClient(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(DatasourceCustomClientPayload)
|
@model_validate(DatasourceCustomClientPayload)
|
||||||
def post(self, req_data: DatasourceCustomClientPayload, current_tenant_id: str, provider_id: str):
|
def post(self, req_data: DatasourceCustomClientPayload, current_tenant_id: str, provider_id: str):
|
||||||
@ -477,7 +477,7 @@ class DatasourceAuthOauthCustomClient(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def delete(self, current_tenant_id: str, provider_id: str):
|
def delete(self, current_tenant_id: str, provider_id: str):
|
||||||
@ -498,7 +498,7 @@ class DatasourceAuthDefaultApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(DatasourceDefaultPayload)
|
@model_validate(DatasourceDefaultPayload)
|
||||||
def post(self, req_data: DatasourceDefaultPayload, current_tenant_id: str, provider_id: str):
|
def post(self, req_data: DatasourceDefaultPayload, current_tenant_id: str, provider_id: str):
|
||||||
@ -520,7 +520,7 @@ class DatasourceUpdateProviderNameApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(DatasourceUpdateNamePayload)
|
@model_validate(DatasourceUpdateNamePayload)
|
||||||
def post(self, req_data: DatasourceUpdateNamePayload, current_tenant_id: str, provider_id: str):
|
def post(self, req_data: DatasourceUpdateNamePayload, current_tenant_id: str, provider_id: str):
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from werkzeug.exceptions import Forbidden, NotFound
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import SimpleDataResponse
|
from controllers.common.fields import SimpleDataResponse
|
||||||
|
from controllers.common.rbac import DatasetByPipeline, RBACCheck
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
JsonResponseWithStatus,
|
JsonResponseWithStatus,
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
@ -19,7 +20,6 @@ from controllers.console.app.wraps import with_session
|
|||||||
from controllers.console.datasets.wraps import get_rag_pipeline
|
from controllers.console.datasets.wraps import get_rag_pipeline
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
enterprise_license_required,
|
enterprise_license_required,
|
||||||
knowledge_pipeline_publish_enabled,
|
knowledge_pipeline_publish_enabled,
|
||||||
@ -223,7 +223,7 @@ class PublishCustomizedPipelineTemplateApi(Resource):
|
|||||||
@knowledge_pipeline_publish_enabled
|
@knowledge_pipeline_publish_enabled
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_PIPELINE_RELEASE)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_PIPELINE_RELEASE, DatasetByPipeline()))
|
||||||
@model_validate(CustomizedPipelineTemplatePayload)
|
@model_validate(CustomizedPipelineTemplatePayload)
|
||||||
def post(
|
def post(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from pydantic import BaseModel, Field
|
|||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
||||||
|
from controllers.common.rbac import DatasetByPipeline, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
@ -26,7 +27,6 @@ from controllers.console.app.workflow_draft_variable import (
|
|||||||
from controllers.console.datasets.wraps import get_rag_pipeline
|
from controllers.console.datasets.wraps import get_rag_pipeline
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -82,7 +82,7 @@ def _api_prerequisite[T, **P, R](
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(self: T, current_user: Account, *args: P.args, **kwargs: P.kwargs) -> R | Response:
|
def wrapper(self: T, current_user: Account, *args: P.args, **kwargs: P.kwargs) -> R | Response:
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from pydantic import BaseModel, Field
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from controllers.common.fields import SimpleDataResponse
|
from controllers.common.fields import SimpleDataResponse
|
||||||
|
from controllers.common.rbac import DatasetByPipeline, RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
JsonResponseWithStatus,
|
JsonResponseWithStatus,
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
@ -13,7 +14,6 @@ from controllers.console import console_ns
|
|||||||
from controllers.console.datasets.wraps import get_rag_pipeline
|
from controllers.console.datasets.wraps import get_rag_pipeline
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -81,9 +81,7 @@ class RagPipelineImportApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, Workspace()))
|
||||||
RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@model_validate(RagPipelineImportPayload)
|
@model_validate(RagPipelineImportPayload)
|
||||||
def post(self, req_data: RagPipelineImportPayload, current_user: Account) -> JsonResponseWithStatus:
|
def post(self, req_data: RagPipelineImportPayload, current_user: Account) -> JsonResponseWithStatus:
|
||||||
@ -128,9 +126,7 @@ class RagPipelineImportConfirmApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, Workspace()))
|
||||||
RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def post(self, current_user: Account, import_id: str) -> JsonResponseWithStatus:
|
def post(self, current_user: Account, import_id: str) -> JsonResponseWithStatus:
|
||||||
with Session(db.engine, expire_on_commit=False) as session:
|
with Session(db.engine, expire_on_commit=False) as session:
|
||||||
@ -160,7 +156,7 @@ class RagPipelineImportCheckDependenciesApi(Resource):
|
|||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_READONLY)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_READONLY, DatasetByPipeline()))
|
||||||
def get(self, pipeline: Pipeline) -> JsonResponseWithStatus:
|
def get(self, pipeline: Pipeline) -> JsonResponseWithStatus:
|
||||||
with Session(db.engine, expire_on_commit=False) as session:
|
with Session(db.engine, expire_on_commit=False) as session:
|
||||||
import_service = RagPipelineDslService(session)
|
import_service = RagPipelineDslService(session)
|
||||||
@ -178,7 +174,7 @@ class RagPipelineExportApi(Resource):
|
|||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_IMPORT_EXPORT_DSL)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_IMPORT_EXPORT_DSL, DatasetByPipeline()))
|
||||||
@model_validate(IncludeSecretQuery)
|
@model_validate(IncludeSecretQuery)
|
||||||
def get(self, req_data: IncludeSecretQuery, pipeline: Pipeline) -> JsonResponseWithStatus:
|
def get(self, req_data: IncludeSecretQuery, pipeline: Pipeline) -> JsonResponseWithStatus:
|
||||||
# Add include_secret params
|
# Add include_secret params
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import services
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.controller_schemas import DefaultBlockConfigQuery, WorkflowListQuery, WorkflowUpdatePayload
|
from controllers.common.controller_schemas import DefaultBlockConfigQuery, WorkflowListQuery, WorkflowUpdatePayload
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import DatasetByPipeline, DatasetId, RBACCheck
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
@ -32,7 +33,6 @@ from controllers.console.app.wraps import with_session
|
|||||||
from controllers.console.datasets.wraps import get_rag_pipeline, load_rag_pipeline
|
from controllers.console.datasets.wraps import get_rag_pipeline, load_rag_pipeline
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -197,7 +197,7 @@ class DraftRagPipelineApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
def get(self, pipeline: Pipeline):
|
def get(self, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
Get draft rag pipeline's workflow
|
Get draft rag pipeline's workflow
|
||||||
@ -219,7 +219,7 @@ class DraftRagPipelineApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@console_ns.expect(console_ns.models[DraftWorkflowSyncPayload.__name__])
|
@console_ns.expect(console_ns.models[DraftWorkflowSyncPayload.__name__])
|
||||||
@console_ns.response(200, "Success", console_ns.models[RagPipelineWorkflowSyncResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[RagPipelineWorkflowSyncResponse.__name__])
|
||||||
def post(self, current_user: Account, pipeline: Pipeline):
|
def post(self, current_user: Account, pipeline: Pipeline):
|
||||||
@ -280,7 +280,7 @@ class RagPipelineDraftRunIterationNodeApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@model_validate(NodeRunPayload)
|
@model_validate(NodeRunPayload)
|
||||||
def post(self, req_data: NodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
def post(self, req_data: NodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||||
"""
|
"""
|
||||||
@ -313,7 +313,7 @@ class RagPipelineDraftRunLoopNodeApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@model_validate(NodeRunPayload)
|
@model_validate(NodeRunPayload)
|
||||||
@ -348,7 +348,7 @@ class DraftRagPipelineRunApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(DraftWorkflowRunPayload)
|
@model_validate(DraftWorkflowRunPayload)
|
||||||
@ -382,7 +382,7 @@ class PublishedRagPipelineRunApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(PublishedWorkflowRunPayload)
|
@model_validate(PublishedWorkflowRunPayload)
|
||||||
@ -417,7 +417,7 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@model_validate(DatasourceNodeRunPayload)
|
@model_validate(DatasourceNodeRunPayload)
|
||||||
@ -449,7 +449,7 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@ -486,7 +486,7 @@ class RagPipelineDraftNodeRunApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@ -516,7 +516,7 @@ class RagPipelineTaskStopApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@ -540,7 +540,7 @@ class PublishedRagPipelineApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
def get(self, pipeline: Pipeline):
|
def get(self, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
@ -565,7 +565,7 @@ class PublishedRagPipelineApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
def post(self, current_user: Account, pipeline: Pipeline):
|
def post(self, current_user: Account, pipeline: Pipeline):
|
||||||
@ -600,7 +600,7 @@ class DefaultRagPipelineBlockConfigsApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
def get(self, pipeline: Pipeline):
|
def get(self, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
@ -623,7 +623,7 @@ class DefaultRagPipelineBlockConfigApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@model_validate(DefaultBlockConfigQuery)
|
@model_validate(DefaultBlockConfigQuery)
|
||||||
def get(self, req_data: DefaultBlockConfigQuery, pipeline: Pipeline, block_type: str):
|
def get(self, req_data: DefaultBlockConfigQuery, pipeline: Pipeline, block_type: str):
|
||||||
@ -656,7 +656,7 @@ class PublishedAllRagPipelineApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@model_validate(WorkflowListQuery)
|
@model_validate(WorkflowListQuery)
|
||||||
@ -702,7 +702,7 @@ class RagPipelineDraftWorkflowRestoreApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
def post(self, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
def post(self, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
||||||
@ -737,7 +737,7 @@ class RagPipelineByIdApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@console_ns.expect(console_ns.models[WorkflowUpdatePayload.__name__])
|
@console_ns.expect(console_ns.models[WorkflowUpdatePayload.__name__])
|
||||||
@ -773,7 +773,7 @@ class RagPipelineByIdApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
def delete(self, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
def delete(self, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
||||||
@ -816,7 +816,7 @@ class PublishedRagPipelineSecondStepApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@model_validate(NodeIdQuery)
|
@model_validate(NodeIdQuery)
|
||||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
@ -839,7 +839,7 @@ class PublishedRagPipelineFirstStepApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@model_validate(NodeIdQuery)
|
@model_validate(NodeIdQuery)
|
||||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
@ -862,7 +862,7 @@ class DraftRagPipelineFirstStepApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@model_validate(NodeIdQuery)
|
@model_validate(NodeIdQuery)
|
||||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
@ -885,7 +885,7 @@ class DraftRagPipelineSecondStepApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@model_validate(NodeIdQuery)
|
@model_validate(NodeIdQuery)
|
||||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
@ -1035,7 +1035,7 @@ class RagPipelineTransformApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||||
dataset = DatasetService.get_dataset_for_tenant(str(dataset_id), current_tenant_id, session=session)
|
dataset = DatasetService.get_dataset_for_tenant(str(dataset_id), current_tenant_id, session=session)
|
||||||
@ -1070,7 +1070,7 @@ class RagPipelineDatasourceVariableApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_rag_pipeline
|
@get_rag_pipeline
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline()))
|
||||||
@model_validate(DatasourceVariablesPayload)
|
@model_validate(DatasourceVariablesPayload)
|
||||||
def post(self, req_data: DatasourceVariablesPayload, current_user: Account, pipeline: Pipeline):
|
def post(self, req_data: DatasourceVariablesPayload, current_user: Account, pipeline: Pipeline):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"""Flask adapter for Console API admission."""
|
"""Flask adapter for Console API admission."""
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, Sequence
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Concatenate
|
from typing import Concatenate
|
||||||
|
|
||||||
@ -8,8 +8,7 @@ from flask import Response, abort, request
|
|||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.wraps import enforce_rbac_access
|
from controllers.common.rbac import RBAC_CHECKS_ATTR, RBACCheck, enforce_rbac_checks
|
||||||
from controllers.console.app.wraps import enforce_agent_manage_or_app_scene
|
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
enable_change_email,
|
enable_change_email,
|
||||||
@ -17,11 +16,9 @@ from controllers.console.wraps import (
|
|||||||
setup_required,
|
setup_required,
|
||||||
)
|
)
|
||||||
from core.logging.context import get_request_id, get_trace_id
|
from core.logging.context import get_request_id, get_trace_id
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
|
||||||
from enums import DeploymentEdition
|
from enums import DeploymentEdition
|
||||||
from libs.login import current_account_with_tenant, login_required
|
from libs.login import current_account_with_tenant, login_required
|
||||||
from machinery.context import RequestContext
|
from machinery.context import RequestContext
|
||||||
from machinery.errors import AdmissionConfigurationError
|
|
||||||
from models.account import TenantAccountRole
|
from models.account import TenantAccountRole
|
||||||
from services.system_feature_service import SystemFeatureService
|
from services.system_feature_service import SystemFeatureService
|
||||||
|
|
||||||
@ -50,10 +47,7 @@ def console_account_admission[T, **P, R](
|
|||||||
require_initialized: bool = True,
|
require_initialized: bool = True,
|
||||||
require_valid_enterprise_license: bool = False,
|
require_valid_enterprise_license: bool = False,
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||||
rbac_resource_scope: RBACResourceScope | None = None,
|
rbac_checks: Sequence[RBACCheck] | None = None,
|
||||||
rbac_permission: RBACPermission | None = None,
|
|
||||||
rbac_resource_required: bool = True,
|
|
||||||
agent_manage_fallback: bool = False,
|
|
||||||
) -> Callable[
|
) -> Callable[
|
||||||
[Callable[Concatenate[T, RequestContext, P], R]],
|
[Callable[Concatenate[T, RequestContext, P], R]],
|
||||||
Callable[Concatenate[T, P], R | Response],
|
Callable[Concatenate[T, P], R | Response],
|
||||||
@ -66,40 +60,23 @@ def console_account_admission[T, **P, R](
|
|||||||
context construction.
|
context construction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (rbac_resource_scope is None) != (rbac_permission is None):
|
|
||||||
raise AdmissionConfigurationError("RBAC resource scope and permission must be configured together")
|
|
||||||
if agent_manage_fallback and rbac_resource_scope != RBACResourceScope.APP:
|
|
||||||
raise AdmissionConfigurationError("agent_manage_fallback requires rbac_resource_scope=RBACResourceScope.APP")
|
|
||||||
if agent_manage_fallback and not rbac_resource_required:
|
|
||||||
raise AdmissionConfigurationError("agent_manage_fallback requires rbac_resource_required=True")
|
|
||||||
|
|
||||||
def decorator(
|
def decorator(
|
||||||
view: Callable[Concatenate[T, RequestContext, P], R],
|
view: Callable[Concatenate[T, RequestContext, P], R],
|
||||||
) -> Callable[Concatenate[T, P], R | Response]:
|
) -> Callable[Concatenate[T, P], R | Response]:
|
||||||
@wraps(view)
|
@wraps(view, updated=())
|
||||||
def inject_request_context(self: T, /, *args: P.args, **kwargs: P.kwargs) -> R:
|
def inject_request_context(self: T, /, *args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
account_with_tenant = current_account_with_tenant()
|
account_with_tenant = current_account_with_tenant()
|
||||||
account = account_with_tenant.account
|
account = account_with_tenant.account
|
||||||
tenant_id = account_with_tenant.tenant_id
|
tenant_id = account_with_tenant.tenant_id
|
||||||
if allowed_roles is not None and not dify_config.RBAC_ENABLED and account.role not in allowed_roles:
|
if allowed_roles is not None and not dify_config.RBAC_ENABLED and account.role not in allowed_roles:
|
||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
if rbac_resource_scope is not None and rbac_permission is not None:
|
if rbac_checks is not None:
|
||||||
if agent_manage_fallback:
|
enforce_rbac_checks(
|
||||||
enforce_agent_manage_or_app_scene(
|
tenant_id=tenant_id,
|
||||||
tenant_id=tenant_id,
|
account_id=account.id,
|
||||||
account_id=account.id,
|
checks=rbac_checks,
|
||||||
scene=rbac_permission,
|
path_args=kwargs,
|
||||||
path_args=kwargs,
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
enforce_rbac_access(
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
account_id=account.id,
|
|
||||||
resource_type=rbac_resource_scope,
|
|
||||||
scene=rbac_permission,
|
|
||||||
resource_required=rbac_resource_required,
|
|
||||||
path_args=kwargs,
|
|
||||||
)
|
|
||||||
request_context = RequestContext(
|
request_context = RequestContext(
|
||||||
account_id=account.id,
|
account_id=account.id,
|
||||||
active_workspace_id=tenant_id,
|
active_workspace_id=tenant_id,
|
||||||
@ -108,6 +85,9 @@ def console_account_admission[T, **P, R](
|
|||||||
)
|
)
|
||||||
return view(self, request_context, *args, **kwargs)
|
return view(self, request_context, *args, **kwargs)
|
||||||
|
|
||||||
|
if rbac_checks is not None:
|
||||||
|
setattr(inject_request_context, RBAC_CHECKS_ATTR, rbac_checks)
|
||||||
|
|
||||||
admitted: Callable[Concatenate[T, P], R | Response] = inject_request_context
|
admitted: Callable[Concatenate[T, P], R | Response] = inject_request_context
|
||||||
if require_change_email_enabled:
|
if require_change_email_enabled:
|
||||||
admitted = enable_change_email(admitted)
|
admitted = enable_change_email(admitted)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
|
|||||||
|
|
||||||
from controllers.common.controller_schemas import WorkflowUpdatePayload
|
from controllers.common.controller_schemas import WorkflowUpdatePayload
|
||||||
from controllers.common.fields import GeneratedAppResponse, SimpleResultResponse
|
from controllers.common.fields import GeneratedAppResponse, SimpleResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.app.error import DraftWorkflowNotExist, DraftWorkflowNotSync
|
from controllers.console.app.error import DraftWorkflowNotExist, DraftWorkflowNotSync
|
||||||
@ -34,7 +35,6 @@ from controllers.console.snippets.payloads import (
|
|||||||
)
|
)
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -205,9 +205,7 @@ class SnippetDraftWorkflowApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_snippet
|
@get_snippet
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@model_validate(SnippetDraftSyncPayload)
|
@model_validate(SnippetDraftSyncPayload)
|
||||||
def post(self, req_data: SnippetDraftSyncPayload, current_user: Account, snippet: CustomizedSnippet):
|
def post(self, req_data: SnippetDraftSyncPayload, current_user: Account, snippet: CustomizedSnippet):
|
||||||
"""Sync draft workflow for snippet."""
|
"""Sync draft workflow for snippet."""
|
||||||
@ -294,9 +292,7 @@ class SnippetPublishedWorkflowApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_snippet
|
@get_snippet
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
def post(self, current_user: Account, snippet: CustomizedSnippet):
|
def post(self, current_user: Account, snippet: CustomizedSnippet):
|
||||||
"""Publish snippet workflow."""
|
"""Publish snippet workflow."""
|
||||||
snippet_service = _snippet_service()
|
snippet_service = _snippet_service()
|
||||||
@ -355,9 +351,7 @@ class SnippetPublishedAllWorkflowApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_snippet
|
@get_snippet
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@model_validate(SnippetWorkflowListQuery)
|
@model_validate(SnippetWorkflowListQuery)
|
||||||
def get(self, req_data: SnippetWorkflowListQuery, snippet: CustomizedSnippet):
|
def get(self, req_data: SnippetWorkflowListQuery, snippet: CustomizedSnippet):
|
||||||
"""Get all published workflow versions for snippet."""
|
"""Get all published workflow versions for snippet."""
|
||||||
@ -399,9 +393,7 @@ class SnippetDraftWorkflowRestoreApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_snippet
|
@get_snippet
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
def post(self, current_user: Account, snippet: CustomizedSnippet, workflow_id: str):
|
def post(self, current_user: Account, snippet: CustomizedSnippet, workflow_id: str):
|
||||||
"""Restore a published snippet workflow version into the draft workflow."""
|
"""Restore a published snippet workflow version into the draft workflow."""
|
||||||
snippet_service = _snippet_service()
|
snippet_service = _snippet_service()
|
||||||
@ -441,9 +433,7 @@ class SnippetWorkflowByIdApi(Resource):
|
|||||||
@with_current_user
|
@with_current_user
|
||||||
@get_snippet
|
@get_snippet
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@model_validate(WorkflowUpdatePayload)
|
@model_validate(WorkflowUpdatePayload)
|
||||||
def patch(
|
def patch(
|
||||||
self,
|
self,
|
||||||
@ -487,9 +477,7 @@ class SnippetWorkflowByIdApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_snippet
|
@get_snippet
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
def delete(self, snippet: CustomizedSnippet, workflow_id: str):
|
def delete(self, snippet: CustomizedSnippet, workflow_id: str):
|
||||||
"""Delete a published snippet workflow version."""
|
"""Delete a published snippet workflow version."""
|
||||||
snippet_service = _snippet_service()
|
snippet_service = _snippet_service()
|
||||||
|
|||||||
@ -7,13 +7,12 @@ from werkzeug.exceptions import Forbidden, NotFound
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace, enforce_rbac_checks
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.wraps import enforce_rbac_access
|
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
model_validate,
|
model_validate,
|
||||||
)
|
)
|
||||||
from extensions.ext_application_services import application_services
|
from extensions.ext_application_services import application_services
|
||||||
@ -102,12 +101,10 @@ def _enforce_snippet_tag_rbac_if_needed(tag_type: TagType | str | None, context:
|
|||||||
if not dify_config.RBAC_ENABLED:
|
if not dify_config.RBAC_ENABLED:
|
||||||
return
|
return
|
||||||
|
|
||||||
enforce_rbac_access(
|
enforce_rbac_checks(
|
||||||
tenant_id=context.active_workspace_id,
|
tenant_id=context.active_workspace_id,
|
||||||
account_id=context.account_id,
|
account_id=context.account_id,
|
||||||
resource_type=RBACResourceScope.WORKSPACE,
|
checks=[RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace())],
|
||||||
scene=RBACPermission.SNIPPETS_CREATE_AND_MODIFY,
|
|
||||||
resource_required=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -15,11 +15,11 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from controllers.common.fields import SuccessResponse
|
from controllers.common.fields import SuccessResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -235,7 +235,7 @@ class EndpointCollectionApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -267,7 +267,7 @@ class DeprecatedEndpointCreateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -291,7 +291,7 @@ class EndpointListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -321,7 +321,7 @@ class EndpointListForSinglePluginApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -355,7 +355,7 @@ class EndpointItemApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -377,7 +377,7 @@ class EndpointItemApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -410,7 +410,7 @@ class DeprecatedEndpointDeleteApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -443,7 +443,7 @@ class DeprecatedEndpointUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -468,7 +468,7 @@ class EndpointEnableApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -493,7 +493,7 @@ class EndpointDisableApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
|
|||||||
@ -7,12 +7,12 @@ from pydantic import BaseModel, Field, field_validator
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse, ValidationResultResponse
|
from controllers.common.fields import SimpleResultResponse, ValidationResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -208,7 +208,7 @@ class ModelProviderCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserCredentialId)
|
@model_validate(ParserCredentialId)
|
||||||
@ -226,7 +226,7 @@ class ModelProviderCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_CREATE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserCredentialCreate)
|
@model_validate(ParserCredentialCreate)
|
||||||
@ -250,7 +250,7 @@ class ModelProviderCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserCredentialUpdate)
|
@model_validate(ParserCredentialUpdate)
|
||||||
@ -275,7 +275,7 @@ class ModelProviderCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserCredentialDelete)
|
@model_validate(ParserCredentialDelete)
|
||||||
@ -295,7 +295,7 @@ class ModelProviderCredentialSwitchApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_USE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_USE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserCredentialSwitch)
|
@model_validate(ParserCredentialSwitch)
|
||||||
@ -372,7 +372,7 @@ class PreferredProviderTypeUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_USE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_USE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPreferredProviderType)
|
@model_validate(ParserPreferredProviderType)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from flask_restx import Resource
|
|||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse, ValidationResultResponse
|
from controllers.common.fields import SimpleResultResponse, ValidationResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
register_enum_models,
|
register_enum_models,
|
||||||
@ -14,7 +15,6 @@ from controllers.common.schema import (
|
|||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -227,7 +227,7 @@ class DefaultModelApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPostDefault)
|
@model_validate(ParserPostDefault)
|
||||||
@ -276,7 +276,7 @@ class ModelProviderModelApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPostModels)
|
@model_validate(ParserPostModels)
|
||||||
@ -325,7 +325,7 @@ class ModelProviderModelApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserDeleteModels)
|
@model_validate(ParserDeleteModels)
|
||||||
@ -350,7 +350,7 @@ class ModelProviderModelCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -413,7 +413,7 @@ class ModelProviderModelCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_CREATE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserCreateCredential)
|
@model_validate(ParserCreateCredential)
|
||||||
@ -446,7 +446,7 @@ class ModelProviderModelCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserUpdateCredential)
|
@model_validate(ParserUpdateCredential)
|
||||||
@ -474,7 +474,7 @@ class ModelProviderModelCredentialApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserDeleteCredential)
|
@model_validate(ParserDeleteCredential)
|
||||||
@ -499,7 +499,7 @@ class ModelProviderModelCredentialSwitchApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_USE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_USE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserSwitch)
|
@model_validate(ParserSwitch)
|
||||||
@ -526,7 +526,7 @@ class ModelProviderModelEnableApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@model_validate(ParserDeleteModels)
|
@model_validate(ParserDeleteModels)
|
||||||
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||||
|
|
||||||
@ -548,7 +548,7 @@ class ModelProviderModelDisableApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@model_validate(ParserDeleteModels)
|
@model_validate(ParserDeleteModels)
|
||||||
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from werkzeug.exceptions import Forbidden
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import BinaryFileResponse, SuccessResponse
|
from controllers.common.fields import BinaryFileResponse, SuccessResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
register_enum_models,
|
register_enum_models,
|
||||||
@ -21,7 +22,6 @@ from controllers.console import console_ns
|
|||||||
from controllers.console.workspace import plugin_permission_required
|
from controllers.console.workspace import plugin_permission_required
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -570,7 +570,7 @@ class PluginDebuggingKeyApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_DEBUG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_DEBUG, Workspace()))
|
||||||
@plugin_permission_required(debug_required=True)
|
@plugin_permission_required(debug_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str):
|
def get(self, tenant_id: str):
|
||||||
@ -750,7 +750,7 @@ class PluginUploadFromPkgApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def post(self, tenant_id: str):
|
def post(self, tenant_id: str):
|
||||||
@ -771,7 +771,7 @@ class PluginUploadFromGithubApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserGithubUpload)
|
@model_validate(ParserGithubUpload)
|
||||||
@ -793,7 +793,7 @@ class PluginUploadFromBundleApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def post(self, tenant_id: str):
|
def post(self, tenant_id: str):
|
||||||
@ -814,7 +814,7 @@ class PluginInstallFromPkgApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPluginIdentifiers)
|
@model_validate(ParserPluginIdentifiers)
|
||||||
@ -835,7 +835,7 @@ class PluginInstallFromGithubApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserGithubInstall)
|
@model_validate(ParserGithubInstall)
|
||||||
@ -862,7 +862,7 @@ class PluginInstallFromMarketplaceApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPluginIdentifiers)
|
@model_validate(ParserPluginIdentifiers)
|
||||||
@ -883,7 +883,7 @@ class PluginFetchMarketplacePkgApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPluginIdentifierQuery)
|
@model_validate(ParserPluginIdentifierQuery)
|
||||||
@ -909,7 +909,7 @@ class PluginFetchManifestApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_INSTALL, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_INSTALL, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserPluginIdentifierQuery)
|
@model_validate(ParserPluginIdentifierQuery)
|
||||||
@ -1014,7 +1014,7 @@ class PluginUpgradeFromMarketplaceApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserMarketplaceUpgrade)
|
@model_validate(ParserMarketplaceUpgrade)
|
||||||
@ -1037,7 +1037,7 @@ class PluginUpgradeFromGithubApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserGithubUpgrade)
|
@model_validate(ParserGithubUpgrade)
|
||||||
@ -1065,7 +1065,7 @@ class PluginUninstallApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_DELETE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_DELETE, Workspace()))
|
||||||
@plugin_permission_required(install_required=True)
|
@plugin_permission_required(install_required=True)
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserUninstall)
|
@model_validate(ParserUninstall)
|
||||||
@ -1138,7 +1138,7 @@ class PluginFetchDynamicSelectOptionsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_MODEL_CONFIG, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_MODEL_CONFIG, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -1169,7 +1169,7 @@ class PluginFetchDynamicSelectOptionsWithCredentialsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -1201,7 +1201,7 @@ class PluginChangeAutoUpgradeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserAutoUpgradeChange)
|
@model_validate(ParserAutoUpgradeChange)
|
||||||
@ -1256,7 +1256,7 @@ class PluginAutoUpgradeExcludePluginApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ParserExcludePlugin)
|
@model_validate(ParserExcludePlugin)
|
||||||
def post(self, req_data: ParserExcludePlugin, tenant_id: str):
|
def post(self, req_data: ParserExcludePlugin, tenant_id: str):
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
@ -9,9 +11,10 @@ from sqlalchemy import select
|
|||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
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 import console_ns
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate, rbac_permission_required
|
from controllers.console.wraps import RBACPermission, model_validate, rbac_permission_required
|
||||||
from core.db.session_factory import session_factory
|
from core.db.session_factory import session_factory
|
||||||
from enums import DeploymentEdition
|
from enums import DeploymentEdition
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
@ -50,6 +53,7 @@ register_response_schema_models(
|
|||||||
svc.MyPermissionsResponse,
|
svc.MyPermissionsResponse,
|
||||||
svc.AppAccessMatrix,
|
svc.AppAccessMatrix,
|
||||||
svc.DatasetAccessMatrix,
|
svc.DatasetAccessMatrix,
|
||||||
|
svc.AgentAccessMatrix,
|
||||||
svc.WorkspaceAccessMatrix,
|
svc.WorkspaceAccessMatrix,
|
||||||
svc.ResourceWhitelist,
|
svc.ResourceWhitelist,
|
||||||
svc.ResourceWhitelistConfig,
|
svc.ResourceWhitelistConfig,
|
||||||
@ -282,24 +286,6 @@ class RBACWorkspaceCatalogApi(Resource):
|
|||||||
return _dump(svc.RBACService.Catalog.workspace(tenant_id, account_id))
|
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.
|
# Roles.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@ -323,9 +309,7 @@ class _RoleUpsertRequest(BaseModel):
|
|||||||
@console_ns.route("/workspaces/current/rbac/roles")
|
@console_ns.route("/workspaces/current/rbac/roles")
|
||||||
class RBACRolesApi(Resource):
|
class RBACRolesApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[_RBACRoleList.__name__])
|
@console_ns.response(200, "Success", console_ns.models[_RBACRoleList.__name__])
|
||||||
@model_validate(_RolesListQuery)
|
@model_validate(_RolesListQuery)
|
||||||
def get(self, req_data: _RolesListQuery):
|
def get(self, req_data: _RolesListQuery):
|
||||||
@ -349,9 +333,7 @@ class RBACRolesApi(Resource):
|
|||||||
return _dump(result)
|
return _dump(result)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(201, "Role created", console_ns.models[svc.RBACRole.__name__])
|
@console_ns.response(201, "Role created", console_ns.models[svc.RBACRole.__name__])
|
||||||
def post(self):
|
def post(self):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -363,9 +345,7 @@ class RBACRolesApi(Resource):
|
|||||||
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>")
|
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>")
|
||||||
class RBACRoleItemApi(Resource):
|
class RBACRoleItemApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
||||||
def get(self, role_id):
|
def get(self, role_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -379,9 +359,7 @@ class RBACRoleItemApi(Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
||||||
def put(self, role_id):
|
def put(self, role_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -390,9 +368,7 @@ class RBACRoleItemApi(Resource):
|
|||||||
return _dump(role)
|
return _dump(role)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__])
|
||||||
def delete(self, role_id):
|
def delete(self, role_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -403,9 +379,7 @@ class RBACRoleItemApi(Resource):
|
|||||||
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>/copy")
|
@console_ns.route("/workspaces/current/rbac/roles/<uuid:role_id>/copy")
|
||||||
class RBACRoleCopyApi(Resource):
|
class RBACRoleCopyApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(201, "Role copied", console_ns.models[svc.RBACRole.__name__])
|
@console_ns.response(201, "Role copied", console_ns.models[svc.RBACRole.__name__])
|
||||||
def post(self, role_id):
|
def post(self, role_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -430,9 +404,7 @@ class _AccessPolicyUpdateRequest(BaseModel):
|
|||||||
@console_ns.route("/workspaces/current/rbac/access-policies")
|
@console_ns.route("/workspaces/current/rbac/access-policies")
|
||||||
class RBACAccessPoliciesApi(Resource):
|
class RBACAccessPoliciesApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[_AccessPolicyList.__name__])
|
@console_ns.response(200, "Success", console_ns.models[_AccessPolicyList.__name__])
|
||||||
def get(self):
|
def get(self):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -449,9 +421,7 @@ class RBACAccessPoliciesApi(Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(201, "Policy created", console_ns.models[svc.AccessPolicy.__name__])
|
@console_ns.response(201, "Policy created", console_ns.models[svc.AccessPolicy.__name__])
|
||||||
def post(self):
|
def post(self):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -472,18 +442,14 @@ class RBACAccessPoliciesApi(Resource):
|
|||||||
@console_ns.route("/workspaces/current/rbac/access-policies/<uuid:policy_id>")
|
@console_ns.route("/workspaces/current/rbac/access-policies/<uuid:policy_id>")
|
||||||
class RBACAccessPolicyItemApi(Resource):
|
class RBACAccessPolicyItemApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
||||||
def get(self, policy_id):
|
def get(self, policy_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
return _dump(svc.RBACService.AccessPolicies.get(tenant_id, account_id, str(policy_id)))
|
return _dump(svc.RBACService.AccessPolicies.get(tenant_id, account_id, str(policy_id)))
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
||||||
def put(self, policy_id):
|
def put(self, policy_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -501,9 +467,7 @@ class RBACAccessPolicyItemApi(Resource):
|
|||||||
return _dump(policy)
|
return _dump(policy)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicy.__name__])
|
||||||
def delete(self, policy_id):
|
def delete(self, policy_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -514,9 +478,7 @@ class RBACAccessPolicyItemApi(Resource):
|
|||||||
@console_ns.route("/workspaces/current/rbac/access-policies/<uuid:policy_id>/copy")
|
@console_ns.route("/workspaces/current/rbac/access-policies/<uuid:policy_id>/copy")
|
||||||
class RBACAccessPolicyCopyApi(Resource):
|
class RBACAccessPolicyCopyApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(201, "Policy copied", console_ns.models[svc.AccessPolicy.__name__])
|
@console_ns.response(201, "Policy copied", console_ns.models[svc.AccessPolicy.__name__])
|
||||||
def post(self, policy_id):
|
def post(self, policy_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -527,9 +489,7 @@ class RBACAccessPolicyCopyApi(Resource):
|
|||||||
@console_ns.route("/workspaces/current/rbac/access-policy-bindings/<uuid:binding_id>/lock")
|
@console_ns.route("/workspaces/current/rbac/access-policy-bindings/<uuid:binding_id>/lock")
|
||||||
class RBACAccessPolicyBindingLockApi(Resource):
|
class RBACAccessPolicyBindingLockApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicyBindingState.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicyBindingState.__name__])
|
||||||
def put(self, binding_id):
|
def put(self, binding_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
@ -539,20 +499,13 @@ class RBACAccessPolicyBindingLockApi(Resource):
|
|||||||
@console_ns.route("/workspaces/current/rbac/access-policy-bindings/<uuid:binding_id>/unlock")
|
@console_ns.route("/workspaces/current/rbac/access-policy-bindings/<uuid:binding_id>/unlock")
|
||||||
class RBACAccessPolicyBindingUnlockApi(Resource):
|
class RBACAccessPolicyBindingUnlockApi(Resource):
|
||||||
@login_required
|
@login_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicyBindingState.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.AccessPolicyBindingState.__name__])
|
||||||
def put(self, binding_id):
|
def put(self, binding_id):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
return _dump(svc.RBACService.AccessPolicyBindings.unlock(tenant_id, account_id, str(binding_id)))
|
return _dump(svc.RBACService.AccessPolicyBindings.unlock(tenant_id, account_id, str(binding_id)))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Per-app access (App Access Config).
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class _ResourceAccessScopeRequest(BaseModel):
|
class _ResourceAccessScopeRequest(BaseModel):
|
||||||
automatic_include_workspace_members: bool
|
automatic_include_workspace_members: bool
|
||||||
|
|
||||||
@ -611,346 +564,337 @@ class RBACMyPermissionsApi(Resource):
|
|||||||
account_id,
|
account_id,
|
||||||
app_id=request.args.get("app_id") or None,
|
app_id=request.args.get("app_id") or None,
|
||||||
dataset_id=request.args.get("dataset_id") or None,
|
dataset_id=request.args.get("dataset_id") or None,
|
||||||
|
agent_id=request.args.get("agent_id") or None,
|
||||||
session=db.session(),
|
session=db.session(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/access-policy")
|
type _ResourceAccessClient = (
|
||||||
class RBACAppMatrixApi(Resource):
|
type[svc.RBACService.AppAccess] | type[svc.RBACService.DatasetAccess] | type[svc.RBACService.AgentAccess]
|
||||||
@login_required
|
|
||||||
@console_ns.doc(params=query_params_from_model(_AccessControlLanguageQuery))
|
|
||||||
@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.expect(console_ns.models[_ResourceAccessScopeRequest.__name__])
|
|
||||||
@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)
|
|
||||||
result = svc.RBACService.AppAccess.replace_whitelist(
|
|
||||||
tenant_id,
|
|
||||||
account_id,
|
|
||||||
str(app_id),
|
|
||||||
svc.ReplaceMemberBindings(automatic_include_workspace_members=request.automatic_include_workspace_members),
|
|
||||||
)
|
|
||||||
if request.automatic_include_workspace_members:
|
|
||||||
initialize_created_app_rbac_access_task.delay(tenant_id, account_id, app_id=str(app_id))
|
|
||||||
return _dump(result)
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/whitelist_config")
|
|
||||||
class RBACAppWhitelistConfigApi(Resource):
|
|
||||||
@login_required
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelistConfig.__name__])
|
|
||||||
def get(self, app_id):
|
|
||||||
tenant_id, account_id = _current_ids()
|
|
||||||
return _dump(svc.RBACService.AppAccess.whitelist_config(tenant_id, account_id, str(app_id)))
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/apps/<uuid:app_id>/user-access-policies")
|
|
||||||
class RBACAppUserAccessPoliciesApi(Resource):
|
|
||||||
@login_required
|
|
||||||
@console_ns.doc(params=query_params_from_model(_ResourceUserAccessPoliciesQuery))
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceUserAccessPoliciesResponse.__name__])
|
|
||||||
def get(self, app_id):
|
|
||||||
tenant_id, account_id = _current_ids()
|
|
||||||
options = _pagination_options()
|
|
||||||
result = svc.RBACService.AppAccess.user_access_policies(tenant_id, account_id, str(app_id), options=options)
|
|
||||||
_move_resource_maintainer_first(result.data, svc.app_maintainer_id(tenant_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.expect(console_ns.models[svc.ReplaceUserAccessPolicies.__name__])
|
|
||||||
@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,
|
|
||||||
app_id,
|
|
||||||
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.expect(console_ns.models[_DeleteMemberBindingsRequest.__name__])
|
|
||||||
@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.doc(params=query_params_from_model(_AccessControlLanguageQuery))
|
|
||||||
@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_config")
|
|
||||||
class RBACDatasetWhitelistConfigApi(Resource):
|
|
||||||
@login_required
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelistConfig.__name__])
|
|
||||||
def get(self, dataset_id):
|
|
||||||
tenant_id, account_id = _current_ids()
|
|
||||||
return _dump(svc.RBACService.DatasetAccess.whitelist_config(tenant_id, account_id, str(dataset_id)))
|
|
||||||
|
|
||||||
|
|
||||||
@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.expect(console_ns.models[_ResourceAccessScopeRequest.__name__])
|
|
||||||
@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)
|
|
||||||
result = svc.RBACService.DatasetAccess.replace_whitelist(
|
|
||||||
tenant_id,
|
|
||||||
account_id,
|
|
||||||
str(dataset_id),
|
|
||||||
svc.ReplaceMemberBindings(automatic_include_workspace_members=request.automatic_include_workspace_members),
|
|
||||||
)
|
|
||||||
# Widening the scope only records it: the members still need the default access policy
|
|
||||||
# before they can reach the dataset, same as the app whitelist route above.
|
|
||||||
if request.automatic_include_workspace_members:
|
|
||||||
initialize_created_app_rbac_access_task.delay(tenant_id, account_id, dataset_id=str(dataset_id))
|
|
||||||
return _dump(result)
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/datasets/<uuid:dataset_id>/user-access-policies")
|
|
||||||
class RBACDatasetUserAccessPoliciesApi(Resource):
|
|
||||||
@login_required
|
|
||||||
@console_ns.doc(params=query_params_from_model(_ResourceUserAccessPoliciesQuery))
|
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.ResourceUserAccessPoliciesResponse.__name__])
|
|
||||||
def get(self, dataset_id):
|
|
||||||
tenant_id, account_id = _current_ids()
|
|
||||||
options = _pagination_options()
|
|
||||||
result = svc.RBACService.DatasetAccess.user_access_policies(
|
|
||||||
tenant_id, account_id, str(dataset_id), options=options
|
|
||||||
)
|
|
||||||
_move_resource_maintainer_first(result.data, svc.dataset_maintainer_id(tenant_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.expect(console_ns.models[svc.ReplaceUserAccessPolicies.__name__])
|
|
||||||
@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.expect(console_ns.models[_DeleteMemberBindingsRequest.__name__])
|
|
||||||
@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"}
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policy")
|
@dataclass(frozen=True)
|
||||||
class RBACWorkspaceAppMatrixApi(Resource):
|
class _ResourceAccessRoutes:
|
||||||
@login_required
|
resource_type: svc.RBACResourceType
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.WorkspaceAccessMatrix.__name__])
|
class_prefix: str
|
||||||
def get(self):
|
matrix_model: type[BaseModel]
|
||||||
tenant_id, account_id = _current_ids()
|
access: _ResourceAccessClient
|
||||||
options = _pagination_options()
|
catalog: Callable[[str, str], svc.PermissionCatalogResponse]
|
||||||
result = svc.RBACService.WorkspaceAccess.app_matrix(tenant_id, account_id, options=options)
|
workspace_matrix: Callable[[str, str, svc.ListOption], svc.WorkspaceAccessMatrix]
|
||||||
_hydrate_access_matrix_account_names(result.items)
|
workspace_role_bindings: Callable[[str, str, str], svc.RoleBindingsResponse]
|
||||||
return _dump(result)
|
workspace_member_bindings: Callable[[str, str, str], svc.MemberBindingsResponse]
|
||||||
|
replace_workspace_bindings: Callable[[str, str, str, svc.ReplaceBindings], svc.AccessMatrixItem]
|
||||||
|
maintainer_id: Callable[[str, str], str | None] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_segment(self) -> str:
|
||||||
|
return self.resource_type.route.segment
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id_param(self) -> str:
|
||||||
|
return self.resource_type.route.id_param
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policies/<uuid:policy_id>/role-bindings")
|
@dataclass(frozen=True)
|
||||||
class RBACWorkspaceAppRoleBindingsApi(Resource):
|
class _ResourceAccessApis:
|
||||||
@login_required
|
catalog: type[Resource]
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
matrix: type[Resource]
|
||||||
def get(self, policy_id):
|
whitelist: type[Resource]
|
||||||
tenant_id, account_id = _current_ids()
|
whitelist_config: type[Resource]
|
||||||
return _dump(svc.RBACService.WorkspaceAccess.list_app_role_bindings(tenant_id, account_id, str(policy_id)))
|
user_access_policies: type[Resource]
|
||||||
|
user_access_policy_assignment: type[Resource]
|
||||||
|
role_bindings: type[Resource]
|
||||||
|
member_bindings: type[Resource]
|
||||||
|
workspace_matrix: type[Resource]
|
||||||
|
workspace_role_bindings: type[Resource]
|
||||||
|
workspace_bindings: type[Resource]
|
||||||
|
workspace_member_bindings: type[Resource]
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policies/<uuid:policy_id>/bindings")
|
_RESOURCE_ACCESS_ROUTES = (
|
||||||
class RBACWorkspaceAppBindingsApi(Resource):
|
_ResourceAccessRoutes(
|
||||||
@login_required
|
resource_type=svc.RBACResourceType.APP,
|
||||||
@console_ns.expect(console_ns.models[_ReplaceBindingsRequest.__name__])
|
class_prefix="App",
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.AccessMatrixItem.__name__])
|
matrix_model=svc.AppAccessMatrix,
|
||||||
def put(self, policy_id):
|
access=svc.RBACService.AppAccess,
|
||||||
tenant_id, account_id = _current_ids()
|
catalog=lambda tenant_id, account_id: svc.RBACService.Catalog.app(tenant_id, account_id),
|
||||||
request = _payload(_ReplaceBindingsRequest)
|
workspace_matrix=lambda tenant_id, account_id, options: svc.RBACService.WorkspaceAccess.app_matrix(
|
||||||
return _dump(
|
tenant_id, account_id, options=options
|
||||||
svc.RBACService.WorkspaceAccess.replace_app_bindings(
|
),
|
||||||
|
workspace_role_bindings=lambda tenant_id, account_id, policy_id: (
|
||||||
|
svc.RBACService.WorkspaceAccess.list_app_role_bindings(tenant_id, account_id, policy_id)
|
||||||
|
),
|
||||||
|
workspace_member_bindings=lambda tenant_id, account_id, policy_id: (
|
||||||
|
svc.RBACService.WorkspaceAccess.list_app_member_bindings(tenant_id, account_id, policy_id)
|
||||||
|
),
|
||||||
|
replace_workspace_bindings=lambda tenant_id, account_id, policy_id, payload: (
|
||||||
|
svc.RBACService.WorkspaceAccess.replace_app_bindings(tenant_id, account_id, policy_id, payload)
|
||||||
|
),
|
||||||
|
maintainer_id=lambda tenant_id, resource_id: svc.app_maintainer_id(tenant_id, resource_id),
|
||||||
|
),
|
||||||
|
_ResourceAccessRoutes(
|
||||||
|
resource_type=svc.RBACResourceType.DATASET,
|
||||||
|
class_prefix="Dataset",
|
||||||
|
matrix_model=svc.DatasetAccessMatrix,
|
||||||
|
access=svc.RBACService.DatasetAccess,
|
||||||
|
catalog=lambda tenant_id, account_id: svc.RBACService.Catalog.dataset(tenant_id, account_id),
|
||||||
|
workspace_matrix=lambda tenant_id, account_id, options: svc.RBACService.WorkspaceAccess.dataset_matrix(
|
||||||
|
tenant_id, account_id, options=options
|
||||||
|
),
|
||||||
|
workspace_role_bindings=lambda tenant_id, account_id, policy_id: (
|
||||||
|
svc.RBACService.WorkspaceAccess.list_dataset_role_bindings(tenant_id, account_id, policy_id)
|
||||||
|
),
|
||||||
|
workspace_member_bindings=lambda tenant_id, account_id, policy_id: (
|
||||||
|
svc.RBACService.WorkspaceAccess.list_dataset_member_bindings(tenant_id, account_id, policy_id)
|
||||||
|
),
|
||||||
|
replace_workspace_bindings=lambda tenant_id, account_id, policy_id, payload: (
|
||||||
|
svc.RBACService.WorkspaceAccess.replace_dataset_bindings(tenant_id, account_id, policy_id, payload)
|
||||||
|
),
|
||||||
|
maintainer_id=lambda tenant_id, resource_id: svc.dataset_maintainer_id(tenant_id, resource_id),
|
||||||
|
),
|
||||||
|
_ResourceAccessRoutes(
|
||||||
|
resource_type=svc.RBACResourceType.AGENT,
|
||||||
|
class_prefix="Agent",
|
||||||
|
matrix_model=svc.AgentAccessMatrix,
|
||||||
|
access=svc.RBACService.AgentAccess,
|
||||||
|
catalog=lambda tenant_id, account_id: svc.RBACService.Catalog.agent(tenant_id, account_id),
|
||||||
|
workspace_matrix=lambda tenant_id, account_id, options: svc.RBACService.WorkspaceAccess.agent_matrix(
|
||||||
|
tenant_id, account_id, options=options
|
||||||
|
),
|
||||||
|
workspace_role_bindings=lambda tenant_id, account_id, policy_id: (
|
||||||
|
svc.RBACService.WorkspaceAccess.list_agent_role_bindings(tenant_id, account_id, policy_id)
|
||||||
|
),
|
||||||
|
workspace_member_bindings=lambda tenant_id, account_id, policy_id: (
|
||||||
|
svc.RBACService.WorkspaceAccess.list_agent_member_bindings(tenant_id, account_id, policy_id)
|
||||||
|
),
|
||||||
|
replace_workspace_bindings=lambda tenant_id, account_id, policy_id, payload: (
|
||||||
|
svc.RBACService.WorkspaceAccess.replace_agent_bindings(tenant_id, account_id, policy_id, payload)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_resource_access_apis(spec: _ResourceAccessRoutes) -> _ResourceAccessApis:
|
||||||
|
id_param = spec.id_param
|
||||||
|
resource_prefix = f"/workspaces/current/rbac/{spec.url_segment}/<uuid:{id_param}>"
|
||||||
|
workspace_prefix = f"/workspaces/current/rbac/workspace/{spec.url_segment}"
|
||||||
|
|
||||||
|
def resource_id(path_params: Mapping[str, object]) -> str:
|
||||||
|
return str(path_params[id_param])
|
||||||
|
|
||||||
|
def register(resource: type[Resource], name: str, url: str) -> type[Resource]:
|
||||||
|
resource.__name__ = name
|
||||||
|
resource.__qualname__ = name
|
||||||
|
console_ns.route(url)(resource)
|
||||||
|
return resource
|
||||||
|
|
||||||
|
class CatalogApi(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(spec.catalog(tenant_id, account_id))
|
||||||
|
|
||||||
|
class MatrixApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.doc(params=query_params_from_model(_AccessControlLanguageQuery))
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[spec.matrix_model.__name__])
|
||||||
|
def get(self, **path_params):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
result = spec.access.matrix(tenant_id, account_id, resource_id(path_params))
|
||||||
|
_hydrate_access_matrix_account_names(result.items)
|
||||||
|
return _dump(result)
|
||||||
|
|
||||||
|
class WhitelistApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelist.__name__])
|
||||||
|
def get(self, **path_params):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
return _dump(spec.access.whitelist(tenant_id, account_id, resource_id(path_params)))
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@console_ns.expect(console_ns.models[_ResourceAccessScopeRequest.__name__])
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelist.__name__])
|
||||||
|
def put(self, **path_params):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
target_id = resource_id(path_params)
|
||||||
|
scope = _payload(_ResourceAccessScopeRequest)
|
||||||
|
result = spec.access.replace_whitelist(
|
||||||
tenant_id,
|
tenant_id,
|
||||||
account_id,
|
account_id,
|
||||||
str(policy_id),
|
target_id,
|
||||||
svc.ReplaceBindings(role_ids=list(request.role_ids), account_ids=list(request.account_ids)),
|
svc.ReplaceMemberBindings(
|
||||||
|
automatic_include_workspace_members=scope.automatic_include_workspace_members
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
if scope.automatic_include_workspace_members:
|
||||||
|
initialize_created_app_rbac_access_task.delay(tenant_id, account_id, **{id_param: target_id})
|
||||||
|
return _dump(result)
|
||||||
|
|
||||||
|
class WhitelistConfigApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.ResourceWhitelistConfig.__name__])
|
||||||
|
def get(self, **path_params):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
return _dump(spec.access.whitelist_config(tenant_id, account_id, resource_id(path_params)))
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/apps/access-policies/<uuid:policy_id>/member-bindings")
|
class UserAccessPoliciesApi(Resource):
|
||||||
class RBACWorkspaceAppMemberBindingsApi(Resource):
|
@login_required
|
||||||
@login_required
|
@console_ns.doc(params=query_params_from_model(_ResourceUserAccessPoliciesQuery))
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[svc.ResourceUserAccessPoliciesResponse.__name__])
|
||||||
def get(self, policy_id):
|
def get(self, **path_params):
|
||||||
tenant_id, account_id = _current_ids()
|
tenant_id, account_id = _current_ids()
|
||||||
return _dump(svc.RBACService.WorkspaceAccess.list_app_member_bindings(tenant_id, account_id, str(policy_id)))
|
target_id = resource_id(path_params)
|
||||||
|
options = _pagination_options()
|
||||||
|
result = spec.access.user_access_policies(tenant_id, account_id, target_id, options=options)
|
||||||
|
maintainer_id = spec.maintainer_id(tenant_id, target_id) if spec.maintainer_id else None
|
||||||
|
_move_resource_maintainer_first(result.data, maintainer_id)
|
||||||
|
_hydrate_resource_user_account_names(result.data)
|
||||||
|
return _dump(result)
|
||||||
|
|
||||||
|
class UserAccessPolicyAssignmentApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.expect(console_ns.models[svc.ReplaceUserAccessPolicies.__name__])
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.ReplaceUserAccessPoliciesResponse.__name__])
|
||||||
|
def put(self, target_account_id, **path_params):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
payload = _payload(svc.ReplaceUserAccessPolicies)
|
||||||
|
return _dump(
|
||||||
|
spec.access.replace_user_access_policies(
|
||||||
|
tenant_id,
|
||||||
|
account_id,
|
||||||
|
resource_id(path_params),
|
||||||
|
str(target_account_id),
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policy")
|
class RoleBindingsApi(Resource):
|
||||||
class RBACWorkspaceDatasetMatrixApi(Resource):
|
@login_required
|
||||||
@login_required
|
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.WorkspaceAccessMatrix.__name__])
|
def get(self, policy_id, **path_params):
|
||||||
def get(self):
|
tenant_id, account_id = _current_ids()
|
||||||
tenant_id, account_id = _current_ids()
|
return _dump(
|
||||||
options = _pagination_options()
|
spec.access.list_role_bindings(tenant_id, account_id, resource_id(path_params), str(policy_id))
|
||||||
result = svc.RBACService.WorkspaceAccess.dataset_matrix(tenant_id, account_id, options=options)
|
)
|
||||||
_hydrate_access_matrix_account_names(result.items)
|
|
||||||
return _dump(result)
|
|
||||||
|
|
||||||
|
class MemberBindingsApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||||
|
def get(self, policy_id, **path_params):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
return _dump(
|
||||||
|
spec.access.list_member_bindings(tenant_id, account_id, resource_id(path_params), str(policy_id))
|
||||||
|
)
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policies/<uuid:policy_id>/role-bindings")
|
@login_required
|
||||||
class RBACWorkspaceDatasetRoleBindingsApi(Resource):
|
@console_ns.expect(console_ns.models[_DeleteMemberBindingsRequest.__name__])
|
||||||
@login_required
|
@console_ns.response(200, "Success", console_ns.models[svc.MemberBindingsResponse.__name__])
|
||||||
@console_ns.response(200, "Success", console_ns.models[svc.RoleBindingsResponse.__name__])
|
def delete(self, policy_id, **path_params):
|
||||||
def get(self, policy_id):
|
tenant_id, account_id = _current_ids()
|
||||||
tenant_id, account_id = _current_ids()
|
body = _payload(_DeleteMemberBindingsRequest)
|
||||||
return _dump(svc.RBACService.WorkspaceAccess.list_dataset_role_bindings(tenant_id, account_id, str(policy_id)))
|
spec.access.delete_member_bindings(
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policies/<uuid:policy_id>/bindings")
|
|
||||||
class RBACWorkspaceDatasetBindingsApi(Resource):
|
|
||||||
@login_required
|
|
||||||
@console_ns.expect(console_ns.models[_ReplaceBindingsRequest.__name__])
|
|
||||||
@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,
|
tenant_id,
|
||||||
account_id,
|
account_id,
|
||||||
|
resource_id(path_params),
|
||||||
str(policy_id),
|
str(policy_id),
|
||||||
svc.ReplaceBindings(role_ids=list(request.role_ids), account_ids=list(request.account_ids)),
|
svc.DeleteMemberBindings(account_ids=body.account_ids),
|
||||||
)
|
)
|
||||||
)
|
return {"result": "success"}
|
||||||
|
|
||||||
|
class WorkspaceMatrixApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.WorkspaceAccessMatrix.__name__])
|
||||||
|
def get(self):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
result = spec.workspace_matrix(tenant_id, account_id, _pagination_options())
|
||||||
|
_hydrate_access_matrix_account_names(result.items)
|
||||||
|
return _dump(result)
|
||||||
|
|
||||||
|
class WorkspaceRoleBindingsApi(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(spec.workspace_role_bindings(tenant_id, account_id, str(policy_id)))
|
||||||
|
|
||||||
|
class WorkspaceBindingsApi(Resource):
|
||||||
|
@login_required
|
||||||
|
@console_ns.expect(console_ns.models[_ReplaceBindingsRequest.__name__])
|
||||||
|
@console_ns.response(200, "Success", console_ns.models[svc.AccessMatrixItem.__name__])
|
||||||
|
def put(self, policy_id):
|
||||||
|
tenant_id, account_id = _current_ids()
|
||||||
|
body = _payload(_ReplaceBindingsRequest)
|
||||||
|
return _dump(
|
||||||
|
spec.replace_workspace_bindings(
|
||||||
|
tenant_id,
|
||||||
|
account_id,
|
||||||
|
str(policy_id),
|
||||||
|
svc.ReplaceBindings(role_ids=list(body.role_ids), account_ids=list(body.account_ids)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
class WorkspaceMemberBindingsApi(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(spec.workspace_member_bindings(tenant_id, account_id, str(policy_id)))
|
||||||
|
|
||||||
|
prefix = spec.class_prefix
|
||||||
|
return _ResourceAccessApis(
|
||||||
|
catalog=register(
|
||||||
|
CatalogApi,
|
||||||
|
f"RBAC{prefix}CatalogApi",
|
||||||
|
f"/workspaces/current/rbac/role-permissions/catalog/{spec.resource_type.value}",
|
||||||
|
),
|
||||||
|
matrix=register(MatrixApi, f"RBAC{prefix}MatrixApi", f"{resource_prefix}/access-policy"),
|
||||||
|
whitelist=register(WhitelistApi, f"RBAC{prefix}WhitelistApi", f"{resource_prefix}/whitelist"),
|
||||||
|
whitelist_config=register(
|
||||||
|
WhitelistConfigApi, f"RBAC{prefix}WhitelistConfigApi", f"{resource_prefix}/whitelist_config"
|
||||||
|
),
|
||||||
|
user_access_policies=register(
|
||||||
|
UserAccessPoliciesApi, f"RBAC{prefix}UserAccessPoliciesApi", f"{resource_prefix}/user-access-policies"
|
||||||
|
),
|
||||||
|
user_access_policy_assignment=register(
|
||||||
|
UserAccessPolicyAssignmentApi,
|
||||||
|
f"RBAC{prefix}UserAccessPolicyAssignmentApi",
|
||||||
|
f"{resource_prefix}/users/<uuid:target_account_id>/access-policies",
|
||||||
|
),
|
||||||
|
role_bindings=register(
|
||||||
|
RoleBindingsApi,
|
||||||
|
f"RBAC{prefix}RoleBindingsApi",
|
||||||
|
f"{resource_prefix}/access-policies/<uuid:policy_id>/role-bindings",
|
||||||
|
),
|
||||||
|
member_bindings=register(
|
||||||
|
MemberBindingsApi,
|
||||||
|
f"RBAC{prefix}MemberBindingsApi",
|
||||||
|
f"{resource_prefix}/access-policies/<string:policy_id>/member-bindings",
|
||||||
|
),
|
||||||
|
workspace_matrix=register(
|
||||||
|
WorkspaceMatrixApi, f"RBACWorkspace{prefix}MatrixApi", f"{workspace_prefix}/access-policy"
|
||||||
|
),
|
||||||
|
workspace_role_bindings=register(
|
||||||
|
WorkspaceRoleBindingsApi,
|
||||||
|
f"RBACWorkspace{prefix}RoleBindingsApi",
|
||||||
|
f"{workspace_prefix}/access-policies/<uuid:policy_id>/role-bindings",
|
||||||
|
),
|
||||||
|
workspace_bindings=register(
|
||||||
|
WorkspaceBindingsApi,
|
||||||
|
f"RBACWorkspace{prefix}BindingsApi",
|
||||||
|
f"{workspace_prefix}/access-policies/<uuid:policy_id>/bindings",
|
||||||
|
),
|
||||||
|
workspace_member_bindings=register(
|
||||||
|
WorkspaceMemberBindingsApi,
|
||||||
|
f"RBACWorkspace{prefix}MemberBindingsApi",
|
||||||
|
f"{workspace_prefix}/access-policies/<uuid:policy_id>/member-bindings",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/workspaces/current/rbac/workspace/datasets/access-policies/<uuid:policy_id>/member-bindings")
|
_RESOURCE_ACCESS_APIS = {spec.resource_type: _build_resource_access_apis(spec) for spec in _RESOURCE_ACCESS_ROUTES}
|
||||||
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))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -10,13 +10,13 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from controllers.common.fields import BinaryFileResponse
|
from controllers.common.fields import BinaryFileResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.flask_admission import console_account_admission
|
from controllers.console.flask_admission import console_account_admission
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
)
|
)
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
@ -298,9 +298,7 @@ class WorkspaceSkillsApi(Resource):
|
|||||||
@console_ns.doc(params=query_params_from_model(WorkspaceSkillsQuery))
|
@console_ns.doc(params=query_params_from_model(WorkspaceSkillsQuery))
|
||||||
@console_ns.response(200, "Workspace skills", console_ns.models[SkillListResponse.__name__])
|
@console_ns.response(200, "Workspace skills", console_ns.models[SkillListResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_VIEW, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_VIEW,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, request_context: RequestContext):
|
def get(self, session: Session, request_context: RequestContext):
|
||||||
@ -325,9 +323,7 @@ class WorkspaceSkillsApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[SkillCreatePayload.__name__])
|
@console_ns.expect(console_ns.models[SkillCreatePayload.__name__])
|
||||||
@console_ns.response(201, "Skill created", console_ns.models[SkillDetailResponse.__name__])
|
@console_ns.response(201, "Skill created", console_ns.models[SkillDetailResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_EDIT, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_EDIT,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -391,9 +387,7 @@ class WorkspaceSkillImportApi(Resource):
|
|||||||
@console_ns.doc(description="Import a Skill zip package from multipart form field `file`.")
|
@console_ns.doc(description="Import a Skill zip package from multipart form field `file`.")
|
||||||
@console_ns.response(201, "Skill imported", console_ns.models[SkillDetailResponse.__name__])
|
@console_ns.response(201, "Skill imported", console_ns.models[SkillDetailResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_EDIT, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_EDIT,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -419,9 +413,7 @@ class WorkspaceSkillImportApi(Resource):
|
|||||||
class WorkspaceSkillApi(Resource):
|
class WorkspaceSkillApi(Resource):
|
||||||
@console_ns.response(200, "Skill detail", console_ns.models[SkillDetailResponse.__name__])
|
@console_ns.response(200, "Skill detail", console_ns.models[SkillDetailResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_VIEW, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_VIEW,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, request_context: RequestContext, skill_id: str):
|
def get(self, session: Session, request_context: RequestContext, skill_id: str):
|
||||||
@ -436,9 +428,7 @@ class WorkspaceSkillApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[SkillMetadataPayload.__name__])
|
@console_ns.expect(console_ns.models[SkillMetadataPayload.__name__])
|
||||||
@console_ns.response(200, "Skill updated", console_ns.models[SkillResponse.__name__])
|
@console_ns.response(200, "Skill updated", console_ns.models[SkillResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_EDIT, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_EDIT,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -462,9 +452,7 @@ class WorkspaceSkillApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[SkillDeletePayload.__name__])
|
@console_ns.expect(console_ns.models[SkillDeletePayload.__name__])
|
||||||
@console_ns.response(200, "Skill deleted", console_ns.models[SkillDeleteResponse.__name__])
|
@console_ns.response(200, "Skill deleted", console_ns.models[SkillDeleteResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_DELETE, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_DELETE,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -487,9 +475,7 @@ class WorkspaceSkillApi(Resource):
|
|||||||
class WorkspaceSkillDuplicateApi(Resource):
|
class WorkspaceSkillDuplicateApi(Resource):
|
||||||
@console_ns.response(201, "Skill duplicated", console_ns.models[SkillDetailResponse.__name__])
|
@console_ns.response(201, "Skill duplicated", console_ns.models[SkillDetailResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_EDIT, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_EDIT,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -509,9 +495,7 @@ class WorkspaceSkillDuplicateApi(Resource):
|
|||||||
class WorkspaceSkillExportApi(Resource):
|
class WorkspaceSkillExportApi(Resource):
|
||||||
@console_ns.response(200, "Published Skill zip archive")
|
@console_ns.response(200, "Published Skill zip archive")
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_VIEW, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_VIEW,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@with_session(write=False)
|
@with_session(write=False)
|
||||||
def get(self, session: Session, request_context: RequestContext, skill_id: str):
|
def get(self, session: Session, request_context: RequestContext, skill_id: str):
|
||||||
@ -696,9 +680,7 @@ class WorkspaceSkillPublishApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[SkillPublishPayload.__name__])
|
@console_ns.expect(console_ns.models[SkillPublishPayload.__name__])
|
||||||
@console_ns.response(200, "Skill published", console_ns.models[SkillVersionResponse.__name__])
|
@console_ns.response(200, "Skill published", console_ns.models[SkillVersionResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_PUBLISH, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_PUBLISH,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
@ -723,9 +705,7 @@ class WorkspaceSkillRestoreApi(Resource):
|
|||||||
@console_ns.expect(console_ns.models[SkillRestorePayload.__name__])
|
@console_ns.expect(console_ns.models[SkillRestorePayload.__name__])
|
||||||
@console_ns.response(200, "Skill version restored to draft", console_ns.models[SkillDetailResponse.__name__])
|
@console_ns.response(200, "Skill version restored to draft", console_ns.models[SkillDetailResponse.__name__])
|
||||||
@console_account_admission(
|
@console_account_admission(
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.SKILL_PUBLISH, Workspace())],
|
||||||
rbac_permission=RBACPermission.SKILL_PUBLISH,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@with_session
|
@with_session
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from sqlalchemy.orm import Session, sessionmaker
|
|||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.common.fields import TextFileResponse
|
from controllers.common.fields import TextFileResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
register_response_schema_models,
|
register_response_schema_models,
|
||||||
@ -24,7 +25,6 @@ from controllers.console.snippets.payloads import (
|
|||||||
)
|
)
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
model_validate,
|
model_validate,
|
||||||
@ -147,9 +147,7 @@ class CustomizedSnippetsApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(CreateSnippetPayload)
|
@model_validate(CreateSnippetPayload)
|
||||||
@ -211,9 +209,7 @@ class CustomizedSnippetDetailApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(UpdateSnippetPayload)
|
@model_validate(UpdateSnippetPayload)
|
||||||
@ -258,7 +254,7 @@ class CustomizedSnippetDetailApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_MANAGE, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def delete(self, current_tenant_id: str, current_user: Account, snippet_id: str):
|
def delete(self, current_tenant_id: str, current_user: Account, snippet_id: str):
|
||||||
@ -296,9 +292,7 @@ class CustomizedSnippetExportApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, current_tenant_id: str, snippet_id: str):
|
def get(self, current_tenant_id: str, snippet_id: str):
|
||||||
"""Export snippet as DSL."""
|
"""Export snippet as DSL."""
|
||||||
@ -351,9 +345,7 @@ class CustomizedSnippetImportApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
@model_validate(SnippetImportPayload)
|
@model_validate(SnippetImportPayload)
|
||||||
@ -390,9 +382,7 @@ class CustomizedSnippetImportConfirmApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_session
|
@with_session
|
||||||
def post(self, session: Session, current_user: Account, import_id: str):
|
def post(self, session: Session, current_user: Account, import_id: str):
|
||||||
@ -420,9 +410,7 @@ class CustomizedSnippetCheckDependenciesApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.SNIPPETS_CREATE_AND_MODIFY, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
|
||||||
)
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, current_tenant_id: str, snippet_id: str):
|
def get(self, current_tenant_id: str, snippet_id: str):
|
||||||
"""Check dependencies for a snippet."""
|
"""Check dependencies for a snippet."""
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from werkzeug.exceptions import Forbidden
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import (
|
from controllers.common.schema import (
|
||||||
query_params_from_model,
|
query_params_from_model,
|
||||||
query_params_from_request,
|
query_params_from_request,
|
||||||
@ -29,7 +30,6 @@ from controllers.common.schema import (
|
|||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
enterprise_license_required,
|
enterprise_license_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
@ -563,7 +563,7 @@ class ToolBuiltinProviderDeleteApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(BuiltinToolCredentialDeletePayload)
|
@model_validate(BuiltinToolCredentialDeletePayload)
|
||||||
@ -590,7 +590,7 @@ class ToolBuiltinProviderAddApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_CREATE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -621,7 +621,7 @@ class ToolBuiltinProviderUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -689,7 +689,7 @@ class ToolApiProviderAddApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.TOOL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -769,7 +769,7 @@ class ToolApiProviderUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.TOOL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -800,7 +800,7 @@ class ToolApiProviderDeleteApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.TOOL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -908,7 +908,7 @@ class ToolWorkflowProviderCreateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.TOOL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -938,7 +938,7 @@ class ToolWorkflowProviderUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.TOOL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -968,7 +968,7 @@ class ToolWorkflowProviderDeleteApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.TOOL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.TOOL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -1125,7 +1125,7 @@ class ToolPluginOAuthApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -1251,7 +1251,7 @@ class ToolBuiltinProviderSetDefaultApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_USE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_USE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(BuiltinProviderDefaultCredentialPayload)
|
@model_validate(BuiltinProviderDefaultCredentialPayload)
|
||||||
@ -1273,7 +1273,7 @@ class ToolOAuthCustomClient(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(ToolOAuthCustomClientPayload)
|
@model_validate(ToolOAuthCustomClientPayload)
|
||||||
@ -1297,7 +1297,7 @@ class ToolOAuthCustomClient(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, current_tenant_id: str, provider: str):
|
def get(self, current_tenant_id: str, provider: str):
|
||||||
@ -1309,7 +1309,7 @@ class ToolOAuthCustomClient(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def delete(self, current_tenant_id: str, provider: str):
|
def delete(self, current_tenant_id: str, provider: str):
|
||||||
@ -1379,7 +1379,7 @@ class ToolProviderMCPApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.MCP_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.MCP_MANAGE, Workspace()))
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(MCPProviderCreatePayload)
|
@model_validate(MCPProviderCreatePayload)
|
||||||
@ -1433,7 +1433,7 @@ class ToolProviderMCPApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.MCP_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.MCP_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(MCPProviderUpdatePayload)
|
@model_validate(MCPProviderUpdatePayload)
|
||||||
def put(self, req_data: MCPProviderUpdatePayload, current_tenant_id: str):
|
def put(self, req_data: MCPProviderUpdatePayload, current_tenant_id: str):
|
||||||
@ -1487,7 +1487,7 @@ class ToolProviderMCPApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.MCP_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.MCP_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(MCPProviderDeletePayload)
|
@model_validate(MCPProviderDeletePayload)
|
||||||
def delete(self, req_data: MCPProviderDeletePayload, current_tenant_id: str):
|
def delete(self, req_data: MCPProviderDeletePayload, current_tenant_id: str):
|
||||||
@ -1505,7 +1505,7 @@ class ToolMCPAuthApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.MCP_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.MCP_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(MCPAuthPayload)
|
@model_validate(MCPAuthPayload)
|
||||||
def post(self, req_data: MCPAuthPayload, tenant_id: str):
|
def post(self, req_data: MCPAuthPayload, tenant_id: str):
|
||||||
@ -1617,7 +1617,7 @@ class ToolMCPUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.MCP_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.MCP_MANAGE, Workspace()))
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, provider_id: str):
|
def get(self, tenant_id: str, provider_id: str):
|
||||||
with sessionmaker(db.engine).begin() as session:
|
with sessionmaker(db.engine).begin() as session:
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from werkzeug.exceptions import BadRequest, Forbidden
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.errors import NotFoundError
|
from controllers.common.errors import NotFoundError
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
|
from controllers.common.rbac import RBACCheck, Workspace
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from core.entities.provider_entities import ProviderConfig
|
from core.entities.provider_entities import ProviderConfig
|
||||||
from core.plugin.entities.plugin_daemon import CredentialType
|
from core.plugin.entities.plugin_daemon import CredentialType
|
||||||
@ -35,7 +36,6 @@ from services.trigger.trigger_subscription_operator_service import TriggerSubscr
|
|||||||
from .. import console_ns
|
from .. import console_ns
|
||||||
from ..wraps import (
|
from ..wraps import (
|
||||||
RBACPermission,
|
RBACPermission,
|
||||||
RBACResourceScope,
|
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
is_admin_or_owner_required,
|
is_admin_or_owner_required,
|
||||||
@ -199,7 +199,7 @@ class TriggerSubscriptionListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -235,7 +235,7 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -271,7 +271,7 @@ class TriggerSubscriptionBuilderGetApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -299,7 +299,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -344,7 +344,7 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.CREDENTIAL_MANAGE, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -389,7 +389,7 @@ class TriggerSubscriptionBuilderLogsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -420,7 +420,7 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@ -464,7 +464,7 @@ class TriggerSubscriptionUpdateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||||
@ -524,7 +524,7 @@ class TriggerSubscriptionDeleteApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def post(self, tenant_id: str, subscription_id: str):
|
def post(self, tenant_id: str, subscription_id: str):
|
||||||
@ -716,7 +716,7 @@ class TriggerOAuthClientManageApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, provider: str):
|
def get(self, tenant_id: str, provider: str):
|
||||||
@ -763,7 +763,7 @@ class TriggerOAuthClientManageApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
@model_validate(TriggerOAuthClientPayload)
|
@model_validate(TriggerOAuthClientPayload)
|
||||||
@ -792,7 +792,7 @@ class TriggerOAuthClientManageApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def delete(self, tenant_id: str, provider: str):
|
def delete(self, tenant_id: str, provider: str):
|
||||||
@ -826,7 +826,7 @@ class TriggerSubscriptionVerifyApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
@rbac_permission_required(RBACCheck(RBACPermission.PLUGIN_PREFERENCES, Workspace()))
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@with_current_user
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
|
|||||||
@ -11,11 +11,8 @@ from sqlalchemy import select
|
|||||||
from werkzeug.exceptions import Forbidden, UnprocessableEntity
|
from werkzeug.exceptions import Forbidden, UnprocessableEntity
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.wraps import (
|
from controllers.common.rbac import RBACPermission, RBACResourceScope
|
||||||
RBACPermission,
|
from controllers.common.wraps import rbac_permission_required
|
||||||
RBACResourceScope,
|
|
||||||
rbac_permission_required,
|
|
||||||
)
|
|
||||||
from controllers.console.auth.error import AuthenticationFailedError, EmailCodeError
|
from controllers.console.auth.error import AuthenticationFailedError, EmailCodeError
|
||||||
from controllers.console.workspace.error import AccountNotInitializedError
|
from controllers.console.workspace.error import AccountNotInitializedError
|
||||||
from enums import CloudPlan, DeploymentEdition
|
from enums import CloudPlan, DeploymentEdition
|
||||||
|
|||||||
@ -6,12 +6,12 @@ from flask_restx import Resource
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission, Workspace
|
||||||
from controllers.openapi import openapi_ns
|
from controllers.openapi import openapi_ns
|
||||||
from controllers.openapi._contract import accepts, returns
|
from controllers.openapi._contract import accepts, returns
|
||||||
from controllers.openapi._models import AppDslExportQuery, AppDslExportResponse, AppDslImportPayload
|
from controllers.openapi._models import AppDslExportQuery, AppDslExportResponse, AppDslImportPayload
|
||||||
from controllers.openapi.auth.composition import auth_router
|
from controllers.openapi.auth.composition import auth_router
|
||||||
from controllers.openapi.auth.data import AuthData, RBACRequirement
|
from controllers.openapi.auth.data import AuthData
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
from models import Account, App
|
from models import Account, App
|
||||||
@ -40,11 +40,7 @@ class AppDslImportApi(Resource):
|
|||||||
scope=Scope.WORKSPACE_WRITE,
|
scope=Scope.WORKSPACE_WRITE,
|
||||||
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
||||||
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
||||||
rbac=RBACRequirement(
|
rbac=RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, Workspace()),
|
||||||
resource_type=RBACResourceScope.APP,
|
|
||||||
scene=RBACPermission.APP_IMPORT_EXPORT_DSL,
|
|
||||||
resource_required=False,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
@returns(200, Import, "Import completed")
|
@returns(200, Import, "Import completed")
|
||||||
@returns(202, Import, "Import pending confirmation")
|
@returns(202, Import, "Import pending confirmation")
|
||||||
@ -100,11 +96,7 @@ class AppDslImportConfirmApi(Resource):
|
|||||||
scope=Scope.WORKSPACE_WRITE,
|
scope=Scope.WORKSPACE_WRITE,
|
||||||
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
||||||
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
||||||
rbac=RBACRequirement(
|
rbac=RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, Workspace()),
|
||||||
resource_type=RBACResourceScope.APP,
|
|
||||||
scene=RBACPermission.APP_IMPORT_EXPORT_DSL,
|
|
||||||
resource_required=False,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
@returns(200, Import, "Import confirmed")
|
@returns(200, Import, "Import confirmed")
|
||||||
@returns(400, Import, "Import failed")
|
@returns(400, Import, "Import failed")
|
||||||
@ -144,7 +136,7 @@ class AppDslExportApi(Resource):
|
|||||||
scope=Scope.APPS_READ,
|
scope=Scope.APPS_READ,
|
||||||
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
||||||
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_IMPORT_EXPORT_DSL),
|
rbac=RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, PlainApp()),
|
||||||
)
|
)
|
||||||
@accepts(query=AppDslExportQuery)
|
@accepts(query=AppDslExportQuery)
|
||||||
@returns(200, AppDslExportResponse, "Export successful")
|
@returns(200, AppDslExportResponse, "Export successful")
|
||||||
@ -176,7 +168,7 @@ class AppDslCheckDependenciesApi(Resource):
|
|||||||
scope=Scope.APPS_READ,
|
scope=Scope.APPS_READ,
|
||||||
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
||||||
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
allowed_roles=frozenset({TenantAccountRole.EDITOR, TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_IMPORT_EXPORT_DSL),
|
rbac=RBACCheck(RBACPermission.APP_IMPORT_EXPORT_DSL, PlainApp()),
|
||||||
)
|
)
|
||||||
@returns(200, CheckDependenciesResult, "Dependencies checked")
|
@returns(200, CheckDependenciesResult, "Dependencies checked")
|
||||||
def get(self, app_id: str, *, auth_data: AuthData):
|
def get(self, app_id: str, *, auth_data: AuthData):
|
||||||
|
|||||||
@ -20,14 +20,14 @@ from werkzeug.exceptions import (
|
|||||||
|
|
||||||
import services
|
import services
|
||||||
from controllers.common.fields import EventStreamResponse
|
from controllers.common.fields import EventStreamResponse
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission
|
||||||
from controllers.console.app.wraps import with_session
|
from controllers.console.app.wraps import with_session
|
||||||
from controllers.openapi import openapi_ns
|
from controllers.openapi import openapi_ns
|
||||||
from controllers.openapi._audit import emit_app_run
|
from controllers.openapi._audit import emit_app_run
|
||||||
from controllers.openapi._contract import accepts, returns
|
from controllers.openapi._contract import accepts, returns
|
||||||
from controllers.openapi._models import AppRunRequest, TaskStopResponse
|
from controllers.openapi._models import AppRunRequest, TaskStopResponse
|
||||||
from controllers.openapi.auth.composition import auth_router
|
from controllers.openapi.auth.composition import auth_router
|
||||||
from controllers.openapi.auth.data import AuthData, RBACRequirement
|
from controllers.openapi.auth.data import AuthData
|
||||||
from controllers.service_api.app.error import (
|
from controllers.service_api.app.error import (
|
||||||
AppUnavailableError,
|
AppUnavailableError,
|
||||||
CompletionRequestError,
|
CompletionRequestError,
|
||||||
@ -148,7 +148,7 @@ _DISPATCH: dict[AppMode, Callable[[App, Any, AppRunRequest, Session], Any]] = {
|
|||||||
class AppRunApi(Resource):
|
class AppRunApi(Resource):
|
||||||
@auth_router.guard(
|
@auth_router.guard(
|
||||||
scope=Scope.APPS_RUN,
|
scope=Scope.APPS_RUN,
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_TEST_AND_RUN),
|
rbac=RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()),
|
||||||
)
|
)
|
||||||
@openapi_ns.response(200, "Run result (SSE stream)", openapi_ns.models[EventStreamResponse.__name__])
|
@openapi_ns.response(200, "Run result (SSE stream)", openapi_ns.models[EventStreamResponse.__name__])
|
||||||
@accepts(body=AppRunRequest)
|
@accepts(body=AppRunRequest)
|
||||||
@ -184,7 +184,7 @@ class AppRunApi(Resource):
|
|||||||
class AppRunTaskStopApi(Resource):
|
class AppRunTaskStopApi(Resource):
|
||||||
@auth_router.guard(
|
@auth_router.guard(
|
||||||
scope=Scope.APPS_RUN,
|
scope=Scope.APPS_RUN,
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_TEST_AND_RUN),
|
rbac=RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()),
|
||||||
)
|
)
|
||||||
@returns(200, TaskStopResponse, description="Task stopped")
|
@returns(200, TaskStopResponse, description="Task stopped")
|
||||||
def post(self, app_id: str, task_id: str, *, auth_data: AuthData):
|
def post(self, app_id: str, task_id: str, *, auth_data: AuthData):
|
||||||
|
|||||||
@ -12,8 +12,8 @@ from werkzeug.exceptions import Conflict, NotFound, UnprocessableEntity
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.app_access import AppAccessFilter, resolve_app_access_filter
|
from controllers.common.app_access import AppAccessFilter, resolve_app_access_filter
|
||||||
from controllers.common.fields import Parameters
|
from controllers.common.fields import Parameters
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission
|
||||||
from controllers.common.session import with_session
|
from controllers.common.session import with_session
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
|
||||||
from controllers.openapi import openapi_ns
|
from controllers.openapi import openapi_ns
|
||||||
from controllers.openapi._contract import accepts, returns
|
from controllers.openapi._contract import accepts, returns
|
||||||
from controllers.openapi._input_schema import EMPTY_INPUT_SCHEMA, build_input_schema, resolve_app_config
|
from controllers.openapi._input_schema import EMPTY_INPUT_SCHEMA, build_input_schema, resolve_app_config
|
||||||
@ -27,7 +27,7 @@ from controllers.openapi._models import (
|
|||||||
AppListRow,
|
AppListRow,
|
||||||
)
|
)
|
||||||
from controllers.openapi.auth.composition import auth_router
|
from controllers.openapi.auth.composition import auth_router
|
||||||
from controllers.openapi.auth.data import AuthData, CallerKind, RBACRequirement
|
from controllers.openapi.auth.data import AuthData, CallerKind
|
||||||
from controllers.service_api.app.error import AppUnavailableError
|
from controllers.service_api.app.error import AppUnavailableError
|
||||||
from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict
|
from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
@ -135,7 +135,7 @@ class AppDescribeApi(AppReadResource):
|
|||||||
@auth_router.guard(
|
@auth_router.guard(
|
||||||
scope=Scope.APPS_READ,
|
scope=Scope.APPS_READ,
|
||||||
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
allowed_token_types=frozenset({TokenType.OAUTH_ACCOUNT}),
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_VIEW_LAYOUT),
|
rbac=RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()),
|
||||||
)
|
)
|
||||||
@returns(200, AppDescribeResponse, description="App description")
|
@returns(200, AppDescribeResponse, description="App description")
|
||||||
@accepts(query=AppDescribeQuery)
|
@accepts(query=AppDescribeQuery)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from enum import StrEnum
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
from controllers.common.rbac import RBACCheck
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
from models.account import Account, Tenant, TenantAccountRole
|
from models.account import Account, Tenant, TenantAccountRole
|
||||||
from models.model import App, EndUser
|
from models.model import App, EndUser
|
||||||
@ -25,23 +25,15 @@ class ExternalIdentity(BaseModel):
|
|||||||
issuer: str | None = None
|
issuer: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class RBACRequirement(BaseModel):
|
|
||||||
model_config = ConfigDict(frozen=True)
|
|
||||||
|
|
||||||
resource_type: RBACResourceScope
|
|
||||||
scene: RBACPermission
|
|
||||||
resource_required: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
class RequestContext(BaseModel):
|
class RequestContext(BaseModel):
|
||||||
model_config = ConfigDict(frozen=True)
|
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
|
||||||
|
|
||||||
token_type: TokenType
|
token_type: TokenType
|
||||||
scope: Scope | None = None
|
scope: Scope | None = None
|
||||||
path_params: dict[str, str]
|
path_params: dict[str, str]
|
||||||
workspace_membership: bool = False
|
workspace_membership: bool = False
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None
|
allowed_roles: frozenset[TenantAccountRole] | None = None
|
||||||
rbac: RBACRequirement | None = None
|
rbac: RBACCheck | None = None
|
||||||
|
|
||||||
|
|
||||||
class AuthData(BaseModel):
|
class AuthData(BaseModel):
|
||||||
@ -58,7 +50,7 @@ class AuthData(BaseModel):
|
|||||||
path_params: dict[str, str] = Field(default_factory=dict)
|
path_params: dict[str, str] = Field(default_factory=dict)
|
||||||
|
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None
|
allowed_roles: frozenset[TenantAccountRole] | None = None
|
||||||
rbac: RBACRequirement | None = None
|
rbac: RBACCheck | None = None
|
||||||
|
|
||||||
app: App | None = None
|
app: App | None = None
|
||||||
tenant: Tenant | None = None
|
tenant: Tenant | None = None
|
||||||
|
|||||||
@ -17,11 +17,11 @@ from flask_login import user_logged_in
|
|||||||
from werkzeug.exceptions import Forbidden, NotFound, Unauthorized
|
from werkzeug.exceptions import Forbidden, NotFound, Unauthorized
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.common.rbac import RBACCheck
|
||||||
from controllers.openapi._audit import emit_wrong_surface
|
from controllers.openapi._audit import emit_wrong_surface
|
||||||
from controllers.openapi.auth.data import (
|
from controllers.openapi.auth.data import (
|
||||||
AuthData,
|
AuthData,
|
||||||
ExternalIdentity,
|
ExternalIdentity,
|
||||||
RBACRequirement,
|
|
||||||
RequestContext,
|
RequestContext,
|
||||||
)
|
)
|
||||||
from controllers.openapi.auth.flow import When
|
from controllers.openapi.auth.flow import When
|
||||||
@ -61,7 +61,7 @@ class AuthPipeline:
|
|||||||
scope: Scope | None,
|
scope: Scope | None,
|
||||||
workspace_membership: bool = False,
|
workspace_membership: bool = False,
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||||
rbac: RBACRequirement | None = None,
|
rbac: RBACCheck | None = None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
req_ctx = RequestContext(
|
req_ctx = RequestContext(
|
||||||
token_type=identity.token_type,
|
token_type=identity.token_type,
|
||||||
@ -135,7 +135,7 @@ class PipelineRouter:
|
|||||||
require_valid_enterprise_license: bool = False,
|
require_valid_enterprise_license: bool = False,
|
||||||
workspace_membership: bool = False,
|
workspace_membership: bool = False,
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||||
rbac: RBACRequirement | None = None,
|
rbac: RBACCheck | None = None,
|
||||||
) -> Callable:
|
) -> Callable:
|
||||||
return self._make_decorator(
|
return self._make_decorator(
|
||||||
scope=scope,
|
scope=scope,
|
||||||
@ -155,7 +155,7 @@ class PipelineRouter:
|
|||||||
edition: frozenset[DeploymentEdition] | None = None,
|
edition: frozenset[DeploymentEdition] | None = None,
|
||||||
require_valid_enterprise_license: bool = False,
|
require_valid_enterprise_license: bool = False,
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||||
rbac: RBACRequirement | None = None,
|
rbac: RBACCheck | None = None,
|
||||||
) -> Callable:
|
) -> Callable:
|
||||||
return self._make_decorator(
|
return self._make_decorator(
|
||||||
scope=scope,
|
scope=scope,
|
||||||
@ -176,7 +176,7 @@ class PipelineRouter:
|
|||||||
require_valid_enterprise_license: bool,
|
require_valid_enterprise_license: bool,
|
||||||
workspace_membership: bool,
|
workspace_membership: bool,
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None,
|
allowed_roles: frozenset[TenantAccountRole] | None,
|
||||||
rbac: RBACRequirement | None,
|
rbac: RBACCheck | None,
|
||||||
) -> Callable:
|
) -> Callable:
|
||||||
def decorator(view: Callable) -> Callable:
|
def decorator(view: Callable) -> Callable:
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
@ -210,7 +210,7 @@ class PipelineRouter:
|
|||||||
require_valid_enterprise_license: bool,
|
require_valid_enterprise_license: bool,
|
||||||
workspace_membership: bool = False,
|
workspace_membership: bool = False,
|
||||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||||
rbac: RBACRequirement | None = None,
|
rbac: RBACCheck | None = None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
# 404 not 403 — this edition doesn't expose the feature at all
|
# 404 not 403 — this edition doesn't expose the feature at all
|
||||||
if edition is not None and dify_config.DEPLOYMENT_EDITION not in edition:
|
if edition is not None and dify_config.DEPLOYMENT_EDITION not in edition:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from flask import request
|
|||||||
from werkzeug.exceptions import Forbidden, NotFound, UnprocessableEntity
|
from werkzeug.exceptions import Forbidden, NotFound, UnprocessableEntity
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.common.wraps import enforce_rbac_access
|
from controllers.common.rbac import enforce_rbac_checks
|
||||||
from controllers.openapi.auth.data import AuthData, CallerKind
|
from controllers.openapi.auth.data import AuthData, CallerKind
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
@ -62,12 +62,10 @@ def check_rbac_permission(data: AuthData) -> None:
|
|||||||
return
|
return
|
||||||
if data.account_id is None or data.tenant is None:
|
if data.account_id is None or data.tenant is None:
|
||||||
raise Forbidden("rbac context missing")
|
raise Forbidden("rbac context missing")
|
||||||
enforce_rbac_access(
|
enforce_rbac_checks(
|
||||||
tenant_id=str(data.tenant.id),
|
tenant_id=str(data.tenant.id),
|
||||||
account_id=str(data.account_id),
|
account_id=str(data.account_id),
|
||||||
resource_type=req.resource_type,
|
checks=[req],
|
||||||
scene=req.scene,
|
|
||||||
resource_required=req.resource_required,
|
|
||||||
path_args=dict(data.path_params),
|
path_args=dict(data.path_params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,14 @@ from flask_restx import Resource
|
|||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
from controllers.common.human_input import HumanInputFormSubmitPayload, stringify_form_default_values
|
from controllers.common.human_input import HumanInputFormSubmitPayload, stringify_form_default_values
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission
|
||||||
from controllers.common.schema import register_schema_models
|
from controllers.common.schema import register_schema_models
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
|
||||||
from controllers.openapi import openapi_ns
|
from controllers.openapi import openapi_ns
|
||||||
from controllers.openapi._contract import accepts, returns
|
from controllers.openapi._contract import accepts, returns
|
||||||
from controllers.openapi._errors import HumanInputFormNotFound, RecipientSurfaceMismatch
|
from controllers.openapi._errors import HumanInputFormNotFound, RecipientSurfaceMismatch
|
||||||
from controllers.openapi._models import FormSubmitResponse, HumanInputFormDefinitionResponse
|
from controllers.openapi._models import FormSubmitResponse, HumanInputFormDefinitionResponse
|
||||||
from controllers.openapi.auth.composition import auth_router
|
from controllers.openapi.auth.composition import auth_router
|
||||||
from controllers.openapi.auth.data import AuthData, CallerKind, RBACRequirement
|
from controllers.openapi.auth.data import AuthData, CallerKind
|
||||||
from core.workflow.human_input_policy import (
|
from core.workflow.human_input_policy import (
|
||||||
HumanInputSurface,
|
HumanInputSurface,
|
||||||
is_recipient_type_allowed_for_surface,
|
is_recipient_type_allowed_for_surface,
|
||||||
@ -65,7 +65,7 @@ class OpenApiWorkflowHumanInputFormApi(Resource):
|
|||||||
@openapi_ns.response(200, "Form definition", openapi_ns.models[HumanInputFormDefinitionResponse.__name__])
|
@openapi_ns.response(200, "Form definition", openapi_ns.models[HumanInputFormDefinitionResponse.__name__])
|
||||||
@auth_router.guard(
|
@auth_router.guard(
|
||||||
scope=Scope.APPS_RUN,
|
scope=Scope.APPS_RUN,
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_TEST_AND_RUN),
|
rbac=RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()),
|
||||||
)
|
)
|
||||||
def get(self, app_id: str, form_token: str, *, auth_data: AuthData):
|
def get(self, app_id: str, form_token: str, *, auth_data: AuthData):
|
||||||
app_model, _caller, _caller_kind = auth_data.require_app_context()
|
app_model, _caller, _caller_kind = auth_data.require_app_context()
|
||||||
@ -84,7 +84,7 @@ class OpenApiWorkflowHumanInputFormApi(Resource):
|
|||||||
class OpenApiWorkflowHumanInputFormSubmitApi(Resource):
|
class OpenApiWorkflowHumanInputFormSubmitApi(Resource):
|
||||||
@auth_router.guard(
|
@auth_router.guard(
|
||||||
scope=Scope.APPS_RUN,
|
scope=Scope.APPS_RUN,
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_TEST_AND_RUN),
|
rbac=RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()),
|
||||||
)
|
)
|
||||||
@returns(200, FormSubmitResponse, description="Form submitted")
|
@returns(200, FormSubmitResponse, description="Form submitted")
|
||||||
@accepts(body=HumanInputFormSubmitPayload)
|
@accepts(body=HumanInputFormSubmitPayload)
|
||||||
|
|||||||
@ -18,11 +18,11 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from werkzeug.exceptions import NotFound, UnprocessableEntity
|
from werkzeug.exceptions import NotFound, UnprocessableEntity
|
||||||
|
|
||||||
from controllers.common.fields import EventStreamResponse
|
from controllers.common.fields import EventStreamResponse
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission
|
||||||
from controllers.common.schema import query_params_from_model
|
from controllers.common.schema import query_params_from_model
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
|
||||||
from controllers.openapi import openapi_ns
|
from controllers.openapi import openapi_ns
|
||||||
from controllers.openapi.auth.composition import auth_router
|
from controllers.openapi.auth.composition import auth_router
|
||||||
from controllers.openapi.auth.data import AuthData, CallerKind, RBACRequirement
|
from controllers.openapi.auth.data import AuthData, CallerKind
|
||||||
from core.app.apps.advanced_chat.app_generator import AdvancedChatAppGenerator
|
from core.app.apps.advanced_chat.app_generator import AdvancedChatAppGenerator
|
||||||
from core.app.apps.base_app_generator import BaseAppGenerator
|
from core.app.apps.base_app_generator import BaseAppGenerator
|
||||||
from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter
|
from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter
|
||||||
@ -49,7 +49,7 @@ class OpenApiWorkflowEventsApi(Resource):
|
|||||||
@openapi_ns.response(200, "SSE event stream", openapi_ns.models[EventStreamResponse.__name__])
|
@openapi_ns.response(200, "SSE event stream", openapi_ns.models[EventStreamResponse.__name__])
|
||||||
@auth_router.guard(
|
@auth_router.guard(
|
||||||
scope=Scope.APPS_RUN,
|
scope=Scope.APPS_RUN,
|
||||||
rbac=RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_TEST_AND_RUN),
|
rbac=RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp()),
|
||||||
)
|
)
|
||||||
def get(self, app_id: str, task_id: str, *, auth_data: AuthData):
|
def get(self, app_id: str, task_id: str, *, auth_data: AuthData):
|
||||||
app_model, caller, caller_kind = auth_data.require_app_context()
|
app_model, caller, caller_kind = auth_data.require_app_context()
|
||||||
|
|||||||
@ -5,11 +5,12 @@ class RBACResourceScope(StrEnum):
|
|||||||
"""Resource scopes accepted by the ``rbac_permission_required`` decorator.
|
"""Resource scopes accepted by the ``rbac_permission_required`` decorator.
|
||||||
|
|
||||||
``WORKSPACE`` denotes a workspace-level check that carries no concrete
|
``WORKSPACE`` denotes a workspace-level check that carries no concrete
|
||||||
resource id; ``APP`` and ``DATASET`` are resource-scoped checks.
|
resource id; ``APP``, ``DATASET`` and ``AGENT`` are resource-scoped checks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
APP = "app"
|
APP = "app"
|
||||||
DATASET = "dataset"
|
DATASET = "dataset"
|
||||||
|
AGENT = "agent"
|
||||||
WORKSPACE = "workspace"
|
WORKSPACE = "workspace"
|
||||||
|
|
||||||
|
|
||||||
@ -41,6 +42,19 @@ class RBACPermission(StrEnum):
|
|||||||
APP_DELETE = "app_delete"
|
APP_DELETE = "app_delete"
|
||||||
APP_ACCESS_CONFIG = "app_access_config"
|
APP_ACCESS_CONFIG = "app_access_config"
|
||||||
|
|
||||||
|
AGENT_CREATE = "agent_create"
|
||||||
|
AGENT_PREVIEW = "agent_preview"
|
||||||
|
AGENT_EDIT = "agent_edit"
|
||||||
|
AGENT_TEST_AND_RUN = "agent_test_and_run"
|
||||||
|
AGENT_RELEASE_AND_VERSION = "agent_release_and_version"
|
||||||
|
AGENT_ACCESS_POINT_VIEW = "agent_access_point_view"
|
||||||
|
AGENT_ACCESS_POINT_MANAGE = "agent_access_point_manage"
|
||||||
|
AGENT_LOG_MANAGE = "agent_log_manage"
|
||||||
|
AGENT_MONITOR = "agent_monitor"
|
||||||
|
AGENT_ACCESS_CONFIG = "agent_access_config"
|
||||||
|
AGENT_IMPORT_EXPORT_DSL = "agent_import_export_dsl"
|
||||||
|
AGENT_DELETE = "agent_delete"
|
||||||
|
|
||||||
DATASET_PREVIEW = "dataset_preview"
|
DATASET_PREVIEW = "dataset_preview"
|
||||||
DATASET_READONLY = "dataset_readonly"
|
DATASET_READONLY = "dataset_readonly"
|
||||||
DATASET_EDIT = "dataset_edit"
|
DATASET_EDIT = "dataset_edit"
|
||||||
@ -61,7 +75,6 @@ class RBACPermission(StrEnum):
|
|||||||
WORKSPACE_ROLE_MANAGE = "workspace_role_manage"
|
WORKSPACE_ROLE_MANAGE = "workspace_role_manage"
|
||||||
API_EXTENSION_MANAGE = "api_extension_manage"
|
API_EXTENSION_MANAGE = "api_extension_manage"
|
||||||
CUSTOMIZATION_MANAGE = "customization_manage"
|
CUSTOMIZATION_MANAGE = "customization_manage"
|
||||||
AGENT_MANAGE = "agent_manage"
|
|
||||||
SKILL_VIEW = "skill_view"
|
SKILL_VIEW = "skill_view"
|
||||||
SKILL_EDIT = "skill_edit"
|
SKILL_EDIT = "skill_edit"
|
||||||
SKILL_PUBLISH = "skill_publish"
|
SKILL_PUBLISH = "skill_publish"
|
||||||
@ -83,3 +96,25 @@ class RBACPermission(StrEnum):
|
|||||||
|
|
||||||
TOOL_MANAGE = "tool_manage"
|
TOOL_MANAGE = "tool_manage"
|
||||||
MCP_MANAGE = "mcp_manage"
|
MCP_MANAGE = "mcp_manage"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scope(self) -> RBACResourceScope:
|
||||||
|
if self in _FUNCTION_SCOPED_RESOURCE_SCENES:
|
||||||
|
return RBACResourceScope.WORKSPACE
|
||||||
|
prefix = self.name.split("_", 1)[0]
|
||||||
|
return _SCENE_PREFIX_SCOPE.get(prefix, RBACResourceScope.WORKSPACE)
|
||||||
|
|
||||||
|
|
||||||
|
_SCENE_PREFIX_SCOPE: dict[str, RBACResourceScope] = {
|
||||||
|
"APP": RBACResourceScope.APP,
|
||||||
|
"DATASET": RBACResourceScope.DATASET,
|
||||||
|
"AGENT": RBACResourceScope.AGENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
_FUNCTION_SCOPED_RESOURCE_SCENES: frozenset[RBACPermission] = frozenset(
|
||||||
|
{
|
||||||
|
RBACPermission.APP_CREATE_AND_MANAGEMENT,
|
||||||
|
RBACPermission.DATASET_CREATE_AND_MANAGEMENT,
|
||||||
|
RBACPermission.AGENT_CREATE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@ -27,6 +27,7 @@ def init_app(app: DifyApp):
|
|||||||
import_migration_data,
|
import_migration_data,
|
||||||
install_plugins,
|
install_plugins,
|
||||||
install_rag_pipeline_plugins,
|
install_rag_pipeline_plugins,
|
||||||
|
migrate_agent_permissions_to_rbac,
|
||||||
migrate_data_for_plugin,
|
migrate_data_for_plugin,
|
||||||
migrate_dataset_permissions_to_rbac,
|
migrate_dataset_permissions_to_rbac,
|
||||||
migrate_member_roles_to_rbac,
|
migrate_member_roles_to_rbac,
|
||||||
@ -60,6 +61,7 @@ def init_app(app: DifyApp):
|
|||||||
migrate_data_for_plugin,
|
migrate_data_for_plugin,
|
||||||
migrate_dataset_permissions_to_rbac,
|
migrate_dataset_permissions_to_rbac,
|
||||||
migrate_member_roles_to_rbac,
|
migrate_member_roles_to_rbac,
|
||||||
|
migrate_agent_permissions_to_rbac,
|
||||||
backfill_plugin_auto_upgrade,
|
backfill_plugin_auto_upgrade,
|
||||||
extract_plugins,
|
extract_plugins,
|
||||||
extract_unique_plugins,
|
extract_unique_plugins,
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import sqlalchemy as sa
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import UserMixin # type: ignore[import-untyped]
|
from flask_login import UserMixin # type: ignore[import-untyped]
|
||||||
from sqlalchemy import BigInteger, Float, Index, PrimaryKeyConstraint, String, exists, func, select, text
|
from sqlalchemy import BigInteger, Float, Index, PrimaryKeyConstraint, String, exists, func, select, text
|
||||||
from sqlalchemy.orm import Mapped, Session, mapped_column, validates
|
from sqlalchemy.orm import Mapped, Session, mapped_column, scoped_session, validates
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from constants import DEFAULT_FILE_NUMBER_LIMITS
|
from constants import DEFAULT_FILE_NUMBER_LIMITS
|
||||||
@ -508,7 +508,9 @@ class App(Base):
|
|||||||
agent = self.agent_app_binding_with_session(session=session)
|
agent = self.agent_app_binding_with_session(session=session)
|
||||||
return agent.id if agent else None
|
return agent.id if agent else None
|
||||||
|
|
||||||
def agent_app_binding_with_session(self, *, session: Session, include_archived: bool = False) -> Agent | None:
|
def agent_app_binding_with_session(
|
||||||
|
self, *, session: Session | scoped_session, include_archived: bool = False
|
||||||
|
) -> Agent | None:
|
||||||
"""For an Agent App (mode=agent), the Agent bound to it.
|
"""For an Agent App (mode=agent), the Agent bound to it.
|
||||||
|
|
||||||
A roster Agent is bound through ``Agent.app_id``; a workflow-only Agent
|
A roster Agent is bound through ``Agent.app_id``; a workflow-only Agent
|
||||||
|
|||||||
@ -2172,7 +2172,7 @@ Update annotation settings for an app
|
|||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| ---- | ----------- |
|
| ---- | ----------- |
|
||||||
| 204 | Annotations deleted successfully |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/annotations
|
### [GET] /apps/{app_id}/annotations
|
||||||
Get annotations for an app with pagination
|
Get annotations for an app with pagination
|
||||||
@ -2294,7 +2294,7 @@ Export all annotations for an app with CSV injection protection
|
|||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| ---- | ----------- |
|
| ---- | ----------- |
|
||||||
| 204 | Annotation deleted successfully |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /apps/{app_id}/annotations/{annotation_id}
|
### [POST] /apps/{app_id}/annotations/{annotation_id}
|
||||||
Update or delete an annotation
|
Update or delete an annotation
|
||||||
@ -3324,9 +3324,9 @@ Create a new tracing configuration for an application
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [WorkflowTriggerResponse](#workflowtriggerresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/triggers
|
### [GET] /apps/{app_id}/triggers
|
||||||
**Get app triggers list**
|
**Get app triggers list**
|
||||||
@ -4713,7 +4713,7 @@ Full value for one declared output of a published run.
|
|||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| ---- | ----------- |
|
| ---- | ----------- |
|
||||||
| 204 | Workflow deleted successfully |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PATCH] /apps/{app_id}/workflows/{workflow_id}
|
### [PATCH] /apps/{app_id}/workflows/{workflow_id}
|
||||||
**Update workflow attributes**
|
**Update workflow attributes**
|
||||||
@ -4861,9 +4861,9 @@ Restore a published workflow version into the draft workflow
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /auth/plugin/datasource/{provider_id}/custom-client
|
### [POST] /auth/plugin/datasource/{provider_id}/custom-client
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -5047,16 +5047,16 @@ Get compliance document download link
|
|||||||
### [GET] /data-source/integrates
|
### [GET] /data-source/integrates
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [DataSourceIntegrateListResponse](#datasourceintegratelistresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PATCH] /data-source/integrates
|
### [PATCH] /data-source/integrates
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /data-source/integrates/{binding_id}/{action}
|
### [GET] /data-source/integrates/{binding_id}/{action}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -5068,9 +5068,9 @@ Get compliance document download link
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [DataSourceIntegrateListResponse](#datasourceintegratelistresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PATCH] /data-source/integrates/{binding_id}/{action}
|
### [PATCH] /data-source/integrates/{binding_id}/{action}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -5082,9 +5082,9 @@ Get compliance document download link
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /datasets
|
### [GET] /datasets
|
||||||
Get list of datasets
|
Get list of datasets
|
||||||
@ -7869,17 +7869,11 @@ Update account-level Step-by-step Tour state
|
|||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
| pipeline_id | path | | Yes | string (uuid) |
|
| pipeline_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [DraftWorkflowSyncPayload](#draftworkflowsyncpayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [RagPipelineWorkflowSyncResponse](#ragpipelineworkflowsyncresponse)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /rag/pipelines/{pipeline_id}/workflows/draft/datasource/nodes/{node_id}/run
|
### [POST] /rag/pipelines/{pipeline_id}/workflows/draft/datasource/nodes/{node_id}/run
|
||||||
**Run rag pipeline datasource**
|
**Run rag pipeline datasource**
|
||||||
@ -8179,12 +8173,6 @@ Update account-level Step-by-step Tour state
|
|||||||
| pipeline_id | path | | Yes | string (uuid) |
|
| pipeline_id | path | | Yes | string (uuid) |
|
||||||
| variable_id | path | | Yes | string (uuid) |
|
| variable_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [WorkflowDraftVariablePatchPayload](#workflowdraftvariablepatchpayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description | Schema |
|
||||||
@ -8359,12 +8347,6 @@ Update account-level Step-by-step Tour state
|
|||||||
| pipeline_id | path | | Yes | string (uuid) |
|
| pipeline_id | path | | Yes | string (uuid) |
|
||||||
| workflow_id | path | | Yes | string |
|
| workflow_id | path | | Yes | string |
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [WorkflowUpdatePayload](#workflowupdatepayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description | Schema |
|
||||||
@ -11159,16 +11141,16 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
### [GET] /workspaces/current/rbac/access-policies
|
### [GET] /workspaces/current/rbac/access-policies
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [_AccessPolicyList](#_accesspolicylist)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /workspaces/current/rbac/access-policies
|
### [POST] /workspaces/current/rbac/access-policies
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 201 | Policy created | **application/json**: [AccessPolicy](#accesspolicy)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [DELETE] /workspaces/current/rbac/access-policies/{policy_id}
|
### [DELETE] /workspaces/current/rbac/access-policies/{policy_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11179,9 +11161,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [AccessPolicy](#accesspolicy)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /workspaces/current/rbac/access-policies/{policy_id}
|
### [GET] /workspaces/current/rbac/access-policies/{policy_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11192,9 +11174,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [AccessPolicy](#accesspolicy)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PUT] /workspaces/current/rbac/access-policies/{policy_id}
|
### [PUT] /workspaces/current/rbac/access-policies/{policy_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11205,9 +11187,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [AccessPolicy](#accesspolicy)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /workspaces/current/rbac/access-policies/{policy_id}/copy
|
### [POST] /workspaces/current/rbac/access-policies/{policy_id}/copy
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11218,9 +11200,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 201 | Policy copied | **application/json**: [AccessPolicy](#accesspolicy)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PUT] /workspaces/current/rbac/access-policy-bindings/{binding_id}/lock
|
### [PUT] /workspaces/current/rbac/access-policy-bindings/{binding_id}/lock
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11231,9 +11213,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [AccessPolicyBindingState](#accesspolicybindingstate)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PUT] /workspaces/current/rbac/access-policy-bindings/{binding_id}/unlock
|
### [PUT] /workspaces/current/rbac/access-policy-bindings/{binding_id}/unlock
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11244,9 +11226,153 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| 200 | Success |
|
||||||
|
|
||||||
|
### [DELETE] /workspaces/current/rbac/agents/{agent_id}/access-policies/{policy_id}/member-bindings
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
| policy_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [_DeleteMemberBindingsRequest](#_deletememberbindingsrequest)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description | Schema |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Success | **application/json**: [AccessPolicyBindingState](#accesspolicybindingstate)<br> |
|
| 200 | Success | **application/json**: [MemberBindingsResponse](#memberbindingsresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/agents/{agent_id}/access-policies/{policy_id}/member-bindings
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
| policy_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [MemberBindingsResponse](#memberbindingsresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/agents/{agent_id}/access-policies/{policy_id}/role-bindings
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
| policy_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [RoleBindingsResponse](#rolebindingsresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/agents/{agent_id}/access-policy
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| language | query | Localized policy label language | No | string, <br>**Available values:** "en", "ja", "zh" |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [AgentAccessMatrix](#agentaccessmatrix)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/agents/{agent_id}/user-access-policies
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| language | query | Localized policy label language | No | string, <br>**Available values:** "en", "ja", "zh" |
|
||||||
|
| limit | query | | No | integer |
|
||||||
|
| page | query | | No | integer |
|
||||||
|
| reverse | query | | No | boolean |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [ResourceUserAccessPoliciesResponse](#resourceuseraccesspoliciesresponse)<br> |
|
||||||
|
|
||||||
|
### [PUT] /workspaces/current/rbac/agents/{agent_id}/users/{target_account_id}/access-policies
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
| target_account_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [ReplaceUserAccessPolicies](#replaceuseraccesspolicies)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [ReplaceUserAccessPoliciesResponse](#replaceuseraccesspoliciesresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/agents/{agent_id}/whitelist
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [ResourceWhitelist](#resourcewhitelist)<br> |
|
||||||
|
|
||||||
|
### [PUT] /workspaces/current/rbac/agents/{agent_id}/whitelist
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [_ResourceAccessScopeRequest](#_resourceaccessscoperequest)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [ResourceWhitelist](#resourcewhitelist)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/agents/{agent_id}/whitelist_config
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [ResourceWhitelistConfig](#resourcewhitelistconfig)<br> |
|
||||||
|
|
||||||
### [DELETE] /workspaces/current/rbac/apps/{app_id}/access-policies/{policy_id}/member-bindings
|
### [DELETE] /workspaces/current/rbac/apps/{app_id}/access-policies/{policy_id}/member-bindings
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11582,6 +11708,13 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Success | **application/json**: [PermissionCatalogResponse](#permissioncatalogresponse)<br> |
|
| 200 | Success | **application/json**: [PermissionCatalogResponse](#permissioncatalogresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/role-permissions/catalog/agent
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [PermissionCatalogResponse](#permissioncatalogresponse)<br> |
|
||||||
|
|
||||||
### [GET] /workspaces/current/rbac/role-permissions/catalog/app
|
### [GET] /workspaces/current/rbac/role-permissions/catalog/app
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
@ -11599,16 +11732,16 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
### [GET] /workspaces/current/rbac/roles
|
### [GET] /workspaces/current/rbac/roles
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [_RBACRoleList](#_rbacrolelist)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /workspaces/current/rbac/roles
|
### [POST] /workspaces/current/rbac/roles
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 201 | Role created | **application/json**: [RBACRole](#rbacrole)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [DELETE] /workspaces/current/rbac/roles/{role_id}
|
### [DELETE] /workspaces/current/rbac/roles/{role_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11619,9 +11752,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [RBACRole](#rbacrole)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /workspaces/current/rbac/roles/{role_id}
|
### [GET] /workspaces/current/rbac/roles/{role_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11632,9 +11765,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [RBACRole](#rbacrole)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [PUT] /workspaces/current/rbac/roles/{role_id}
|
### [PUT] /workspaces/current/rbac/roles/{role_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11645,9 +11778,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 200 | Success | **application/json**: [RBACRole](#rbacrole)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [POST] /workspaces/current/rbac/roles/{role_id}/copy
|
### [POST] /workspaces/current/rbac/roles/{role_id}/copy
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11658,9 +11791,9 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- |
|
||||||
| 201 | Role copied | **application/json**: [RBACRole](#rbacrole)<br> |
|
| 200 | Success |
|
||||||
|
|
||||||
### [GET] /workspaces/current/rbac/roles/{role_id}/members
|
### [GET] /workspaces/current/rbac/roles/{role_id}/members
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@ -11675,6 +11808,58 @@ Returns permission flags that control workspace features like member invitations
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Success | **application/json**: [_MembersInRoleList](#_membersinrolelist)<br> |
|
| 200 | Success | **application/json**: [_MembersInRoleList](#_membersinrolelist)<br> |
|
||||||
|
|
||||||
|
### [PUT] /workspaces/current/rbac/workspace/agents/access-policies/{policy_id}/bindings
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| policy_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [_ReplaceBindingsRequest](#_replacebindingsrequest)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [AccessMatrixItem](#accessmatrixitem)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/workspace/agents/access-policies/{policy_id}/member-bindings
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| policy_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [MemberBindingsResponse](#memberbindingsresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/workspace/agents/access-policies/{policy_id}/role-bindings
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| policy_id | path | | Yes | string (uuid) |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [RoleBindingsResponse](#rolebindingsresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /workspaces/current/rbac/workspace/agents/access-policy
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Success | **application/json**: [WorkspaceAccessMatrix](#workspaceaccessmatrix)<br> |
|
||||||
|
|
||||||
### [PUT] /workspaces/current/rbac/workspace/apps/access-policies/{policy_id}/bindings
|
### [PUT] /workspaces/current/rbac/workspace/apps/access-policies/{policy_id}/bindings
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
@ -13401,6 +13586,13 @@ Model class for AI model.
|
|||||||
| chat_prompt_config | object | | No |
|
| chat_prompt_config | object | | No |
|
||||||
| completion_prompt_config | object | | No |
|
| completion_prompt_config | object | | No |
|
||||||
|
|
||||||
|
#### AgentAccessMatrix
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| agent_id | string | | No |
|
||||||
|
| items | [ [AccessMatrixItem](#accessmatrixitem) ] | | No |
|
||||||
|
|
||||||
#### AgentApiAccessResponse
|
#### AgentApiAccessResponse
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -19598,6 +19790,7 @@ Model with provider entity.
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| agent | [ResourcePermissionSnapshot](#resourcepermissionsnapshot) | | No |
|
||||||
| app | [ResourcePermissionSnapshot](#resourcepermissionsnapshot) | | No |
|
| app | [ResourcePermissionSnapshot](#resourcepermissionsnapshot) | | No |
|
||||||
| dataset | [ResourcePermissionSnapshot](#resourcepermissionsnapshot) | | No |
|
| dataset | [ResourcePermissionSnapshot](#resourcepermissionsnapshot) | | No |
|
||||||
| workspace | [WorkspacePermissionSnapshot](#workspacepermissionsnapshot) | | No |
|
| workspace | [WorkspacePermissionSnapshot](#workspacepermissionsnapshot) | | No |
|
||||||
|
|||||||
@ -44,6 +44,7 @@ from services.agent.workspace_service import AgentWorkspaceNotFoundError, AgentW
|
|||||||
from services.app_service import AppService, CreateAppParams
|
from services.app_service import AppService, CreateAppParams
|
||||||
from services.enterprise.enterprise_service import EnterpriseService
|
from services.enterprise.enterprise_service import EnterpriseService
|
||||||
from services.entities.agent_entities import RosterAgentCreatePayload, RosterAgentUpdatePayload
|
from services.entities.agent_entities import RosterAgentCreatePayload, RosterAgentUpdatePayload
|
||||||
|
from services.rbac_agent_access_service import initialize_agent_rbac_access
|
||||||
from services.system_feature_service import SystemFeatureService
|
from services.system_feature_service import SystemFeatureService
|
||||||
from tasks.collect_agent_resources_task import enqueue_agent_resource_collection
|
from tasks.collect_agent_resources_task import enqueue_agent_resource_collection
|
||||||
|
|
||||||
@ -314,11 +315,13 @@ class AgentRosterService:
|
|||||||
source=source,
|
source=source,
|
||||||
)
|
)
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
return agent
|
|
||||||
except IntegrityError as exc:
|
except IntegrityError as exc:
|
||||||
self._session.rollback()
|
self._session.rollback()
|
||||||
raise AgentNameConflictError() from exc
|
raise AgentNameConflictError() from exc
|
||||||
|
|
||||||
|
initialize_agent_rbac_access(tenant_id=tenant_id, agent_id=agent.id, creator_account_id=account_id)
|
||||||
|
return agent
|
||||||
|
|
||||||
def _create_roster_agent_in_transaction(
|
def _create_roster_agent_in_transaction(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@ -44,6 +44,7 @@ from graphon.nodes.question_classifier.entities import QuestionClassifierNodeDat
|
|||||||
from graphon.nodes.tool.entities import ToolNodeData
|
from graphon.nodes.tool.entities import ToolNodeData
|
||||||
from libs.datetime_utils import naive_utc_now
|
from libs.datetime_utils import naive_utc_now
|
||||||
from models import Account, App, AppMode
|
from models import Account, App, AppMode
|
||||||
|
from models.agent import AgentScope
|
||||||
from models.model import AppModelConfig, AppModelConfigDict, IconType, load_annotation_reply_config
|
from models.model import AppModelConfig, AppModelConfigDict, IconType, load_annotation_reply_config
|
||||||
from models.workflow import Workflow
|
from models.workflow import Workflow
|
||||||
from services.agent.dsl_service import AgentDslService, AgentPackage
|
from services.agent.dsl_service import AgentDslService, AgentPackage
|
||||||
@ -456,20 +457,27 @@ class AppDslService:
|
|||||||
raise NoPermissionError("You do not have permission to overwrite this app")
|
raise NoPermissionError("You do not have permission to overwrite this app")
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@staticmethod
|
def _ensure_agent_import_permission(self, account: Account, *, app: App | None) -> None:
|
||||||
def _ensure_agent_manage_permission(account: Account) -> None:
|
|
||||||
"""Importing an Agent DSL creates a roster Agent, which requires ``agent.manage``."""
|
|
||||||
if not dify_config.RBAC_ENABLED:
|
if not dify_config.RBAC_ENABLED:
|
||||||
return
|
return
|
||||||
if account.current_tenant_id is None:
|
if account.current_tenant_id is None:
|
||||||
raise ValueError("Current tenant is not set")
|
raise ValueError("Current tenant is not set")
|
||||||
|
binding = (
|
||||||
|
app.agent_app_binding_with_session(session=self._session, include_archived=True)
|
||||||
|
if app is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if binding is not None and binding.scope == AgentScope.WORKFLOW_ONLY:
|
||||||
|
raise NoPermissionError("Agent DSL import permission is required to import an Agent App")
|
||||||
allowed = RBACService.CheckAccess.check(
|
allowed = RBACService.CheckAccess.check(
|
||||||
account.current_tenant_id,
|
account.current_tenant_id,
|
||||||
account.id,
|
account.id,
|
||||||
scene=RBACPermission.AGENT_MANAGE,
|
scene=RBACPermission.AGENT_IMPORT_EXPORT_DSL,
|
||||||
|
resource_type=RBACResourceScope.AGENT if binding is not None else None,
|
||||||
|
resource_id=str(binding.id) if binding is not None else None,
|
||||||
)
|
)
|
||||||
if not allowed:
|
if not allowed:
|
||||||
raise NoPermissionError("Agent management permission is required to import an Agent App")
|
raise NoPermissionError("Agent DSL import permission is required to import an Agent App")
|
||||||
|
|
||||||
def _create_or_update_app(
|
def _create_or_update_app(
|
||||||
self,
|
self,
|
||||||
@ -492,7 +500,7 @@ class AppDslService:
|
|||||||
raise ValueError("loss app mode")
|
raise ValueError("loss app mode")
|
||||||
app_mode = AppMode(app_mode)
|
app_mode = AppMode(app_mode)
|
||||||
if app_mode == AppMode.AGENT:
|
if app_mode == AppMode.AGENT:
|
||||||
self._ensure_agent_manage_permission(account)
|
self._ensure_agent_import_permission(account, app=app)
|
||||||
|
|
||||||
# Set icon type
|
# Set icon type
|
||||||
icon_type_value = icon_type or app_data.get("icon_type")
|
icon_type_value = icon_type or app_data.get("icon_type")
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Sequence
|
from collections.abc import Callable, Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Literal, NotRequired, TypedDict, cast, override
|
from typing import Any, Literal, NotRequired, TypedDict, cast, override
|
||||||
@ -50,6 +50,7 @@ from services.billing_service import BillingService
|
|||||||
from services.enterprise import rbac_service as enterprise_rbac_service
|
from services.enterprise import rbac_service as enterprise_rbac_service
|
||||||
from services.enterprise.enterprise_service import EnterpriseService
|
from services.enterprise.enterprise_service import EnterpriseService
|
||||||
from services.openapi.visibility import apply_openapi_gate, is_openapi_visible
|
from services.openapi.visibility import apply_openapi_gate, is_openapi_visible
|
||||||
|
from services.rbac_agent_access_service import initialize_agent_rbac_access
|
||||||
from services.system_feature_service import SystemFeatureService
|
from services.system_feature_service import SystemFeatureService
|
||||||
from services.tag_service import TagService
|
from services.tag_service import TagService
|
||||||
from tasks.collect_agent_resources_task import enqueue_agent_resource_collection
|
from tasks.collect_agent_resources_task import enqueue_agent_resource_collection
|
||||||
@ -74,6 +75,38 @@ RECENT_APP_MODES: tuple[RecentAppMode, ...] = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _CreatedApp:
|
||||||
|
tenant_id: str
|
||||||
|
creator_account_id: str
|
||||||
|
app_id: str
|
||||||
|
backing_agent_id: str | None
|
||||||
|
|
||||||
|
|
||||||
|
def _initialize_created_app_access(created: _CreatedApp) -> None:
|
||||||
|
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
|
||||||
|
created.tenant_id,
|
||||||
|
created.creator_account_id,
|
||||||
|
enterprise_rbac_service.RBACResourceType.APP,
|
||||||
|
created.app_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _initialize_created_agent_access(created: _CreatedApp) -> None:
|
||||||
|
if created.backing_agent_id is None:
|
||||||
|
raise ValueError(f"agent app {created.app_id} was created without a backing agent")
|
||||||
|
initialize_agent_rbac_access(
|
||||||
|
tenant_id=created.tenant_id,
|
||||||
|
agent_id=created.backing_agent_id,
|
||||||
|
creator_account_id=created.creator_account_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_CREATED_APP_ACCESS_INITIALIZERS: dict[AppMode, Callable[[_CreatedApp], None]] = {
|
||||||
|
AppMode.AGENT: _initialize_created_agent_access,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AppListBaseParams(BaseModel):
|
class AppListBaseParams(BaseModel):
|
||||||
page: int = Field(default=1, ge=1)
|
page: int = Field(default=1, ge=1)
|
||||||
limit: int = Field(default=20, ge=1, le=100)
|
limit: int = Field(default=20, ge=1, le=100)
|
||||||
@ -670,12 +703,13 @@ class AppService:
|
|||||||
# Created in the same transaction so the App and its backing Agent persist
|
# Created in the same transaction so the App and its backing Agent persist
|
||||||
# atomically; the Agent Soul (model/prompt/tools) is configured afterward
|
# atomically; the Agent Soul (model/prompt/tools) is configured afterward
|
||||||
# in the Composer.
|
# in the Composer.
|
||||||
|
backing_agent: Agent | None = None
|
||||||
if app_mode == AppMode.AGENT:
|
if app_mode == AppMode.AGENT:
|
||||||
from services.agent.roster_service import AgentRosterService
|
from services.agent.roster_service import AgentRosterService
|
||||||
|
|
||||||
icon_type = AgentIconType(params.icon_type) if params.icon_type else None
|
icon_type = AgentIconType(params.icon_type) if params.icon_type else None
|
||||||
try:
|
try:
|
||||||
AgentRosterService(session).create_backing_agent_for_app(
|
backing_agent = AgentRosterService(session).create_backing_agent_for_app(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
account_id=account.id,
|
account_id=account.id,
|
||||||
app_id=app.id,
|
app_id=app.id,
|
||||||
@ -696,11 +730,14 @@ class AppService:
|
|||||||
session.commit()
|
session.commit()
|
||||||
app_was_created.send(app, account=account, session=session)
|
app_was_created.send(app, account=account, session=session)
|
||||||
session.commit()
|
session.commit()
|
||||||
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
|
initialize_access = _CREATED_APP_ACCESS_INITIALIZERS.get(app_mode, _initialize_created_app_access)
|
||||||
tenant_id,
|
initialize_access(
|
||||||
account.id,
|
_CreatedApp(
|
||||||
enterprise_rbac_service.RBACResourceType.APP,
|
tenant_id=tenant_id,
|
||||||
app.id,
|
creator_account_id=account.id,
|
||||||
|
app_id=app.id,
|
||||||
|
backing_agent_id=backing_agent.id if backing_agent else None,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if SystemFeatureService.is_webapp_auth_enabled():
|
if SystemFeatureService.is_webapp_auth_enabled():
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
35
api/services/rbac_agent_access_service.py
Normal file
35
api/services/rbac_agent_access_service.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from configs import dify_config
|
||||||
|
from services.enterprise import rbac_service as enterprise_rbac_service
|
||||||
|
from tasks.initialize_created_app_rbac_access_task import initialize_created_app_rbac_access_task
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_agent_rbac_access(*, tenant_id: str, agent_id: str, creator_account_id: str) -> None:
|
||||||
|
if not dify_config.RBAC_ENABLED:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
initialize_created_app_rbac_access_task.delay(tenant_id, creator_account_id, agent_id=agent_id)
|
||||||
|
enterprise_rbac_service.RBACService.AccessPolicies.sync_creator_access_policy_member_bindings(
|
||||||
|
tenant_id,
|
||||||
|
creator_account_id,
|
||||||
|
resource_type=enterprise_rbac_service.RBACResourceType.AGENT,
|
||||||
|
resource_id=agent_id,
|
||||||
|
)
|
||||||
|
enterprise_rbac_service.RBACService.AgentAccess.replace_whitelist(
|
||||||
|
tenant_id,
|
||||||
|
creator_account_id,
|
||||||
|
agent_id,
|
||||||
|
enterprise_rbac_service.ReplaceMemberBindings(automatic_include_workspace_members=True),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.warning(
|
||||||
|
"Failed to initialize RBAC access for agent: tenant_id=%s agent_id=%s account_id=%s",
|
||||||
|
tenant_id,
|
||||||
|
agent_id,
|
||||||
|
creator_account_id,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
40
api/services/rbac_resource_service.py
Normal file
40
api/services/rbac_resource_service.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from extensions.ext_database import db
|
||||||
|
from models.dataset import Dataset
|
||||||
|
from models.model import App
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from models.agent import Agent
|
||||||
|
|
||||||
|
|
||||||
|
class RBACResourceService:
|
||||||
|
@staticmethod
|
||||||
|
def get_app_agent_binding(tenant_id: str, app_id: str) -> Agent | None:
|
||||||
|
app_model = db.session.scalar(select(App).where(App.id == app_id, App.tenant_id == tenant_id))
|
||||||
|
if app_model is None:
|
||||||
|
return None
|
||||||
|
return app_model.agent_app_binding_with_session(session=db.session, include_archived=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_app_maintainer(tenant_id: str, app_id: str) -> str | None:
|
||||||
|
return db.session.scalar(
|
||||||
|
select(App.maintainer).where(App.id == app_id, App.tenant_id == tenant_id, App.status == "normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_dataset_maintainer(tenant_id: str, dataset_id: str) -> str | None:
|
||||||
|
return db.session.scalar(
|
||||||
|
select(Dataset.maintainer).where(Dataset.id == dataset_id, Dataset.tenant_id == tenant_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_dataset_id_by_pipeline(tenant_id: str, pipeline_id: str) -> str | None:
|
||||||
|
dataset_id = db.session.scalar(
|
||||||
|
select(Dataset.id).where(Dataset.pipeline_id == pipeline_id, Dataset.tenant_id == tenant_id)
|
||||||
|
)
|
||||||
|
return None if dataset_id is None else str(dataset_id)
|
||||||
@ -1,14 +1,15 @@
|
|||||||
"""Initialize default RBAC access for existing workspace members after app creation."""
|
"""Initialize default RBAC access for existing workspace members after app creation."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Iterator
|
from collections.abc import Callable, Iterator, Sequence
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from models import App, Dataset, TenantAccountJoin, TenantAccountRole
|
from models import Agent, App, Dataset, TenantAccountJoin, TenantAccountRole
|
||||||
from services.account_service import TenantService
|
from services.account_service import TenantService
|
||||||
from services.enterprise import rbac_service as enterprise_rbac_service
|
from services.enterprise import rbac_service as enterprise_rbac_service
|
||||||
|
|
||||||
@ -30,43 +31,122 @@ def _owner_account_id(tenant_id: str) -> str | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _WhitelistResourceKind[ItemT]:
|
||||||
|
resource_type: enterprise_rbac_service.RBACResourceType
|
||||||
|
model: type[App] | type[Dataset] | type[Agent]
|
||||||
|
build_item: Callable[[str, str], ItemT]
|
||||||
|
append_members: Callable[[str, str | None, Sequence[ItemT]], None]
|
||||||
|
replace_user_access_policies: Callable[
|
||||||
|
[str, str, str, enterprise_rbac_service.ReplaceUserAccessPolicies],
|
||||||
|
enterprise_rbac_service.ReplaceUserAccessPoliciesResponse,
|
||||||
|
]
|
||||||
|
|
||||||
|
def iter_id_batches(self, tenant_id: str, batch_size: int) -> Iterator[list[str]]:
|
||||||
|
last_id: str | None = None
|
||||||
|
while True:
|
||||||
|
stmt = (
|
||||||
|
select(self.model.id)
|
||||||
|
.where(self.model.tenant_id == tenant_id)
|
||||||
|
.order_by(self.model.id.asc())
|
||||||
|
.limit(batch_size)
|
||||||
|
)
|
||||||
|
if last_id:
|
||||||
|
stmt = stmt.where(self.model.id > last_id)
|
||||||
|
ids = [str(resource_id) for resource_id in db.session().scalars(stmt).all()]
|
||||||
|
if not ids:
|
||||||
|
return
|
||||||
|
yield ids
|
||||||
|
last_id = ids[-1]
|
||||||
|
|
||||||
|
def append_member(self, tenant_id: str, actor_account_id: str, resource_ids: list[str], member_account_id: str):
|
||||||
|
self.append_members(
|
||||||
|
tenant_id,
|
||||||
|
actor_account_id,
|
||||||
|
[self.build_item(resource_id, member_account_id) for resource_id in resource_ids],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_WHITELIST_RESOURCE_KINDS = (
|
||||||
|
_WhitelistResourceKind(
|
||||||
|
resource_type=enterprise_rbac_service.RBACResourceType.APP,
|
||||||
|
model=App,
|
||||||
|
build_item=lambda app_id, member: enterprise_rbac_service.AppendAppWhitelistMembersBatchItem(
|
||||||
|
app_id=app_id, account_ids=[member], policy_id=APP_RBAC_DEFAULT_ACCESS_POLICY_ID
|
||||||
|
),
|
||||||
|
append_members=lambda tenant_id, account_id, data: (
|
||||||
|
enterprise_rbac_service.RBACService.AppAccess.append_whitelist_members_batch(
|
||||||
|
tenant_id=tenant_id, account_id=account_id, data=data
|
||||||
|
)
|
||||||
|
),
|
||||||
|
replace_user_access_policies=lambda tenant_id, account_id, resource_id, payload: (
|
||||||
|
enterprise_rbac_service.RBACService.AppAccess.replace_user_access_policies(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
account_id=account_id,
|
||||||
|
app_id=resource_id,
|
||||||
|
target_account_id=None,
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_WhitelistResourceKind(
|
||||||
|
resource_type=enterprise_rbac_service.RBACResourceType.DATASET,
|
||||||
|
model=Dataset,
|
||||||
|
build_item=lambda dataset_id, member: enterprise_rbac_service.AppendDatasetWhitelistMembersBatchItem(
|
||||||
|
dataset_id=dataset_id, account_ids=[member], policy_id=APP_RBAC_DEFAULT_ACCESS_POLICY_ID
|
||||||
|
),
|
||||||
|
append_members=lambda tenant_id, account_id, data: (
|
||||||
|
enterprise_rbac_service.RBACService.DatasetAccess.append_whitelist_members_batch(
|
||||||
|
tenant_id=tenant_id, account_id=account_id, data=data
|
||||||
|
)
|
||||||
|
),
|
||||||
|
replace_user_access_policies=lambda tenant_id, account_id, resource_id, payload: (
|
||||||
|
enterprise_rbac_service.RBACService.DatasetAccess.replace_user_access_policies(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
account_id=account_id,
|
||||||
|
dataset_id=resource_id,
|
||||||
|
target_account_id=None,
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_WhitelistResourceKind(
|
||||||
|
resource_type=enterprise_rbac_service.RBACResourceType.AGENT,
|
||||||
|
model=Agent,
|
||||||
|
build_item=lambda agent_id, member: enterprise_rbac_service.AppendAgentWhitelistMembersBatchItem(
|
||||||
|
agent_id=agent_id, account_ids=[member], policy_id=APP_RBAC_DEFAULT_ACCESS_POLICY_ID
|
||||||
|
),
|
||||||
|
append_members=lambda tenant_id, account_id, data: (
|
||||||
|
enterprise_rbac_service.RBACService.AgentAccess.append_whitelist_members_batch(
|
||||||
|
tenant_id=tenant_id, account_id=account_id, data=data
|
||||||
|
)
|
||||||
|
),
|
||||||
|
replace_user_access_policies=lambda tenant_id, account_id, resource_id, payload: (
|
||||||
|
enterprise_rbac_service.RBACService.AgentAccess.replace_user_access_policies(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
account_id=account_id,
|
||||||
|
agent_id=resource_id,
|
||||||
|
target_account_id=None,
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
_WHITELIST_RESOURCE_KIND_BY_TYPE = {kind.resource_type: kind for kind in _WHITELIST_RESOURCE_KINDS}
|
||||||
|
|
||||||
|
|
||||||
def _iter_resource_config_batches(
|
def _iter_resource_config_batches(
|
||||||
tenant_id: str,
|
tenant_id: str,
|
||||||
batch_size: int,
|
batch_size: int,
|
||||||
) -> Iterator[list[enterprise_rbac_service.ResourceWhitelistConfigResource]]:
|
) -> Iterator[list[enterprise_rbac_service.ResourceWhitelistConfigResource]]:
|
||||||
last_app_id: str | None = None
|
for kind in _WHITELIST_RESOURCE_KINDS:
|
||||||
while True:
|
for ids in kind.iter_id_batches(tenant_id, batch_size):
|
||||||
stmt = select(App.id).where(App.tenant_id == tenant_id).order_by(App.id.asc()).limit(batch_size)
|
yield [
|
||||||
if last_app_id:
|
enterprise_rbac_service.ResourceWhitelistConfigResource(
|
||||||
stmt = stmt.where(App.id > last_app_id)
|
resource_type=kind.resource_type, resource_id=resource_id
|
||||||
app_ids = [str(app_id) for app_id in db.session().scalars(stmt).all()]
|
)
|
||||||
if not app_ids:
|
for resource_id in ids
|
||||||
break
|
]
|
||||||
yield [
|
|
||||||
enterprise_rbac_service.ResourceWhitelistConfigResource(
|
|
||||||
resource_type=enterprise_rbac_service.RBACResourceType.APP,
|
|
||||||
resource_id=app_id,
|
|
||||||
)
|
|
||||||
for app_id in app_ids
|
|
||||||
]
|
|
||||||
last_app_id = app_ids[-1]
|
|
||||||
|
|
||||||
last_dataset_id: str | None = None
|
|
||||||
while True:
|
|
||||||
stmt = select(Dataset.id).where(Dataset.tenant_id == tenant_id).order_by(Dataset.id.asc()).limit(batch_size)
|
|
||||||
if last_dataset_id:
|
|
||||||
stmt = stmt.where(Dataset.id > last_dataset_id)
|
|
||||||
dataset_ids = [str(dataset_id) for dataset_id in db.session().scalars(stmt).all()]
|
|
||||||
if not dataset_ids:
|
|
||||||
break
|
|
||||||
yield [
|
|
||||||
enterprise_rbac_service.ResourceWhitelistConfigResource(
|
|
||||||
resource_type=enterprise_rbac_service.RBACResourceType.DATASET,
|
|
||||||
resource_id=dataset_id,
|
|
||||||
)
|
|
||||||
for dataset_id in dataset_ids
|
|
||||||
]
|
|
||||||
last_dataset_id = dataset_ids[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def _chunks[T](items: list[T], chunk_size: int) -> Iterator[list[T]]:
|
def _chunks[T](items: list[T], chunk_size: int) -> Iterator[list[T]]:
|
||||||
@ -74,52 +154,64 @@ def _chunks[T](items: list[T], chunk_size: int) -> Iterator[list[T]]:
|
|||||||
yield items[index : index + chunk_size]
|
yield items[index : index + chunk_size]
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_target_resource(
|
||||||
|
resource_ids: dict[enterprise_rbac_service.RBACResourceType, str | None],
|
||||||
|
) -> tuple[_WhitelistResourceKind, str]:
|
||||||
|
provided = [(resource_type, rid) for resource_type, rid in resource_ids.items() if rid is not None]
|
||||||
|
if len(provided) != 1:
|
||||||
|
given = ", ".join(sorted(resource_type.value for resource_type, _ in provided)) or "none"
|
||||||
|
raise ValueError(f"exactly one of app_id, dataset_id, agent_id must be given, got: {given}")
|
||||||
|
resource_type, resource_id = provided[0]
|
||||||
|
return _WHITELIST_RESOURCE_KIND_BY_TYPE[resource_type], resource_id
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue=APP_RBAC_QUEUE, bind=True, max_retries=3, default_retry_delay=60)
|
@shared_task(queue=APP_RBAC_QUEUE, bind=True, max_retries=3, default_retry_delay=60)
|
||||||
def initialize_created_app_rbac_access_task(
|
def initialize_created_app_rbac_access_task(
|
||||||
self, tenant_id: str, account_id: str, app_id: str | None = None, dataset_id: str | None = None
|
self,
|
||||||
|
tenant_id: str,
|
||||||
|
account_id: str,
|
||||||
|
app_id: str | None = None,
|
||||||
|
dataset_id: str | None = None,
|
||||||
|
agent_id: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Grant the default app policy to current workspace members.
|
"""Grant the default policy on one app, dataset or agent to current workspace members.
|
||||||
|
|
||||||
App scope is persisted synchronously before this task is queued. Replacing
|
The resource scope is persisted synchronously before this task is queued. Replacing
|
||||||
member policies is idempotent, so retrying the whole synchronization is safe
|
member policies is idempotent, so retrying the whole synchronization is safe
|
||||||
when the enterprise RBAC service is temporarily unavailable.
|
when the enterprise RBAC service is temporarily unavailable.
|
||||||
"""
|
"""
|
||||||
if not dify_config.RBAC_ENABLED:
|
if not dify_config.RBAC_ENABLED:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
kind, resource_id = _resolve_target_resource(
|
||||||
|
{
|
||||||
|
enterprise_rbac_service.RBACResourceType.APP: app_id,
|
||||||
|
enterprise_rbac_service.RBACResourceType.DATASET: dataset_id,
|
||||||
|
enterprise_rbac_service.RBACResourceType.AGENT: agent_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for account_ids in TenantService.iter_member_account_id_batches(
|
for account_ids in TenantService.iter_member_account_id_batches(
|
||||||
tenant_id,
|
tenant_id,
|
||||||
APP_RBAC_ACCOUNT_POLICY_BATCH_SIZE,
|
APP_RBAC_ACCOUNT_POLICY_BATCH_SIZE,
|
||||||
session=db.session(),
|
session=db.session(),
|
||||||
):
|
):
|
||||||
if app_id is not None:
|
kind.replace_user_access_policies(
|
||||||
enterprise_rbac_service.RBACService.AppAccess.replace_user_access_policies(
|
tenant_id,
|
||||||
tenant_id=tenant_id,
|
account_id,
|
||||||
account_id=account_id,
|
resource_id,
|
||||||
app_id=app_id,
|
enterprise_rbac_service.ReplaceUserAccessPolicies(
|
||||||
target_account_id=None,
|
access_policy_ids=[APP_RBAC_DEFAULT_ACCESS_POLICY_ID],
|
||||||
payload=enterprise_rbac_service.ReplaceUserAccessPolicies(
|
account_ids=account_ids,
|
||||||
access_policy_ids=[APP_RBAC_DEFAULT_ACCESS_POLICY_ID],
|
),
|
||||||
account_ids=account_ids,
|
)
|
||||||
),
|
|
||||||
)
|
|
||||||
elif dataset_id is not None:
|
|
||||||
enterprise_rbac_service.RBACService.DatasetAccess.replace_user_access_policies(
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
account_id=account_id,
|
|
||||||
dataset_id=dataset_id,
|
|
||||||
target_account_id=None,
|
|
||||||
payload=enterprise_rbac_service.ReplaceUserAccessPolicies(
|
|
||||||
access_policy_ids=[APP_RBAC_DEFAULT_ACCESS_POLICY_ID],
|
|
||||||
account_ids=account_ids,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to initialize app RBAC access; retrying: tenant_id=%s app_id=%s attempt=%s",
|
"Failed to initialize RBAC access; retrying: tenant_id=%s resource_type=%s resource_id=%s attempt=%s",
|
||||||
tenant_id,
|
tenant_id,
|
||||||
app_id,
|
kind.resource_type.value,
|
||||||
|
resource_id,
|
||||||
self.request.retries + 1,
|
self.request.retries + 1,
|
||||||
)
|
)
|
||||||
raise self.retry(exc=exc)
|
raise self.retry(exc=exc)
|
||||||
@ -146,8 +238,9 @@ def sync_joined_workspace_member_rbac_access_task(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
app_ids: list[str] = []
|
auto_included: dict[enterprise_rbac_service.RBACResourceType, list[str]] = {
|
||||||
dataset_ids: list[str] = []
|
kind.resource_type: [] for kind in _WHITELIST_RESOURCE_KINDS
|
||||||
|
}
|
||||||
for resources in _iter_resource_config_batches(tenant_id, APP_RBAC_RESOURCE_CONFIG_BATCH_SIZE):
|
for resources in _iter_resource_config_batches(tenant_id, APP_RBAC_RESOURCE_CONFIG_BATCH_SIZE):
|
||||||
configs = enterprise_rbac_service.RBACService.ResourceWhitelistConfigs.batch_get(
|
configs = enterprise_rbac_service.RBACService.ResourceWhitelistConfigs.batch_get(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
@ -157,38 +250,14 @@ def sync_joined_workspace_member_rbac_access_task(
|
|||||||
for config in configs.data:
|
for config in configs.data:
|
||||||
if not config.automatic_include_workspace_members:
|
if not config.automatic_include_workspace_members:
|
||||||
continue
|
continue
|
||||||
if config.resource_type == enterprise_rbac_service.RBACResourceType.APP:
|
ids = auto_included.get(config.resource_type)
|
||||||
app_ids.append(config.resource_id)
|
if ids is not None:
|
||||||
elif config.resource_type == enterprise_rbac_service.RBACResourceType.DATASET:
|
ids.append(config.resource_id)
|
||||||
dataset_ids.append(config.resource_id)
|
|
||||||
|
|
||||||
for app_id_batch in _chunks(app_ids, APP_RBAC_MEMBER_APPEND_BATCH_SIZE):
|
for resource_type, resource_ids in auto_included.items():
|
||||||
enterprise_rbac_service.RBACService.AppAccess.append_whitelist_members_batch(
|
kind = _WHITELIST_RESOURCE_KIND_BY_TYPE[resource_type]
|
||||||
tenant_id=tenant_id,
|
for id_batch in _chunks(resource_ids, APP_RBAC_MEMBER_APPEND_BATCH_SIZE):
|
||||||
account_id=actor_account_id,
|
kind.append_member(tenant_id, actor_account_id, id_batch, member_account_id)
|
||||||
data=[
|
|
||||||
enterprise_rbac_service.AppendAppWhitelistMembersBatchItem(
|
|
||||||
app_id=app_id,
|
|
||||||
account_ids=[member_account_id],
|
|
||||||
policy_id=APP_RBAC_DEFAULT_ACCESS_POLICY_ID,
|
|
||||||
)
|
|
||||||
for app_id in app_id_batch
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
for dataset_id_batch in _chunks(dataset_ids, APP_RBAC_MEMBER_APPEND_BATCH_SIZE):
|
|
||||||
enterprise_rbac_service.RBACService.DatasetAccess.append_whitelist_members_batch(
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
account_id=actor_account_id,
|
|
||||||
data=[
|
|
||||||
enterprise_rbac_service.AppendDatasetWhitelistMembersBatchItem(
|
|
||||||
dataset_id=dataset_id,
|
|
||||||
account_ids=[member_account_id],
|
|
||||||
policy_id=APP_RBAC_DEFAULT_ACCESS_POLICY_ID,
|
|
||||||
)
|
|
||||||
for dataset_id in dataset_id_batch
|
|
||||||
],
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to sync joined member RBAC access; retrying: tenant_id=%s member=%s attempt=%s",
|
"Failed to sync joined member RBAC access; retrying: tenant_id=%s member=%s attempt=%s",
|
||||||
|
|||||||
193
api/tests/unit_tests/commands/test_rbac_agent_migration.py
Normal file
193
api/tests/unit_tests/commands/test_rbac_agent_migration.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import json
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from contextlib import ExitStack
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from click.testing import CliRunner, Result
|
||||||
|
|
||||||
|
from commands.rbac import migrate_agent_permissions_to_rbac
|
||||||
|
from services.enterprise.rbac_service import (
|
||||||
|
LegacyAgentMigrationReport,
|
||||||
|
LegacyAgentRoleMigration,
|
||||||
|
_LegacyResourceWhitelistConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
MODULE = "commands.rbac"
|
||||||
|
|
||||||
|
|
||||||
|
def _events(output: str) -> list[dict[str, object]]:
|
||||||
|
return [json.loads(line) for line in output.splitlines() if line.startswith("{")]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _no_agents() -> Iterator[None]:
|
||||||
|
with patch(f"{MODULE}._iter_agent_row_batches", return_value=iter(())):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
def _roles() -> list[LegacyAgentRoleMigration]:
|
||||||
|
return [
|
||||||
|
LegacyAgentRoleMigration(
|
||||||
|
role_id="r1",
|
||||||
|
role_name="ops",
|
||||||
|
added_keys=["agent.create"],
|
||||||
|
removed_keys=["agent.manage"],
|
||||||
|
bound_policies=["agent.full_access"],
|
||||||
|
),
|
||||||
|
LegacyAgentRoleMigration(role_id="r2", role_name="stuck", skipped="policy row missing"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_flag_writes_and_reports_applied() -> None:
|
||||||
|
with (
|
||||||
|
patch(f"{MODULE}._iter_tenant_ids", return_value=iter(["t1", "t2"])),
|
||||||
|
patch(
|
||||||
|
f"{MODULE}.RBACService.Migrations.migrate_agent_manage_roles",
|
||||||
|
return_value=LegacyAgentMigrationReport(roles=_roles()[:1]),
|
||||||
|
) as migrate,
|
||||||
|
):
|
||||||
|
result = CliRunner().invoke(migrate_agent_permissions_to_rbac, ["--apply"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert migrate.call_count == 2
|
||||||
|
assert all(call.kwargs == {"apply": True} for call in migrate.call_args_list)
|
||||||
|
events = _events(result.output)
|
||||||
|
assert [e["event"] for e in events] == ["agent_manage_role_migration_applied"] * 2
|
||||||
|
assert {e["tenant_id"] for e in events} == {"t1", "t2"}
|
||||||
|
assert "changed" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def _whitelist_config(
|
||||||
|
scope: str | None = "all",
|
||||||
|
account_ids: list[str] | None = None,
|
||||||
|
) -> _LegacyResourceWhitelistConfig:
|
||||||
|
return _LegacyResourceWhitelistConfig(rbac_whitelist_scope=scope, account_ids=account_ids or [])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _AgentPhaseMocks:
|
||||||
|
agent_whitelist_config: MagicMock
|
||||||
|
app_whitelist_config: MagicMock
|
||||||
|
replace_whitelist: MagicMock
|
||||||
|
replace_user_access_policies: MagicMock
|
||||||
|
sync_creator_bindings: MagicMock
|
||||||
|
owner_account_id: MagicMock
|
||||||
|
member_batches: MagicMock
|
||||||
|
write_order: MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _AgentPhaseSetup:
|
||||||
|
agents: list[tuple[str, str | None, str | None]]
|
||||||
|
configured_agent_ids: list[str] = field(default_factory=list)
|
||||||
|
agent_configs: list[_LegacyResourceWhitelistConfig] | None = None
|
||||||
|
app_config: _LegacyResourceWhitelistConfig = field(default_factory=_whitelist_config)
|
||||||
|
workspace_members: list[str] = field(default_factory=lambda: ["m1", "m2", "m3"])
|
||||||
|
owner_account_id: str = "owner-1"
|
||||||
|
|
||||||
|
|
||||||
|
def _run_agent_phase(args: list[str], setup: _AgentPhaseSetup) -> tuple[Result, _AgentPhaseMocks]:
|
||||||
|
def _member_batches(_tenant_id: str, batch_size: int) -> Iterator[list[str]]:
|
||||||
|
for start in range(0, len(setup.workspace_members), batch_size):
|
||||||
|
yield setup.workspace_members[start : start + batch_size]
|
||||||
|
|
||||||
|
with ExitStack() as stack:
|
||||||
|
stack.enter_context(patch(f"{MODULE}._iter_tenant_ids", return_value=iter(["t1"])))
|
||||||
|
stack.enter_context(
|
||||||
|
patch(
|
||||||
|
f"{MODULE}.RBACService.Migrations.migrate_agent_manage_roles",
|
||||||
|
return_value=LegacyAgentMigrationReport(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
stack.enter_context(patch(f"{MODULE}._iter_agent_row_batches", return_value=iter([setup.agents])))
|
||||||
|
stack.enter_context(
|
||||||
|
patch(
|
||||||
|
f"{MODULE}.RBACService.Migrations.list_configured_agent_ids",
|
||||||
|
return_value=setup.configured_agent_ids,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
agent_whitelist_config = stack.enter_context(patch(f"{MODULE}.RBACService.AgentAccess.legacy_whitelist_config"))
|
||||||
|
if setup.agent_configs is None:
|
||||||
|
agent_whitelist_config.return_value = _whitelist_config()
|
||||||
|
else:
|
||||||
|
agent_whitelist_config.side_effect = setup.agent_configs
|
||||||
|
mocks = _AgentPhaseMocks(
|
||||||
|
agent_whitelist_config=agent_whitelist_config,
|
||||||
|
app_whitelist_config=stack.enter_context(
|
||||||
|
patch(f"{MODULE}.RBACService.AppAccess.legacy_whitelist_config", return_value=setup.app_config)
|
||||||
|
),
|
||||||
|
replace_whitelist=stack.enter_context(patch(f"{MODULE}.RBACService.AgentAccess.replace_whitelist")),
|
||||||
|
replace_user_access_policies=stack.enter_context(
|
||||||
|
patch(f"{MODULE}.RBACService.AgentAccess.replace_user_access_policies")
|
||||||
|
),
|
||||||
|
sync_creator_bindings=stack.enter_context(
|
||||||
|
patch(f"{MODULE}.RBACService.AccessPolicies.sync_creator_access_policy_member_bindings")
|
||||||
|
),
|
||||||
|
owner_account_id=stack.enter_context(
|
||||||
|
patch(f"{MODULE}._owner_account_id", return_value=setup.owner_account_id)
|
||||||
|
),
|
||||||
|
member_batches=stack.enter_context(
|
||||||
|
patch(f"{MODULE}._workspace_member_account_id_batches", side_effect=_member_batches)
|
||||||
|
),
|
||||||
|
write_order=MagicMock(),
|
||||||
|
)
|
||||||
|
mocks.write_order.attach_mock(mocks.replace_user_access_policies, "seed_members")
|
||||||
|
mocks.write_order.attach_mock(mocks.sync_creator_bindings, "sync_creator")
|
||||||
|
mocks.write_order.attach_mock(mocks.replace_whitelist, "replace_whitelist")
|
||||||
|
result = CliRunner().invoke(migrate_agent_permissions_to_rbac, args)
|
||||||
|
return result, mocks
|
||||||
|
|
||||||
|
|
||||||
|
def _write_order(mocks: _AgentPhaseMocks) -> list[str]:
|
||||||
|
return [name for name, _, _ in mocks.write_order.mock_calls]
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_bootstrap_apply_writes_whitelist_member_batches_and_creator_sync() -> None:
|
||||||
|
result, mocks = _run_agent_phase(
|
||||||
|
["--apply", "--member-batch-size", "2"],
|
||||||
|
_AgentPhaseSetup(agents=[("ag1", "c1", None)], workspace_members=["m1", "m2", "m3"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert [e["event"] for e in _events(result.output)] == ["agent_access_bootstrap_applied"]
|
||||||
|
assert _events(result.output)[0]["dry_run"] is False
|
||||||
|
|
||||||
|
mocks.replace_whitelist.assert_called_once()
|
||||||
|
assert mocks.replace_whitelist.call_args.kwargs["agent_id"] == "ag1"
|
||||||
|
assert mocks.replace_whitelist.call_args.kwargs["account_id"] == "c1"
|
||||||
|
assert mocks.replace_whitelist.call_args.kwargs["payload"].automatic_include_workspace_members is True
|
||||||
|
|
||||||
|
mocks.member_batches.assert_called_once_with("t1", 2)
|
||||||
|
assert mocks.replace_user_access_policies.call_count == 2
|
||||||
|
calls = mocks.replace_user_access_policies.call_args_list
|
||||||
|
assert [call.kwargs["payload"].account_ids for call in calls] == [["m1", "m2"], ["m3"]]
|
||||||
|
assert all(call.kwargs["payload"].access_policy_ids == ["default"] for call in calls)
|
||||||
|
assert all(call.kwargs["target_account_id"] is None for call in calls)
|
||||||
|
|
||||||
|
mocks.sync_creator_bindings.assert_called_once()
|
||||||
|
assert mocks.sync_creator_bindings.call_args.kwargs["resource_id"] == "ag1"
|
||||||
|
assert mocks.sync_creator_bindings.call_args.kwargs["account_id"] == "c1"
|
||||||
|
assert "1 agent(s) changed, 0 already initialised" in result.output
|
||||||
|
|
||||||
|
assert _write_order(mocks) == ["seed_members", "seed_members", "sync_creator", "replace_whitelist"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_bootstrap_is_idempotent_on_a_second_apply() -> None:
|
||||||
|
result, mocks = _run_agent_phase(
|
||||||
|
["--apply"],
|
||||||
|
_AgentPhaseSetup(
|
||||||
|
agents=[("ag1", "c1", None), ("ag2", "c2", None)],
|
||||||
|
configured_agent_ids=["ag1", "ag2"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
events = _events(result.output)
|
||||||
|
assert [e["event"] for e in events] == ["agent_access_bootstrap_skipped"] * 2
|
||||||
|
assert {e["reason"] for e in events} == {"already_initialized"}
|
||||||
|
mocks.replace_whitelist.assert_not_called()
|
||||||
|
mocks.sync_creator_bindings.assert_not_called()
|
||||||
|
mocks.replace_user_access_policies.assert_not_called()
|
||||||
|
assert "0 agent(s) changed, 2 already initialised" in result.output
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from inspect import getclosurevars, unwrap
|
from inspect import unwrap
|
||||||
from types import FunctionType, SimpleNamespace
|
from types import FunctionType, SimpleNamespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -11,6 +11,7 @@ from controllers.console import agent_app_sandbox as module
|
|||||||
from models.account import Account
|
from models.account import Account
|
||||||
from models.model import App, AppMode, IconType
|
from models.model import App, AppMode, IconType
|
||||||
from services.agent_app_sandbox_service import AgentSandboxDownload, AgentSandboxInfo, AgentSandboxInspectorError
|
from services.agent_app_sandbox_service import AgentSandboxDownload, AgentSandboxInfo, AgentSandboxInspectorError
|
||||||
|
from tests.unit_tests.controllers.rbac_introspection import rbac_checks
|
||||||
|
|
||||||
|
|
||||||
class _AgentAppService:
|
class _AgentAppService:
|
||||||
@ -155,12 +156,10 @@ def _account() -> Account:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_sandbox_resources_require_app_view_layout(method: FunctionType) -> None:
|
def test_sandbox_resources_require_app_view_layout(method: FunctionType) -> None:
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
[check] = rbac_checks(method)
|
||||||
config = getclosurevars(rbac_wrapper).nonlocals
|
|
||||||
|
|
||||||
assert config["resource_type"] == module.RBACResourceScope.APP
|
assert isinstance(check.locator, module.PlainApp)
|
||||||
assert config["scene"] == module.RBACPermission.APP_VIEW_LAYOUT
|
assert check.scene == module.RBACPermission.APP_VIEW_LAYOUT
|
||||||
assert config["resource_required"] is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_handle_maps_sandbox_and_agent_backend_errors() -> None:
|
def test_handle_maps_sandbox_and_agent_backend_errors() -> None:
|
||||||
|
|||||||
@ -1,186 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from sqlalchemy.orm import Session, scoped_session, sessionmaker
|
|
||||||
from werkzeug.exceptions import Forbidden
|
|
||||||
|
|
||||||
from controllers.console.app.error import AppNotFoundError
|
|
||||||
from controllers.console.app.wraps import agent_manage_required_for_agent_app
|
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
|
||||||
from models import Account
|
|
||||||
from models.agent import Agent, AgentScope, AgentSource, AgentStatus
|
|
||||||
from models.model import App, AppMode
|
|
||||||
from tests.unit_tests.config_override import config_overrides_context
|
|
||||||
|
|
||||||
TENANT_ID = "tenant-1"
|
|
||||||
|
|
||||||
|
|
||||||
def _guarded_view():
|
|
||||||
calls: list[dict[str, object]] = []
|
|
||||||
|
|
||||||
@agent_manage_required_for_agent_app
|
|
||||||
def view(*args, **kwargs):
|
|
||||||
calls.append(kwargs)
|
|
||||||
return "ok"
|
|
||||||
|
|
||||||
return view, calls
|
|
||||||
|
|
||||||
|
|
||||||
def _persist_app(
|
|
||||||
session: Session,
|
|
||||||
*,
|
|
||||||
scope: AgentScope | None = None,
|
|
||||||
status: AgentStatus = AgentStatus.ACTIVE,
|
|
||||||
) -> App:
|
|
||||||
app_model = App(
|
|
||||||
id="app-1",
|
|
||||||
tenant_id=TENANT_ID,
|
|
||||||
name="Managed App",
|
|
||||||
mode=AppMode.AGENT if scope is not None else AppMode.CHAT,
|
|
||||||
enable_site=True,
|
|
||||||
enable_api=False,
|
|
||||||
)
|
|
||||||
session.add(app_model)
|
|
||||||
if scope is not None:
|
|
||||||
agent = Agent(
|
|
||||||
tenant_id=TENANT_ID,
|
|
||||||
name=f"{scope.value} agent",
|
|
||||||
scope=scope,
|
|
||||||
source=AgentSource.AGENT_APP if scope == AgentScope.ROSTER else AgentSource.WORKFLOW,
|
|
||||||
status=status,
|
|
||||||
app_id=app_model.id if scope == AgentScope.ROSTER else None,
|
|
||||||
backing_app_id=app_model.id if scope == AgentScope.WORKFLOW_ONLY else None,
|
|
||||||
)
|
|
||||||
session.add(agent)
|
|
||||||
session.commit()
|
|
||||||
return app_model
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_guard(account: Account, rbac_enabled: bool):
|
|
||||||
return (
|
|
||||||
patch("controllers.console.app.wraps.current_account_with_tenant", return_value=(account, TENANT_ID)),
|
|
||||||
config_overrides_context(RBAC_ENABLED=rbac_enabled),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAgentManageRequiredForAgentApp:
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _bind_database(
|
|
||||||
self,
|
|
||||||
sqlite_session: Session,
|
|
||||||
sqlite_session_factory: sessionmaker[Session],
|
|
||||||
):
|
|
||||||
self.sqlite_session = sqlite_session
|
|
||||||
self.account = Account(name="Guard User", email="guard@example.com")
|
|
||||||
self.account.id = "account-1"
|
|
||||||
session_proxy = scoped_session(sqlite_session_factory)
|
|
||||||
with patch("controllers.console.app.wraps.db.session", session_proxy):
|
|
||||||
yield
|
|
||||||
session_proxy.remove()
|
|
||||||
|
|
||||||
def test_non_agent_app_passes_through_without_workspace_check(self):
|
|
||||||
view, calls = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
assert view(app_id="app-1") == "ok"
|
|
||||||
|
|
||||||
gate.assert_not_called()
|
|
||||||
assert calls == [{"app_id": "app-1"}]
|
|
||||||
|
|
||||||
def test_roster_agent_app_requires_agent_manage_when_rbac_enabled(self):
|
|
||||||
view, _ = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.ROSTER)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
assert view(app_id="app-1") == "ok"
|
|
||||||
|
|
||||||
gate.assert_called_once_with(
|
|
||||||
tenant_id=TENANT_ID,
|
|
||||||
account_id=self.account.id,
|
|
||||||
resource_type=RBACResourceScope.WORKSPACE,
|
|
||||||
scene=RBACPermission.AGENT_MANAGE,
|
|
||||||
resource_required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_roster_agent_app_denied_without_agent_manage(self):
|
|
||||||
view, calls = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.ROSTER)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with (
|
|
||||||
patches[0],
|
|
||||||
patches[1],
|
|
||||||
patch("controllers.console.app.wraps.enforce_rbac_access", side_effect=Forbidden()),
|
|
||||||
):
|
|
||||||
with pytest.raises(Forbidden):
|
|
||||||
view(app_id="app-1")
|
|
||||||
|
|
||||||
assert calls == []
|
|
||||||
|
|
||||||
def test_roster_agent_app_skips_workspace_check_when_rbac_disabled(self):
|
|
||||||
view, _ = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.ROSTER)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=False)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
assert view(app_id="app-1") == "ok"
|
|
||||||
|
|
||||||
gate.assert_not_called()
|
|
||||||
|
|
||||||
def test_hidden_backing_app_is_rejected_even_without_rbac(self):
|
|
||||||
"""A workflow-only backing App is not part of the general app management plane."""
|
|
||||||
view, calls = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.WORKFLOW_ONLY)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=False)
|
|
||||||
|
|
||||||
with patches[0], patches[1]:
|
|
||||||
with pytest.raises(AppNotFoundError):
|
|
||||||
view(app_id="app-1")
|
|
||||||
|
|
||||||
assert calls == []
|
|
||||||
|
|
||||||
def test_hidden_backing_app_is_rejected_before_workspace_check(self):
|
|
||||||
view, calls = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.WORKFLOW_ONLY)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
with pytest.raises(AppNotFoundError):
|
|
||||||
view(app_id="app-1")
|
|
||||||
|
|
||||||
gate.assert_not_called()
|
|
||||||
assert calls == []
|
|
||||||
|
|
||||||
def test_binding_lookup_covers_archived_agents(self):
|
|
||||||
"""An Agent App stays gated after its roster Agent is archived."""
|
|
||||||
view, _ = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.ROSTER, status=AgentStatus.ARCHIVED)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
view(app_id="app-1")
|
|
||||||
|
|
||||||
gate.assert_called_once()
|
|
||||||
|
|
||||||
def test_resource_id_path_alias_is_resolved(self):
|
|
||||||
view, _ = _guarded_view()
|
|
||||||
_persist_app(self.sqlite_session, scope=AgentScope.ROSTER)
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
assert view(resource_id="app-1") == "ok"
|
|
||||||
|
|
||||||
gate.assert_called_once()
|
|
||||||
|
|
||||||
def test_unknown_app_passes_through_for_downstream_handling(self):
|
|
||||||
view, calls = _guarded_view()
|
|
||||||
patches = _patch_guard(self.account, rbac_enabled=True)
|
|
||||||
|
|
||||||
with patches[0], patches[1], patch("controllers.console.app.wraps.enforce_rbac_access") as gate:
|
|
||||||
assert view(app_id="app-1") == "ok"
|
|
||||||
|
|
||||||
gate.assert_not_called()
|
|
||||||
assert calls == [{"app_id": "app-1"}]
|
|
||||||
@ -111,11 +111,11 @@ def test_agent_console_audio_api_uses_agent_draft(
|
|||||||
calls["asr"] = kwargs
|
calls["asr"] = kwargs
|
||||||
return {"text": "agent transcript"}
|
return {"text": "agent transcript"}
|
||||||
|
|
||||||
def enforce_rbac_access(**kwargs):
|
def enforce_rbac_checks(**kwargs):
|
||||||
calls["rbac"] = kwargs
|
calls["rbac"] = kwargs
|
||||||
|
|
||||||
monkeypatch.setattr(audio_module, "resolve_agent_runtime_app_model", resolve_agent_runtime_app_model)
|
monkeypatch.setattr(audio_module, "resolve_agent_runtime_app_model", resolve_agent_runtime_app_model)
|
||||||
monkeypatch.setattr(audio_module, "enforce_rbac_access", enforce_rbac_access)
|
monkeypatch.setattr(audio_module, "enforce_rbac_checks", enforce_rbac_checks)
|
||||||
monkeypatch.setattr(AgentComposerService, "load_agent_soul_for_debug", load_agent_soul_for_debug)
|
monkeypatch.setattr(AgentComposerService, "load_agent_soul_for_debug", load_agent_soul_for_debug)
|
||||||
monkeypatch.setattr(AudioService, "transcript_agent_asr", transcript_agent_asr)
|
monkeypatch.setattr(AudioService, "transcript_agent_asr", transcript_agent_asr)
|
||||||
|
|
||||||
@ -138,13 +138,13 @@ def test_agent_console_audio_api_uses_agent_draft(
|
|||||||
|
|
||||||
assert response == {"text": "agent transcript"}
|
assert response == {"text": "agent transcript"}
|
||||||
assert calls["resolver"] == {"session": session, "tenant_id": "tenant-1", "agent_id": agent_id}
|
assert calls["resolver"] == {"session": session, "tenant_id": "tenant-1", "agent_id": agent_id}
|
||||||
assert calls["rbac"] == {
|
rbac_call = calls["rbac"]
|
||||||
"tenant_id": "tenant-1",
|
assert rbac_call["tenant_id"] == "tenant-1"
|
||||||
"account_id": "account-1",
|
assert rbac_call["account_id"] == "account-1"
|
||||||
"resource_type": audio_module.RBACResourceScope.APP,
|
assert rbac_call["path_args"] == {"app_id": "backing-app-1"}
|
||||||
"scene": audio_module.RBACPermission.APP_TEST_AND_RUN,
|
(rbac_check,) = rbac_call["checks"]
|
||||||
"path_args": {"app_id": "backing-app-1"},
|
assert rbac_check.scene is audio_module.RBACPermission.APP_TEST_AND_RUN
|
||||||
}
|
assert isinstance(rbac_check.locator, audio_module.PlainApp)
|
||||||
assert calls["draft"] == {
|
assert calls["draft"] == {
|
||||||
"tenant_id": "tenant-1",
|
"tenant_id": "tenant-1",
|
||||||
"agent_id": str(agent_id),
|
"agent_id": str(agent_id),
|
||||||
@ -216,7 +216,7 @@ def test_agent_console_audio_api_checks_rbac_with_backing_app_id(
|
|||||||
soul_loaded = True
|
soul_loaded = True
|
||||||
return AgentSoulConfig()
|
return AgentSoulConfig()
|
||||||
|
|
||||||
monkeypatch.setattr(audio_module, "enforce_rbac_access", deny_access)
|
monkeypatch.setattr(audio_module, "enforce_rbac_checks", deny_access)
|
||||||
monkeypatch.setattr(AgentComposerService, "load_agent_soul_for_debug", load_agent_soul_for_debug)
|
monkeypatch.setattr(AgentComposerService, "load_agent_soul_for_debug", load_agent_soul_for_debug)
|
||||||
|
|
||||||
api = AgentChatMessageAudioApi()
|
api = AgentChatMessageAudioApi()
|
||||||
|
|||||||
@ -315,19 +315,20 @@ class TestAppMCPServerRefreshController:
|
|||||||
"controllers.common.wraps.current_account_with_tenant",
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
return_value=(current_user, "tenant-1"),
|
return_value=(current_user, "tenant-1"),
|
||||||
),
|
),
|
||||||
|
patch("controllers.common.rbac.locators.agent_binding", return_value=None),
|
||||||
|
patch("controllers.common.rbac.locators.PlainApp.owner_id", return_value=None),
|
||||||
patch(
|
patch(
|
||||||
"controllers.common.wraps.enforce_rbac_access",
|
"controllers.common.rbac.checks.RBACService.CheckAccess.check",
|
||||||
side_effect=PermissionCheckedError,
|
side_effect=PermissionCheckedError,
|
||||||
) as enforce_rbac_access,
|
) as check_access,
|
||||||
pytest.raises(PermissionCheckedError),
|
pytest.raises(PermissionCheckedError),
|
||||||
):
|
):
|
||||||
method(AppMCPServerRefreshController(), app_id="app-1")
|
method(AppMCPServerRefreshController(), app_id="app-1")
|
||||||
|
|
||||||
enforce_rbac_access.assert_called_once_with(
|
check_access.assert_called_once_with(
|
||||||
tenant_id="tenant-1",
|
"tenant-1",
|
||||||
account_id="account-1",
|
"account-1",
|
||||||
resource_type=RBACResourceScope.APP,
|
|
||||||
scene=RBACPermission.APP_EDIT,
|
scene=RBACPermission.APP_EDIT,
|
||||||
resource_required=True,
|
resource_type=RBACResourceScope.APP,
|
||||||
path_args={"app_id": "app-1"},
|
resource_id="app-1",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import pytest
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp
|
||||||
from controllers.console import flask_admission
|
from controllers.console import flask_admission
|
||||||
from controllers.console.app import ops_trace as ops_trace_module
|
from controllers.console.app import ops_trace as ops_trace_module
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
@ -138,19 +139,19 @@ def test_trace_config_mutations_require_app_tracing_permission_when_rbac_is_enab
|
|||||||
lambda: AccountWithTenant(account=account, tenant_id=WORKSPACE_ID),
|
lambda: AccountWithTenant(account=account, tenant_id=WORKSPACE_ID),
|
||||||
)
|
)
|
||||||
denied = MagicMock(side_effect=Forbidden())
|
denied = MagicMock(side_effect=Forbidden())
|
||||||
monkeypatch.setattr(flask_admission, "enforce_rbac_access", denied)
|
monkeypatch.setattr(flask_admission, "enforce_rbac_checks", denied)
|
||||||
|
|
||||||
with app.test_request_context(), pytest.raises(Forbidden):
|
with app.test_request_context(), pytest.raises(Forbidden):
|
||||||
_admission_injector(method)(None, app_id=UUID(APP_ID))
|
_admission_injector(method)(None, app_id=UUID(APP_ID))
|
||||||
|
|
||||||
denied.assert_called_once_with(
|
denied.assert_called_once()
|
||||||
tenant_id=WORKSPACE_ID,
|
call = denied.call_args.kwargs
|
||||||
account_id=ACCOUNT_ID,
|
assert call["tenant_id"] == WORKSPACE_ID
|
||||||
resource_type=ops_trace_module.RBACResourceScope.APP,
|
assert call["account_id"] == ACCOUNT_ID
|
||||||
scene=ops_trace_module.RBACPermission.APP_TRACING_CONFIG,
|
assert call["path_args"] == {"app_id": UUID(APP_ID)}
|
||||||
resource_required=True,
|
(check,) = call["checks"]
|
||||||
path_args={"app_id": UUID(APP_ID)},
|
assert check.scene == ops_trace_module.RBACPermission.APP_TRACING_CONFIG
|
||||||
)
|
assert isinstance(check.locator, PlainApp)
|
||||||
|
|
||||||
|
|
||||||
def test_trace_config_get_preserves_read_access_for_normal_member() -> None:
|
def test_trace_config_get_preserves_read_access_for_normal_member() -> None:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from inspect import getclosurevars, unwrap
|
from inspect import unwrap
|
||||||
from unittest.mock import ANY, PropertyMock, patch
|
from unittest.mock import ANY, PropertyMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -28,6 +28,7 @@ from services.entities.knowledge_entities.rag_pipeline_entities import PipelineT
|
|||||||
from services.errors.account import NoPermissionError
|
from services.errors.account import NoPermissionError
|
||||||
from services.errors.rag_pipeline import RagPipelineResourceNotFoundError
|
from services.errors.rag_pipeline import RagPipelineResourceNotFoundError
|
||||||
from tests.unit_tests.config_override import config_overrides_context
|
from tests.unit_tests.config_override import config_overrides_context
|
||||||
|
from tests.unit_tests.controllers.rbac_introspection import rbac_checks
|
||||||
|
|
||||||
|
|
||||||
def _template_item() -> dict[str, object]:
|
def _template_item() -> dict[str, object]:
|
||||||
@ -362,11 +363,8 @@ class TestCustomizedPipelineTemplateApi:
|
|||||||
|
|
||||||
class TestPublishCustomizedPipelineTemplateApi:
|
class TestPublishCustomizedPipelineTemplateApi:
|
||||||
def test_post_uses_pipeline_release_rbac_scene(self) -> None:
|
def test_post_uses_pipeline_release_rbac_scene(self) -> None:
|
||||||
method = PublishCustomizedPipelineTemplateApi.post
|
[check] = rbac_checks(PublishCustomizedPipelineTemplateApi.post)
|
||||||
while "scene" not in getclosurevars(method).nonlocals:
|
assert check.scene == module.RBACPermission.DATASET_PIPELINE_RELEASE
|
||||||
method = method.__wrapped__
|
|
||||||
|
|
||||||
assert getclosurevars(method).nonlocals["scene"] == module.RBACPermission.DATASET_PIPELINE_RELEASE
|
|
||||||
|
|
||||||
def test_post_validates_payload_and_returns_empty_204(self) -> None:
|
def test_post_validates_payload_and_returns_empty_204(self) -> None:
|
||||||
api = PublishCustomizedPipelineTemplateApi()
|
api = PublishCustomizedPipelineTemplateApi()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Iterator
|
||||||
from inspect import getclosurevars, unwrap
|
from inspect import unwrap
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ from sqlalchemy import Engine
|
|||||||
from sqlalchemy.orm import Session, scoped_session, sessionmaker
|
from sqlalchemy.orm import Session, scoped_session, sessionmaker
|
||||||
|
|
||||||
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
||||||
|
from controllers.common.rbac import RBACResourceScope
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.error import DraftWorkflowNotExist
|
from controllers.console.app.error import DraftWorkflowNotExist
|
||||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable import (
|
from controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable import (
|
||||||
@ -21,7 +22,7 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable impor
|
|||||||
RagPipelineVariableResetApi,
|
RagPipelineVariableResetApi,
|
||||||
WorkflowDraftVariablePatchPayload,
|
WorkflowDraftVariablePatchPayload,
|
||||||
)
|
)
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope
|
from controllers.console.wraps import RBACPermission
|
||||||
from core.workflow.llm_environment_variable import LLMEnvironmentVariable
|
from core.workflow.llm_environment_variable import LLMEnvironmentVariable
|
||||||
from core.workflow.variable_prefixes import SYSTEM_VARIABLE_NODE_ID
|
from core.workflow.variable_prefixes import SYSTEM_VARIABLE_NODE_ID
|
||||||
from factories.variable_factory import build_segment
|
from factories.variable_factory import build_segment
|
||||||
@ -30,6 +31,7 @@ from graphon.variables import IntegerVariable, StringVariable
|
|||||||
from models.account import Account, TenantAccountRole
|
from models.account import Account, TenantAccountRole
|
||||||
from models.dataset import Pipeline
|
from models.dataset import Pipeline
|
||||||
from models.workflow import Workflow, WorkflowDraftVariable, WorkflowType
|
from models.workflow import Workflow, WorkflowDraftVariable, WorkflowType
|
||||||
|
from tests.unit_tests.controllers.rbac_introspection import rbac_checks
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -102,12 +104,11 @@ def restx_config(app):
|
|||||||
def test_rag_draft_variable_routes_require_dataset_edit_permission() -> None:
|
def test_rag_draft_variable_routes_require_dataset_edit_permission() -> None:
|
||||||
route = RagPipelineVariableApi.get
|
route = RagPipelineVariableApi.get
|
||||||
legacy_gate = unwrap(route, stop=lambda decorator: "edit_permission_required" in decorator.__code__.co_qualname)
|
legacy_gate = unwrap(route, stop=lambda decorator: "edit_permission_required" in decorator.__code__.co_qualname)
|
||||||
rbac_gate = unwrap(route, stop=lambda decorator: "scene" in getclosurevars(decorator).nonlocals)
|
|
||||||
|
|
||||||
assert "edit_permission_required" in legacy_gate.__code__.co_qualname
|
assert "edit_permission_required" in legacy_gate.__code__.co_qualname
|
||||||
permissions = getclosurevars(rbac_gate).nonlocals
|
|
||||||
assert permissions["resource_type"] == RBACResourceScope.DATASET
|
[check] = rbac_checks(route)
|
||||||
assert permissions["scene"] == RBACPermission.DATASET_EDIT
|
assert check.locator.scope == RBACResourceScope.DATASET
|
||||||
|
assert check.scene == RBACPermission.DATASET_EDIT
|
||||||
|
|
||||||
|
|
||||||
class TestRagPipelineVariableCollectionApi:
|
class TestRagPipelineVariableCollectionApi:
|
||||||
|
|||||||
@ -166,7 +166,7 @@ class TestTagListApi:
|
|||||||
app.test_request_context("/"),
|
app.test_request_context("/"),
|
||||||
config_overrides_context(RBAC_ENABLED=True),
|
config_overrides_context(RBAC_ENABLED=True),
|
||||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||||
patch.object(module, "enforce_rbac_access") as enforce_rbac_access,
|
patch.object(module, "enforce_rbac_checks") as enforce_rbac_checks,
|
||||||
):
|
):
|
||||||
unwrap(TagListApi().post)(
|
unwrap(TagListApi().post)(
|
||||||
TagListApi(),
|
TagListApi(),
|
||||||
@ -174,13 +174,13 @@ class TestTagListApi:
|
|||||||
request_context,
|
request_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
enforce_rbac_access.assert_called_once_with(
|
enforce_rbac_checks.assert_called_once()
|
||||||
tenant_id="tenant-1",
|
rbac_kwargs = enforce_rbac_checks.call_args.kwargs
|
||||||
account_id="user-1",
|
assert rbac_kwargs["tenant_id"] == "tenant-1"
|
||||||
resource_type=module.RBACResourceScope.WORKSPACE,
|
assert rbac_kwargs["account_id"] == "user-1"
|
||||||
scene=module.RBACPermission.SNIPPETS_CREATE_AND_MODIFY,
|
(rbac_check,) = rbac_kwargs["checks"]
|
||||||
resource_required=False,
|
assert rbac_check.scene is module.RBACPermission.SNIPPETS_CREATE_AND_MODIFY
|
||||||
)
|
assert isinstance(rbac_check.locator, module.Workspace)
|
||||||
|
|
||||||
def test_post_rejects_read_only_member(self, app: Flask, request_context: RequestContext) -> None:
|
def test_post_rejects_read_only_member(self, app: Flask, request_context: RequestContext) -> None:
|
||||||
readonly = _account(TenantAccountRole.NORMAL)
|
readonly = _account(TenantAccountRole.NORMAL)
|
||||||
@ -249,7 +249,7 @@ class TestTagUpdateDeleteApi:
|
|||||||
app.test_request_context("/"),
|
app.test_request_context("/"),
|
||||||
config_overrides_context(RBAC_ENABLED=True),
|
config_overrides_context(RBAC_ENABLED=True),
|
||||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||||
patch.object(module, "enforce_rbac_access") as enforce_rbac_access,
|
patch.object(module, "enforce_rbac_checks") as enforce_rbac_checks,
|
||||||
):
|
):
|
||||||
result, status = unwrap(TagUpdateDeleteApi().patch)(
|
result, status = unwrap(TagUpdateDeleteApi().patch)(
|
||||||
TagUpdateDeleteApi(),
|
TagUpdateDeleteApi(),
|
||||||
@ -258,13 +258,13 @@ class TestTagUpdateDeleteApi:
|
|||||||
"tag-1",
|
"tag-1",
|
||||||
)
|
)
|
||||||
|
|
||||||
enforce_rbac_access.assert_called_once_with(
|
enforce_rbac_checks.assert_called_once()
|
||||||
tenant_id="tenant-1",
|
rbac_kwargs = enforce_rbac_checks.call_args.kwargs
|
||||||
account_id="user-1",
|
assert rbac_kwargs["tenant_id"] == "tenant-1"
|
||||||
resource_type=module.RBACResourceScope.WORKSPACE,
|
assert rbac_kwargs["account_id"] == "user-1"
|
||||||
scene=module.RBACPermission.SNIPPETS_CREATE_AND_MODIFY,
|
(rbac_check,) = rbac_kwargs["checks"]
|
||||||
resource_required=False,
|
assert rbac_check.scene is module.RBACPermission.SNIPPETS_CREATE_AND_MODIFY
|
||||||
)
|
assert isinstance(rbac_check.locator, module.Workspace)
|
||||||
tags_service.update_tag.assert_called_once_with(request_context, "tag-1", UpdateTagInput(name="Updated"))
|
tags_service.update_tag.assert_called_once_with(request_context, "tag-1", UpdateTagInput(name="Updated"))
|
||||||
assert status == 200
|
assert status == 200
|
||||||
assert result["binding_count"] == "3"
|
assert result["binding_count"] == "3"
|
||||||
@ -345,12 +345,12 @@ class TestTagUpdateDeleteApi:
|
|||||||
app.test_request_context("/"),
|
app.test_request_context("/"),
|
||||||
config_overrides_context(RBAC_ENABLED=True),
|
config_overrides_context(RBAC_ENABLED=True),
|
||||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||||
patch.object(module, "enforce_rbac_access") as enforce_rbac_access,
|
patch.object(module, "enforce_rbac_checks") as enforce_rbac_checks,
|
||||||
):
|
):
|
||||||
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
||||||
|
|
||||||
tags_service.get_tag_type.assert_called_once_with(request_context, "tag-1")
|
tags_service.get_tag_type.assert_called_once_with(request_context, "tag-1")
|
||||||
enforce_rbac_access.assert_called_once()
|
enforce_rbac_checks.assert_called_once()
|
||||||
|
|
||||||
def test_delete_does_not_authorize_tag_outside_current_workspace(
|
def test_delete_does_not_authorize_tag_outside_current_workspace(
|
||||||
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
self, app: Flask, request_context: RequestContext, tags_service: MagicMock
|
||||||
@ -363,12 +363,12 @@ class TestTagUpdateDeleteApi:
|
|||||||
app.test_request_context("/"),
|
app.test_request_context("/"),
|
||||||
config_overrides_context(RBAC_ENABLED=True),
|
config_overrides_context(RBAC_ENABLED=True),
|
||||||
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
patch.object(module, "current_account_with_tenant", return_value=(owner, "tenant-1")),
|
||||||
patch.object(module, "enforce_rbac_access") as enforce_rbac_access,
|
patch.object(module, "enforce_rbac_checks") as enforce_rbac_checks,
|
||||||
):
|
):
|
||||||
with pytest.raises(NotFound):
|
with pytest.raises(NotFound):
|
||||||
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
unwrap(TagUpdateDeleteApi().delete)(TagUpdateDeleteApi(), request_context, "tag-1")
|
||||||
|
|
||||||
enforce_rbac_access.assert_not_called()
|
enforce_rbac_checks.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class TestTagBindings:
|
class TestTagBindings:
|
||||||
|
|||||||
@ -57,9 +57,9 @@ def _make_account(role: TenantAccountRole) -> Account:
|
|||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
||||||
def _persist_app(session: Session, *, mode: AppMode = AppMode.CHAT) -> App:
|
def _persist_app(session: Session, *, mode: AppMode = AppMode.CHAT, app_id: str = "app-1") -> App:
|
||||||
app = App(
|
app = App(
|
||||||
id="app-1",
|
id=app_id,
|
||||||
tenant_id="tenant-1",
|
tenant_id="tenant-1",
|
||||||
name="API key app",
|
name="API key app",
|
||||||
mode=mode,
|
mode=mode,
|
||||||
@ -260,15 +260,23 @@ def test_api_key_lists_require_matching_rbac_permission(config_overrides: Callab
|
|||||||
cases = [
|
cases = [
|
||||||
(
|
(
|
||||||
lambda: AppApiKeyListResource().get(resource_id=api_id),
|
lambda: AppApiKeyListResource().get(resource_id=api_id),
|
||||||
[(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION, True)],
|
{
|
||||||
|
"scene": RBACPermission.APP_RELEASE_AND_VERSION,
|
||||||
|
"resource_type": RBACResourceScope.APP,
|
||||||
|
"resource_id": str(api_id),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
lambda: DatasetApiKeyApi().get(),
|
lambda: DatasetApiKeyApi().get(),
|
||||||
[(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE, False)],
|
{"scene": RBACPermission.DATASET_API_KEY_MANAGE, "resource_type": None, "resource_id": None},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
lambda: DatasetApiKeyListResource().get(resource_id=api_id),
|
lambda: DatasetApiKeyListResource().get(resource_id=api_id),
|
||||||
[(RBACResourceScope.DATASET, RBACPermission.DATASET_API_KEY_MANAGE, True)],
|
{
|
||||||
|
"scene": RBACPermission.DATASET_API_KEY_MANAGE,
|
||||||
|
"resource_type": RBACResourceScope.DATASET,
|
||||||
|
"resource_id": str(api_id),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -276,20 +284,25 @@ def test_api_key_lists_require_matching_rbac_permission(config_overrides: Callab
|
|||||||
app.test_request_context("/"),
|
app.test_request_context("/"),
|
||||||
patch("controllers.console.wraps.current_account_with_tenant", return_value=(account, "tenant-1")),
|
patch("controllers.console.wraps.current_account_with_tenant", return_value=(account, "tenant-1")),
|
||||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(account, "tenant-1")),
|
patch("controllers.common.wraps.current_account_with_tenant", return_value=(account, "tenant-1")),
|
||||||
|
patch("controllers.common.rbac.locators.agent_binding", return_value=None),
|
||||||
|
patch("controllers.common.rbac.locators.PlainApp.owner_id", return_value=None),
|
||||||
|
patch("controllers.common.rbac.locators.DatasetId.owner_id", return_value=None),
|
||||||
patch.object(BaseApiKeyListResource, "_get_api_key_list") as get_api_key_list,
|
patch.object(BaseApiKeyListResource, "_get_api_key_list") as get_api_key_list,
|
||||||
):
|
):
|
||||||
for invoke, expected_gates in cases:
|
for invoke, expected_kwargs in cases:
|
||||||
with patch(
|
with patch(
|
||||||
"controllers.common.wraps.enforce_rbac_access",
|
"controllers.common.rbac.checks.RBACService.CheckAccess.check", return_value=False
|
||||||
side_effect=[None] * (len(expected_gates) - 1) + [Forbidden()],
|
) as check_access:
|
||||||
) as enforce_rbac_access:
|
|
||||||
with pytest.raises(Forbidden):
|
with pytest.raises(Forbidden):
|
||||||
invoke()
|
invoke()
|
||||||
|
|
||||||
assert [
|
check_access.assert_called_once_with(
|
||||||
(kwargs["resource_type"], kwargs["scene"], kwargs["resource_required"])
|
"tenant-1",
|
||||||
for _, kwargs in enforce_rbac_access.call_args_list
|
account.id,
|
||||||
] == expected_gates
|
scene=expected_kwargs["scene"],
|
||||||
|
resource_type=expected_kwargs["resource_type"],
|
||||||
|
resource_id=expected_kwargs["resource_id"],
|
||||||
|
)
|
||||||
|
|
||||||
get_api_key_list.assert_not_called()
|
get_api_key_list.assert_not_called()
|
||||||
|
|
||||||
|
|||||||
@ -1,28 +1,27 @@
|
|||||||
from inspect import getclosurevars, unwrap
|
from inspect import unwrap
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
from controllers.common.rbac import RBACPermission, Workspace
|
||||||
from controllers.console.datasets.rag_pipeline.datasource_auth import DatasourceAuthOauthCustomClient
|
from controllers.console.datasets.rag_pipeline.datasource_auth import DatasourceAuthOauthCustomClient
|
||||||
from controllers.console.workspace.tool_providers import ToolOAuthCustomClient
|
from controllers.console.workspace.tool_providers import ToolOAuthCustomClient
|
||||||
|
from tests.unit_tests.controllers.rbac_introspection import rbac_checks
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("method", "legacy_gate", "resource_type"),
|
("method", "legacy_gate"),
|
||||||
[
|
[
|
||||||
(ToolOAuthCustomClient.delete, "is_admin_or_owner_required", RBACResourceScope.WORKSPACE),
|
(ToolOAuthCustomClient.delete, "is_admin_or_owner_required"),
|
||||||
(DatasourceAuthOauthCustomClient.delete, "edit_permission_required", RBACResourceScope.DATASET),
|
(DatasourceAuthOauthCustomClient.delete, "edit_permission_required"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_custom_oauth_client_delete_requires_management_permission(
|
def test_custom_oauth_client_delete_requires_management_permission(method: FunctionType, legacy_gate: str) -> None:
|
||||||
method: FunctionType, legacy_gate: str, resource_type: RBACResourceScope
|
|
||||||
) -> None:
|
|
||||||
legacy_wrapper = unwrap(method, stop=lambda wrapper: legacy_gate in wrapper.__code__.co_qualname)
|
legacy_wrapper = unwrap(method, stop=lambda wrapper: legacy_gate in wrapper.__code__.co_qualname)
|
||||||
assert legacy_gate in legacy_wrapper.__code__.co_qualname
|
assert legacy_gate in legacy_wrapper.__code__.co_qualname
|
||||||
|
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
# Both routes pass resource_required=False, so their bundle resolves to a Workspace
|
||||||
rbac_config = getclosurevars(rbac_wrapper).nonlocals
|
# locator regardless of the resource_type they declared (see _single_check).
|
||||||
assert rbac_config["resource_type"] == resource_type
|
[check] = rbac_checks(method)
|
||||||
assert rbac_config["scene"] == RBACPermission.CREDENTIAL_MANAGE
|
assert check.scene == RBACPermission.CREDENTIAL_MANAGE
|
||||||
assert rbac_config["resource_required"] is False
|
assert isinstance(check.locator, Workspace)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
from inspect import getclosurevars, unwrap
|
from inspect import unwrap
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from controllers.common.wraps import RBACPermission, RBACResourceScope
|
from controllers.common.rbac import AgentId, RBACPermission, Workspace
|
||||||
from controllers.console.agent.composer import AgentComposerApi
|
from controllers.console.agent.composer import AgentComposerApi
|
||||||
from controllers.console.agent.roster import AgentAppApi
|
from controllers.console.agent.roster import AgentAppApi
|
||||||
from controllers.console.datasets.data_source import DataSourceApi
|
from controllers.console.datasets.data_source import DataSourceApi
|
||||||
@ -11,6 +11,7 @@ from controllers.console.datasets.rag_pipeline.datasource_auth import Datasource
|
|||||||
from controllers.console.workspace.model_providers import ModelProviderCredentialApi
|
from controllers.console.workspace.model_providers import ModelProviderCredentialApi
|
||||||
from controllers.console.workspace.models import ModelProviderModelCredentialApi
|
from controllers.console.workspace.models import ModelProviderModelCredentialApi
|
||||||
from controllers.console.workspace.tool_providers import ToolBuiltinProviderAddApi, ToolOAuthCustomClient
|
from controllers.console.workspace.tool_providers import ToolBuiltinProviderAddApi, ToolOAuthCustomClient
|
||||||
|
from tests.unit_tests.controllers.rbac_introspection import rbac_checks
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -27,11 +28,9 @@ def test_workspace_credential_mutations_require_management_permission(
|
|||||||
legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname)
|
legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname)
|
||||||
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
||||||
|
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
[check] = rbac_checks(method)
|
||||||
rbac_config = getclosurevars(rbac_wrapper).nonlocals
|
assert check.scene == permission
|
||||||
assert rbac_config["resource_type"] == RBACResourceScope.WORKSPACE
|
assert isinstance(check.locator, Workspace)
|
||||||
assert rbac_config["scene"] == permission
|
|
||||||
assert rbac_config["resource_required"] is False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -49,11 +48,9 @@ def test_model_provider_credential_get_requires_admin_and_rbac(
|
|||||||
legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname)
|
legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname)
|
||||||
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
||||||
|
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
[check] = rbac_checks(method)
|
||||||
rbac_config = getclosurevars(rbac_wrapper).nonlocals
|
assert check.scene == RBACPermission.CREDENTIAL_MANAGE
|
||||||
assert rbac_config["resource_type"] == RBACResourceScope.WORKSPACE
|
assert isinstance(check.locator, Workspace)
|
||||||
assert rbac_config["scene"] == RBACPermission.CREDENTIAL_MANAGE
|
|
||||||
assert rbac_config["resource_required"] is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_tool_oauth_custom_client_get_requires_admin_and_rbac() -> None:
|
def test_tool_oauth_custom_client_get_requires_admin_and_rbac() -> None:
|
||||||
@ -64,11 +61,9 @@ def test_tool_oauth_custom_client_get_requires_admin_and_rbac() -> None:
|
|||||||
legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname)
|
legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname)
|
||||||
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
||||||
|
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
[check] = rbac_checks(method)
|
||||||
rbac_config = getclosurevars(rbac_wrapper).nonlocals
|
assert check.scene == RBACPermission.CREDENTIAL_MANAGE
|
||||||
assert rbac_config["resource_type"] == RBACResourceScope.WORKSPACE
|
assert isinstance(check.locator, Workspace)
|
||||||
assert rbac_config["scene"] == RBACPermission.CREDENTIAL_MANAGE
|
|
||||||
assert rbac_config["resource_required"] is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_datasource_auth_get_requires_edit_and_rbac() -> None:
|
def test_datasource_auth_get_requires_edit_and_rbac() -> None:
|
||||||
@ -79,11 +74,9 @@ def test_datasource_auth_get_requires_edit_and_rbac() -> None:
|
|||||||
edit_wrapper = unwrap(method, stop=lambda wrapper: "edit_permission_required" in wrapper.__code__.co_qualname)
|
edit_wrapper = unwrap(method, stop=lambda wrapper: "edit_permission_required" in wrapper.__code__.co_qualname)
|
||||||
assert "edit_permission_required" in edit_wrapper.__code__.co_qualname
|
assert "edit_permission_required" in edit_wrapper.__code__.co_qualname
|
||||||
|
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
[check] = rbac_checks(method)
|
||||||
rbac_config = getclosurevars(rbac_wrapper).nonlocals
|
assert check.scene == RBACPermission.CREDENTIAL_MANAGE
|
||||||
assert rbac_config["resource_type"] == RBACResourceScope.DATASET
|
assert isinstance(check.locator, Workspace)
|
||||||
assert rbac_config["scene"] == RBACPermission.CREDENTIAL_MANAGE
|
|
||||||
assert rbac_config["resource_required"] is False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -94,10 +87,8 @@ def test_datasource_auth_get_requires_edit_and_rbac() -> None:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_agent_app_get_requires_rbac(method: FunctionType) -> None:
|
def test_agent_app_get_requires_rbac(method: FunctionType) -> None:
|
||||||
"""GET endpoints that return agent app details or composer state must enforce
|
"""GET endpoints that return agent app details or composer state must require
|
||||||
the same RBAC gates as their sibling PUT/DELETE methods."""
|
the agent preview scene on the agent resource, same as their sibling routes."""
|
||||||
rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname)
|
[check] = rbac_checks(method)
|
||||||
rbac_config = getclosurevars(rbac_wrapper).nonlocals
|
assert check.scene == RBACPermission.AGENT_PREVIEW
|
||||||
assert rbac_config["resource_type"] == RBACResourceScope.WORKSPACE
|
assert isinstance(check.locator, AgentId)
|
||||||
assert rbac_config["scene"] == RBACPermission.AGENT_MANAGE
|
|
||||||
assert rbac_config["resource_required"] is False
|
|
||||||
|
|||||||
@ -4,14 +4,14 @@ from typing import override
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask, request
|
from flask import Flask
|
||||||
from flask_login import LoginManager, UserMixin
|
from flask_login import LoginManager, UserMixin
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from controllers.common.wraps import _extract_resource_id
|
from controllers.common.rbac import DatasetId, PlainApp, RBACCheck, Workspace
|
||||||
from controllers.console import api as console_api
|
from controllers.console import api as console_api
|
||||||
from controllers.console import flask_admission
|
from controllers.console import flask_admission
|
||||||
from controllers.console import wraps as wraps_module
|
from controllers.console import wraps as wraps_module
|
||||||
@ -41,10 +41,10 @@ from controllers.console.wraps import (
|
|||||||
from enums import DeploymentEdition
|
from enums import DeploymentEdition
|
||||||
from libs.login import AccountWithTenant
|
from libs.login import AccountWithTenant
|
||||||
from machinery.context import RequestContext
|
from machinery.context import RequestContext
|
||||||
from machinery.errors import ActiveWorkspaceRequiredError, AdmissionConfigurationError
|
from machinery.errors import ActiveWorkspaceRequiredError
|
||||||
from models import Account, DifySetup
|
from models import Account, DifySetup
|
||||||
from models.account import AccountStatus, TenantAccountRole
|
from models.account import AccountStatus, TenantAccountRole
|
||||||
from models.dataset import Dataset, RateLimitLog
|
from models.dataset import RateLimitLog
|
||||||
from services.entities.feature_entities import LicenseStatus
|
from services.entities.feature_entities import LicenseStatus
|
||||||
from tests.unit_tests.config_override import config_overrides_context
|
from tests.unit_tests.config_override import config_overrides_context
|
||||||
|
|
||||||
@ -359,15 +359,13 @@ class TestCurrentContextInjection:
|
|||||||
),
|
),
|
||||||
patch("controllers.console.flask_admission.get_request_id", return_value="request-1"),
|
patch("controllers.console.flask_admission.get_request_id", return_value="request-1"),
|
||||||
patch("controllers.console.flask_admission.get_trace_id", return_value=None),
|
patch("controllers.console.flask_admission.get_trace_id", return_value=None),
|
||||||
patch("controllers.console.flask_admission.enforce_rbac_access") as enforce_rbac_access,
|
patch("controllers.console.flask_admission.enforce_rbac_checks") as enforce_rbac_checks,
|
||||||
):
|
):
|
||||||
|
|
||||||
class Handler:
|
class Handler:
|
||||||
@flask_admission.console_account_admission(
|
@flask_admission.console_account_admission(
|
||||||
allowed_roles=frozenset({TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
allowed_roles=frozenset({TenantAccountRole.ADMIN, TenantAccountRole.OWNER}),
|
||||||
rbac_resource_scope=RBACResourceScope.WORKSPACE,
|
rbac_checks=[RBACCheck(RBACPermission.CREDENTIAL_CREATE, Workspace())],
|
||||||
rbac_permission=RBACPermission.CREDENTIAL_CREATE,
|
|
||||||
rbac_resource_required=False,
|
|
||||||
)
|
)
|
||||||
def post(self, request_context: RequestContext):
|
def post(self, request_context: RequestContext):
|
||||||
return request_context
|
return request_context
|
||||||
@ -378,18 +376,14 @@ class TestCurrentContextInjection:
|
|||||||
assert isinstance(result, RequestContext)
|
assert isinstance(result, RequestContext)
|
||||||
assert result.active_workspace_id == "tenant-123"
|
assert result.active_workspace_id == "tenant-123"
|
||||||
assert result.trace_id == "trace-1"
|
assert result.trace_id == "trace-1"
|
||||||
enforce_rbac_access.assert_called_once_with(
|
enforce_rbac_checks.assert_called_once()
|
||||||
tenant_id="tenant-123",
|
call_kwargs = enforce_rbac_checks.call_args.kwargs
|
||||||
account_id=current_user.id,
|
assert call_kwargs["tenant_id"] == "tenant-123"
|
||||||
resource_type=RBACResourceScope.WORKSPACE,
|
assert call_kwargs["account_id"] == current_user.id
|
||||||
scene=RBACPermission.CREDENTIAL_CREATE,
|
assert call_kwargs["path_args"] == {}
|
||||||
resource_required=False,
|
(check,) = call_kwargs["checks"]
|
||||||
path_args={},
|
assert check.scene is RBACPermission.CREDENTIAL_CREATE
|
||||||
)
|
assert isinstance(check.locator, Workspace)
|
||||||
|
|
||||||
def test_console_account_admission_rejects_incomplete_rbac_requirement(self):
|
|
||||||
with pytest.raises(AdmissionConfigurationError, match="configured together"):
|
|
||||||
flask_admission.console_account_admission(rbac_resource_scope=RBACResourceScope.WORKSPACE)
|
|
||||||
|
|
||||||
def test_console_account_admission_can_admit_uninitialized_accounts(self):
|
def test_console_account_admission_can_admit_uninitialized_accounts(self):
|
||||||
current_user = make_account()
|
current_user = make_account()
|
||||||
@ -501,67 +495,72 @@ class TestRbacPermissionRequired:
|
|||||||
def test_resource_scoped_check_uses_resource_id(self):
|
def test_resource_scoped_check_uses_resource_id(self):
|
||||||
current_user = make_account("account-1")
|
current_user = make_account("account-1")
|
||||||
|
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_DELETE)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_DELETE, PlainApp()))
|
||||||
def protected_view(**kwargs):
|
def protected_view(**kwargs):
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-1")),
|
Flask(__name__).test_request_context("/"),
|
||||||
patch("controllers.common.wraps._extract_resource_id", return_value="app-123") as mock_extract,
|
patch(
|
||||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=False) as mock_owned,
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
return_value=(current_user, "tenant-1"),
|
||||||
|
),
|
||||||
|
patch("controllers.common.rbac.locators.agent_binding", return_value=None) as mock_binding,
|
||||||
|
patch("controllers.common.rbac.locators.PlainApp.owner_id", return_value=None) as mock_owner,
|
||||||
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
||||||
):
|
):
|
||||||
assert protected_view(app_id="app-123") == "ok"
|
assert protected_view(app_id="app-123") == "ok"
|
||||||
|
|
||||||
mock_extract.assert_called_once_with(RBACResourceScope.APP, "tenant-1", {"app_id": "app-123"})
|
mock_binding.assert_called_once_with("tenant-1", "app-123")
|
||||||
mock_owned.assert_called_once_with("tenant-1", "account-1", "app", "app-123")
|
mock_owner.assert_called_once()
|
||||||
mock_check.assert_called_once_with(
|
mock_check.assert_called_once_with(
|
||||||
"tenant-1",
|
"tenant-1",
|
||||||
"account-1",
|
"account-1",
|
||||||
scene="app_delete",
|
scene=RBACPermission.APP_DELETE,
|
||||||
resource_type="app",
|
resource_type=RBACResourceScope.APP,
|
||||||
resource_id="app-123",
|
resource_id="app-123",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_workspace_scoped_check_skips_resource_id_extraction(self):
|
def test_workspace_scoped_check_skips_resource_id_extraction(self):
|
||||||
current_user = make_account("account-2")
|
current_user = make_account("account-2")
|
||||||
|
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, Workspace()))
|
||||||
RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT, resource_required=False
|
|
||||||
)
|
|
||||||
def protected_view():
|
def protected_view():
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-2")),
|
Flask(__name__).test_request_context("/"),
|
||||||
patch("controllers.common.wraps._extract_resource_id") as mock_extract,
|
patch(
|
||||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=False) as mock_owned,
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
return_value=(current_user, "tenant-2"),
|
||||||
|
),
|
||||||
|
patch("controllers.common.rbac.locators.DatasetId.owner_id") as mock_owner,
|
||||||
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
||||||
):
|
):
|
||||||
assert protected_view() == "ok"
|
assert protected_view() == "ok"
|
||||||
|
|
||||||
mock_extract.assert_not_called()
|
mock_owner.assert_not_called()
|
||||||
mock_owned.assert_not_called()
|
|
||||||
mock_check.assert_called_once_with(
|
mock_check.assert_called_once_with(
|
||||||
"tenant-2",
|
"tenant-2",
|
||||||
"account-2",
|
"account-2",
|
||||||
scene="dataset_create_and_management",
|
scene=RBACPermission.DATASET_CREATE_AND_MANAGEMENT,
|
||||||
resource_type="dataset",
|
resource_type=None,
|
||||||
resource_id=None,
|
resource_id=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_workspace_scene_omits_resource_type(self):
|
def test_workspace_scene_omits_resource_type(self):
|
||||||
current_user = make_account("account-3")
|
current_user = make_account("account-3")
|
||||||
|
|
||||||
@rbac_permission_required(
|
@rbac_permission_required(RBACCheck(RBACPermission.WORKSPACE_ROLE_MANAGE, Workspace()))
|
||||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
|
||||||
)
|
|
||||||
def protected_view():
|
def protected_view():
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-3")),
|
patch(
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
|
return_value=(current_user, "tenant-3"),
|
||||||
|
),
|
||||||
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
||||||
):
|
):
|
||||||
assert protected_view() == "ok"
|
assert protected_view() == "ok"
|
||||||
|
|
||||||
@ -576,137 +575,46 @@ class TestRbacPermissionRequired:
|
|||||||
def test_resource_owned_app_skips_rbac_check(self):
|
def test_resource_owned_app_skips_rbac_check(self):
|
||||||
current_user = make_account("account-4")
|
current_user = make_account("account-4")
|
||||||
|
|
||||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_DELETE)
|
@rbac_permission_required(RBACCheck(RBACPermission.APP_DELETE, PlainApp()))
|
||||||
def protected_view(**kwargs):
|
def protected_view(**kwargs):
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-4")),
|
Flask(__name__).test_request_context("/"),
|
||||||
patch("controllers.common.wraps._extract_resource_id", return_value="app-123"),
|
patch(
|
||||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=True) as mock_owned,
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check") as mock_check,
|
return_value=(current_user, "tenant-4"),
|
||||||
|
),
|
||||||
|
patch("controllers.common.rbac.locators.agent_binding", return_value=None),
|
||||||
|
patch("controllers.common.rbac.locators.PlainApp.owner_id", return_value="account-4") as mock_owner,
|
||||||
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check") as mock_check,
|
||||||
):
|
):
|
||||||
assert protected_view(app_id="app-123") == "ok"
|
assert protected_view(app_id="app-123") == "ok"
|
||||||
|
|
||||||
mock_owned.assert_called_once_with("tenant-4", "account-4", "app", "app-123")
|
mock_owner.assert_called_once()
|
||||||
mock_check.assert_not_called()
|
mock_check.assert_not_called()
|
||||||
|
|
||||||
def test_resource_owned_dataset_skips_rbac_check(self):
|
def test_resource_owned_dataset_skips_rbac_check(self):
|
||||||
current_user = make_account("account-5")
|
current_user = make_account("account-5")
|
||||||
|
|
||||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
@rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetId()))
|
||||||
def protected_view(**kwargs):
|
def protected_view(**kwargs):
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-5")),
|
Flask(__name__).test_request_context("/"),
|
||||||
patch("controllers.common.wraps._extract_resource_id", return_value="dataset-123"),
|
patch(
|
||||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=True) as mock_owned,
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check") as mock_check,
|
return_value=(current_user, "tenant-5"),
|
||||||
|
),
|
||||||
|
patch("controllers.common.rbac.locators.DatasetId.owner_id", return_value="account-5") as mock_owner,
|
||||||
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check") as mock_check,
|
||||||
):
|
):
|
||||||
assert protected_view(dataset_id="dataset-123") == "ok"
|
assert protected_view(dataset_id="dataset-123") == "ok"
|
||||||
|
|
||||||
mock_owned.assert_called_once_with("tenant-5", "account-5", "dataset", "dataset-123")
|
mock_owner.assert_called_once()
|
||||||
mock_check.assert_not_called()
|
mock_check.assert_not_called()
|
||||||
|
|
||||||
def test_extract_resource_id_prefers_path_args(self):
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
with app.test_request_context("/"):
|
|
||||||
request.view_args = {"app_id": "view-app"}
|
|
||||||
|
|
||||||
assert _extract_resource_id("app", "tenant-1", {"app_id": "path-app"}) == "path-app"
|
|
||||||
|
|
||||||
def test_extract_resource_id_falls_back_to_request_view_args(self):
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
with app.test_request_context("/"):
|
|
||||||
request.view_args = {"app_id": "view-app"}
|
|
||||||
|
|
||||||
assert _extract_resource_id("app", "tenant-1") == "view-app"
|
|
||||||
|
|
||||||
def test_extract_resource_id_supports_legacy_route_aliases(self):
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
with app.test_request_context("/apps/app-1/api-keys"):
|
|
||||||
request.view_args = {"resource_id": "app-1"}
|
|
||||||
assert _extract_resource_id(RBACResourceScope.APP, "tenant-1") == "app-1"
|
|
||||||
|
|
||||||
with app.test_request_context("/datasets/dataset-1/api-keys"):
|
|
||||||
request.view_args = {"resource_id": "dataset-1"}
|
|
||||||
assert _extract_resource_id(RBACResourceScope.DATASET, "tenant-1") == "dataset-1"
|
|
||||||
|
|
||||||
def test_extract_resource_id_scopes_pipeline_resolution_to_the_calling_tenant(self, sqlite_session: Session):
|
|
||||||
app = Flask(__name__)
|
|
||||||
pipeline_id = "00000000-0000-0000-0000-000000000001"
|
|
||||||
current_tenant_id = "00000000-0000-0000-0000-000000000002"
|
|
||||||
foreign_dataset = Dataset(
|
|
||||||
id="00000000-0000-0000-0000-000000000003",
|
|
||||||
tenant_id="00000000-0000-0000-0000-000000000004",
|
|
||||||
name="Foreign decoy",
|
|
||||||
created_by="00000000-0000-0000-0000-000000000005",
|
|
||||||
pipeline_id=pipeline_id,
|
|
||||||
)
|
|
||||||
current_dataset = Dataset(
|
|
||||||
id="00000000-0000-0000-0000-000000000006",
|
|
||||||
tenant_id=current_tenant_id,
|
|
||||||
name="Current tenant dataset",
|
|
||||||
created_by="00000000-0000-0000-0000-000000000007",
|
|
||||||
pipeline_id=pipeline_id,
|
|
||||||
)
|
|
||||||
sqlite_session.add_all([foreign_dataset, current_dataset])
|
|
||||||
|
|
||||||
unscoped_dataset = sqlite_session.scalar(select(Dataset).where(Dataset.pipeline_id == pipeline_id))
|
|
||||||
assert unscoped_dataset is foreign_dataset
|
|
||||||
|
|
||||||
with (
|
|
||||||
app.test_request_context("/rag/pipelines/pipeline-1"),
|
|
||||||
patch("controllers.common.wraps.db", SimpleNamespace(session=sqlite_session)),
|
|
||||||
):
|
|
||||||
request.view_args = {"pipeline_id": pipeline_id}
|
|
||||||
assert _extract_resource_id(RBACResourceScope.DATASET, current_tenant_id) == current_dataset.id
|
|
||||||
|
|
||||||
def test_extract_resource_id_resolves_agent_to_its_authz_app(self):
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
with (
|
|
||||||
app.test_request_context("/agent/agent-1/chat-messages"),
|
|
||||||
patch("controllers.common.wraps.AgentRosterService") as mock_service,
|
|
||||||
):
|
|
||||||
request.view_args = {"agent_id": "agent-1"}
|
|
||||||
mock_service.return_value.peek_authz_app_id.return_value = "parent-app-1"
|
|
||||||
|
|
||||||
assert _extract_resource_id(RBACResourceScope.APP, "tenant-1") == "parent-app-1"
|
|
||||||
|
|
||||||
def test_extract_resource_id_scopes_agent_resolution_to_the_calling_tenant(self):
|
|
||||||
"""The tenant must reach the resolver, or an Agent id from any tenant resolves."""
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
with (
|
|
||||||
app.test_request_context("/agent/agent-1/chat-messages"),
|
|
||||||
patch("controllers.common.wraps.AgentRosterService") as mock_service,
|
|
||||||
):
|
|
||||||
request.view_args = {"agent_id": "agent-1"}
|
|
||||||
mock_service.return_value.peek_authz_app_id.return_value = "parent-app-1"
|
|
||||||
|
|
||||||
_extract_resource_id(RBACResourceScope.APP, "tenant-9")
|
|
||||||
|
|
||||||
mock_service.return_value.peek_authz_app_id.assert_called_once_with(
|
|
||||||
tenant_id="tenant-9", agent_id="agent-1"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_extract_resource_id_keeps_agent_id_when_the_agent_does_not_resolve(self):
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
with (
|
|
||||||
app.test_request_context("/agent/agent-1/chat-messages"),
|
|
||||||
patch("controllers.common.wraps.AgentRosterService") as mock_service,
|
|
||||||
):
|
|
||||||
request.view_args = {"agent_id": "agent-1"}
|
|
||||||
mock_service.return_value.peek_authz_app_id.return_value = None
|
|
||||||
|
|
||||||
assert _extract_resource_id(RBACResourceScope.APP, "tenant-1") == "agent-1"
|
|
||||||
|
|
||||||
def test_legacy_admin_decorator_noops_when_rbac_enabled(self):
|
def test_legacy_admin_decorator_noops_when_rbac_enabled(self):
|
||||||
@is_admin_or_owner_required
|
@is_admin_or_owner_required
|
||||||
def protected_view():
|
def protected_view():
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from unittest.mock import patch
|
|||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
from controllers.common.rbac import Workspace
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.workspace.endpoint import (
|
from controllers.console.workspace.endpoint import (
|
||||||
DeprecatedEndpointCreateApi,
|
DeprecatedEndpointCreateApi,
|
||||||
@ -24,10 +25,11 @@ from controllers.console.workspace.endpoint import (
|
|||||||
EndpointUpdatePayload,
|
EndpointUpdatePayload,
|
||||||
LegacyEndpointUpdatePayload,
|
LegacyEndpointUpdatePayload,
|
||||||
)
|
)
|
||||||
from controllers.console.wraps import RBACPermission, RBACResourceScope
|
from controllers.console.wraps import RBACPermission
|
||||||
from core.entities.provider_entities import ProviderConfig, ProviderConfigType
|
from core.entities.provider_entities import ProviderConfig, ProviderConfigType
|
||||||
from core.plugin.entities.endpoint import EndpointEntityWithInstance, EndpointProviderDeclaration
|
from core.plugin.entities.endpoint import EndpointEntityWithInstance, EndpointProviderDeclaration
|
||||||
from core.plugin.impl.exc import PluginPermissionDeniedError
|
from core.plugin.impl.exc import PluginPermissionDeniedError
|
||||||
|
from tests.unit_tests.controllers.rbac_introspection import rbac_checks
|
||||||
|
|
||||||
|
|
||||||
def _endpoint_entity() -> EndpointEntityWithInstance:
|
def _endpoint_entity() -> EndpointEntityWithInstance:
|
||||||
@ -65,13 +67,9 @@ def test_endpoint_lists_require_management_permission(method: FunctionType) -> N
|
|||||||
)
|
)
|
||||||
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname
|
||||||
|
|
||||||
rbac_wrapper = inspect.unwrap(
|
[check] = rbac_checks(method)
|
||||||
method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname
|
assert check.scene == RBACPermission.PLUGIN_MODEL_CONFIG
|
||||||
)
|
assert isinstance(check.locator, Workspace)
|
||||||
rbac_config = inspect.getclosurevars(rbac_wrapper).nonlocals
|
|
||||||
assert rbac_config["resource_type"] == RBACResourceScope.WORKSPACE
|
|
||||||
assert rbac_config["scene"] == RBACPermission.PLUGIN_MODEL_CONFIG
|
|
||||||
assert rbac_config["resource_required"] is False
|
|
||||||
|
|
||||||
|
|
||||||
class TestEndpointCollectionApi:
|
class TestEndpointCollectionApi:
|
||||||
|
|||||||
@ -85,6 +85,25 @@ class TestMyPermissions:
|
|||||||
assert response["app"]["default_permission_keys"] == ["app.acl.deploy"]
|
assert response["app"]["default_permission_keys"] == ["app.acl.deploy"]
|
||||||
mock_get.assert_called_once()
|
mock_get.assert_called_once()
|
||||||
|
|
||||||
|
def test_forwards_agent_id_query_param(self, app):
|
||||||
|
permissions = rbac_mod.svc.MyPermissionsResponse(
|
||||||
|
agent=rbac_mod.svc.ResourcePermissionSnapshot(
|
||||||
|
default_permission_keys=["agent.acl.preview"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
app.test_request_context("/workspaces/current/rbac/my-permissions?agent_id=agent-1"),
|
||||||
|
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
||||||
|
patch(
|
||||||
|
"controllers.console.workspace.rbac.svc.RBACService.MyPermissions.get",
|
||||||
|
return_value=permissions,
|
||||||
|
) as mock_get,
|
||||||
|
):
|
||||||
|
response = inspect.unwrap(rbac_mod.RBACMyPermissionsApi.get)(rbac_mod.RBACMyPermissionsApi())
|
||||||
|
|
||||||
|
assert mock_get.call_args.kwargs["agent_id"] == "agent-1"
|
||||||
|
assert response["agent"]["default_permission_keys"] == ["agent.acl.preview"]
|
||||||
|
|
||||||
|
|
||||||
class TestAccessMatrixAccountNames:
|
class TestAccessMatrixAccountNames:
|
||||||
def test_hydrates_missing_account_names(self):
|
def test_hydrates_missing_account_names(self):
|
||||||
@ -332,172 +351,6 @@ class TestPaginationMapping:
|
|||||||
assert options.reverse is True
|
assert options.reverse is True
|
||||||
|
|
||||||
|
|
||||||
class TestResourceAccessScopeBindings:
|
|
||||||
def test_app_whitelist_all_schedules_member_policy_sync(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/apps/app-1/whitelist",
|
|
||||||
method="PUT",
|
|
||||||
json={"automatic_include_workspace_members": True},
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.AppAccess.replace_whitelist",
|
|
||||||
return_value=rbac_mod.svc.ResourceWhitelist(automatic_include_workspace_members=True),
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac.initialize_created_app_rbac_access_task") as mock_sync_task,
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACAppWhitelistApi.put)(rbac_mod.RBACAppWhitelistApi(), "app-1")
|
|
||||||
|
|
||||||
mock_sync_task.delay.assert_called_once_with("tenant-1", "acct-actor", app_id="app-1")
|
|
||||||
|
|
||||||
def test_dataset_whitelist_all_schedules_member_policy_sync(self, app):
|
|
||||||
# Widening a dataset to the whole workspace only records the scope; without granting the
|
|
||||||
# default policy to the current members nobody actually gains access.
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/datasets/dataset-1/whitelist",
|
|
||||||
method="PUT",
|
|
||||||
json={"automatic_include_workspace_members": True},
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.replace_whitelist",
|
|
||||||
return_value=rbac_mod.svc.ResourceWhitelist(automatic_include_workspace_members=True),
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac.initialize_created_app_rbac_access_task") as mock_sync_task,
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACDatasetWhitelistApi.put)(rbac_mod.RBACDatasetWhitelistApi(), "dataset-1")
|
|
||||||
|
|
||||||
mock_sync_task.delay.assert_called_once_with("tenant-1", "acct-actor", dataset_id="dataset-1")
|
|
||||||
|
|
||||||
def test_dataset_whitelist_specific_does_not_schedule_member_policy_sync(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/datasets/dataset-1/whitelist",
|
|
||||||
method="PUT",
|
|
||||||
json={"automatic_include_workspace_members": False},
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.replace_whitelist",
|
|
||||||
return_value=rbac_mod.svc.ResourceWhitelist(automatic_include_workspace_members=False),
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac.initialize_created_app_rbac_access_task") as mock_sync_task,
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACDatasetWhitelistApi.put)(rbac_mod.RBACDatasetWhitelistApi(), "dataset-1")
|
|
||||||
|
|
||||||
mock_sync_task.delay.assert_not_called()
|
|
||||||
|
|
||||||
def test_app_whitelist_config_returns_switch_state_only(self, app):
|
|
||||||
result = rbac_mod.svc.ResourceWhitelistConfig(automatic_include_workspace_members=True)
|
|
||||||
with (
|
|
||||||
app.test_request_context("/workspaces/current/rbac/apps/app-1/whitelist_config"),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.AppAccess.whitelist_config",
|
|
||||||
return_value=result,
|
|
||||||
) as mock_get,
|
|
||||||
):
|
|
||||||
response = inspect.unwrap(rbac_mod.RBACAppWhitelistConfigApi.get)(
|
|
||||||
rbac_mod.RBACAppWhitelistConfigApi(),
|
|
||||||
"app-1",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response == {"automatic_include_workspace_members": True}
|
|
||||||
mock_get.assert_called_once_with("tenant-1", "acct-1", "app-1")
|
|
||||||
|
|
||||||
def test_dataset_whitelist_config_returns_switch_state_only(self, app):
|
|
||||||
result = rbac_mod.svc.ResourceWhitelistConfig(automatic_include_workspace_members=False)
|
|
||||||
with (
|
|
||||||
app.test_request_context("/workspaces/current/rbac/datasets/dataset-1/whitelist_config"),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.whitelist_config",
|
|
||||||
return_value=result,
|
|
||||||
) as mock_get,
|
|
||||||
):
|
|
||||||
response = inspect.unwrap(rbac_mod.RBACDatasetWhitelistConfigApi.get)(
|
|
||||||
rbac_mod.RBACDatasetWhitelistConfigApi(),
|
|
||||||
"dataset-1",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response == {"automatic_include_workspace_members": False}
|
|
||||||
mock_get.assert_called_once_with("tenant-1", "acct-1", "dataset-1")
|
|
||||||
|
|
||||||
def test_app_user_access_policy_assignment_forwards_ids(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/apps/app-1/users/acct-target/access-policies",
|
|
||||||
method="PUT",
|
|
||||||
json={"access_policy_ids": ["policy-1", "policy-2"]},
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.AppAccess.replace_user_access_policies"
|
|
||||||
) as mock_replace,
|
|
||||||
patch("controllers.console.workspace.rbac._dump", return_value={}),
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACAppUserAccessPolicyAssignmentApi.put)(
|
|
||||||
rbac_mod.RBACAppUserAccessPolicyAssignmentApi(),
|
|
||||||
"app-1",
|
|
||||||
"acct-target",
|
|
||||||
)
|
|
||||||
|
|
||||||
tenant_id, actor_id, app_id, target_id, payload = mock_replace.call_args.args
|
|
||||||
assert (tenant_id, actor_id, app_id, target_id) == (
|
|
||||||
"tenant-1",
|
|
||||||
"acct-actor",
|
|
||||||
"app-1",
|
|
||||||
"acct-target",
|
|
||||||
)
|
|
||||||
assert payload.access_policy_ids == ["policy-1", "policy-2"]
|
|
||||||
|
|
||||||
def test_app_member_bindings_delete_forwards_account_ids(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/apps/app-1/access-policies/policy-1/member-bindings",
|
|
||||||
method="DELETE",
|
|
||||||
json={"account_ids": ["acct-2", "acct-3"]},
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")),
|
|
||||||
patch("controllers.console.workspace.rbac.svc.RBACService.AppAccess.delete_member_bindings") as mock_delete,
|
|
||||||
):
|
|
||||||
response = inspect.unwrap(rbac_mod.RBACAppMemberBindingsApi.delete)(
|
|
||||||
rbac_mod.RBACAppMemberBindingsApi(),
|
|
||||||
"app-1",
|
|
||||||
"policy-1",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response == {"result": "success"}
|
|
||||||
tenant_id, actor_id, app_id, policy_id, payload = mock_delete.call_args.args
|
|
||||||
assert (tenant_id, actor_id, app_id, policy_id) == ("tenant-1", "acct-actor", "app-1", "policy-1")
|
|
||||||
assert payload.account_ids == ["acct-2", "acct-3"]
|
|
||||||
|
|
||||||
def test_dataset_member_bindings_delete_forwards_account_ids(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/datasets/dataset-1/access-policies/policy-1/member-bindings",
|
|
||||||
method="DELETE",
|
|
||||||
json={"account_ids": ["acct-2"]},
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.delete_member_bindings"
|
|
||||||
) as mock_delete,
|
|
||||||
):
|
|
||||||
response = inspect.unwrap(rbac_mod.RBACDatasetMemberBindingsApi.delete)(
|
|
||||||
rbac_mod.RBACDatasetMemberBindingsApi(),
|
|
||||||
"dataset-1",
|
|
||||||
"policy-1",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response == {"result": "success"}
|
|
||||||
tenant_id, actor_id, dataset_id, policy_id, payload = mock_delete.call_args.args
|
|
||||||
assert (tenant_id, actor_id, dataset_id, policy_id) == ("tenant-1", "acct-actor", "dataset-1", "policy-1")
|
|
||||||
assert payload.account_ids == ["acct-2"]
|
|
||||||
|
|
||||||
|
|
||||||
class TestPaginationForwarding:
|
class TestPaginationForwarding:
|
||||||
def test_access_policies_get_forwards_outer_pagination_params(self, app):
|
def test_access_policies_get_forwards_outer_pagination_params(self, app):
|
||||||
with (
|
with (
|
||||||
@ -517,88 +370,6 @@ class TestPaginationForwarding:
|
|||||||
assert options.results_per_page == 25
|
assert options.results_per_page == 25
|
||||||
assert options.reverse is False
|
assert options.reverse is False
|
||||||
|
|
||||||
def test_workspace_app_matrix_forwards_outer_pagination_params(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context("/workspaces/current/rbac/workspace/apps/access-policy?page=4&limit=10"),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
|
||||||
patch("controllers.console.workspace.rbac.svc.RBACService.WorkspaceAccess.app_matrix") as mock_list,
|
|
||||||
patch("controllers.console.workspace.rbac._dump", return_value={}),
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACWorkspaceAppMatrixApi.get)(rbac_mod.RBACWorkspaceAppMatrixApi())
|
|
||||||
|
|
||||||
_, kwargs = mock_list.call_args
|
|
||||||
options = kwargs["options"]
|
|
||||||
assert options.page_number == 4
|
|
||||||
assert options.results_per_page == 10
|
|
||||||
assert options.reverse is None
|
|
||||||
|
|
||||||
def test_workspace_dataset_matrix_forwards_outer_pagination_params(self, app):
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/workspace/datasets/access-policy?page=5&limit=15&reverse=true"
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
|
||||||
patch("controllers.console.workspace.rbac.svc.RBACService.WorkspaceAccess.dataset_matrix") as mock_list,
|
|
||||||
patch("controllers.console.workspace.rbac._dump", return_value={}),
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACWorkspaceDatasetMatrixApi.get)(rbac_mod.RBACWorkspaceDatasetMatrixApi())
|
|
||||||
|
|
||||||
_, kwargs = mock_list.call_args
|
|
||||||
options = kwargs["options"]
|
|
||||||
assert options.page_number == 5
|
|
||||||
assert options.results_per_page == 15
|
|
||||||
assert options.reverse is True
|
|
||||||
|
|
||||||
def test_app_user_access_policies_forwards_outer_pagination_params(self, app):
|
|
||||||
result = rbac_mod.svc.ResourceUserAccessPoliciesResponse()
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/apps/app-1/user-access-policies?page=2&limit=30&reverse=false"
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.AppAccess.user_access_policies",
|
|
||||||
return_value=result,
|
|
||||||
) as mock_list,
|
|
||||||
patch("controllers.console.workspace.rbac._dump", return_value={}),
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACAppUserAccessPoliciesApi.get)(
|
|
||||||
rbac_mod.RBACAppUserAccessPoliciesApi(),
|
|
||||||
"app-1",
|
|
||||||
)
|
|
||||||
|
|
||||||
args, kwargs = mock_list.call_args
|
|
||||||
assert args == ("tenant-1", "acct-1", "app-1")
|
|
||||||
options = kwargs["options"]
|
|
||||||
assert options.page_number == 2
|
|
||||||
assert options.results_per_page == 30
|
|
||||||
assert options.reverse is False
|
|
||||||
|
|
||||||
def test_dataset_user_access_policies_forwards_outer_pagination_params(self, app):
|
|
||||||
result = rbac_mod.svc.ResourceUserAccessPoliciesResponse()
|
|
||||||
with (
|
|
||||||
app.test_request_context(
|
|
||||||
"/workspaces/current/rbac/datasets/dataset-1/user-access-policies?page=3&limit=40&reverse=true"
|
|
||||||
),
|
|
||||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
|
||||||
patch(
|
|
||||||
"controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.user_access_policies",
|
|
||||||
return_value=result,
|
|
||||||
) as mock_list,
|
|
||||||
patch("controllers.console.workspace.rbac._dump", return_value={}),
|
|
||||||
):
|
|
||||||
inspect.unwrap(rbac_mod.RBACDatasetUserAccessPoliciesApi.get)(
|
|
||||||
rbac_mod.RBACDatasetUserAccessPoliciesApi(),
|
|
||||||
"dataset-1",
|
|
||||||
)
|
|
||||||
|
|
||||||
args, kwargs = mock_list.call_args
|
|
||||||
assert args == ("tenant-1", "acct-1", "dataset-1")
|
|
||||||
options = kwargs["options"]
|
|
||||||
assert options.page_number == 3
|
|
||||||
assert options.results_per_page == 40
|
|
||||||
assert options.reverse is True
|
|
||||||
|
|
||||||
|
|
||||||
class TestAccessPolicyBindingLockUnlock:
|
class TestAccessPolicyBindingLockUnlock:
|
||||||
def test_lock_forwards_binding_id(self, app):
|
def test_lock_forwards_binding_id(self, app):
|
||||||
@ -653,7 +424,7 @@ class TestWorkspaceRbacGuards:
|
|||||||
"controllers.common.wraps.current_account_with_tenant",
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
return_value=(_account(), "tenant-1"),
|
return_value=(_account(), "tenant-1"),
|
||||||
),
|
),
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check", return_value=False),
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check", return_value=False),
|
||||||
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.create") as mock_create,
|
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.create") as mock_create,
|
||||||
):
|
):
|
||||||
with pytest.raises(Forbidden):
|
with pytest.raises(Forbidden):
|
||||||
@ -672,7 +443,7 @@ class TestWorkspaceRbacGuards:
|
|||||||
"controllers.common.wraps.current_account_with_tenant",
|
"controllers.common.wraps.current_account_with_tenant",
|
||||||
return_value=(_account(), "tenant-1"),
|
return_value=(_account(), "tenant-1"),
|
||||||
),
|
),
|
||||||
patch("controllers.common.wraps.RBACService.CheckAccess.check", return_value=False),
|
patch("controllers.common.rbac.checks.RBACService.CheckAccess.check", return_value=False),
|
||||||
patch("controllers.console.workspace.rbac.svc.RBACService.AccessPolicies.create") as mock_create,
|
patch("controllers.console.workspace.rbac.svc.RBACService.AccessPolicies.create") as mock_create,
|
||||||
):
|
):
|
||||||
with pytest.raises(Forbidden):
|
with pytest.raises(Forbidden):
|
||||||
|
|||||||
@ -0,0 +1,167 @@
|
|||||||
|
"""Contract tests for the generated app / dataset / agent access-permission routes.
|
||||||
|
|
||||||
|
``controllers.console.workspace.rbac`` builds the same twelve endpoints for every
|
||||||
|
resource kind from ``_RESOURCE_ACCESS_ROUTES``. These tests pin the two things the
|
||||||
|
generation must not get wrong: the registered URLs (with their class names and HTTP
|
||||||
|
methods) and which inner-API client each handler reaches for.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
from collections.abc import Callable, Generator
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.workspace import rbac as rbac_mod
|
||||||
|
from enums import DeploymentEdition
|
||||||
|
from services.enterprise import rbac_service as svc
|
||||||
|
|
||||||
|
RESOURCE_ID = {
|
||||||
|
svc.RBACResourceType.APP: "app-1",
|
||||||
|
svc.RBACResourceType.DATASET: "dataset-1",
|
||||||
|
svc.RBACResourceType.AGENT: "agent-1",
|
||||||
|
}
|
||||||
|
|
||||||
|
# The URL segment and path parameter of each resource kind, spelled out here instead of read
|
||||||
|
# back from the same enum the controller builds its URLs from, so a rename fails this test.
|
||||||
|
RESOURCE_URL_PARTS = {
|
||||||
|
svc.RBACResourceType.APP: ("apps", "app_id"),
|
||||||
|
svc.RBACResourceType.DATASET: ("datasets", "dataset_id"),
|
||||||
|
svc.RBACResourceType.AGENT: ("agents", "agent_id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _segment(spec: rbac_mod._ResourceAccessRoutes) -> str:
|
||||||
|
return RESOURCE_URL_PARTS[spec.resource_type][0]
|
||||||
|
|
||||||
|
|
||||||
|
def _id_param(spec: rbac_mod._ResourceAccessRoutes) -> str:
|
||||||
|
return RESOURCE_URL_PARTS[spec.resource_type][1]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app() -> Flask:
|
||||||
|
flask_app = Flask(__name__)
|
||||||
|
flask_app.config["TESTING"] = True
|
||||||
|
return flask_app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _rbac_config(config_overrides: Callable[..., None]) -> None:
|
||||||
|
config_overrides(
|
||||||
|
DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE,
|
||||||
|
RBAC_ENABLED=True,
|
||||||
|
LOGIN_DISABLED=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=rbac_mod._RESOURCE_ACCESS_ROUTES, ids=lambda spec: spec.resource_type.value)
|
||||||
|
def spec(request: pytest.FixtureRequest) -> rbac_mod._ResourceAccessRoutes:
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def apis(spec: rbac_mod._ResourceAccessRoutes) -> rbac_mod._ResourceAccessApis:
|
||||||
|
return rbac_mod._RESOURCE_ACCESS_APIS[spec.resource_type]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def resource_id(spec: rbac_mod._ResourceAccessRoutes) -> str:
|
||||||
|
return RESOURCE_ID[spec.resource_type]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _patched_current_ids() -> Generator[None]:
|
||||||
|
with patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
def _expected_routes(spec: rbac_mod._ResourceAccessRoutes) -> dict[str, tuple[str, set[str]]]:
|
||||||
|
resource = f"/workspaces/current/rbac/{_segment(spec)}/<uuid:{_id_param(spec)}>"
|
||||||
|
workspace = f"/workspaces/current/rbac/workspace/{_segment(spec)}"
|
||||||
|
prefix = spec.class_prefix
|
||||||
|
return {
|
||||||
|
f"/workspaces/current/rbac/role-permissions/catalog/{spec.resource_type.value}": (
|
||||||
|
f"RBAC{prefix}CatalogApi",
|
||||||
|
{"GET"},
|
||||||
|
),
|
||||||
|
f"{resource}/access-policy": (f"RBAC{prefix}MatrixApi", {"GET"}),
|
||||||
|
f"{resource}/whitelist": (f"RBAC{prefix}WhitelistApi", {"GET", "PUT"}),
|
||||||
|
f"{resource}/whitelist_config": (f"RBAC{prefix}WhitelistConfigApi", {"GET"}),
|
||||||
|
f"{resource}/user-access-policies": (f"RBAC{prefix}UserAccessPoliciesApi", {"GET"}),
|
||||||
|
f"{resource}/users/<uuid:target_account_id>/access-policies": (
|
||||||
|
f"RBAC{prefix}UserAccessPolicyAssignmentApi",
|
||||||
|
{"PUT"},
|
||||||
|
),
|
||||||
|
f"{resource}/access-policies/<uuid:policy_id>/role-bindings": (f"RBAC{prefix}RoleBindingsApi", {"GET"}),
|
||||||
|
f"{resource}/access-policies/<string:policy_id>/member-bindings": (
|
||||||
|
f"RBAC{prefix}MemberBindingsApi",
|
||||||
|
{"GET", "DELETE"},
|
||||||
|
),
|
||||||
|
f"{workspace}/access-policy": (f"RBACWorkspace{prefix}MatrixApi", {"GET"}),
|
||||||
|
f"{workspace}/access-policies/<uuid:policy_id>/role-bindings": (
|
||||||
|
f"RBACWorkspace{prefix}RoleBindingsApi",
|
||||||
|
{"GET"},
|
||||||
|
),
|
||||||
|
f"{workspace}/access-policies/<uuid:policy_id>/bindings": (f"RBACWorkspace{prefix}BindingsApi", {"PUT"}),
|
||||||
|
f"{workspace}/access-policies/<uuid:policy_id>/member-bindings": (
|
||||||
|
f"RBACWorkspace{prefix}MemberBindingsApi",
|
||||||
|
{"GET"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _registered_resource_by_url() -> dict[str, type]:
|
||||||
|
registered: dict[str, type] = {}
|
||||||
|
for route in console_ns.resources:
|
||||||
|
for url in route.urls:
|
||||||
|
registered[url] = route.resource
|
||||||
|
return registered
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_resource_kind_registers_the_same_twelve_routes(spec: rbac_mod._ResourceAccessRoutes) -> None:
|
||||||
|
registered = _registered_resource_by_url()
|
||||||
|
|
||||||
|
for url, (class_name, methods) in _expected_routes(spec).items():
|
||||||
|
assert url in registered, f"{url} is not registered"
|
||||||
|
resource = registered[url]
|
||||||
|
assert resource.__name__ == class_name
|
||||||
|
assert set(resource.methods or ()) == methods
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("automatic_include_workspace_members", [True, False])
|
||||||
|
def test_whitelist_put_queues_the_seed_task_only_when_auto_including(
|
||||||
|
app: Flask,
|
||||||
|
apis: rbac_mod._ResourceAccessApis,
|
||||||
|
spec: rbac_mod._ResourceAccessRoutes,
|
||||||
|
resource_id: str,
|
||||||
|
automatic_include_workspace_members: bool,
|
||||||
|
) -> None:
|
||||||
|
replace = MagicMock(
|
||||||
|
return_value=svc.ResourceWhitelist(
|
||||||
|
automatic_include_workspace_members=automatic_include_workspace_members,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
app.test_request_context(
|
||||||
|
f"/{_segment(spec)}/{resource_id}/whitelist",
|
||||||
|
method="PUT",
|
||||||
|
json={"automatic_include_workspace_members": automatic_include_workspace_members},
|
||||||
|
),
|
||||||
|
patch.object(spec.access, "replace_whitelist", replace),
|
||||||
|
patch("controllers.console.workspace.rbac.initialize_created_app_rbac_access_task") as seed_task,
|
||||||
|
):
|
||||||
|
inspect.unwrap(vars(apis.whitelist)["put"])(apis.whitelist(), **{_id_param(spec): resource_id})
|
||||||
|
|
||||||
|
tenant_id, actor_id, target_id, payload = replace.call_args.args
|
||||||
|
assert (tenant_id, actor_id, target_id) == ("tenant-1", "acct-actor", resource_id)
|
||||||
|
assert payload.automatic_include_workspace_members is automatic_include_workspace_members
|
||||||
|
|
||||||
|
if automatic_include_workspace_members:
|
||||||
|
seed_task.delay.assert_called_once_with("tenant-1", "acct-actor", **{_id_param(spec): resource_id})
|
||||||
|
else:
|
||||||
|
seed_task.delay.assert_not_called()
|
||||||
@ -1,7 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.openapi.auth.composition import account_pipeline, auth_router, external_sso_pipeline
|
from controllers.openapi.auth.composition import account_pipeline, auth_router, external_sso_pipeline
|
||||||
from controllers.openapi.auth.data import RBACRequirement, RequestContext
|
from controllers.openapi.auth.data import RequestContext
|
||||||
from controllers.openapi.auth.flow import When
|
from controllers.openapi.auth.flow import When
|
||||||
from controllers.openapi.auth.pipeline import AuthPipeline, PipelineRoute, PipelineRouter
|
from controllers.openapi.auth.pipeline import AuthPipeline, PipelineRoute, PipelineRouter
|
||||||
from controllers.openapi.auth.verify import (
|
from controllers.openapi.auth.verify import (
|
||||||
@ -12,7 +13,7 @@ from controllers.openapi.auth.verify import (
|
|||||||
check_workspace_mismatch,
|
check_workspace_mismatch,
|
||||||
check_workspace_role,
|
check_workspace_role,
|
||||||
)
|
)
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
from core.rbac import RBACPermission
|
||||||
from enums import DeploymentEdition
|
from enums import DeploymentEdition
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
from models.account import TenantAccountRole
|
from models.account import TenantAccountRole
|
||||||
@ -206,7 +207,7 @@ def _selected_auth_steps_with_rbac(rbac):
|
|||||||
|
|
||||||
|
|
||||||
def test_account_pipeline_selects_rbac_step_when_required():
|
def test_account_pipeline_selects_rbac_step_when_required():
|
||||||
rbac = RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_VIEW_LAYOUT)
|
rbac = RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp())
|
||||||
assert check_rbac_permission in _selected_auth_steps_with_rbac(rbac)
|
assert check_rbac_permission in _selected_auth_steps_with_rbac(rbac)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
from controllers.openapi.auth.conditions import (
|
from controllers.openapi.auth.conditions import (
|
||||||
EDITION_CLOUD,
|
EDITION_CLOUD,
|
||||||
EDITION_COMMUNITY,
|
EDITION_COMMUNITY,
|
||||||
@ -18,8 +19,8 @@ from controllers.openapi.auth.conditions import (
|
|||||||
data_cond,
|
data_cond,
|
||||||
request_cond,
|
request_cond,
|
||||||
)
|
)
|
||||||
from controllers.openapi.auth.data import AuthData, RBACRequirement, RequestContext
|
from controllers.openapi.auth.data import AuthData, RequestContext
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
from core.rbac import RBACPermission
|
||||||
from enums import DeploymentEdition
|
from enums import DeploymentEdition
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
from models.account import TenantAccountRole
|
from models.account import TenantAccountRole
|
||||||
@ -153,7 +154,7 @@ def test_webapp_run_scoped_false_when_scope_none():
|
|||||||
|
|
||||||
|
|
||||||
def _rbac_req():
|
def _rbac_req():
|
||||||
return RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_TEST_AND_RUN)
|
return RBACCheck(RBACPermission.APP_TEST_AND_RUN, PlainApp())
|
||||||
|
|
||||||
|
|
||||||
def test_has_rbac_true():
|
def test_has_rbac_true():
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import pytest
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
from controllers.openapi.auth.data import AuthData, RBACRequirement
|
from controllers.common.rbac import PlainApp, RBACCheck
|
||||||
|
from controllers.openapi.auth.data import AuthData
|
||||||
from controllers.openapi.auth.verify import (
|
from controllers.openapi.auth.verify import (
|
||||||
check_acl,
|
check_acl,
|
||||||
check_app_access,
|
check_app_access,
|
||||||
@ -18,7 +19,7 @@ from controllers.openapi.auth.verify import (
|
|||||||
check_workspace_mismatch,
|
check_workspace_mismatch,
|
||||||
check_workspace_role,
|
check_workspace_role,
|
||||||
)
|
)
|
||||||
from core.rbac import RBACPermission, RBACResourceScope
|
from core.rbac import RBACPermission
|
||||||
from libs.oauth_bearer import Scope, TokenType
|
from libs.oauth_bearer import Scope, TokenType
|
||||||
from models.account import Tenant, TenantAccountRole
|
from models.account import Tenant, TenantAccountRole
|
||||||
from models.model import App
|
from models.model import App
|
||||||
@ -85,25 +86,25 @@ def test_check_app_access_raises_when_not_member():
|
|||||||
|
|
||||||
# --- check_rbac_permission ---
|
# --- check_rbac_permission ---
|
||||||
|
|
||||||
_RBAC_REQ = RBACRequirement(resource_type=RBACResourceScope.APP, scene=RBACPermission.APP_VIEW_LAYOUT)
|
_RBAC_REQ = RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp())
|
||||||
|
|
||||||
|
|
||||||
def test_check_rbac_noop_when_no_requirement():
|
def test_check_rbac_noop_when_no_requirement():
|
||||||
with patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce:
|
with patch("controllers.openapi.auth.verify.enforce_rbac_checks") as mock_enforce:
|
||||||
check_rbac_permission(_data(rbac=None, caller_kind="account"))
|
check_rbac_permission(_data(rbac=None, caller_kind="account"))
|
||||||
mock_enforce.assert_not_called()
|
mock_enforce.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_check_rbac_noop_when_rbac_disabled(config_overrides: Callable[..., None]):
|
def test_check_rbac_noop_when_rbac_disabled(config_overrides: Callable[..., None]):
|
||||||
config_overrides(RBAC_ENABLED=False)
|
config_overrides(RBAC_ENABLED=False)
|
||||||
with patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce:
|
with patch("controllers.openapi.auth.verify.enforce_rbac_checks") as mock_enforce:
|
||||||
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="account"))
|
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="account"))
|
||||||
mock_enforce.assert_not_called()
|
mock_enforce.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_check_rbac_skips_end_user_caller():
|
def test_check_rbac_skips_end_user_caller():
|
||||||
with (
|
with (
|
||||||
patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce,
|
patch("controllers.openapi.auth.verify.enforce_rbac_checks") as mock_enforce,
|
||||||
):
|
):
|
||||||
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="end_user"))
|
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="end_user"))
|
||||||
mock_enforce.assert_not_called()
|
mock_enforce.assert_not_called()
|
||||||
@ -126,17 +127,18 @@ def test_check_rbac_enforces_for_account_caller():
|
|||||||
path_params={"app_id": "app-1"},
|
path_params={"app_id": "app-1"},
|
||||||
)
|
)
|
||||||
with (
|
with (
|
||||||
patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce,
|
patch("controllers.openapi.auth.verify.enforce_rbac_checks") as mock_enforce,
|
||||||
):
|
):
|
||||||
check_rbac_permission(data)
|
check_rbac_permission(data)
|
||||||
mock_enforce.assert_called_once_with(
|
mock_enforce.assert_called_once()
|
||||||
tenant_id="t1",
|
call_kwargs = mock_enforce.call_args.kwargs
|
||||||
account_id=str(account_id),
|
assert call_kwargs["tenant_id"] == "t1"
|
||||||
resource_type=RBACResourceScope.APP,
|
assert call_kwargs["account_id"] == str(account_id)
|
||||||
scene=RBACPermission.APP_VIEW_LAYOUT,
|
assert call_kwargs["path_args"] == {"app_id": "app-1"}
|
||||||
resource_required=True,
|
(check,) = call_kwargs["checks"]
|
||||||
path_args={"app_id": "app-1"},
|
assert isinstance(check, RBACCheck)
|
||||||
)
|
assert check.scene is RBACPermission.APP_VIEW_LAYOUT
|
||||||
|
assert isinstance(check.locator, PlainApp)
|
||||||
|
|
||||||
|
|
||||||
def test_check_acl_raises_when_app_or_mode_missing():
|
def test_check_acl_raises_when_app_or_mode_missing():
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user