diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 0c113e237bf..dcdf784d6e1 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -20213,7 +20213,7 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | data | [ [ResourceUserAccessPolicies](#resourceuseraccesspolicies) ] | | No | -| scope | string | | Yes | +| scope | [RBACResourceWhitelistScope](#rbacresourcewhitelistscope) | | Yes | #### ResourceWhitelist diff --git a/api/services/enterprise/rbac_service.py b/api/services/enterprise/rbac_service.py index 16878ae0045..450e4d0be33 100644 --- a/api/services/enterprise/rbac_service.py +++ b/api/services/enterprise/rbac_service.py @@ -249,7 +249,7 @@ class ResourceUserAccessPolicies(_RBACModel): class ResourceUserAccessPoliciesResponse(_RBACModel): - scope: str + scope: RBACResourceWhitelistScope data: list[ResourceUserAccessPolicies] = Field(default_factory=list) diff --git a/api/tests/unit_tests/services/enterprise/test_rbac_service.py b/api/tests/unit_tests/services/enterprise/test_rbac_service.py index b491e3833f8..fdf921265b2 100644 --- a/api/tests/unit_tests/services/enterprise/test_rbac_service.py +++ b/api/tests/unit_tests/services/enterprise/test_rbac_service.py @@ -299,7 +299,7 @@ class TestResourceAccess: def test_app_user_access_policies(self, mock_send: MagicMock): mock_send.return_value = { - "scope": "app", + "scope": "specific", "data": [ { "account": {"account_id": "acct-1", "account_name": "Alice"}, diff --git a/packages/contracts/generated/api/console/workspaces/types.gen.ts b/packages/contracts/generated/api/console/workspaces/types.gen.ts index 46715660bc4..bbf7e0fb88e 100644 --- a/packages/contracts/generated/api/console/workspaces/types.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/types.gen.ts @@ -551,7 +551,7 @@ export type AppAccessMatrix = { export type ResourceUserAccessPoliciesResponse = { data?: Array - scope: string + scope: RbacResourceWhitelistScope } export type ReplaceUserAccessPolicies = { diff --git a/packages/contracts/generated/api/console/workspaces/zod.gen.ts b/packages/contracts/generated/api/console/workspaces/zod.gen.ts index 7aae78dbb79..bc449edbc0a 100644 --- a/packages/contracts/generated/api/console/workspaces/zod.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/zod.gen.ts @@ -1841,7 +1841,7 @@ export const zResourceUserAccessPolicies = z.object({ */ export const zResourceUserAccessPoliciesResponse = z.object({ data: z.array(zResourceUserAccessPolicies).optional(), - scope: z.string(), + scope: zRbacResourceWhitelistScope, }) /** diff --git a/web/app/components/access-rules-editor/__tests__/index.spec.tsx b/web/app/components/access-rules-editor/__tests__/index.spec.tsx index abb6508afa1..e501e1c9067 100644 --- a/web/app/components/access-rules-editor/__tests__/index.spec.tsx +++ b/web/app/components/access-rules-editor/__tests__/index.spec.tsx @@ -134,10 +134,13 @@ describe('AccessRulesEditor', () => { expect(screen.getByRole('button', { name: 'permission.accessRule.resourceOpenScopeDescription' })).toBeInTheDocument() const allMembersButton = screen.getByRole('button', { name: /permission\.accessRule\.allPermittedMembers/ }) + const onlyMeButton = screen.getByRole('button', { name: /permission\.accessRule\.onlyMe/ }) const specificMembersButton = screen.getByRole('button', { name: /permission\.accessRule\.specificMembersOnly/ }) expect(allMembersButton).toBeDisabled() + expect(onlyMeButton).toBeDisabled() expect(specificMembersButton).toBeDisabled() expect(allMembersButton).toHaveAttribute('aria-pressed', 'false') + expect(onlyMeButton).toHaveAttribute('aria-pressed', 'false') expect(specificMembersButton).toHaveAttribute('aria-pressed', 'false') }) @@ -181,6 +184,30 @@ describe('AccessRulesEditor', () => { expect(onRemoveAccessPolicyMemberBinding).toHaveBeenCalledWith('account-1', 'app-policy-id') }) + it('should render and update the only-me resource access scope', () => { + const onOpenScopeChange = vi.fn() + + render( + , + ) + + expect(screen.getByRole('button', { name: /permission\.accessRule\.onlyMe/ })).toHaveAttribute('aria-pressed', 'true') + + fireEvent.click(screen.getByRole('button', { name: /permission\.accessRule\.specificMembersOnly/ })) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.change' })) + + expect(onOpenScopeChange).toHaveBeenCalledWith('specific') + }) + it('should render the fixed default option when an account has no exception policy', () => { render( -
+
+ ()) - .output(type()) - -const appUserAccessSettingsContract = base - .route({ - path: '/workspaces/current/rbac/apps/{appId}/user-access-policies', - method: 'GET', - }) - .input(type<{ - params: { - appId: string - } - query: { - language: AccessControlTemplateLanguage - } - }>()) - .output(type()) - -const updateAppUserAccessSettingsContract = base - .route({ - path: '/workspaces/current/rbac/apps/{appId}/users/{accountId}/access-policies', - method: 'PUT', - }) - .input(type<{ - params: { - appId: string - accountId: string - } - body: { - access_policy_ids: string[] - } - }>()) - .output(type()) - -const removeAppAccessPolicyMemberBindingsContract = base - .route({ - path: '/workspaces/current/rbac/apps/{appId}/access-policies/{policyId}/member-bindings', - method: 'DELETE', - }) - .input(type<{ - params: { - appId: string - policyId: string - } - body: { - account_ids: string[] - } - }>()) - .output(type()) - -const updateAppOpenScopeContract = base - .route({ - path: '/workspaces/current/rbac/apps/{appId}/whitelist', - method: 'PUT', - }) - .input(type<{ - params: { - appId: string - } - body: { - scope: ResourceOpenScope - } - }>()) - .output(type()) - -const datasetAccessRulesContract = base - .route({ - path: '/workspaces/current/rbac/datasets/{datasetId}/access-policy', - method: 'GET', - }) - .input(type<{ - params: { - datasetId: string - } - query: { - language: AccessControlTemplateLanguage - } - }>()) - .output(type()) - -const datasetUserAccessSettingsContract = base - .route({ - path: '/workspaces/current/rbac/datasets/{datasetId}/user-access-policies', - method: 'GET', - }) - .input(type<{ - params: { - datasetId: string - } - query: { - language: AccessControlTemplateLanguage - } - }>()) - .output(type()) - -const updateDatasetUserAccessSettingsContract = base - .route({ - path: '/workspaces/current/rbac/datasets/{datasetId}/users/{accountId}/access-policies', - method: 'PUT', - }) - .input(type<{ - params: { - datasetId: string - accountId: string - } - body: { - access_policy_ids: string[] - } - }>()) - .output(type()) - -const removeDatasetAccessPolicyMemberBindingsContract = base - .route({ - path: '/workspaces/current/rbac/datasets/{datasetId}/access-policies/{policyId}/member-bindings', - method: 'DELETE', - }) - .input(type<{ - params: { - datasetId: string - policyId: string - } - body: { - account_ids: string[] - } - }>()) - .output(type()) - -const updateDatasetOpenScopeContract = base - .route({ - path: '/workspaces/current/rbac/datasets/{datasetId}/whitelist', - method: 'PUT', - }) - .input(type<{ - params: { - datasetId: string - } - body: { - scope: ResourceOpenScope - } - }>()) - .output(type()) - -export const rbacAccessConfigContract = { - apps: { - accessRules: appAccessRulesContract, - userAccessSettings: appUserAccessSettingsContract, - updateUserAccessSettings: updateAppUserAccessSettingsContract, - removeMemberBindings: removeAppAccessPolicyMemberBindingsContract, - updateOpenScope: updateAppOpenScopeContract, - }, - datasets: { - accessRules: datasetAccessRulesContract, - userAccessSettings: datasetUserAccessSettingsContract, - updateUserAccessSettings: updateDatasetUserAccessSettingsContract, - removeMemberBindings: removeDatasetAccessPolicyMemberBindingsContract, - updateOpenScope: updateDatasetOpenScopeContract, - }, -} diff --git a/web/contract/router.ts b/web/contract/router.ts index 4ec8181977d..8f49f1aed60 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -45,7 +45,6 @@ import { workflowGenerate } from '@dify/contracts/api/console/workflow-generate/ import { workflow } from '@dify/contracts/api/console/workflow/orpc.gen' import { workspaces } from '@dify/contracts/api/console/workspaces/orpc.gen' import { contract as enterpriseContract } from '@dify/contracts/enterprise/orpc.gen' -import { rbacAccessConfigContract } from './console/access-control' import { exploreRouterContract } from './console/explore' import { modelProvidersRouterContract } from './console/model-providers' import { pluginsRouterContract } from './console/plugins' @@ -108,7 +107,6 @@ export const consoleRouterContract = { explore: exploreRouterContract, modelProviders: modelProvidersRouterContract, plugins: pluginsRouterContract, - rbacAccessConfig: rbacAccessConfigContract, snippets: snippetsRouterContract, triggers: triggersRouterContract, trialApps: trialAppsRouterContract, diff --git a/web/i18n/ar-TN/permission.json b/web/i18n/ar-TN/permission.json index 1d36580156a..eb1f06d9380 100644 --- a/web/i18n/ar-TN/permission.json +++ b/web/i18n/ar-TN/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "لا يوجد وصف", "accessRule.noRules": "لا توجد قواعد وصول", "accessRule.noUserAccessSettings": "لا توجد إعدادات أذونات فردية", + "accessRule.onlyMe": "أنا فقط", + "accessRule.onlyMeDescription": "يمكنك وحدك الوصول إلى هذا المورد.", "accessRule.permission": "الإذن", "accessRule.resourceOpenScope": "نطاق فتح المورد", "accessRule.resourceOpenScopeDescription": "اختر من يُفتح له هذا المورد. لا تزال أذونات الأدوار تحدد ما يمكن لكل عضو فعله.", diff --git a/web/i18n/de-DE/permission.json b/web/i18n/de-DE/permission.json index c33b446b6f2..2a6d14d495f 100644 --- a/web/i18n/de-DE/permission.json +++ b/web/i18n/de-DE/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Keine Beschreibung", "accessRule.noRules": "Keine Zugriffsregeln", "accessRule.noUserAccessSettings": "Keine individuellen Berechtigungseinstellungen", + "accessRule.onlyMe": "Nur ich", + "accessRule.onlyMeDescription": "Nur Sie können auf diese Ressource zugreifen.", "accessRule.permission": "Berechtigung", "accessRule.resourceOpenScope": "Freigabebereich der Ressource", "accessRule.resourceOpenScopeDescription": "Wählen Sie, für wen diese Ressource geöffnet ist. Rollenberechtigungen bestimmen weiterhin, was jedes Mitglied tun kann.", diff --git a/web/i18n/en-US/permission.json b/web/i18n/en-US/permission.json index 68625df8872..d2b79d75d5c 100644 --- a/web/i18n/en-US/permission.json +++ b/web/i18n/en-US/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "No description", "accessRule.noRules": "No access rules", "accessRule.noUserAccessSettings": "No individual permission settings", + "accessRule.onlyMe": "Only me", + "accessRule.onlyMeDescription": "Only you can access this resource.", "accessRule.permission": "Permission", "accessRule.resourceOpenScope": "Resource open scope", "accessRule.resourceOpenScopeDescription": "Choose who this resource is open to. Role permissions still decide what each member can do.", diff --git a/web/i18n/es-ES/permission.json b/web/i18n/es-ES/permission.json index 3cae40f01b9..bb015fa211b 100644 --- a/web/i18n/es-ES/permission.json +++ b/web/i18n/es-ES/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Sin descripción", "accessRule.noRules": "Sin reglas de acceso", "accessRule.noUserAccessSettings": "Sin ajustes de permisos individuales", + "accessRule.onlyMe": "Solo yo", + "accessRule.onlyMeDescription": "Solo tú puedes acceder a este recurso.", "accessRule.permission": "Permiso", "accessRule.resourceOpenScope": "Ámbito de apertura del recurso", "accessRule.resourceOpenScopeDescription": "Elige a quién está abierto este recurso. Los permisos de rol siguen decidiendo lo que puede hacer cada miembro.", diff --git a/web/i18n/fa-IR/permission.json b/web/i18n/fa-IR/permission.json index 818f61170d3..519d51c3cb5 100644 --- a/web/i18n/fa-IR/permission.json +++ b/web/i18n/fa-IR/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "بدون توضیحات", "accessRule.noRules": "بدون قانون دسترسی", "accessRule.noUserAccessSettings": "بدون تنظیمات مجوز فردی", + "accessRule.onlyMe": "فقط من", + "accessRule.onlyMeDescription": "فقط شما می‌توانید به این منبع دسترسی داشته باشید.", "accessRule.permission": "مجوز", "accessRule.resourceOpenScope": "دامنه دسترسی منبع", "accessRule.resourceOpenScopeDescription": "انتخاب کنید این منبع برای چه کسانی باز است. مجوزهای نقش همچنان تعیین می‌کنند هر عضو چه کاری می‌تواند انجام دهد.", diff --git a/web/i18n/fr-FR/permission.json b/web/i18n/fr-FR/permission.json index 983df8a9d64..969d78f2b6f 100644 --- a/web/i18n/fr-FR/permission.json +++ b/web/i18n/fr-FR/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Aucune description", "accessRule.noRules": "Aucune règle d'accès", "accessRule.noUserAccessSettings": "Aucun paramètre d'autorisation individuel", + "accessRule.onlyMe": "Moi uniquement", + "accessRule.onlyMeDescription": "Vous seul pouvez accéder à cette ressource.", "accessRule.permission": "Autorisation", "accessRule.resourceOpenScope": "Portée d'ouverture de la ressource", "accessRule.resourceOpenScopeDescription": "Choisissez à qui cette ressource est ouverte. Les autorisations de rôle déterminent toujours ce que chaque membre peut faire.", diff --git a/web/i18n/hi-IN/permission.json b/web/i18n/hi-IN/permission.json index 2f9da7a4b77..9c764a03e08 100644 --- a/web/i18n/hi-IN/permission.json +++ b/web/i18n/hi-IN/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "कोई विवरण नहीं", "accessRule.noRules": "कोई एक्सेस नियम नहीं", "accessRule.noUserAccessSettings": "कोई व्यक्तिगत अनुमति सेटिंग्स नहीं", + "accessRule.onlyMe": "केवल मैं", + "accessRule.onlyMeDescription": "केवल आप इस संसाधन तक पहुँच सकते हैं।", "accessRule.permission": "अनुमति", "accessRule.resourceOpenScope": "संसाधन का खुला दायरा", "accessRule.resourceOpenScopeDescription": "चुनें कि यह संसाधन किसके लिए खुला है। भूमिका अनुमतियां अभी भी तय करती हैं कि प्रत्येक सदस्य क्या कर सकता है।", diff --git a/web/i18n/id-ID/permission.json b/web/i18n/id-ID/permission.json index 4497116a6b6..26b9ed36915 100644 --- a/web/i18n/id-ID/permission.json +++ b/web/i18n/id-ID/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Tidak ada deskripsi", "accessRule.noRules": "Tidak ada aturan akses", "accessRule.noUserAccessSettings": "Tidak ada pengaturan izin individu", + "accessRule.onlyMe": "Hanya saya", + "accessRule.onlyMeDescription": "Hanya Anda yang dapat mengakses resource ini.", "accessRule.permission": "Izin", "accessRule.resourceOpenScope": "Cakupan akses sumber daya", "accessRule.resourceOpenScopeDescription": "Pilih siapa yang dapat mengakses sumber daya ini. Izin peran tetap menentukan apa yang dapat dilakukan setiap anggota.", diff --git a/web/i18n/it-IT/permission.json b/web/i18n/it-IT/permission.json index c4e84a1e2f7..d4fb2011cf5 100644 --- a/web/i18n/it-IT/permission.json +++ b/web/i18n/it-IT/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Nessuna descrizione", "accessRule.noRules": "Nessuna regola di accesso", "accessRule.noUserAccessSettings": "Nessuna impostazione dei permessi individuali", + "accessRule.onlyMe": "Solo io", + "accessRule.onlyMeDescription": "Solo tu puoi accedere a questa risorsa.", "accessRule.permission": "Permesso", "accessRule.resourceOpenScope": "Ambito di apertura della risorsa", "accessRule.resourceOpenScopeDescription": "Scegli a chi è aperta questa risorsa. I permessi di ruolo determinano comunque cosa può fare ciascun membro.", diff --git a/web/i18n/ja-JP/permission.json b/web/i18n/ja-JP/permission.json index 197ab166f47..01c7fc462fa 100644 --- a/web/i18n/ja-JP/permission.json +++ b/web/i18n/ja-JP/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "説明なし", "accessRule.noRules": "アクセスルールがありません", "accessRule.noUserAccessSettings": "個別権限設定がありません", + "accessRule.onlyMe": "自分のみ", + "accessRule.onlyMeDescription": "このリソースにアクセスできるのは自分だけです。", "accessRule.permission": "権限", "accessRule.resourceOpenScope": "リソース公開範囲", "accessRule.resourceOpenScopeDescription": "このリソースを公開する対象を選択します。各メンバーが実行できる操作はロール権限で決まります。", diff --git a/web/i18n/ko-KR/permission.json b/web/i18n/ko-KR/permission.json index fc1116a006f..0a12fe7babf 100644 --- a/web/i18n/ko-KR/permission.json +++ b/web/i18n/ko-KR/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "설명 없음", "accessRule.noRules": "접근 규칙 없음", "accessRule.noUserAccessSettings": "개별 권한 설정 없음", + "accessRule.onlyMe": "나만", + "accessRule.onlyMeDescription": "이 리소스에는 본인만 접근할 수 있습니다.", "accessRule.permission": "권한", "accessRule.resourceOpenScope": "리소스 공개 범위", "accessRule.resourceOpenScopeDescription": "이 리소스를 누구에게 공개할지 선택합니다. 각 멤버가 할 수 있는 작업은 여전히 역할 권한이 결정합니다.", diff --git a/web/i18n/nl-NL/permission.json b/web/i18n/nl-NL/permission.json index 6aa2d981181..279a6313a7f 100644 --- a/web/i18n/nl-NL/permission.json +++ b/web/i18n/nl-NL/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Geen beschrijving", "accessRule.noRules": "Geen toegangsregels", "accessRule.noUserAccessSettings": "Geen individuele rechteninstellingen", + "accessRule.onlyMe": "Alleen ik", + "accessRule.onlyMeDescription": "Alleen jij kunt toegang krijgen tot deze resource.", "accessRule.permission": "Recht", "accessRule.resourceOpenScope": "Toegangsbereik van resource", "accessRule.resourceOpenScopeDescription": "Kies voor wie deze resource toegankelijk is. Rolrechten bepalen nog steeds wat elk lid kan doen.", diff --git a/web/i18n/pl-PL/permission.json b/web/i18n/pl-PL/permission.json index bcbb44d74ca..fcfcc908918 100644 --- a/web/i18n/pl-PL/permission.json +++ b/web/i18n/pl-PL/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Brak opisu", "accessRule.noRules": "Brak reguł dostępu", "accessRule.noUserAccessSettings": "Brak indywidualnych ustawień uprawnień", + "accessRule.onlyMe": "Tylko ja", + "accessRule.onlyMeDescription": "Tylko Ty możesz uzyskać dostęp do tego zasobu.", "accessRule.permission": "Uprawnienie", "accessRule.resourceOpenScope": "Zakres otwarcia zasobu", "accessRule.resourceOpenScopeDescription": "Wybierz, dla kogo ten zasób jest otwarty. Uprawnienia ról nadal decydują o tym, co każdy członek może robić.", diff --git a/web/i18n/pt-BR/permission.json b/web/i18n/pt-BR/permission.json index 49822e479f0..edef0bea18f 100644 --- a/web/i18n/pt-BR/permission.json +++ b/web/i18n/pt-BR/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Sem descrição", "accessRule.noRules": "Sem regras de acesso", "accessRule.noUserAccessSettings": "Sem configurações de permissão individuais", + "accessRule.onlyMe": "Somente eu", + "accessRule.onlyMeDescription": "Somente você pode acessar este recurso.", "accessRule.permission": "Permissão", "accessRule.resourceOpenScope": "Escopo de abertura do recurso", "accessRule.resourceOpenScopeDescription": "Escolha para quem este recurso está aberto. As permissões de função ainda determinam o que cada membro pode fazer.", diff --git a/web/i18n/ro-RO/permission.json b/web/i18n/ro-RO/permission.json index d69ab076749..19e5815e357 100644 --- a/web/i18n/ro-RO/permission.json +++ b/web/i18n/ro-RO/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Fără descriere", "accessRule.noRules": "Fără reguli de acces", "accessRule.noUserAccessSettings": "Fără setări individuale de permisiuni", + "accessRule.onlyMe": "Doar eu", + "accessRule.onlyMeDescription": "Numai tu poți accesa această resursă.", "accessRule.permission": "Permisiune", "accessRule.resourceOpenScope": "Domeniul de deschidere al resursei", "accessRule.resourceOpenScopeDescription": "Alege cui îi este deschisă această resursă. Permisiunile de rol decid în continuare ce poate face fiecare membru.", diff --git a/web/i18n/ru-RU/permission.json b/web/i18n/ru-RU/permission.json index ff8d1e9123f..87578651c7f 100644 --- a/web/i18n/ru-RU/permission.json +++ b/web/i18n/ru-RU/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Без описания", "accessRule.noRules": "Нет правил доступа", "accessRule.noUserAccessSettings": "Нет индивидуальных настроек прав", + "accessRule.onlyMe": "Только я", + "accessRule.onlyMeDescription": "Только вы можете получить доступ к этому ресурсу.", "accessRule.permission": "Право", "accessRule.resourceOpenScope": "Область открытости ресурса", "accessRule.resourceOpenScopeDescription": "Выберите, кому открыт этот ресурс. Права роли по-прежнему определяют, что может делать каждый участник.", diff --git a/web/i18n/sl-SI/permission.json b/web/i18n/sl-SI/permission.json index 0bd53b1f805..28fbe824fdb 100644 --- a/web/i18n/sl-SI/permission.json +++ b/web/i18n/sl-SI/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Brez opisa", "accessRule.noRules": "Brez pravil za dostop", "accessRule.noUserAccessSettings": "Brez individualnih nastavitev dovoljenj", + "accessRule.onlyMe": "Samo jaz", + "accessRule.onlyMeDescription": "Do tega vira lahko dostopate samo vi.", "accessRule.permission": "Dovoljenje", "accessRule.resourceOpenScope": "Obseg odprtosti vira", "accessRule.resourceOpenScopeDescription": "Izberite, komu je ta vir na voljo. Dovoljenja vlog še vedno določajo, kaj lahko vsak član počne.", diff --git a/web/i18n/th-TH/permission.json b/web/i18n/th-TH/permission.json index e4e9be94c0a..a9cd1566b0e 100644 --- a/web/i18n/th-TH/permission.json +++ b/web/i18n/th-TH/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "ไม่มีคําอธิบาย", "accessRule.noRules": "ไม่มีกฎการเข้าถึง", "accessRule.noUserAccessSettings": "ไม่มีการตั้งค่าสิทธิ์เฉพาะบุคคล", + "accessRule.onlyMe": "ฉันเท่านั้น", + "accessRule.onlyMeDescription": "เฉพาะคุณเท่านั้นที่เข้าถึงทรัพยากรนี้ได้", "accessRule.permission": "สิทธิ์", "accessRule.resourceOpenScope": "ขอบเขตการเปิดของทรัพยากร", "accessRule.resourceOpenScopeDescription": "เลือกว่าทรัพยากรนี้เปิดให้ใครเข้าถึง สิทธิ์ตามบทบาทยังคงเป็นตัวกําหนดว่าสมาชิกแต่ละคนทําอะไรได้บ้าง", diff --git a/web/i18n/tr-TR/permission.json b/web/i18n/tr-TR/permission.json index c15c33ad4f0..e8578b4384c 100644 --- a/web/i18n/tr-TR/permission.json +++ b/web/i18n/tr-TR/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Açıklama yok", "accessRule.noRules": "Erişim kuralı yok", "accessRule.noUserAccessSettings": "Bireysel izin ayarı yok", + "accessRule.onlyMe": "Yalnızca ben", + "accessRule.onlyMeDescription": "Bu kaynağa yalnızca siz erişebilirsiniz.", "accessRule.permission": "İzin", "accessRule.resourceOpenScope": "Kaynak açık kapsamı", "accessRule.resourceOpenScopeDescription": "Bu kaynağın kime açık olduğunu seçin. Her üyenin ne yapabileceğine yine de rol izinleri karar verir.", diff --git a/web/i18n/uk-UA/permission.json b/web/i18n/uk-UA/permission.json index 0b219ef4204..101c208f5ab 100644 --- a/web/i18n/uk-UA/permission.json +++ b/web/i18n/uk-UA/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Немає опису", "accessRule.noRules": "Немає правил доступу", "accessRule.noUserAccessSettings": "Немає індивідуальних налаштувань дозволів", + "accessRule.onlyMe": "Лише я", + "accessRule.onlyMeDescription": "Лише ви можете отримати доступ до цього ресурсу.", "accessRule.permission": "Дозвіл", "accessRule.resourceOpenScope": "Сфера відкритості ресурсу", "accessRule.resourceOpenScopeDescription": "Виберіть, кому відкрито цей ресурс. Дозволи ролей усе одно визначають, що може робити кожен учасник.", diff --git a/web/i18n/vi-VN/permission.json b/web/i18n/vi-VN/permission.json index 8bb1b7ece78..58b59832745 100644 --- a/web/i18n/vi-VN/permission.json +++ b/web/i18n/vi-VN/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "Không có mô tả", "accessRule.noRules": "Không có quy tắc truy cập", "accessRule.noUserAccessSettings": "Không có cài đặt quyền riêng lẻ", + "accessRule.onlyMe": "Chỉ mình tôi", + "accessRule.onlyMeDescription": "Chỉ bạn có thể truy cập tài nguyên này.", "accessRule.permission": "Quyền", "accessRule.resourceOpenScope": "Phạm vi mở của tài nguyên", "accessRule.resourceOpenScopeDescription": "Chọn tài nguyên này được mở cho ai. Quyền vai trò vẫn quyết định mỗi thành viên có thể làm gì.", diff --git a/web/i18n/zh-Hans/permission.json b/web/i18n/zh-Hans/permission.json index c8e7b32f14f..ce7ccaf6bda 100644 --- a/web/i18n/zh-Hans/permission.json +++ b/web/i18n/zh-Hans/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "无描述", "accessRule.noRules": "暂无访问规则", "accessRule.noUserAccessSettings": "暂无个人权限设置", + "accessRule.onlyMe": "仅自己", + "accessRule.onlyMeDescription": "仅你自己可以访问此资源。", "accessRule.permission": "权限", "accessRule.resourceOpenScope": "访问范围", "accessRule.resourceOpenScopeDescription": "选择此资源对哪些成员开放。角色权限仍决定每位成员可执行的操作。", diff --git a/web/i18n/zh-Hant/permission.json b/web/i18n/zh-Hant/permission.json index 60c1ab81573..a0b97191112 100644 --- a/web/i18n/zh-Hant/permission.json +++ b/web/i18n/zh-Hant/permission.json @@ -28,6 +28,8 @@ "accessRule.noDescription": "無描述", "accessRule.noRules": "暫無訪問規則", "accessRule.noUserAccessSettings": "暫無個人權限設定", + "accessRule.onlyMe": "僅自己", + "accessRule.onlyMeDescription": "僅你自己可以存取此資源。", "accessRule.permission": "權限", "accessRule.resourceOpenScope": "訪問範圍", "accessRule.resourceOpenScopeDescription": "選擇此資源對哪些成員開放。角色權限仍決定每位成員可執行的操作。", diff --git a/web/models/access-control.ts b/web/models/access-control.ts index db13e9159e0..7a5e9341f3c 100644 --- a/web/models/access-control.ts +++ b/web/models/access-control.ts @@ -263,8 +263,4 @@ export type RemoveAppAccessPolicyMemberBindingsRequest = RemoveResourceAccessPol export type RemoveDatasetAccessPolicyMemberBindingsRequest = RemoveResourceAccessPolicyMemberBindingsRequest -export type GetAccessPolicyDetailResponse = { - access_policies: Pick[] -} - -export type ResourceOpenScope = 'all' | 'specific' +export type ResourceOpenScope = 'all' | 'only_me' | 'specific' diff --git a/web/service/access-control/__tests__/normalizers.spec.ts b/web/service/access-control/__tests__/normalizers.spec.ts new file mode 100644 index 00000000000..8a21b64e046 --- /dev/null +++ b/web/service/access-control/__tests__/normalizers.spec.ts @@ -0,0 +1,23 @@ +import type { ResourceUserAccessPoliciesResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import { normalizeAppUserAccessPolicies } from '../normalizers' + +const createResourceUserAccessPoliciesResponse = ( + overrides: Partial = {}, +): ResourceUserAccessPoliciesResponse => ({ + data: [], + scope: 'specific', + ...overrides, +}) + +describe('access-control normalizers', () => { + // Resource access scope values come from the RBAC whitelist enum and must not be collapsed. + describe('Resource user access policies', () => { + it('should preserve only-me open scope when normalizing app user access policies', () => { + const response = createResourceUserAccessPoliciesResponse({ + scope: 'only_me', + }) + + expect(normalizeAppUserAccessPolicies(response).scope).toBe('only_me') + }) + }) +}) diff --git a/web/service/access-control/__tests__/use-app-access-config.spec.tsx b/web/service/access-control/__tests__/use-app-access-config.spec.tsx index 879fba01e45..0b0b93ce2d0 100644 --- a/web/service/access-control/__tests__/use-app-access-config.spec.tsx +++ b/web/service/access-control/__tests__/use-app-access-config.spec.tsx @@ -28,25 +28,55 @@ const mocks = vi.hoisted(() => ({ vi.mock('@/service/client', () => ({ consoleClient: { - rbacAccessConfig: { - apps: { - updateOpenScope: mocks.updateOpenScope, - updateUserAccessSettings: mocks.updateUserAccessSettings, - removeMemberBindings: mocks.removeMemberBindings, + workspaces: { + current: { + rbac: { + apps: { + byAppId: { + accessPolicies: { + byPolicyId: { + memberBindings: { + delete: mocks.removeMemberBindings, + }, + }, + }, + users: { + byTargetAccountId: { + accessPolicies: { + put: mocks.updateUserAccessSettings, + }, + }, + }, + whitelist: { + put: mocks.updateOpenScope, + }, + }, + }, + }, }, }, }, consoleQuery: { - rbacAccessConfig: { - apps: { - accessRules: { - key: mocks.accessRulesKey, - queryOptions: mocks.accessRulesQueryOptions, - }, - userAccessSettings: { - key: mocks.userAccessSettingsKey, - queryKey: mocks.userAccessSettingsQueryKey, - queryOptions: mocks.userAccessSettingsQueryOptions, + workspaces: { + current: { + rbac: { + apps: { + byAppId: { + accessPolicy: { + get: { + key: mocks.accessRulesKey, + queryOptions: mocks.accessRulesQueryOptions, + }, + }, + userAccessPolicies: { + get: { + key: mocks.userAccessSettingsKey, + queryKey: mocks.userAccessSettingsQueryKey, + queryOptions: mocks.userAccessSettingsQueryOptions, + }, + }, + }, + }, }, }, }, @@ -79,7 +109,7 @@ describe('use-app-access-config', () => { expect(mocks.accessRulesQueryOptions).toHaveBeenCalledWith({ input: { params: { - appId: 'app-1', + app_id: 'app-1', }, query: { language: 'zh', @@ -97,7 +127,7 @@ describe('use-app-access-config', () => { expect(mocks.userAccessSettingsQueryOptions).toHaveBeenCalledWith({ input: { params: { - appId: 'app-1', + app_id: 'app-1', }, query: { language: 'en', @@ -115,8 +145,8 @@ describe('use-app-access-config', () => { expect(mocks.updateUserAccessSettings).toHaveBeenCalledWith({ params: { - appId: 'app-1', - accountId: 'account-1', + app_id: 'app-1', + target_account_id: 'account-1', }, body: { access_policy_ids: ['policy-1', 'policy-2'], @@ -135,8 +165,8 @@ describe('use-app-access-config', () => { expect(mocks.removeMemberBindings).toHaveBeenCalledWith({ params: { - appId: 'app-1', - policyId: 'policy-1', + app_id: 'app-1', + policy_id: 'policy-1', }, body: { account_ids: ['account-1'], @@ -155,7 +185,7 @@ describe('use-app-access-config', () => { expect(mocks.updateOpenScope).toHaveBeenCalledWith({ params: { - appId: 'app-1', + app_id: 'app-1', }, body: { scope: 'all', diff --git a/web/service/access-control/__tests__/use-dataset-access-config.spec.tsx b/web/service/access-control/__tests__/use-dataset-access-config.spec.tsx index da9ab1b7b4e..3d504e5378c 100644 --- a/web/service/access-control/__tests__/use-dataset-access-config.spec.tsx +++ b/web/service/access-control/__tests__/use-dataset-access-config.spec.tsx @@ -28,25 +28,55 @@ const mocks = vi.hoisted(() => ({ vi.mock('@/service/client', () => ({ consoleClient: { - rbacAccessConfig: { - datasets: { - updateOpenScope: mocks.updateOpenScope, - updateUserAccessSettings: mocks.updateUserAccessSettings, - removeMemberBindings: mocks.removeMemberBindings, + workspaces: { + current: { + rbac: { + datasets: { + byDatasetId: { + accessPolicies: { + byPolicyId: { + memberBindings: { + delete: mocks.removeMemberBindings, + }, + }, + }, + users: { + byTargetAccountId: { + accessPolicies: { + put: mocks.updateUserAccessSettings, + }, + }, + }, + whitelist: { + put: mocks.updateOpenScope, + }, + }, + }, + }, }, }, }, consoleQuery: { - rbacAccessConfig: { - datasets: { - accessRules: { - key: mocks.accessRulesKey, - queryOptions: mocks.accessRulesQueryOptions, - }, - userAccessSettings: { - key: mocks.userAccessSettingsKey, - queryKey: mocks.userAccessSettingsQueryKey, - queryOptions: mocks.userAccessSettingsQueryOptions, + workspaces: { + current: { + rbac: { + datasets: { + byDatasetId: { + accessPolicy: { + get: { + key: mocks.accessRulesKey, + queryOptions: mocks.accessRulesQueryOptions, + }, + }, + userAccessPolicies: { + get: { + key: mocks.userAccessSettingsKey, + queryKey: mocks.userAccessSettingsQueryKey, + queryOptions: mocks.userAccessSettingsQueryOptions, + }, + }, + }, + }, }, }, }, @@ -79,7 +109,7 @@ describe('use-dataset-access-config', () => { expect(mocks.accessRulesQueryOptions).toHaveBeenCalledWith({ input: { params: { - datasetId: 'dataset-1', + dataset_id: 'dataset-1', }, query: { language: 'ja', @@ -97,7 +127,7 @@ describe('use-dataset-access-config', () => { expect(mocks.userAccessSettingsQueryOptions).toHaveBeenCalledWith({ input: { params: { - datasetId: 'dataset-1', + dataset_id: 'dataset-1', }, query: { language: 'zh', @@ -115,8 +145,8 @@ describe('use-dataset-access-config', () => { expect(mocks.updateUserAccessSettings).toHaveBeenCalledWith({ params: { - datasetId: 'dataset-1', - accountId: 'account-1', + dataset_id: 'dataset-1', + target_account_id: 'account-1', }, body: { access_policy_ids: ['policy-1', 'policy-2'], @@ -135,8 +165,8 @@ describe('use-dataset-access-config', () => { expect(mocks.removeMemberBindings).toHaveBeenCalledWith({ params: { - datasetId: 'dataset-1', - policyId: 'policy-1', + dataset_id: 'dataset-1', + policy_id: 'policy-1', }, body: { account_ids: ['account-1'], @@ -155,7 +185,7 @@ describe('use-dataset-access-config', () => { expect(mocks.updateOpenScope).toHaveBeenCalledWith({ params: { - datasetId: 'dataset-1', + dataset_id: 'dataset-1', }, body: { scope: 'specific', diff --git a/web/service/access-control/normalizers.ts b/web/service/access-control/normalizers.ts new file mode 100644 index 00000000000..512064d02fe --- /dev/null +++ b/web/service/access-control/normalizers.ts @@ -0,0 +1,175 @@ +import type { + AccessPolicy, + AccessPolicyResourceType, + AccessPolicyWithBindings, + GetAppAccessPolicyByAppIdResponse, + GetAppUserAccessSettingsResponse, + GetDatasetAccessPolicyByDatasetIdResponse, + GetDatasetUserAccessSettingsResponse, + ResourceOpenScope, + ResourceUserAccessSetting, + Role, +} from '@/models/access-control' + +type GeneratedAccessMatrixItem = import('@dify/contracts/api/console/workspaces/types.gen').AccessMatrixItem +type GeneratedAccessPolicy = import('@dify/contracts/api/console/workspaces/types.gen').AccessPolicy +type GeneratedAccessPolicyAccount = import('@dify/contracts/api/console/workspaces/types.gen').AccessPolicyAccount +type GeneratedAccessPolicyRole = import('@dify/contracts/api/console/workspaces/types.gen').AccessPolicyRole +type GeneratedAppAccessMatrix = import('@dify/contracts/api/console/workspaces/types.gen').AppAccessMatrix +type GeneratedDatasetAccessMatrix = import('@dify/contracts/api/console/workspaces/types.gen').DatasetAccessMatrix +type GeneratedRbacRole = import('@dify/contracts/api/console/workspaces/types.gen').RbacRole +type GeneratedRbacRoleAccount = import('@dify/contracts/api/console/workspaces/types.gen').RbacRoleAccount +type GeneratedResourceOpenScope = import('@dify/contracts/api/console/workspaces/types.gen').RbacResourceWhitelistScope +type GeneratedResourceUserAccessPoliciesResponse = import('@dify/contracts/api/console/workspaces/types.gen').ResourceUserAccessPoliciesResponse +type GeneratedResourceUserAccessPolicies = NonNullable[number] + +const normalizeRoleCategory = (category?: string): Role['category'] => { + if (category === 'global_system_default') + return 'global_system_default' + + return 'global_custom' +} + +const normalizeRoleType = (type?: string): Role['type'] => { + if (type === 'app' || type === 'dataset') + return type + + return 'workspace' +} + +const normalizeRoleTag = (roleTag?: string): Role['role_tag'] => { + if (roleTag === 'owner') + return 'owner' + + return '' +} + +const normalizeResourceType = (resourceType: string, fallback: AccessPolicyResourceType): AccessPolicyResourceType => { + if (resourceType === 'app' || resourceType === 'dataset') + return resourceType + + return fallback +} + +const normalizeAccessPolicy = ( + policy: GeneratedAccessPolicy, + fallbackResourceType: AccessPolicyResourceType, +): AccessPolicy => ({ + id: policy.id, + tenant_id: policy.tenant_id ?? '', + resource_type: normalizeResourceType(policy.resource_type, fallbackResourceType), + policy_key: policy.policy_key ?? '', + name: policy.name, + description: policy.description ?? '', + permission_keys: policy.permission_keys ?? [], + is_builtin: policy.is_builtin ?? false, + category: normalizeRoleCategory(policy.category), + created_at: String(policy.created_at ?? 0), + updated_at: String(policy.updated_at ?? 0), +}) + +const normalizeAccessPolicyRole = (role: GeneratedAccessPolicyRole) => ({ + role_id: role.role_id, + role_name: role.role_name, + binding_id: role.binding_id, + is_locked: role.is_locked ?? false, + role_tag: normalizeRoleTag(role.role_tag), +}) + +const normalizeAccessPolicyAccount = (account: GeneratedAccessPolicyAccount) => ({ + account_id: account.account_id, + account_name: account.account_name, + binding_id: account.binding_id, + is_locked: account.is_locked ?? false, + avatar: account.avatar ?? '', +}) + +const normalizeAccessMatrixItem = ( + item: GeneratedAccessMatrixItem, + fallbackResourceType: AccessPolicyResourceType, +): AccessPolicyWithBindings | null => { + if (!item.policy) + return null + + return { + policy: normalizeAccessPolicy(item.policy, fallbackResourceType), + roles: (item.roles ?? []).map(normalizeAccessPolicyRole), + accounts: (item.accounts ?? []).map(normalizeAccessPolicyAccount), + } +} + +const isAccessPolicyWithBindings = (item: AccessPolicyWithBindings | null): item is AccessPolicyWithBindings => { + return item !== null +} + +const normalizeResourceOpenScope = (scope: GeneratedResourceOpenScope): ResourceOpenScope => { + switch (scope) { + case 'all': + return 'all' + case 'only_me': + return 'only_me' + case 'specific': + return 'specific' + } +} + +const normalizeAccount = (account: GeneratedRbacRoleAccount): ResourceUserAccessSetting['account'] => ({ + account_id: account.account_id, + account_name: account.account_name ?? '', + email: account.email ?? '', + avatar: account.avatar ?? '', +}) + +const normalizeRole = (role: GeneratedRbacRole): ResourceUserAccessSetting['roles'][number] => ({ + id: role.id, + type: normalizeRoleType(role.type), + category: normalizeRoleCategory(role.category), + name: role.name, + is_builtin: role.is_builtin ?? false, + permission_keys: role.permission_keys ?? [], +}) + +const normalizeResourceUserAccessSetting = ( + setting: GeneratedResourceUserAccessPolicies, + fallbackResourceType: AccessPolicyResourceType, +): ResourceUserAccessSetting => ({ + account: normalizeAccount(setting.account), + roles: (setting.roles ?? []).map(normalizeRole), + access_policies: (setting.access_policies ?? []).map(policy => normalizeAccessPolicy(policy, fallbackResourceType)), +}) + +const normalizeResourceUserAccessPolicies = ( + response: GeneratedResourceUserAccessPoliciesResponse, + fallbackResourceType: AccessPolicyResourceType, +): GetAppUserAccessSettingsResponse | GetDatasetUserAccessSettingsResponse => ({ + data: (response.data ?? []).map(setting => normalizeResourceUserAccessSetting(setting, fallbackResourceType)), + scope: normalizeResourceOpenScope(response.scope), +}) + +export const normalizeAppAccessMatrix = (response: GeneratedAppAccessMatrix): GetAppAccessPolicyByAppIdResponse => ({ + app_id: response.app_id ?? '', + items: (response.items ?? []) + .map(item => normalizeAccessMatrixItem(item, 'app')) + .filter(isAccessPolicyWithBindings), +}) + +export const normalizeDatasetAccessMatrix = ( + response: GeneratedDatasetAccessMatrix, +): GetDatasetAccessPolicyByDatasetIdResponse => ({ + dataset_id: response.dataset_id ?? '', + items: (response.items ?? []) + .map(item => normalizeAccessMatrixItem(item, 'dataset')) + .filter(isAccessPolicyWithBindings), +}) + +export const normalizeAppUserAccessPolicies = ( + response: GeneratedResourceUserAccessPoliciesResponse, +): GetAppUserAccessSettingsResponse => { + return normalizeResourceUserAccessPolicies(response, 'app') +} + +export const normalizeDatasetUserAccessPolicies = ( + response: GeneratedResourceUserAccessPoliciesResponse, +): GetDatasetUserAccessSettingsResponse => { + return normalizeResourceUserAccessPolicies(response, 'dataset') +} diff --git a/web/service/access-control/use-app-access-config.ts b/web/service/access-control/use-app-access-config.ts index 79f9426f279..125e05175f9 100644 --- a/web/service/access-control/use-app-access-config.ts +++ b/web/service/access-control/use-app-access-config.ts @@ -6,33 +6,42 @@ import type { } from '@/models/access-control' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { consoleClient, consoleQuery } from '@/service/client' +import { normalizeAppAccessMatrix, normalizeAppUserAccessPolicies } from './normalizers' const NAME_SPACE = 'app-access-config' +const appRbacContract = consoleQuery.workspaces.current.rbac.apps.byAppId +const appRbacClient = consoleClient.workspaces.current.rbac.apps.byAppId export const useAppAccessRules = (appId: string, language: AccessControlTemplateLanguage) => { - return useQuery(consoleQuery.rbacAccessConfig.apps.accessRules.queryOptions({ - input: { - params: { - appId, + return useQuery({ + ...appRbacContract.accessPolicy.get.queryOptions({ + input: { + params: { + app_id: appId, + }, + query: { + language, + }, }, - query: { - language, - }, - }, - })) + }), + select: normalizeAppAccessMatrix, + }) } export const useAppUserAccessSettings = (appId: string, language: AccessControlTemplateLanguage) => { - return useQuery(consoleQuery.rbacAccessConfig.apps.userAccessSettings.queryOptions({ - input: { - params: { - appId, + return useQuery({ + ...appRbacContract.userAccessPolicies.get.queryOptions({ + input: { + params: { + app_id: appId, + }, + query: { + language, + }, }, - query: { - language, - }, - }, - })) + }), + select: normalizeAppUserAccessPolicies, + }) } export const useUpdateAppUserAccessSettings = (appId: string) => { @@ -40,10 +49,10 @@ export const useUpdateAppUserAccessSettings = (appId: string) => { return useMutation({ mutationKey: [NAME_SPACE, 'update-app-user-access-settings', appId], - mutationFn: (payload: UpdateAppUserAccessSettingsRequest) => consoleClient.rbacAccessConfig.apps.updateUserAccessSettings({ + mutationFn: (payload: UpdateAppUserAccessSettingsRequest) => appRbacClient.users.byTargetAccountId.accessPolicies.put({ params: { - appId, - accountId: payload.accountId, + app_id: appId, + target_account_id: payload.accountId, }, body: { access_policy_ids: payload.accessPolicyIds, @@ -52,10 +61,10 @@ export const useUpdateAppUserAccessSettings = (appId: string) => { onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.apps.userAccessSettings.key(), + queryKey: appRbacContract.userAccessPolicies.get.key({ type: 'query' }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.apps.accessRules.key(), + queryKey: appRbacContract.accessPolicy.get.key({ type: 'query' }), }), ]) }, @@ -67,10 +76,10 @@ export const useRemoveAppAccessPolicyMemberBindings = (appId: string) => { return useMutation({ mutationKey: [NAME_SPACE, 'remove-app-access-policy-member-bindings', appId], - mutationFn: (payload: RemoveAppAccessPolicyMemberBindingsRequest) => consoleClient.rbacAccessConfig.apps.removeMemberBindings({ + mutationFn: (payload: RemoveAppAccessPolicyMemberBindingsRequest) => appRbacClient.accessPolicies.byPolicyId.memberBindings.delete({ params: { - appId, - policyId: payload.accessPolicyId, + app_id: appId, + policy_id: payload.accessPolicyId, }, body: { account_ids: payload.accountIds, @@ -79,10 +88,10 @@ export const useRemoveAppAccessPolicyMemberBindings = (appId: string) => { onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.apps.userAccessSettings.key(), + queryKey: appRbacContract.userAccessPolicies.get.key({ type: 'query' }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.apps.accessRules.key(), + queryKey: appRbacContract.accessPolicy.get.key({ type: 'query' }), }), ]) }, @@ -94,9 +103,9 @@ export const useUpdateAppOpenScope = (appId: string) => { return useMutation({ mutationKey: [NAME_SPACE, 'update-app-open-scope', appId], - mutationFn: (openScope: ResourceOpenScope) => consoleClient.rbacAccessConfig.apps.updateOpenScope({ + mutationFn: (openScope: ResourceOpenScope) => appRbacClient.whitelist.put({ params: { - appId, + app_id: appId, }, body: { scope: openScope, @@ -105,7 +114,7 @@ export const useUpdateAppOpenScope = (appId: string) => { onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.apps.userAccessSettings.key(), + queryKey: appRbacContract.userAccessPolicies.get.key({ type: 'query' }), }), ]) }, diff --git a/web/service/access-control/use-dataset-access-config.ts b/web/service/access-control/use-dataset-access-config.ts index 36f3727daf9..c79b47dac1c 100644 --- a/web/service/access-control/use-dataset-access-config.ts +++ b/web/service/access-control/use-dataset-access-config.ts @@ -6,8 +6,11 @@ import type { } from '@/models/access-control' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { consoleClient, consoleQuery } from '@/service/client' +import { normalizeDatasetAccessMatrix, normalizeDatasetUserAccessPolicies } from './normalizers' const NAME_SPACE = 'dataset-access-config' +const datasetRbacContract = consoleQuery.workspaces.current.rbac.datasets.byDatasetId +const datasetRbacClient = consoleClient.workspaces.current.rbac.datasets.byDatasetId type DatasetAccessConfigQueryOptions = { enabled?: boolean @@ -15,10 +18,10 @@ type DatasetAccessConfigQueryOptions = { export const useDatasetAccessRules = (datasetId: string, language: AccessControlTemplateLanguage, options?: DatasetAccessConfigQueryOptions) => { return useQuery({ - ...consoleQuery.rbacAccessConfig.datasets.accessRules.queryOptions({ + ...datasetRbacContract.accessPolicy.get.queryOptions({ input: { params: { - datasetId, + dataset_id: datasetId, }, query: { language, @@ -26,15 +29,16 @@ export const useDatasetAccessRules = (datasetId: string, language: AccessControl }, }), enabled: options?.enabled ?? true, + select: normalizeDatasetAccessMatrix, }) } export const useDatasetUserAccessSettings = (datasetId: string, language: AccessControlTemplateLanguage, options?: DatasetAccessConfigQueryOptions) => { return useQuery({ - ...consoleQuery.rbacAccessConfig.datasets.userAccessSettings.queryOptions({ + ...datasetRbacContract.userAccessPolicies.get.queryOptions({ input: { params: { - datasetId, + dataset_id: datasetId, }, query: { language, @@ -42,6 +46,7 @@ export const useDatasetUserAccessSettings = (datasetId: string, language: Access }, }), enabled: options?.enabled ?? true, + select: normalizeDatasetUserAccessPolicies, }) } @@ -50,10 +55,10 @@ export const useUpdateDatasetUserAccessSettings = (datasetId: string) => { return useMutation({ mutationKey: [NAME_SPACE, 'update-dataset-user-access-settings', datasetId], - mutationFn: (payload: UpdateDatasetUserAccessSettingsRequest) => consoleClient.rbacAccessConfig.datasets.updateUserAccessSettings({ + mutationFn: (payload: UpdateDatasetUserAccessSettingsRequest) => datasetRbacClient.users.byTargetAccountId.accessPolicies.put({ params: { - datasetId, - accountId: payload.accountId, + dataset_id: datasetId, + target_account_id: payload.accountId, }, body: { access_policy_ids: payload.accessPolicyIds, @@ -62,10 +67,10 @@ export const useUpdateDatasetUserAccessSettings = (datasetId: string) => { onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.datasets.userAccessSettings.key(), + queryKey: datasetRbacContract.userAccessPolicies.get.key({ type: 'query' }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.datasets.accessRules.key(), + queryKey: datasetRbacContract.accessPolicy.get.key({ type: 'query' }), }), ]) }, @@ -77,10 +82,10 @@ export const useRemoveDatasetAccessPolicyMemberBindings = (datasetId: string) => return useMutation({ mutationKey: [NAME_SPACE, 'remove-dataset-access-policy-member-bindings', datasetId], - mutationFn: (payload: RemoveDatasetAccessPolicyMemberBindingsRequest) => consoleClient.rbacAccessConfig.datasets.removeMemberBindings({ + mutationFn: (payload: RemoveDatasetAccessPolicyMemberBindingsRequest) => datasetRbacClient.accessPolicies.byPolicyId.memberBindings.delete({ params: { - datasetId, - policyId: payload.accessPolicyId, + dataset_id: datasetId, + policy_id: payload.accessPolicyId, }, body: { account_ids: payload.accountIds, @@ -89,10 +94,10 @@ export const useRemoveDatasetAccessPolicyMemberBindings = (datasetId: string) => onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.datasets.userAccessSettings.key(), + queryKey: datasetRbacContract.userAccessPolicies.get.key({ type: 'query' }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.datasets.accessRules.key(), + queryKey: datasetRbacContract.accessPolicy.get.key({ type: 'query' }), }), ]) }, @@ -104,9 +109,9 @@ export const useUpdateDatasetOpenScope = (datasetId: string) => { return useMutation({ mutationKey: [NAME_SPACE, 'update-dataset-open-scope', datasetId], - mutationFn: (openScope: ResourceOpenScope) => consoleClient.rbacAccessConfig.datasets.updateOpenScope({ + mutationFn: (openScope: ResourceOpenScope) => datasetRbacClient.whitelist.put({ params: { - datasetId, + dataset_id: datasetId, }, body: { scope: openScope, @@ -115,7 +120,7 @@ export const useUpdateDatasetOpenScope = (datasetId: string) => { onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.rbacAccessConfig.datasets.userAccessSettings.key(), + queryKey: datasetRbacContract.userAccessPolicies.get.key({ type: 'query' }), }), ]) }, diff --git a/web/service/console-router-loader.ts b/web/service/console-router-loader.ts index d50344d4a2a..5cc657ab359 100644 --- a/web/service/console-router-loader.ts +++ b/web/service/console-router-loader.ts @@ -17,8 +17,6 @@ const customConsoleContractLoaders: Record Promise import('@/contract/console/model-providers').then(({ modelProvidersRouterContract }) => wrapConsoleContract('modelProviders', modelProvidersRouterContract)), plugins: () => import('@/contract/console/plugins').then(({ pluginsRouterContract }) => wrapConsoleContract('plugins', pluginsRouterContract)), - rbacAccessConfig: () => - import('@/contract/console/access-control').then(({ rbacAccessConfigContract }) => wrapConsoleContract('rbacAccessConfig', rbacAccessConfigContract)), snippets: () => import('@/contract/console/snippets').then(({ snippetsRouterContract }) => wrapConsoleContract('snippets', snippetsRouterContract)), triggers: () => import('@/contract/console/trigger').then(({ triggersRouterContract }) => wrapConsoleContract('triggers', triggersRouterContract)), trialApps: () => import('@/contract/console/try-app').then(({ trialAppsRouterContract }) => wrapConsoleContract('trialApps', trialAppsRouterContract)),