diff --git a/x-pack/plugins/fleet/common/services/agent_policies_helper.test.ts b/x-pack/plugins/fleet/common/services/agent_policies_helper.test.ts index ded9ae843ad6..2ddac31675e3 100644 --- a/x-pack/plugins/fleet/common/services/agent_policies_helper.test.ts +++ b/x-pack/plugins/fleet/common/services/agent_policies_helper.test.ts @@ -61,4 +61,8 @@ describe('getInheritedNamespace', () => { it('should return default namespace when there are no agent policies', () => { expect(getInheritedNamespace([])).toEqual('default'); }); + + it('should allow to override default namespace when there are no agent policies', () => { + expect(getInheritedNamespace([], 'test')).toEqual('test'); + }); }); diff --git a/x-pack/plugins/fleet/common/services/agent_policies_helpers.ts b/x-pack/plugins/fleet/common/services/agent_policies_helpers.ts index 7260e389e14f..8a1e26861468 100644 --- a/x-pack/plugins/fleet/common/services/agent_policies_helpers.ts +++ b/x-pack/plugins/fleet/common/services/agent_policies_helpers.ts @@ -45,9 +45,9 @@ function policyHasIntegration(agentPolicy: AgentPolicy, packageName: string) { return agentPolicy.package_policies?.some((p) => p.package?.name === packageName); } -export function getInheritedNamespace(agentPolicies: AgentPolicy[]): string { +export function getInheritedNamespace(agentPolicies: AgentPolicy[], defaultValue?: string): string { if (agentPolicies.length === 1) { return agentPolicies[0].namespace; } - return 'default'; + return defaultValue ?? 'default'; } diff --git a/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts index ebd6b98a732b..f35fb4af2f14 100644 --- a/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts +++ b/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts @@ -14,6 +14,10 @@ describe('Fleet - isValidNamespace', () => { expect(isValidNamespace('testlengthπŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€').valid).toBe( true ); + expect(isValidNamespace('', true).valid).toBe(true); + expect(isValidNamespace('', true, ['test']).valid).toBe(true); + expect(isValidNamespace('test', false, ['test']).valid).toBe(true); + expect(isValidNamespace('test_dev', false, ['test']).valid).toBe(true); }); it('returns false for invalid namespaces', () => { @@ -36,5 +40,6 @@ describe('Fleet - isValidNamespace', () => { 'testlengthπŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€πŸ˜€' ).valid ).toBe(false); + expect(isValidNamespace('default', false, ['test']).valid).toBe(false); }); }); diff --git a/x-pack/plugins/fleet/common/services/is_valid_namespace.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts index dcc1a2b9b5cb..bb7d6bb2f49c 100644 --- a/x-pack/plugins/fleet/common/services/is_valid_namespace.ts +++ b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts @@ -12,9 +12,43 @@ import { i18n } from '@kbn/i18n'; // and implements a limit based on https://github.com/elastic/kibana/issues/75846 export function isValidNamespace( namespace: string, - allowBlankNamespace?: boolean + allowBlankNamespace?: boolean, + allowedNamespacePrefixes?: string[] ): { valid: boolean; error?: string } { - return isValidEntity(namespace, 'Namespace', allowBlankNamespace); + if (!namespace.trim() && allowBlankNamespace) { + return { valid: true }; + } + + const { valid, error } = isValidEntity(namespace, 'Namespace', allowBlankNamespace); + if (!valid) { + return { valid, error }; + } + + for (const prefix of allowedNamespacePrefixes || []) { + if (!namespace.trim().startsWith(prefix)) { + return allowedNamespacePrefixes?.length === 1 + ? { + valid: false, + error: i18n.translate('xpack.fleet.namespaceValidation.notAllowedPrefixError', { + defaultMessage: 'Namespace should start with {allowedNamespacePrefixes}', + values: { + allowedNamespacePrefixes: allowedNamespacePrefixes?.[0], + }, + }), + } + : { + valid: false, + error: i18n.translate('xpack.fleet.namespaceValidation.notAllowedPrefixesError', { + defaultMessage: + 'Namespace should start with one of these prefixes {allowedNamespacePrefixes}', + values: { + allowedNamespacePrefixes: allowedNamespacePrefixes?.join(', ') ?? '', + }, + }), + }; + } + } + return { valid: true }; } export function isValidDataset( diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index decef8fe628d..1b8551d89b83 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -287,6 +287,7 @@ export const settingsRoutesService = { getInfoPath: () => SETTINGS_API_ROUTES.INFO_PATTERN, getUpdatePath: () => SETTINGS_API_ROUTES.UPDATE_PATTERN, getEnrollmentInfoPath: () => SETTINGS_API_ROUTES.ENROLLMENT_INFO_PATTERN, + getSpaceInfoPath: () => SETTINGS_API_ROUTES.SPACE_INFO_PATTERN, }; export const appRoutesService = { diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index 1de38822cbc8..69a26e674777 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -56,7 +56,8 @@ export type PackagePolicyValidationResults = { export const validatePackagePolicy = ( packagePolicy: NewPackagePolicy, packageInfo: PackageInfo, - safeLoadYaml: (yaml: string) => any + safeLoadYaml: (yaml: string) => any, + spaceSettings?: { allowedNamespacePrefixes?: string[] } ): PackagePolicyValidationResults => { const hasIntegrations = doesPackageHaveIntegrations(packageInfo); const validationResults: PackagePolicyValidationResults = { @@ -75,7 +76,11 @@ export const validatePackagePolicy = ( } if (packagePolicy?.namespace) { - const namespaceValidation = isValidNamespace(packagePolicy?.namespace, true); + const namespaceValidation = isValidNamespace( + packagePolicy?.namespace, + true, + spaceSettings?.allowedNamespacePrefixes + ); if (!namespaceValidation.valid && namespaceValidation.error) { validationResults.namespace = [namespaceValidation.error]; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 5d3dee34339c..65a57cc81523 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -26,6 +26,7 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import type { FleetConfigType, FleetStartServices } from '../../plugin'; import { PackageInstallProvider } from '../integrations/hooks'; +import { SpaceSettingsContextProvider } from '../../hooks/use_space_settings_context'; import { type FleetStatusProviderProps, useAuthz, useFleetStatus, useFlyoutContext } from './hooks'; @@ -213,11 +214,13 @@ export const FleetAppContext: React.FC<{ - - - {children} - - + + + + {children} + + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index ef5dc9b8e3c4..c17e3345bfd1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -63,6 +63,7 @@ import { CustomFields } from './custom_fields'; interface Props { agentPolicy: Partial; + allowedNamespacePrefixes?: string[]; updateAgentPolicy: (u: Partial) => void; validation: ValidationResults; disabled?: boolean; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx index 4ca70267c7ed..96e1056f736c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx @@ -22,14 +22,12 @@ import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; +import { useSpaceSettingsContext } from '../../../../../hooks/use_space_settings_context'; import type { AgentPolicy, NewAgentPolicy } from '../../../types'; - import { sendCreateAgentPolicy, useStartServices, useAuthz } from '../../../hooks'; - import { generateNewAgentPolicyWithDefaults } from '../../../../../../common/services/generate_new_agent_policy'; import { agentPolicyFormValidation } from '.'; - import { AgentPolicyAdvancedOptionsContent } from './agent_policy_advanced_fields'; import { AgentPolicyFormSystemMonitoringCheckbox } from './agent_policy_system_monitoring_field'; @@ -58,11 +56,13 @@ export const AgentPolicyCreateInlineForm: React.FunctionComponent = ({ const [isLoading, setIsLoading] = useState(false); const isDisabled = !authz.fleet.allAgentPolicies || isLoading; + const spaceSettings = useSpaceSettingsContext(); const [newAgentPolicy, setNewAgentPolicy] = useState( generateNewAgentPolicyWithDefaults({ name: agentPolicyName, has_fleet_server: isFleetServerPolicy, + namespace: spaceSettings.defaultNamespace, }) ); @@ -76,7 +76,9 @@ export const AgentPolicyCreateInlineForm: React.FunctionComponent = ({ [setNewAgentPolicy, newAgentPolicy] ); - const validation = agentPolicyFormValidation(newAgentPolicy); + const validation = agentPolicyFormValidation(newAgentPolicy, { + allowedNamespacePrefixes: spaceSettings.allowedNamespacePrefixes, + }); const createAgentPolicy = useCallback(async () => { try { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_validation.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_validation.tsx index 4ba769c31152..bb4e39b265f0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_validation.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_validation.tsx @@ -15,10 +15,15 @@ export interface ValidationResults { } export const agentPolicyFormValidation = ( - agentPolicy: Partial + agentPolicy: Partial, + options?: { allowedNamespacePrefixes?: string[] } ): ValidationResults => { const errors: ValidationResults = {}; - const namespaceValidation = isValidNamespace(agentPolicy.namespace || ''); + const namespaceValidation = isValidNamespace( + agentPolicy.namespace || '', + false, + options?.allowedNamespacePrefixes + ); if (!agentPolicy.name?.trim()) { errors.name = [ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 02e364f3e3d3..4f8d616b2ff6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -11,6 +11,7 @@ import { safeLoad } from 'js-yaml'; import { isEqual } from 'lodash'; +import { useSpaceSettingsContext } from '../../../../../../../hooks/use_space_settings_context'; import type { AgentPolicy, NewPackagePolicy, @@ -152,6 +153,7 @@ export function useOnSubmit({ }) { const { notifications } = useStartServices(); const confirmForceInstall = useConfirmForceInstall(); + const spaceSettings = useSpaceSettingsContext(); // only used to store the resulting package policy once saved const [savedPackagePolicy, setSavedPackagePolicy] = useState(); // Form state @@ -204,7 +206,8 @@ export function useOnSubmit({ const newValidationResult = validatePackagePolicy( newPackagePolicy || packagePolicy, packageInfo, - safeLoad + safeLoad, + spaceSettings ); setValidationResults(newValidationResult); // eslint-disable-next-line no-console @@ -213,7 +216,7 @@ export function useOnSubmit({ return newValidationResult; } }, - [packagePolicy, packageInfo] + [packagePolicy, packageInfo, spaceSettings] ); // Update package policy method const updatePackagePolicy = useCallback( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index 886761404563..67275a3cf403 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -25,6 +25,7 @@ import { } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; +import { useSpaceSettingsContext } from '../../../../../../hooks/use_space_settings_context'; import { SECRETS_MINIMUM_FLEET_SERVER_VERSION } from '../../../../../../../common/constants'; import { @@ -111,12 +112,18 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ const { params } = useRouteMatch(); const fleetStatus = useFleetStatus(); const { docLinks } = useStartServices(); + const spaceSettings = useSpaceSettingsContext(); const [newAgentPolicy, setNewAgentPolicy] = useState( - generateNewAgentPolicyWithDefaults({ name: 'Agent policy 1' }) + generateNewAgentPolicyWithDefaults({ + name: 'Agent policy 1', + namespace: spaceSettings.defaultNamespace, + }) ); const [withSysMonitoring, setWithSysMonitoring] = useState(true); - const validation = agentPolicyFormValidation(newAgentPolicy); + const validation = agentPolicyFormValidation(newAgentPolicy, { + allowedNamespacePrefixes: spaceSettings.allowedNamespacePrefixes, + }); const [selectedPolicyTab, setSelectedPolicyTab] = useState( queryParamsPolicyId ? SelectedPolicyTab.EXISTING : SelectedPolicyTab.NEW @@ -379,7 +386,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ ) : packageInfo ? ( <> ( const [agentPolicy, setAgentPolicy] = useState({ ...originalAgentPolicy, }); + const spaceSettings = useSpaceSettingsContext(); + const [isLoading, setIsLoading] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [agentCount, setAgentCount] = useState(0); const [withSysMonitoring, setWithSysMonitoring] = useState(true); - const validation = agentPolicyFormValidation(agentPolicy); + const validation = agentPolicyFormValidation(agentPolicy, { + allowedNamespacePrefixes: spaceSettings?.allowedNamespacePrefixes, + }); const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState(false); const updateAgentPolicy = (updatedFields: Partial) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx index 9ce4d8b81157..9ade778c74f3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx @@ -10,6 +10,7 @@ import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import type { EuiStepProps } from '@elastic/eui'; +import { useSpaceSettingsContext } from '../../../../../../hooks/use_space_settings_context'; import type { AgentPolicy, NewAgentPolicy, NewPackagePolicy } from '../../../../../../../common'; import { generateNewAgentPolicyWithDefaults } from '../../../../../../../common/services'; import { SelectedPolicyTab, StepSelectHosts } from '../../create_package_policy_page/components'; @@ -49,8 +50,12 @@ export function usePackagePolicySteps({ packagePolicyId, setNewAgentPolicyName, }: Params) { + const spaceSettings = useSpaceSettingsContext(); const [newAgentPolicy, setNewAgentPolicy] = useState( - generateNewAgentPolicyWithDefaults({ name: 'Agent policy 1' }) + generateNewAgentPolicyWithDefaults({ + name: 'Agent policy 1', + namespace: spaceSettings.defaultNamespace, + }) ); const [withSysMonitoring, setWithSysMonitoring] = useState(true); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx index 39b30b601714..f147f7e112ea 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx @@ -24,6 +24,7 @@ import { EuiSpacer, } from '@elastic/eui'; +import { useSpaceSettingsContext } from '../../../../../../hooks/use_space_settings_context'; import type { NewAgentPolicy, AgentPolicy } from '../../../../types'; import { MAX_FLYOUT_WIDTH } from '../../../../constants'; import { useAuthz, useStartServices, sendCreateAgentPolicy } from '../../../../hooks'; @@ -48,12 +49,17 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ }) => { const { notifications } = useStartServices(); const hasFleetAllAgentPoliciesPrivileges = useAuthz().fleet.allAgentPolicies; + const spaceSettings = useSpaceSettingsContext(); const [agentPolicy, setAgentPolicy] = useState( - generateNewAgentPolicyWithDefaults() + generateNewAgentPolicyWithDefaults({ + namespace: spaceSettings.defaultNamespace, + }) ); const [isLoading, setIsLoading] = useState(false); const [withSysMonitoring, setWithSysMonitoring] = useState(true); - const validation = agentPolicyFormValidation(agentPolicy); + const validation = agentPolicyFormValidation(agentPolicy, { + allowedNamespacePrefixes: spaceSettings?.allowedNamespacePrefixes, + }); const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState(false); const updateAgentPolicy = (updatedFields: Partial) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx index 5927bb4140e4..fc697c33af04 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx @@ -51,6 +51,7 @@ describe('AgentDetailsActionMenu', () => { readAgents: true, allAgents: true, }, + integrations: {}, } as any); mockedUseAgentVersion.mockReturnValue('8.10.2'); }); @@ -133,6 +134,7 @@ describe('AgentDetailsActionMenu', () => { fleet: { readAgents: false, }, + integrations: {}, } as any); const res = renderAndGetDiagnosticsButton({ agent: { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx index b13135516d18..10848d3a13c4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx @@ -61,6 +61,7 @@ describe('TableRowActions', () => { readAgents: true, allAgents: true, }, + integrations: {}, } as any); mockedUseAgentVersion.mockReturnValue('8.10.2'); }); @@ -97,6 +98,7 @@ describe('TableRowActions', () => { fleet: { allAgents: false, }, + integrations: {}, } as any); const res = renderAndGetDiagnosticsButton({ agent: { @@ -192,6 +194,7 @@ describe('TableRowActions', () => { fleet: { readAgents: false, }, + integrations: {}, } as any); const res = renderAndGetRestartUpgradeButton({ agent: { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx index f85e7d4a8751..7720361b3346 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx @@ -12,6 +12,7 @@ import { useFleetStatus } from '../../../../hooks/use_fleet_status'; import { useAuthz } from '../../../../hooks/use_authz'; import { AgentsApp } from '.'; +import { useGetSpaceSettings } from '../../hooks'; jest.mock('../../../../hooks/use_fleet_status', () => ({ ...jest.requireActual('../../../../hooks/use_fleet_status'), @@ -49,7 +50,9 @@ describe('AgentApp', () => { readAgents: true, allAgents: true, }, + integrations: {}, } as any); + jest.mocked(useGetSpaceSettings).mockReturnValue({} as any); }); it('should render the loading component if the status is loading', async () => { diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index 21e2ba249b69..992ecff3e49a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -28,6 +28,7 @@ import { KibanaVersionContext, useFleetStatus, } from '../../hooks'; +import { SpaceSettingsContextProvider } from '../../hooks/use_space_settings_context'; import { FleetServerFlyout } from '../fleet/components'; @@ -104,24 +105,26 @@ export const IntegrationsAppContext: React.FC<{ - - - - - - - - - {children} - - - - - - - + + + + + + + + + + {children} + + + + + + + + diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.tsx index 0cdf9f2eed9c..6658eb5a8e0b 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.tsx @@ -21,6 +21,7 @@ jest.mock('../../hooks', () => { addAgents: true, addFleetServers: true, }, + integrations: {}, }), useFleetStatus: jest.fn().mockReturnValue({ isReady: true }), }; @@ -41,6 +42,7 @@ jest.mock('../../hooks/use_request', () => { sendGetOneAgentPolicy: jest.fn().mockResolvedValue({ data: { item: { package_policies: [] } }, }), + useGetSpaceSettings: jest.fn().mockReturnValue({}), useGetAgentPolicies: jest.fn(), useGetEnrollmentSettings: jest.fn().mockReturnValue({ isLoading: false, diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index f8f185491150..19709c55665f 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -22,8 +22,6 @@ import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agent import type { FlyOutProps } from './types'; import { AgentEnrollmentFlyout } from '.'; -jest.mock('../../hooks/use_authz'); - const render = (props?: Partial) => { cleanup(); const renderer = createFleetTestRendererMock(); @@ -53,6 +51,7 @@ describe('', () => { fleet: { readAgentPolicies: true, }, + integrations: {}, } as any); jest.mocked(useFleetServerStandalone).mockReturnValue({ isFleetServerStandalone: false }); diff --git a/x-pack/plugins/fleet/public/hooks/use_request/settings.ts b/x-pack/plugins/fleet/public/hooks/use_request/settings.ts index 671f7af64471..f9fc3028e1d6 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/settings.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/settings.ts @@ -14,6 +14,7 @@ import type { GetSettingsResponse, GetEnrollmentSettingsRequest, GetEnrollmentSettingsResponse, + GetSpaceSettingsResponse, } from '../../types'; import { API_VERSIONS } from '../../../common/constants'; @@ -42,6 +43,19 @@ export function useGetSettings() { }); } +export function useGetSpaceSettings({ enabled }: { enabled?: boolean }) { + return useQuery({ + queryKey: ['space_settings'], + enabled, + queryFn: () => + sendRequestForRq({ + method: 'get', + path: settingsRoutesService.getSpaceInfoPath(), + version: API_VERSIONS.public.v1, + }), + }); +} + export function sendGetSettings() { return sendRequest({ method: 'get', diff --git a/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx b/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx new file mode 100644 index 000000000000..6f3d286db370 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { createFleetTestRendererMock } from '../mock'; +import { ExperimentalFeaturesService } from '../services'; + +import { useGetSpaceSettings } from './use_request'; +import { + SpaceSettingsContextProvider, + useSpaceSettingsContext, +} from './use_space_settings_context'; + +jest.mock('./use_request'); +jest.mock('../services'); + +describe('useSpaceSettingsContext', () => { + function renderHook() { + return createFleetTestRendererMock().renderHook( + () => useSpaceSettingsContext(), + ({ children }: { children: any }) => ( + {children} + ) + ); + } + beforeEach(() => { + jest.mocked(ExperimentalFeaturesService.get).mockReturnValue({ + useSpaceAwareness: true, + } as any); + jest.mocked(useGetSpaceSettings).mockReturnValue({} as any); + }); + it('should return default defaultNamespace if no restrictions', () => { + const res = renderHook(); + expect(res.result.current.defaultNamespace).toBe('default'); + }); + + it('should return restricted defaultNamespace if there is namespace prefix restrictions', () => { + jest.mocked(useGetSpaceSettings).mockReturnValue({ + isInitialLoading: false, + data: { + item: { + allowed_namespace_prefixes: ['test'], + }, + }, + } as any); + const res = renderHook(); + expect(res.result.current.defaultNamespace).toBe('test'); + }); +}); diff --git a/x-pack/plugins/fleet/public/hooks/use_space_settings_context.tsx b/x-pack/plugins/fleet/public/hooks/use_space_settings_context.tsx new file mode 100644 index 000000000000..665ceca8c3dd --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_space_settings_context.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext } from 'react'; + +import { ExperimentalFeaturesService } from '../services'; + +import { useAuthz } from './use_authz'; +import { useGetSpaceSettings } from './use_request'; + +const spaceSettingsContext = createContext<{ + isInitialLoading?: boolean; + allowedNamespacePrefixes: string[]; + defaultNamespace: string; +}>({ + allowedNamespacePrefixes: [], + defaultNamespace: 'default', +}); + +export const SpaceSettingsContextProvider: React.FC<{ + enabled?: boolean; + children?: React.ReactNode; +}> = ({ enabled = true, children }) => { + const useSpaceAwareness = ExperimentalFeaturesService.get()?.useSpaceAwareness ?? false; + const authz = useAuthz(); + const isAllowed = + authz.fleet.allAgentPolicies || + authz.fleet.allSettings || + authz.integrations.writeIntegrationPolicies; + const spaceSettingsReq = useGetSpaceSettings({ + enabled: useSpaceAwareness && enabled && isAllowed, + }); + + const settings = React.useMemo(() => { + return { + isInitialLoading: spaceSettingsReq.isInitialLoading, + allowedNamespacePrefixes: spaceSettingsReq.data?.item.allowed_namespace_prefixes ?? [], + defaultNamespace: spaceSettingsReq.data?.item.allowed_namespace_prefixes?.[0] ?? 'default', + }; + }, [spaceSettingsReq.isInitialLoading, spaceSettingsReq.data]); + + return {children}; +}; + +export function useSpaceSettingsContext() { + return useContext(spaceSettingsContext); +} diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index ded8351892e2..3a69f5fdc52e 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -112,9 +112,17 @@ export const createFleetTestRendererMock = (): TestRenderer => { ); }), HookWrapper, - renderHook: (callback) => { + renderHook: ( + callback, + ExtraWrapper: WrapperComponent = memo(({ children }) => <>{children}) + ) => { + const wrapper: WrapperComponent = ({ children }) => ( + + {children} + + ); return renderHook(callback, { - wrapper: testRendererMocks.HookWrapper, + wrapper, }); }, render: (ui, options) => { @@ -135,6 +143,7 @@ export const createFleetTestRendererMock = (): TestRenderer => { export const createIntegrationsTestRendererMock = (): TestRenderer => { const basePath = '/mock'; const extensions: UIExtensionsStorage = {}; + ExperimentalFeaturesService.init(allowedExperimentalValues); const startServices = createStartServices(basePath); const HookWrapper = memo(({ children }: { children?: React.ReactNode }) => { return ( diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index aeb6d302adaa..20d94e6d44fa 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -144,6 +144,7 @@ export type { EnrollmentSettingsFleetServerPolicy, GetEnrollmentSettingsRequest, GetEnrollmentSettingsResponse, + GetSpaceSettingsResponse, } from '../../common/types'; export { entries, diff --git a/x-pack/plugins/fleet/server/routes/settings/settings_handler.ts b/x-pack/plugins/fleet/server/routes/settings/settings_handler.ts index c959638d0fc6..4123c2ea37e6 100644 --- a/x-pack/plugins/fleet/server/routes/settings/settings_handler.ts +++ b/x-pack/plugins/fleet/server/routes/settings/settings_handler.ts @@ -42,7 +42,7 @@ export const putSpaceSettingsHandler: FleetRequestHandler< }, spaceId: soClient.getCurrentNamespace(), }); - const settings = await settingsService.getSettings(soClient); + const settings = await getSpaceSettings(soClient.getCurrentNamespace()); const body = { item: settings, };