mirror of
https://github.com/langgenius/dify.git
synced 2026-06-25 22:31:10 +08:00
fix: respect legacy plugin permissions without RBAC (#37903)
This commit is contained in:
parent
2483c091aa
commit
d349e892f4
@ -5,7 +5,10 @@ import { hasPermission } from '@/utils/permission'
|
||||
const pluginReadAndUpdatePermissionKeys = ['plugin.install', 'plugin.manage']
|
||||
|
||||
const useWorkspacePluginInstallPermission = () => {
|
||||
const { langGeniusVersionInfo, workspacePermissionKeys } = useAppContext()
|
||||
const {
|
||||
langGeniusVersionInfo,
|
||||
workspacePermissionKeys,
|
||||
} = useAppContext()
|
||||
|
||||
const canInstallPlugin = useMemo(() => {
|
||||
return hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
|
||||
@ -164,7 +164,7 @@ describe('useReferenceSetting Hook', () => {
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
})
|
||||
|
||||
it('should use plugin keys even when legacy admin permission is configured', () => {
|
||||
it('should use plugin keys even when legacy admin permission is configured and RBAC is enabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
@ -179,11 +179,40 @@ describe('useReferenceSetting Hook', () => {
|
||||
},
|
||||
} as ReturnType<typeof usePluginPermissionSettings>)
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool))
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), {
|
||||
systemFeatures: { rbac_enabled: true },
|
||||
})
|
||||
|
||||
expect(result.current.canManagement).toBe(true)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
|
||||
it('should apply legacy noOne plugin permissions when RBAC is disabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.manage', 'plugin.debug'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
install_permission: PermissionType.noOne,
|
||||
debug_permission: PermissionType.noOne,
|
||||
},
|
||||
} as ReturnType<typeof usePluginPermissionSettings>)
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), {
|
||||
systemFeatures: { rbac_enabled: false },
|
||||
})
|
||||
|
||||
expect(result.current.canInstallPlugin).toBe(false)
|
||||
expect(result.current.canManagement).toBe(false)
|
||||
expect(result.current.canUpdatePlugin).toBe(false)
|
||||
expect(result.current.canViewInstalledPlugins).toBe(true)
|
||||
expect(result.current.canManagePlugin).toBe(false)
|
||||
expect(result.current.canDebugPlugin).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('canSetPermissions', () => {
|
||||
@ -446,15 +475,33 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
expect(result.current.canInstallPluginFromMarketplace).toBe(false)
|
||||
})
|
||||
|
||||
it('should not fetch legacy plugin permissions or category auto-upgrade settings', () => {
|
||||
it('should fetch legacy plugin permissions but not category auto-upgrade settings', () => {
|
||||
renderHook(() => useCanInstallPluginFromMarketplace(), {
|
||||
systemFeatures: { enable_marketplace: true },
|
||||
})
|
||||
|
||||
expect(usePluginPermissionSettings).not.toHaveBeenCalled()
|
||||
expect(usePluginPermissionSettings).toHaveBeenCalled()
|
||||
expect(usePluginAutoUpgradeSettings).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return false when legacy install permission is noOne and RBAC is disabled', () => {
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
install_permission: PermissionType.noOne,
|
||||
debug_permission: PermissionType.everyone,
|
||||
},
|
||||
} as ReturnType<typeof usePluginPermissionSettings>)
|
||||
|
||||
const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), {
|
||||
systemFeatures: {
|
||||
enable_marketplace: true,
|
||||
rbac_enabled: false,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.current.canInstallPluginFromMarketplace).toBe(false)
|
||||
})
|
||||
|
||||
it('should use plugin.install when marketplace and RBAC are enabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
isCurrentWorkspaceManager: false,
|
||||
|
||||
@ -7,6 +7,7 @@ import { useAppContext } from '@/context/app-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, useMutationReferenceSettings, usePluginAutoUpgradeSettings, usePluginPermissionSettings } from '@/service/use-plugins'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { hasLegacyPluginPermissionAccess } from '../plugin-permissions'
|
||||
|
||||
const pluginReadAndUpdatePermissionKeys = ['plugin.install', 'plugin.manage']
|
||||
|
||||
@ -26,7 +27,11 @@ const useCanSetPluginSettings = () => {
|
||||
|
||||
export const usePluginSettingsAccess = () => {
|
||||
const { t } = useTranslation()
|
||||
const { workspacePermissionKeys, langGeniusVersionInfo } = useAppContext()
|
||||
const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner, workspacePermissionKeys, langGeniusVersionInfo } = useAppContext()
|
||||
const { data: rbacEnabled } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: s => s.rbac_enabled,
|
||||
})
|
||||
const { canSetPermissions, canSetPluginPreferences } = useCanSetPluginSettings()
|
||||
const permissionQuery = usePluginPermissionSettings()
|
||||
const { data: permissions } = permissionQuery
|
||||
@ -35,11 +40,22 @@ export const usePluginSettingsAccess = () => {
|
||||
toast.success(t('api.actionSuccess', { ns: 'common' }))
|
||||
},
|
||||
})
|
||||
const canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
const canUpdatePlugin = hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys)
|
||||
const isAdminOrOwner = isCurrentWorkspaceManager || isCurrentWorkspaceOwner
|
||||
const legacyCanInstallPlugin = hasLegacyPluginPermissionAccess({
|
||||
isAdminOrOwner,
|
||||
permission: permissions?.install_permission,
|
||||
rbacEnabled,
|
||||
})
|
||||
const legacyCanDebugPlugin = hasLegacyPluginPermissionAccess({
|
||||
isAdminOrOwner,
|
||||
permission: permissions?.debug_permission,
|
||||
rbacEnabled,
|
||||
})
|
||||
const canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install') && legacyCanInstallPlugin
|
||||
const canUpdatePlugin = hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys) && legacyCanInstallPlugin
|
||||
const canViewInstalledPlugins = hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys)
|
||||
const canManagePlugin = hasPermission(workspacePermissionKeys, 'plugin.manage')
|
||||
const canDebugPlugin = hasPermission(workspacePermissionKeys, 'plugin.debug')
|
||||
const canManagePlugin = hasPermission(workspacePermissionKeys, 'plugin.manage') && legacyCanInstallPlugin
|
||||
const canDebugPlugin = hasPermission(workspacePermissionKeys, 'plugin.debug') && legacyCanDebugPlugin
|
||||
|
||||
return {
|
||||
permission: permissions,
|
||||
@ -102,12 +118,18 @@ const useReferenceSetting = (category: PluginCategoryEnum) => {
|
||||
}
|
||||
|
||||
export const useCanInstallPluginFromMarketplace = () => {
|
||||
const { data: marketplaceAccess } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: s => s.enable_marketplace,
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const marketplaceAccess = systemFeatures.enable_marketplace
|
||||
const rbacEnabled = systemFeatures.rbac_enabled
|
||||
const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner, workspacePermissionKeys } = useAppContext()
|
||||
const permissionQuery = usePluginPermissionSettings()
|
||||
const { data: permissions } = permissionQuery
|
||||
const legacyCanInstallPlugin = hasLegacyPluginPermissionAccess({
|
||||
isAdminOrOwner: isCurrentWorkspaceManager || isCurrentWorkspaceOwner,
|
||||
permission: permissions?.install_permission,
|
||||
rbacEnabled,
|
||||
})
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
const canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install') && legacyCanInstallPlugin
|
||||
|
||||
const canInstallPluginFromMarketplace = useMemo(() => {
|
||||
return Boolean(marketplaceAccess && canInstallPlugin)
|
||||
|
||||
27
web/app/components/plugins/plugin-permissions.ts
Normal file
27
web/app/components/plugins/plugin-permissions.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { PermissionType } from './types'
|
||||
|
||||
type LegacyPluginPermissionAccessOptions = {
|
||||
isAdminOrOwner: boolean
|
||||
permission?: PermissionType
|
||||
rbacEnabled?: boolean
|
||||
}
|
||||
|
||||
export const hasLegacyPluginPermissionAccess = ({
|
||||
isAdminOrOwner,
|
||||
permission,
|
||||
rbacEnabled,
|
||||
}: LegacyPluginPermissionAccessOptions) => {
|
||||
if (rbacEnabled !== false)
|
||||
return true
|
||||
|
||||
if (!permission)
|
||||
return false
|
||||
|
||||
if (permission === PermissionType.everyone)
|
||||
return true
|
||||
|
||||
if (permission === PermissionType.admin)
|
||||
return isAdminOrOwner
|
||||
|
||||
return false
|
||||
}
|
||||
@ -15,6 +15,10 @@ const { mockRouterPush } = vi.hoisted(() => ({
|
||||
mockRouterPush: vi.fn(),
|
||||
}))
|
||||
|
||||
const { mockCanInstallPlugin } = vi.hoisted(() => ({
|
||||
mockCanInstallPlugin: vi.fn(() => true),
|
||||
}))
|
||||
|
||||
const listRenderSpy = vi.fn()
|
||||
vi.mock('@/app/components/plugins/marketplace/list', () => ({
|
||||
default: (props: {
|
||||
@ -30,6 +34,12 @@ vi.mock('@/app/components/plugins/marketplace/list', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
|
||||
usePluginSettingsAccess: () => ({
|
||||
canInstallPlugin: mockCanInstallPlugin(),
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockUseMarketplaceCollectionsAndPlugins = vi.fn()
|
||||
const mockUseMarketplacePlugins = vi.fn()
|
||||
vi.mock('@/app/components/plugins/marketplace/hooks', () => ({
|
||||
@ -108,6 +118,7 @@ const createMarketplaceContext = (overrides: Partial<ReturnType<typeof useMarket
|
||||
describe('Marketplace', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockCanInstallPlugin.mockReturnValue(true)
|
||||
})
|
||||
|
||||
// Rendering the marketplace panel based on loading and visibility state.
|
||||
@ -154,6 +165,28 @@ describe('Marketplace', () => {
|
||||
showInstallButton: true,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should hide install actions when plugin install permission is missing', () => {
|
||||
mockCanInstallPlugin.mockReturnValue(false)
|
||||
const marketplaceContext = createMarketplaceContext({
|
||||
isLoading: false,
|
||||
plugins: [createPlugin()],
|
||||
})
|
||||
|
||||
render(
|
||||
<Marketplace
|
||||
searchPluginText=""
|
||||
filterPluginTags={[]}
|
||||
isMarketplaceArrowVisible={false}
|
||||
showMarketplacePanel={vi.fn()}
|
||||
marketplaceContext={marketplaceContext}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(listRenderSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
showInstallButton: false,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
// Prop-driven UI output such as links and action triggers.
|
||||
|
||||
@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useLocale } from '#i18n'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import { usePluginSettingsAccess } from '@/app/components/plugins/plugin-page/use-reference-setting'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
import { toolsContentInsetClassNames, toolsUnifiedContentFrameClassName } from '../content-inset'
|
||||
@ -35,6 +36,7 @@ const Marketplace = ({
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const router = useRouter()
|
||||
const { canInstallPlugin } = usePluginSettingsAccess()
|
||||
const {
|
||||
isLoading,
|
||||
marketplaceCollections,
|
||||
@ -127,7 +129,7 @@ const Marketplace = ({
|
||||
marketplaceCollections={marketplaceCollections || []}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
|
||||
plugins={plugins}
|
||||
showInstallButton
|
||||
showInstallButton={canInstallPlugin}
|
||||
cardContainerClassName={cardContainerClassName}
|
||||
onCollectionMoreClick={handleCollectionMoreClick}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user