Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 11 additions & 24 deletions api/controllers/console/app/app_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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_response_schema_models, register_schema_models
from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import (
RBACPermission,
Expand All @@ -18,16 +18,10 @@
with_current_user,
)
from extensions.ext_database import db
from extensions.ext_redis import redis_client
from libs.login import current_account_with_tenant, login_required
from models.account import Account
from models.model import App
from services.app_dsl_service import (
IMPORT_INFO_REDIS_KEY_PREFIX,
AppDslService,
Import,
PendingData,
)
from services.app_dsl_service import AppDslService, Import, initialize_imported_app_creator_access
from services.enterprise.enterprise_service import EnterpriseService
from services.entities.dsl_entities import CheckDependenciesResult, ImportStatus
from services.errors.account import NoPermissionError
Expand All @@ -50,7 +44,8 @@ class AppImportPayload(BaseModel):


register_enum_models(console_ns, ImportStatus)
register_schema_models(console_ns, AppImportPayload, Import, CheckDependenciesResult)
register_schema_models(console_ns, AppImportPayload, CheckDependenciesResult)
register_response_schema_models(console_ns, Import)


def _current_user_and_tenant_id(current_user: Account | None) -> tuple[Account, str | None]:
Expand Down Expand Up @@ -114,13 +109,14 @@ def post(self, req_data: AppImportPayload, current_user: Account | None = None):
else:
session.commit()

is_created_app = req_data.app_id is None and result.status in {
is_created_app = result.app_created and result.status in {
ImportStatus.COMPLETED,
ImportStatus.COMPLETED_WITH_WARNINGS,
}
if dify_config.RBAC_ENABLED and is_created_app and result.app_id:
current_user, current_tenant_id = _current_user_and_tenant_id(current_user)
if current_tenant_id:
initialize_imported_app_creator_access(result, tenant_id=current_tenant_id, account_id=current_user.id)
result.permission_keys = get_app_permission_keys(
current_tenant_id,
current_user.id,
Expand Down Expand Up @@ -153,11 +149,6 @@ class AppImportConfirmApi(Resource):
@with_current_user
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]
redis_key = f"{IMPORT_INFO_REDIS_KEY_PREFIX}{import_id}"
pending_data_raw = redis_client.get(redis_key)
pending_data: PendingData | None = None
if pending_data_raw:
pending_data = PendingData.model_validate_json(pending_data_raw)

with Session(db.engine, expire_on_commit=False) as session:
import_service = AppDslService(session)
Expand All @@ -172,18 +163,14 @@ def post(self, current_user: Account | None = None, import_id: str = ""):
else:
session.commit()

is_created_app = bool(
pending_data
and pending_data.app_id is None
and result.status
in {
ImportStatus.COMPLETED,
ImportStatus.COMPLETED_WITH_WARNINGS,
}
)
is_created_app = result.app_created and result.status in {
ImportStatus.COMPLETED,
ImportStatus.COMPLETED_WITH_WARNINGS,
}
if dify_config.RBAC_ENABLED and is_created_app and result.app_id:
current_user, current_tenant_id = _current_user_and_tenant_id(current_user)
if current_tenant_id:
initialize_imported_app_creator_access(result, tenant_id=current_tenant_id, account_id=current_user.id)
result.permission_keys = get_app_permission_keys(
current_tenant_id,
current_user.id,
Expand Down
6 changes: 6 additions & 0 deletions api/controllers/console/datasets/datasets_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,12 @@ def post(self, session: Session, current_tenant_id: str, current_user: Account):
dataset.id,
enterprise_rbac_service.ReplaceMemberBindings(automatic_include_workspace_members=False),
)
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
current_tenant_id,
current_user.id,
enterprise_rbac_service.RBACResourceType.DATASET,
dataset.id,
)

return dump_response(
DatasetAndDocumentResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ def post(
dataset_id,
enterprise_rbac_service.ReplaceMemberBindings(automatic_include_workspace_members=False),
)
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
current_tenant_id,
current_user.id,
enterprise_rbac_service.RBACResourceType.DATASET,
dataset_id,
)

return dump_response(RagPipelineImportResponse, import_info), 201

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from libs.login import login_required
from models.account import Account
from models.dataset import Pipeline
from services.enterprise import rbac_service as enterprise_rbac_service
from services.entities.dsl_entities import ImportStatus
from services.rag_pipeline.rag_pipeline_dsl_service import RagPipelineDslService

Expand Down Expand Up @@ -107,6 +108,19 @@ def post(self, req_data: RagPipelineImportPayload, current_user: Account) -> Jso
else:
session.commit()

if (
result.status in {ImportStatus.COMPLETED, ImportStatus.COMPLETED_WITH_WARNINGS}
and result.dataset_created
and result.dataset_id
):
assert current_user.current_tenant_id is not None
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
current_user.current_tenant_id,
current_user.id,
enterprise_rbac_service.RBACResourceType.DATASET,
result.dataset_id,
)

# Return appropriate status code based on result
status = result.status
match status:
Expand Down Expand Up @@ -138,6 +152,19 @@ def post(self, current_user: Account, import_id: str) -> JsonResponseWithStatus:
else:
session.commit()

if (
result.status in {ImportStatus.COMPLETED, ImportStatus.COMPLETED_WITH_WARNINGS}
and result.dataset_created
and result.dataset_id
):
assert current_user.current_tenant_id is not None
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
current_user.current_tenant_id,
current_user.id,
enterprise_rbac_service.RBACResourceType.DATASET,
result.dataset_id,
)

# Return appropriate status code based on result
if result.status == ImportStatus.FAILED:
return dump_response(RagPipelineImportResponse, result), 400
Expand Down
3 changes: 2 additions & 1 deletion api/controllers/inner_api/app/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from extensions.ext_database import db
from models import Account, App
from models.account import AccountStatus
from services.app_dsl_service import AppDslService
from services.app_dsl_service import AppDslService, initialize_imported_app_creator_access
from services.entities.dsl_entities import ImportMode, ImportStatus
from services.errors.app import IsDraftWorkflowError, WorkflowNotFoundError

Expand Down Expand Up @@ -83,6 +83,7 @@ def post(self, args: InnerAppDSLImportPayload, workspace_id: str):
session.rollback()
else:
session.commit()
initialize_imported_app_creator_access(result, tenant_id=workspace_id, account_id=account.id)

if result.status == ImportStatus.FAILED:
return result.model_dump(mode="json"), 400
Expand Down
4 changes: 3 additions & 1 deletion api/controllers/openapi/app_dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from libs.oauth_bearer import Scope, TokenType
from models import Account, App
from models.account import TenantAccountRole
from services.app_dsl_service import AppDslService, Import
from services.app_dsl_service import AppDslService, Import, initialize_imported_app_creator_access
from services.entities.dsl_entities import CheckDependenciesResult, ImportStatus
from services.errors.account import NoPermissionError
from services.errors.app import WorkflowNotFoundError
Expand Down Expand Up @@ -70,6 +70,7 @@ def post(self, workspace_id: str, *, auth_data: AuthData, body: AppDslImportPayl
session.rollback()
else:
session.commit()
initialize_imported_app_creator_access(result, tenant_id=workspace_id, account_id=account.id)

match result.status:
case ImportStatus.FAILED:
Expand Down Expand Up @@ -113,6 +114,7 @@ def post(self, workspace_id: str, import_id: str, *, auth_data: AuthData):
session.rollback()
else:
session.commit()
initialize_imported_app_creator_access(result, tenant_id=workspace_id, account_id=account.id)

if result.status == ImportStatus.FAILED:
return result, 400
Expand Down
27 changes: 27 additions & 0 deletions api/services/app_dsl_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from services.agent.workflow_publish_service import WorkflowAgentPublishService
from services.dsl_content import DSL_MAX_SIZE, dsl_content_size
from services.dsl_version import check_version_compatibility
from services.enterprise import rbac_service
from services.enterprise.enterprise_service import EnterpriseService
from services.enterprise.rbac_service import RBACService
from services.entities.dsl_entities import (
Expand Down Expand Up @@ -86,13 +87,34 @@ class Import(BaseModel):
status: ImportStatus
app_id: str | None = None
app_mode: str | None = None
app_created: bool = Field(default=False, exclude=True)
permission_keys: list[str] = Field(default_factory=list)
current_dsl_version: str = CURRENT_DSL_VERSION
imported_dsl_version: str = ""
error: str = ""
warnings: list[DslImportWarning] = Field(default_factory=list)


def initialize_imported_app_creator_access(result: Import, *, tenant_id: str, account_id: str) -> None:
"""Initialize ordinary App creator access only after the caller commits the import."""
if (
result.status in (ImportStatus.COMPLETED, ImportStatus.COMPLETED_WITH_WARNINGS)
and result.app_created
and result.app_id
and result.app_mode
in (
AppMode.CHAT,
AppMode.ADVANCED_CHAT,
AppMode.COMPLETION,
AppMode.WORKFLOW,
AppMode.AGENT_CHAT,
)
):
rbac_service.try_sync_creator_access_policy_member_bindings(
tenant_id, account_id, rbac_service.RBACResourceType.APP, result.app_id
)


class PendingData(PendingImportOwner):
import_mode: str
yaml_content: str
Expand Down Expand Up @@ -152,6 +174,7 @@ def copy_app(
self._session.rollback()
return result, None
self._session.commit()
initialize_imported_app_creator_access(result, tenant_id=tenant_id, account_id=account.id)

if result.app_id and SystemFeatureService.is_webapp_auth_enabled():
try:
Expand Down Expand Up @@ -345,6 +368,7 @@ def import_app(
)

# Create or update app
app_created = app is None
app = self._create_or_update_app(
app=app,
data=data,
Expand All @@ -365,6 +389,7 @@ def import_app(
id=import_id,
status=result_status,
app_id=app.id,
app_created=app_created,
app_mode=app.mode,
imported_dsl_version=imported_version,
warnings=self._warnings,
Expand Down Expand Up @@ -433,6 +458,7 @@ def confirm_import(self, *, import_id: str, account: Account) -> Import:
)

# Create or update app
app_created = app is None
app = self._create_or_update_app(
app=app,
data=data,
Expand All @@ -451,6 +477,7 @@ def confirm_import(self, *, import_id: str, account: Account) -> Import:
id=import_id,
status=self._status_with_warnings(ImportStatus.COMPLETED),
app_id=app.id,
app_created=app_created,
app_mode=app.mode,
current_dsl_version=CURRENT_DSL_VERSION,
imported_dsl_version=data.get("version", "0.1.0"),
Expand Down
9 changes: 9 additions & 0 deletions api/services/dataset_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,16 @@ def create_empty_rag_pipeline_dataset(
pipeline_id=pipeline.id,
)
session.add(dataset)
session.flush()
dataset_id = dataset.id
creator_id = current_user.id
session.commit()
enterprise_rbac_service.try_sync_creator_access_policy_member_bindings(
tenant_id,
creator_id,
enterprise_rbac_service.RBACResourceType.DATASET,
dataset_id,
)
return dataset

@staticmethod
Expand Down
41 changes: 40 additions & 1 deletion api/services/enterprise/rbac_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,15 +925,54 @@ def try_sync_creator_access_policy_member_bindings(
resource_type: RBACResourceType | str,
resource_id: str,
) -> None:
"""Best-effort creator bindings, including app and dataset whitelist visibility gates."""
if not dify_config.RBAC_ENABLED:
return
try:
RBACService.AccessPolicies.sync_creator_access_policy_member_bindings(
bindings = RBACService.AccessPolicies.sync_creator_access_policy_member_bindings(
tenant_id,
account_id,
resource_type=resource_type,
resource_id=resource_id,
)
if resource_type in (RBACResourceType.APP, RBACResourceType.DATASET):
policy_ids = sorted(
{
binding.access_policy_id
for binding in bindings
if binding.tenant_id == tenant_id
and binding.resource_type == resource_type
and binding.resource_id == resource_id
and binding.account_id == account_id
and binding.access_policy_id
}
)
if not policy_ids:
raise ValueError("Creator sync returned no matching resource policy binding")
# Policy bindings alone do not satisfy the resource whitelist gate.
# Append the returned creator policies without replacing other members or policies.
if resource_type == RBACResourceType.APP:
RBACService.AppAccess.append_whitelist_members_batch(
tenant_id,
account_id,
[
AppendAppWhitelistMembersBatchItem(
app_id=resource_id, account_ids=[account_id], policy_id=policy_id
)
for policy_id in policy_ids
],
)
else:
RBACService.DatasetAccess.append_whitelist_members_batch(
tenant_id,
account_id,
[
AppendDatasetWhitelistMembersBatchItem(
dataset_id=resource_id, account_ids=[account_id], policy_id=policy_id
)
for policy_id in policy_ids
],
)
except Exception:
logger.warning(
"Failed to sync creator access policy member binding for "
Expand Down
Loading
Loading