From b7dd5ba601eb9d1a020ceff36e47289739c9ea9a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:56:22 -0400 Subject: [PATCH 1/2] skip failing test suite (#148557) --- x-pack/test/functional/apps/dashboard/group2/sync_colors.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts index fc68346ac5745..16d551485b467 100644 --- a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts +++ b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts @@ -34,7 +34,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return colorMapping; } - describe('sync colors', function () { + // Failing: See https://github.com/elastic/kibana/issues/148557 + describe.skip('sync colors', function () { before(async function () { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( From cf9f5bb807764a3fa16f3ba26ed7c87a2a67a652 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 14 Jun 2023 22:07:15 +0200 Subject: [PATCH 2/2] [Synthetics] Remove fleet permission requirement for private location monitor cruds (#159378) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dominique Belcher Co-authored-by: florent-leborgne --- .../server/routes/agent_policy/handlers.ts | 9 +- .../fleet/server/services/agent_policy.ts | 21 ++- .../common/constants/synthetics/rest_api.ts | 1 + .../journeys/private_locations.journey.ts | 3 +- .../common/components/permissions.tsx | 58 ++----- .../journey_screenshot_dialog.test.tsx | 12 +- .../getting_started_page.test.tsx | 83 +++++----- .../getting_started/getting_started_page.tsx | 11 +- .../getting_started/simple_monitor_form.tsx | 16 +- .../monitor_add_edit/form/submit.tsx | 16 +- .../monitor_summary/edit_monitor_link.tsx | 12 +- .../monitors_page/create_monitor_button.tsx | 15 +- .../management/monitor_list_table/columns.tsx | 24 +-- .../monitor_list_table/monitor_enabled.tsx | 10 +- .../overview/overview/actions_popover.tsx | 29 +--- .../private_locations/add_location_flyout.tsx | 60 ++++--- .../private_locations/agent_policy_needed.tsx | 76 +++++---- .../private_locations/delete_location.tsx | 23 ++- .../private_locations/empty_locations.tsx | 66 ++++---- .../private_locations/location_form.tsx | 147 ++++++++---------- .../private_locations/locations_table.tsx | 10 +- .../private_locations/manage_empty_state.tsx | 6 +- .../manage_private_locations.test.tsx | 75 +++++---- .../manage_private_locations.tsx | 25 +-- .../project_api_keys/project_api_keys.tsx | 4 +- .../synthetics/hooks/use_fleet_permissions.ts | 8 +- .../state/monitor_management/api.ts | 2 +- .../synthetics/state/private_locations/api.ts | 17 +- .../plugins/synthetics/server/routes/index.ts | 2 + .../monitor_cruds/add_monitor_project.ts | 1 + .../routes/monitor_cruds/delete_monitor.ts | 18 +-- .../monitor_cruds/delete_monitor_project.ts | 17 -- .../routes/monitor_cruds/edit_monitor.test.ts | 5 - .../routes/monitor_cruds/edit_monitor.ts | 1 - .../routes/monitor_cruds/get_api_key.ts | 2 +- .../private_locations/get_agent_policies.ts | 28 ++++ .../server/synthetics_service/get_api_key.ts | 8 +- .../synthetics_private_location.test.ts | 125 +++++++-------- .../synthetics_private_location.ts | 115 +++++--------- .../project_monitor_formatter.test.ts | 110 ++----------- .../project_monitor_formatter.ts | 30 ---- .../synthetics_monitor_client.ts | 13 +- x-pack/plugins/synthetics/tsconfig.json | 1 + .../translations/translations/fr-FR.json | 6 +- .../translations/translations/ja-JP.json | 6 +- .../translations/translations/zh-CN.json | 6 +- .../add_monitor_private_location.ts | 2 - .../apis/synthetics/add_monitor_project.ts | 57 +------ .../apis/synthetics/delete_monitor.ts | 15 +- .../apis/synthetics/delete_monitor_project.ts | 8 +- .../apis/synthetics/edit_monitor.ts | 7 +- 51 files changed, 506 insertions(+), 916 deletions(-) create mode 100644 x-pack/plugins/synthetics/server/routes/settings/private_locations/get_agent_policies.ts diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 3673face8aea7..b89bf90dc11ed 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -51,7 +51,7 @@ import type { import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors'; import { createAgentPolicyWithPackages } from '../../services/agent_policy_create'; -async function populateAssignedAgentsCount( +export async function populateAssignedAgentsCount( esClient: ElasticsearchClient, soClient: SavedObjectsClientContract, agentPolicies: AgentPolicy[] @@ -81,7 +81,9 @@ export const getAgentPoliciesHandler: FleetRequestHandler< try { const { items, total, page, perPage } = await agentPolicyService.list(soClient, { withPackagePolicies, + esClient, ...restOfQuery, + withAgentCount: !noAgentCount, }); const body: GetAgentPoliciesResponse = { @@ -90,11 +92,6 @@ export const getAgentPoliciesHandler: FleetRequestHandler< page, perPage, }; - if (!noAgentCount) { - await populateAssignedAgentsCount(esClient, soClient, items); - } else { - items.forEach((item) => (item.agents = 0)); - } return response.ok({ body }); } catch (error) { return defaultFleetErrorHandler({ error, response }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 3dc86c91b9693..d32737e0eb5c1 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -22,6 +22,8 @@ import type { BulkResponseItem } from '@elastic/elasticsearch/lib/api/typesWithB import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; +import { populateAssignedAgentsCount } from '../routes/agent_policy/handlers'; + import type { HTTPAuthorizationHeader } from '../../common/http_authorization_header'; import { @@ -370,6 +372,8 @@ class AgentPolicyService { options: ListWithKuery & { withPackagePolicies?: boolean; fields?: string[]; + esClient?: ElasticsearchClient; + withAgentCount?: boolean; } ): Promise<{ items: AgentPolicy[]; @@ -385,6 +389,8 @@ class AgentPolicyService { kuery, withPackagePolicies = false, fields, + esClient, + withAgentCount = false, } = options; const baseFindParams = { @@ -424,14 +430,8 @@ class AgentPolicyService { ...agentPolicySO.attributes, }; if (withPackagePolicies) { - const agentPolicyWithPackagePolicies = await this.get( - soClient, - agentPolicySO.id, - withPackagePolicies - ); - if (agentPolicyWithPackagePolicies) { - agentPolicy.package_policies = agentPolicyWithPackagePolicies.package_policies; - } + agentPolicy.package_policies = + (await packagePolicyService.findAllForAgentPolicy(soClient, agentPolicySO.id)) || []; } return agentPolicy; }, @@ -445,6 +445,11 @@ class AgentPolicyService { savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, }); } + if (esClient && withAgentCount) { + await populateAssignedAgentsCount(esClient, soClient, agentPolicies); + } else { + agentPolicies.forEach((item) => (item.agents = 0)); + } return { items: agentPolicies, diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index 1dbe0d6dcba48..62f16b75fdb96 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -25,6 +25,7 @@ export enum SYNTHETICS_API_URLS { OVERVIEW_STATUS = `/internal/synthetics/overview_status`, INDEX_SIZE = `/internal/synthetics/index_size`, PARAMS = `/internal/synthetics/params`, + AGENT_POLICIES = `/internal/synthetics/agent_policies`, PRIVATE_LOCATIONS = `/internal/synthetics/private_locations`, PRIVATE_LOCATIONS_MONITORS = `/internal/synthetics/private_locations/monitors`, SYNC_GLOBAL_PARAMS = `/internal/synthetics/sync_global_params`, diff --git a/x-pack/plugins/synthetics/e2e/synthetics/journeys/private_locations.journey.ts b/x-pack/plugins/synthetics/e2e/synthetics/journeys/private_locations.journey.ts index 970569b16307c..46d9e00ccb774 100644 --- a/x-pack/plugins/synthetics/e2e/synthetics/journeys/private_locations.journey.ts +++ b/x-pack/plugins/synthetics/e2e/synthetics/journeys/private_locations.journey.ts @@ -159,8 +159,9 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => { step('viewer user cannot add locations', async () => { await syntheticsApp.navigateToSettings(false); await page.click('text=Private Locations'); + await page.hover(byTestId('syntheticsEmptyLocationsButton'), { force: true }); await page.waitForSelector( - `text="You're missing some Kibana privileges to manage private locations"` + `text="You do not have sufficient permissions to perform this action."` ); const createLocationBtn = await page.getByRole('button', { name: 'Create location' }); expect(await createLocationBtn.getAttribute('disabled')).toEqual(''); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx index 17580afbf2cf4..b45e1eb07e7f8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx @@ -14,30 +14,28 @@ export const FleetPermissionsCallout = () => { return (

{NEED_PRIVATE_LOCATIONS_PERMISSION}

+

+ +

); }; /** - * If any of the canEditSynthetics or canUpdatePrivateMonitor is false, then wrap the children with a tooltip + * If canEditSynthetics is false, then wrap the children with a tooltip * so that a reason can be conveyed to the user explaining why the action is disabled. */ export const NoPermissionsTooltip = ({ canEditSynthetics = true, - canUpdatePrivateMonitor = true, - canAddPrivateMonitor = true, children, }: { canEditSynthetics?: boolean; - canUpdatePrivateMonitor?: boolean; - canAddPrivateMonitor?: boolean; children: ReactNode; }) => { - const disabledMessage = getRestrictionReasonLabel( - canEditSynthetics, - canUpdatePrivateMonitor, - canAddPrivateMonitor - ); + const disabledMessage = getRestrictionReasonLabel(canEditSynthetics); if (disabledMessage) { return ( @@ -49,18 +47,8 @@ export const NoPermissionsTooltip = ({ return <>{children}; }; -function getRestrictionReasonLabel( - canEditSynthetics = true, - canUpdatePrivateMonitor = true, - canAddPrivateMonitor = true -): string | undefined { - return !canEditSynthetics - ? CANNOT_PERFORM_ACTION_SYNTHETICS - : !canUpdatePrivateMonitor - ? CANNOT_PERFORM_ACTION_FLEET - : !canAddPrivateMonitor - ? PRIVATE_LOCATIONS_NOT_ALLOWED_MESSAGE - : undefined; +function getRestrictionReasonLabel(canEditSynthetics = true): string | undefined { + return !canEditSynthetics ? CANNOT_PERFORM_ACTION_SYNTHETICS : undefined; } export const NEED_PERMISSIONS_PRIVATE_LOCATIONS = i18n.translate( @@ -77,40 +65,16 @@ export const ALL = i18n.translate('xpack.synthetics.monitorManagement.priviledge export const NEED_PRIVATE_LOCATIONS_PERMISSION = ( {`"${ALL}"`}, }} /> ); -export const CANNOT_SAVE_INTEGRATION_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.cannotSaveIntegration', - { - defaultMessage: - 'You are not authorized to manage private locations. It requires the "All" Kibana privilege for both Fleet and Integrations.', - } -); - -const CANNOT_PERFORM_ACTION_FLEET = i18n.translate( - 'xpack.synthetics.monitorManagement.noFleetPermission', - { - defaultMessage: - 'You are not authorized to perform this action. It requires the "All" Kibana privilege for Integrations.', - } -); - export const CANNOT_PERFORM_ACTION_SYNTHETICS = i18n.translate( 'xpack.synthetics.monitorManagement.noSyntheticsPermissions', { defaultMessage: 'You do not have sufficient permissions to perform this action.', } ); - -const PRIVATE_LOCATIONS_NOT_ALLOWED_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage', - { - defaultMessage: - 'You do not have permission to add monitors to private locations. Contact your administrator to request access.', - } -); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx index 44e6db576d3fb..414d51c5a7b38 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx @@ -57,22 +57,22 @@ describe('JourneyScreenshotDialog', () => { }); it('shows loading indicator when image is loading', async () => { - const { queryByTestId } = render(); + const { getByTestId, queryByTestId } = render(); expect(queryByTestId('screenshotImageLoadingProgress')).not.toBeInTheDocument(); - userEvent.click(queryByTestId('screenshotImageNextButton')); + userEvent.click(getByTestId('screenshotImageNextButton')); }); it('respects maxSteps', () => { - const { queryByTestId } = render(); + const { getByTestId, queryByTestId } = render(); expect(queryByTestId('screenshotImageLoadingProgress')).not.toBeInTheDocument(); - userEvent.click(queryByTestId('screenshotImageNextButton')); - expect(queryByTestId('screenshotImageNextButton')).toHaveProperty('disabled'); + userEvent.click(getByTestId('screenshotImageNextButton')); + expect(getByTestId('screenshotImageNextButton')).toHaveProperty('disabled'); }); it('shows correct image source and step name', () => { - const { queryByTestId, getByText } = render(); + const { getByText, queryByTestId } = render(); expect(queryByTestId('stepScreenshotThumbnail')).toHaveProperty( 'src', 'http://localhost/test-img-url-1' diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx index 811a2b676503e..3630517831653 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx @@ -10,6 +10,9 @@ import * as permissionsHooks from '../../hooks/use_fleet_permissions'; import { render } from '../../utils/testing/rtl_helpers'; import { GettingStartedPage } from './getting_started_page'; import * as privateLocationsHooks from '../settings/private_locations/hooks/use_locations_api'; +import * as settingsHooks from '../../contexts/synthetics_settings_context'; +import { SyntheticsSettingsContextValues } from '../../contexts/synthetics_settings_context'; +import { fireEvent } from '@testing-library/react'; describe('GettingStartedPage', () => { beforeEach(() => { @@ -66,6 +69,10 @@ describe('GettingStartedPage', () => { }); it('shows need agent flyout when isAddingNewPrivateLocation is true and agentPolicies.length === 0', async () => { + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave: true, + } as SyntheticsSettingsContextValues); + const { getByText, getByRole, queryByLabelText } = render(, { state: { serviceLocations: { @@ -87,7 +94,7 @@ describe('GettingStartedPage', () => { expect(getByRole('heading', { name: 'Create private location', level: 2 })); expect(getByText('No agent policies found')).toBeInTheDocument(); - expect(getByRole('link', { name: 'Create agent policy' })).toBeEnabled(); + expect(getByRole('button', { name: 'Create agent policy' })).not.toBeEnabled(); expect(queryByLabelText('Location name')).not.toBeInTheDocument(); expect(queryByLabelText('Agent policy')).not.toBeInTheDocument(); }); @@ -119,57 +126,41 @@ describe('GettingStartedPage', () => { expect(getByLabelText('Agent policy')).toBeInTheDocument(); }); - it('shows permissions callout and hides form when agent policies are available but the user does not have permissions', async () => { - jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(false); - const { getByText, getByRole, queryByLabelText, queryByRole } = render(, { - state: { - serviceLocations: { - locations: [], - locationsLoaded: true, - loading: false, - }, - agentPolicies: { - data: { - total: 1, - items: [{}], + it('shows permissions tooltip when the user does not have permissions', async () => { + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave: false, + } as SyntheticsSettingsContextValues); + const { getByText, getByRole, queryByLabelText, queryByRole, findByText } = render( + , + { + state: { + serviceLocations: { + locations: [], + locationsLoaded: true, + loading: false, + }, + agentPolicies: { + data: { + total: 1, + items: [{}], + }, + isAddingNewPrivateLocation: true, }, - isAddingNewPrivateLocation: true, - }, - }, - }); - - // page is loaded - expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); - - expect(getByRole('heading', { name: 'Create private location', level: 2 })); - expect(queryByLabelText('Location name')).not.toBeInTheDocument(); - expect(queryByLabelText('Agent policy')).not.toBeInTheDocument(); - expect(queryByRole('button', { name: 'Save' })).not.toBeInTheDocument(); - expect(getByText("You're missing some Kibana privileges to manage private locations")); - }); - - it('shows permissions callout when agent policy is needed but the user does not have permissions', async () => { - jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(false); - const { getByText, getByRole, queryByLabelText } = render(, { - state: { - serviceLocations: { - locations: [], - locationsLoaded: true, - loading: false, - }, - agentPolicies: { - data: undefined, // data will be undefined when user does not have permissions - isAddingNewPrivateLocation: true, }, - }, - }); + } + ); // page is loaded expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); expect(getByRole('heading', { name: 'Create private location', level: 2 })); - expect(queryByLabelText('Location name')).not.toBeInTheDocument(); - expect(queryByLabelText('Agent policy')).not.toBeInTheDocument(); - expect(getByText("You're missing some Kibana privileges to manage private locations")); + expect(queryByLabelText('Location name')).toBeInTheDocument(); + expect(queryByLabelText('Agent policy')).toBeInTheDocument(); + expect(queryByRole('button', { name: 'Save' })).toBeInTheDocument(); + expect(queryByRole('button', { name: 'Save' })).toBeDisabled(); + fireEvent.mouseOver(getByRole('button', { name: 'Save' })); + expect( + await findByText(/You do not have sufficient permissions to perform this action./) + ).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx index f42b781089aea..60f1d4e5c9648 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx @@ -20,7 +20,7 @@ import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; -import { useBreadcrumbs, useLocations, useFleetPermissions } from '../../hooks'; +import { useBreadcrumbs, useLocations } from '../../hooks'; import { usePrivateLocationsAPI } from '../settings/private_locations/hooks/use_locations_api'; import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout'; import { @@ -40,17 +40,14 @@ export const GettingStartedPage = () => { const dispatch = useDispatch(); const history = useHistory(); - const { canReadAgentPolicies } = useFleetPermissions(); - useEffect(() => { dispatch(getServiceLocations()); - if (canReadAgentPolicies) { - dispatch(getAgentPoliciesAction.get()); - } + dispatch(getAgentPoliciesAction.get()); + return () => { dispatch(cleanMonitorListState()); }; - }, [canReadAgentPolicies, dispatch]); + }, [dispatch]); useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx index a4d3460225462..5b65c0ecdb346 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx @@ -18,10 +18,9 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useSimpleMonitor } from './use_simple_monitor'; import { ServiceLocationsField } from './form_fields/service_locations'; -import { ConfigKey, ServiceLocation, ServiceLocations } from '../../../../../common/runtime_types'; +import { ConfigKey, ServiceLocations } from '../../../../../common/runtime_types'; import { useCanEditSynthetics } from '../../../../hooks/use_capabilities'; import { useFormWrapped } from '../../../../hooks/use_form_wrapped'; -import { useFleetPermissions } from '../../hooks'; import { NoPermissionsTooltip } from '../common/components/permissions'; export interface SimpleFormData { @@ -35,7 +34,6 @@ export const SimpleMonitorForm = () => { register, handleSubmit, formState: { errors, isValid, isSubmitted }, - getValues, } = useFormWrapped({ mode: 'onSubmit', reValidateMode: 'onChange', @@ -52,11 +50,6 @@ export const SimpleMonitorForm = () => { const { loading, data: newMonitor } = useSimpleMonitor({ monitorData }); const canEditSynthetics = useCanEditSynthetics(); - const { canSaveIntegrations } = useFleetPermissions(); - const hasAnyPrivateLocationSelected = (getValues(ConfigKey.LOCATIONS) as ServiceLocations)?.some( - ({ isServiceManaged }: ServiceLocation) => !isServiceManaged - ); - const canSavePrivateLocation = !hasAnyPrivateLocationSelected || canSaveIntegrations; const hasURLError = !!errors?.[ConfigKey.URLS]; @@ -88,17 +81,14 @@ export const SimpleMonitorForm = () => { - + {CREATE_MONITOR_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx index 5c40cbee83418..00996009cf98d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx @@ -13,11 +13,10 @@ import { useFormContext } from 'react-hook-form'; import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public'; import { RunTestButton } from './run_test_btn'; import { useCanEditSynthetics } from '../../../../../hooks/use_capabilities'; -import { useFleetPermissions } from '../../../hooks'; import { useMonitorSave } from '../hooks/use_monitor_save'; import { NoPermissionsTooltip } from '../../common/components/permissions'; import { DeleteMonitor } from '../../monitors_page/management/monitor_list_table/delete_monitor'; -import { ConfigKey, ServiceLocation, SourceType, SyntheticsMonitor } from '../types'; +import { ConfigKey, SourceType, SyntheticsMonitor } from '../types'; import { format } from './formatter'; import { MONITORS_ROUTE } from '../../../../../../common/constants'; @@ -41,11 +40,6 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { const { status, loading, isEdit } = useMonitorSave({ monitorData }); const canEditSynthetics = useCanEditSynthetics(); - const { canSaveIntegrations } = useFleetPermissions(); - const hasAnyPrivateLocationSelected = getValues(ConfigKey.LOCATIONS)?.some( - ({ isServiceManaged }: ServiceLocation) => !isServiceManaged - ); - const canSavePrivateLocation = !hasAnyPrivateLocationSelected || canSaveIntegrations; const formSubmitter = (formData: Record) => { // An additional invalid field check to account for customHook managed validation @@ -91,17 +85,13 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { - + {isEdit ? UPDATE_MONITOR_LABEL : CREATE_MONITOR_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/edit_monitor_link.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/edit_monitor_link.tsx index bf1c467c1d2ae..083e0a267afd8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/edit_monitor_link.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/edit_monitor_link.tsx @@ -10,31 +10,23 @@ import { useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { EuiButton } from '@elastic/eui'; -import { EncryptedSyntheticsMonitor } from '../../../../../../common/runtime_types'; import { useCanEditSynthetics } from '../../../../../hooks/use_capabilities'; import { useSyntheticsSettingsContext } from '../../../contexts'; -import { useCanUpdatePrivateMonitor } from '../../../hooks'; import { NoPermissionsTooltip } from '../../common/components/permissions'; -import { useSelectedMonitor } from '../hooks/use_selected_monitor'; export const EditMonitorLink = () => { const { basePath } = useSyntheticsSettingsContext(); const { monitorId } = useParams<{ monitorId: string }>(); - const { monitor } = useSelectedMonitor(); const canEditSynthetics = useCanEditSynthetics(); - const canUpdatePrivateMonitor = useCanUpdatePrivateMonitor(monitor as EncryptedSyntheticsMonitor); - const isLinkDisabled = !canEditSynthetics || !canUpdatePrivateMonitor; + const isLinkDisabled = !canEditSynthetics; const linkProps = isLinkDisabled ? { disabled: true } : { href: `${basePath}/app/synthetics/edit-monitor/${monitorId}` }; return ( - + { } = useEnablement(); const canEditSynthetics = useCanEditSynthetics(); - const { canSaveIntegrations } = useFleetPermissions(); - const { locations } = useLocations(); - - const hasPublicLocation = locations.some((loc) => loc.isServiceManaged); - - const canAddMonitor = canEditSynthetics && (hasPublicLocation || canSaveIntegrations); return ( - + { return alertStatus(fields[ConfigKey.CONFIG_ID]) === FETCH_STATUS.LOADING; @@ -168,20 +166,14 @@ export function useMonitorListColumns({ 'data-test-subj': 'syntheticsMonitorEditAction', isPrimary: true, name: (fields) => ( - + {labels.EDIT_LABEL} ), description: labels.EDIT_LABEL, icon: 'pencil' as const, type: 'icon' as const, - enabled: (fields) => - canEditSynthetics && - !isActionLoading(fields) && - canUpdatePrivateMonitor(fields, canSaveIntegrations), + enabled: (fields) => canEditSynthetics && !isActionLoading(fields), onClick: (fields) => { history.push({ pathname: `/edit-monitor/${fields[ConfigKey.CONFIG_ID]}`, @@ -192,10 +184,7 @@ export function useMonitorListColumns({ 'data-test-subj': 'syntheticsMonitorDeleteAction', isPrimary: true, name: (fields) => ( - + {labels.DELETE_LABEL} ), @@ -203,10 +192,7 @@ export function useMonitorListColumns({ icon: 'trash' as const, type: 'icon' as const, color: 'danger' as const, - enabled: (fields) => - canEditSynthetics && - !isActionLoading(fields) && - canUpdatePrivateMonitor(fields, canSaveIntegrations), + enabled: (fields) => canEditSynthetics && !isActionLoading(fields), onClick: (fields) => { setMonitorPendingDeletion(fields); }, @@ -252,7 +238,7 @@ export function useMonitorListColumns({ defaultMessage: 'Actions', }), render: () => ( - + { - const canUpdatePrivateMonitor = useCanUpdatePrivateMonitor(monitor); const canEditSynthetics = useCanEditSynthetics(); const monitorName = monitor[ConfigKey.NAME]; @@ -64,14 +63,11 @@ export const MonitorEnabled = ({ {isLoading || initialLoading ? ( ) : ( - + ({ @@ -206,28 +195,22 @@ export function ActionsPopover({ }, { name: ( - + {actionsMenuEditMonitorName} ), icon: 'pencil', - disabled: !canEditSynthetics || !canUpdatePrivateMonitor, + disabled: !canEditSynthetics, href: editUrl, }, { name: ( - + {enableLabel} ), icon: 'invert', - disabled: !canEditSynthetics || !canUpdatePrivateMonitor, + disabled: !canEditSynthetics, onClick: () => { if (status !== FETCH_STATUS.LOADING) { updateMonitorEnabledState(!monitor.isEnabled); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx index fd5c7b76d358a..d6f2836ff2d87 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx @@ -19,10 +19,10 @@ import { EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useCanManagePrivateLocation } from '../../../hooks/use_fleet_permissions'; +import { NoPermissionsTooltip } from '../../common/components/permissions'; +import { useSyntheticsSettingsContext } from '../../../contexts'; import { useFormWrapped } from '../../../../../hooks/use_form_wrapped'; import { PrivateLocation } from '../../../../../../common/runtime_types'; -import { FleetPermissionsCallout } from '../../common/components/permissions'; import { LocationForm } from './location_form'; import { ManageEmptyState } from './manage_empty_state'; @@ -53,10 +53,9 @@ export const AddLocationFlyout = ({ }, }); - const { handleSubmit } = form; - - const canManagePrivateLocation = useCanManagePrivateLocation(); + const { canSave } = useSyntheticsSettingsContext(); + const { handleSubmit } = form; const closeFlyout = () => { setIsOpen(false); }; @@ -70,45 +69,38 @@ export const AddLocationFlyout = ({ - - {!canManagePrivateLocation && } - + + - {canManagePrivateLocation && ( - - - - - {CANCEL_LABEL} - - - + + + + + {CANCEL_LABEL} + + + + {SAVE_LABEL} - - - - )} + + + + ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx index 7870ee25003ef..36e01eaff9959 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx @@ -6,45 +6,55 @@ */ import React from 'react'; -import { EuiLink, EuiButton, EuiEmptyPrompt, EuiTitle } from '@elastic/eui'; +import { EuiLink, EuiButton, EuiEmptyPrompt, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useFleetPermissions } from '../../../hooks'; +import { FleetPermissionsCallout, NoPermissionsTooltip } from '../../common/components/permissions'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { LEARN_MORE, READ_DOCS } from './empty_locations'; -export const AgentPolicyNeeded = ({ disabled }: { disabled: boolean }) => { - const { basePath } = useSyntheticsSettingsContext(); +export const AgentPolicyNeeded = () => { + const { basePath, canSave } = useSyntheticsSettingsContext(); + + const { canCreateAgentPolicies } = useFleetPermissions(); return ( - {AGENT_POLICY_NEEDED}} - body={

{ADD_AGENT_POLICY_DESCRIPTION}

} - actions={ - - {CREATE_AGENT_POLICY} - - } - footer={ - <> - -

{LEARN_MORE}

-
- - {READ_DOCS} - - - } - /> + <> + {!canCreateAgentPolicies && } + + {AGENT_POLICY_NEEDED}} + body={

{ADD_AGENT_POLICY_DESCRIPTION}

} + actions={ + + + {CREATE_AGENT_POLICY} + + + } + footer={ + <> + +

{LEARN_MORE}

+
+ + {READ_DOCS} + + + } + /> + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx index cfaf62f891599..acff970300c8b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx @@ -9,8 +9,6 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiConfirmModal, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useSyntheticsSettingsContext } from '../../../contexts'; -import { useFleetPermissions, useCanManagePrivateLocation } from '../../../hooks'; -import { CANNOT_SAVE_INTEGRATION_LABEL } from '../../common/components/permissions'; export const DeleteLocation = ({ loading, @@ -29,18 +27,17 @@ export const DeleteLocation = ({ const canDelete = monCount === 0; const { canSave } = useSyntheticsSettingsContext(); - const { canSaveIntegrations } = useFleetPermissions(); - const canManagePrivateLocation = useCanManagePrivateLocation(); const [isModalOpen, setIsModalOpen] = useState(false); - const deleteDisabledReason = !canSaveIntegrations - ? CANNOT_SAVE_INTEGRATION_LABEL - : i18n.translate('xpack.synthetics.monitorManagement.cannotDelete.description', { - defaultMessage: `You can't delete this location because it is used in {monCount, number} {monCount, plural,one {monitor} other {monitors}}. + const deleteDisabledReason = i18n.translate( + 'xpack.synthetics.monitorManagement.cannotDelete.description', + { + defaultMessage: `You can't delete this location because it is used in {monCount, number} {monCount, plural,one {monitor} other {monitors}}. Remove this location from all monitors first.`, - values: { monCount }, - }); + values: { monCount }, + } + ); const deleteModal = ( {isModalOpen && deleteModal} - + { setIsModalOpen(true); }} - isDisabled={!canDelete || !canManagePrivateLocation || !canSave} + isDisabled={!canDelete || !canSave} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx index e6ac91746b44f..8a3af2c275584 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx @@ -10,23 +10,25 @@ import { useHistory } from 'react-router-dom'; import { EuiEmptyPrompt, EuiButton, EuiLink, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useDispatch } from 'react-redux'; +import { NoPermissionsTooltip } from '../../common/components/permissions'; +import { useSyntheticsSettingsContext } from '../../../contexts'; import { PRIVATE_LOCATIOSN_ROUTE } from '../../../../../../common/constants'; import { setAddingNewPrivateLocation, setManageFlyoutOpen } from '../../../state/private_locations'; export const EmptyLocations = ({ inFlyout = true, setIsAddingNew, - disabled, redirectToSettings, }: { inFlyout?: boolean; - disabled?: boolean; setIsAddingNew?: (val: boolean) => void; redirectToSettings?: boolean; }) => { const dispatch = useDispatch(); const history = useHistory(); + const { canSave } = useSyntheticsSettingsContext(); + return ( } actions={ - redirectToSettings ? ( - - {ADD_LOCATION} - - ) : ( - { - setIsAddingNew?.(true); - dispatch(setManageFlyoutOpen(true)); - dispatch(setAddingNewPrivateLocation(true)); - }} - > - {ADD_LOCATION} - - ) + + {redirectToSettings ? ( + + {ADD_LOCATION} + + ) : ( + { + setIsAddingNew?.(true); + dispatch(setManageFlyoutOpen(true)); + dispatch(setAddingNewPrivateLocation(true)); + }} + > + {ADD_LOCATION} + + )} + } footer={ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx index 287ec8e9c5867..cc2b835fbfdd8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx @@ -24,14 +24,7 @@ import { AgentPolicyNeeded } from './agent_policy_needed'; import { PolicyHostsField } from './policy_hosts'; import { selectAgentPolicies } from '../../../state/private_locations'; -export const LocationForm = ({ - privateLocations, - hasPermissions, -}: { - onDiscard?: () => void; - privateLocations: PrivateLocation[]; - hasPermissions: boolean; -}) => { +export const LocationForm = ({ privateLocations }: { privateLocations: PrivateLocation[] }) => { const { data } = useSelector(selectAgentPolicies); const { control, register, watch } = useFormContext(); const { errors } = useFormState(); @@ -45,50 +38,82 @@ export const LocationForm = ({ return ( <> - {data?.items.length === 0 && } - {hasPermissions ? ( - - } + + + { + return privateLocations.some((loc) => loc.label === val) + ? NAME_ALREADY_EXISTS + : undefined; + }, + })} + /> + + + + + + + +

+ { + elastic-agent-complete, + link: ( + + + + ), + }} + /> + } +

+
+ + + {selectedPolicy?.agents === 0 && ( + - { - return privateLocations.some((loc) => loc.label === val) - ? NAME_ALREADY_EXISTS - : undefined; - }, - })} - /> -
- - - - - -

{ elastic-agent-complete, link: ( - - - {selectedPolicy?.agents === 0 && ( - -

- { - - - - ), - }} - /> - } -

-
- )} -
- ) : null} + )} + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx index 068718f760740..97f0db2276c75 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx @@ -22,14 +22,10 @@ import { ViewLocationMonitors } from './view_location_monitors'; import { TableTitle } from '../../common/components/table_title'; import { TAGS_LABEL } from '../components/tags_field'; import { useSyntheticsSettingsContext } from '../../../contexts'; -import { useCanManagePrivateLocation } from '../../../hooks'; import { setAddingNewPrivateLocation } from '../../../state/private_locations'; import { PrivateLocationDocsLink, START_ADDING_LOCATIONS_DESCRIPTION } from './empty_locations'; import { PrivateLocation } from '../../../../../../common/runtime_types'; -import { - CANNOT_SAVE_INTEGRATION_LABEL, - NoPermissionsTooltip, -} from '../../common/components/permissions'; +import { NoPermissionsTooltip } from '../../common/components/permissions'; import { DeleteLocation } from './delete_location'; import { useLocationMonitors } from './hooks/use_location_monitors'; import { PolicyName } from './policy_name'; @@ -56,7 +52,6 @@ export const PrivateLocationsTable = ({ const { locationMonitors, loading } = useLocationMonitors(); const { canSave } = useSyntheticsSettingsContext(); - const canManagePrivateLocations = useCanManagePrivateLocation(); const tagsList = privateLocations.reduce((acc, item) => { const tags = item.tags || []; @@ -137,10 +132,9 @@ export const PrivateLocationsTable = ({ fill data-test-subj={'addPrivateLocationButton'} isLoading={loading} - disabled={!canManagePrivateLocations || !canSave} + disabled={!canSave} onClick={() => setIsAddingNew(true)} iconType="plusInCircle" - title={!canManagePrivateLocations ? CANNOT_SAVE_INTEGRATION_LABEL : undefined} > {ADD_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx index 216ae53b04286..6e9573e2d14d1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx @@ -14,7 +14,6 @@ import { selectAgentPolicies } from '../../../state/private_locations'; export const ManageEmptyState: FC<{ privateLocations: PrivateLocation[]; - hasFleetPermissions: boolean; setIsAddingNew?: (val: boolean) => void; showNeedAgentPolicy?: boolean; showEmptyLocations?: boolean; @@ -22,18 +21,17 @@ export const ManageEmptyState: FC<{ children, privateLocations, setIsAddingNew, - hasFleetPermissions, showNeedAgentPolicy = true, showEmptyLocations = true, }) => { const { data: agentPolicies } = useSelector(selectAgentPolicies); if (agentPolicies?.total === 0 && showNeedAgentPolicy) { - return ; + return ; } if (privateLocations.length === 0 && showEmptyLocations) { - return ; + return ; } return <>{children}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx index 484157d3e28a9..818b398315610 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx @@ -13,6 +13,7 @@ import * as settingsHooks from '../../../contexts/synthetics_settings_context'; import type { SyntheticsSettingsContextValues } from '../../../contexts'; import { ManagePrivateLocations } from './manage_private_locations'; import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { fireEvent } from '@testing-library/react'; jest.mock('../../../hooks'); jest.mock('./hooks/use_locations_api'); @@ -24,6 +25,7 @@ describe('', () => { jest.spyOn(permissionsHooks, 'useFleetPermissions').mockReturnValue({ canReadAgentPolicies: true, canSaveIntegrations: false, + canCreateAgentPolicies: false, }); jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({ formData: {} as PrivateLocation, @@ -33,18 +35,15 @@ describe('', () => { onDelete: jest.fn(), deleteLoading: false, }); - jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ - canSave: true, - } as SyntheticsSettingsContextValues); }); it.each([true, false])( 'handles no agent found when the user does and does not have permissions', - (hasFleetPermissions) => { - jest - .spyOn(permissionsHooks, 'useCanManagePrivateLocation') - .mockReturnValue(hasFleetPermissions); - const { getByText, getByRole, queryByText } = render(, { + async (canSave) => { + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave, + } as SyntheticsSettingsContextValues); + const { getByText, getByRole, findByText } = render(, { state: { agentPolicies: { data: { @@ -62,27 +61,28 @@ describe('', () => { }); expect(getByText('No agent policies found')).toBeInTheDocument(); - if (hasFleetPermissions) { - const button = getByRole('link', { name: 'Create agent policy' }); - expect(button).not.toBeDisabled(); - expect( - queryByText(/You are not authorized to manage private locations./) - ).not.toBeInTheDocument(); + if (canSave) { + const button = getByRole('button', { name: 'Create agent policy' }); + expect(button).toBeDisabled(); } else { const button = getByRole('button', { name: 'Create agent policy' }); expect(button).toBeDisabled(); - expect(getByText(/You are not authorized to manage private locations./)); + // hover over the button to see the tooltip + fireEvent.mouseOver(button); + expect( + await findByText(/You do not have sufficient permissions to perform this action./) + ).toBeInTheDocument(); } } ); it.each([true, false])( 'handles create first location when the user does and does not have permissions', - (hasFleetPermissions) => { - jest - .spyOn(permissionsHooks, 'useCanManagePrivateLocation') - .mockReturnValue(hasFleetPermissions); - const { getByText, getByRole, queryByText } = render(, { + async (canSave) => { + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave, + } as SyntheticsSettingsContextValues); + const { getByText, getByRole, findByText } = render(, { state: { agentPolicies: { data: { @@ -101,29 +101,26 @@ describe('', () => { expect(getByText('Create your first private location')).toBeInTheDocument(); const button = getByRole('button', { name: 'Create location' }); - if (hasFleetPermissions) { + if (canSave) { expect(button).not.toBeDisabled(); - expect( - queryByText(/You are not authorized to manage private locations./) - ).not.toBeInTheDocument(); } else { expect(button).toBeDisabled(); - expect(getByText(/You are not authorized to manage private locations./)); + fireEvent.mouseOver(button); + expect( + await findByText(/You do not have sufficient permissions to perform this action./) + ).toBeInTheDocument(); } } ); it.each([true, false])( 'handles location table when the user does and does not have permissions', - (hasFleetPermissions) => { + async (canSave) => { const privateLocationName = 'Test private location'; - jest - .spyOn(permissionsHooks, 'useCanManagePrivateLocation') - .mockReturnValue(hasFleetPermissions); - jest.spyOn(permissionsHooks, 'useFleetPermissions').mockReturnValue({ - canSaveIntegrations: hasFleetPermissions, - canReadAgentPolicies: hasFleetPermissions, - }); + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave, + } as SyntheticsSettingsContextValues); + jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({ formData: {} as PrivateLocation, loading: false, @@ -140,7 +137,7 @@ describe('', () => { onDelete: jest.fn(), deleteLoading: false, }); - const { getByText, getByRole, queryByText } = render(, { + const { getByText, getByRole, findByText } = render(, { state: { agentPolicies: { data: { @@ -159,14 +156,14 @@ describe('', () => { expect(getByText(privateLocationName)).toBeInTheDocument(); const button = getByRole('button', { name: 'Create location' }); - if (hasFleetPermissions) { + if (canSave) { expect(button).not.toBeDisabled(); - expect( - queryByText(/You are not authorized to manage private locations./) - ).not.toBeInTheDocument(); } else { expect(button).toBeDisabled(); - expect(getByText(/You are not authorized to manage private locations./)); + fireEvent.mouseOver(button); + expect( + await findByText('You do not have sufficient permissions to perform this action.') + ).toBeInTheDocument(); } } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx index fc199eb4eb235..bbd668dbadccf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx @@ -6,10 +6,8 @@ */ import React, { useEffect, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { EuiSpacer } from '@elastic/eui'; import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; import { PrivateLocationsTable } from './locations_table'; -import { useCanManagePrivateLocation, useFleetPermissions } from '../../../hooks'; import { ManageEmptyState } from './manage_empty_state'; import { AddLocationFlyout } from './add_location_flyout'; import { usePrivateLocationsAPI } from './hooks/use_locations_api'; @@ -20,7 +18,6 @@ import { } from '../../../state/private_locations'; import { PrivateLocation } from '../../../../../../common/runtime_types'; import { getServiceLocations } from '../../../state'; -import { FleetPermissionsCallout } from '../../common/components/permissions'; export const ManagePrivateLocations = () => { const dispatch = useDispatch(); @@ -33,20 +30,15 @@ export const ManagePrivateLocations = () => { const { onSubmit, loading, privateLocations, onDelete, deleteLoading } = usePrivateLocationsAPI(); - const { canReadAgentPolicies } = useFleetPermissions(); - const canManagePrivateLocation = useCanManagePrivateLocation(); - // make sure flyout is closed when first visiting the page useEffect(() => { setIsAddingNew(false); }, [setIsAddingNew]); useEffect(() => { - if (canReadAgentPolicies) { - dispatch(getAgentPoliciesAction.get()); - } + dispatch(getAgentPoliciesAction.get()); dispatch(getServiceLocations()); - }, [dispatch, canReadAgentPolicies]); + }, [dispatch]); const handleSubmit = (formData: PrivateLocation) => { onSubmit(formData); @@ -54,21 +46,10 @@ export const ManagePrivateLocations = () => { return ( <> - {!canManagePrivateLocation && ( - <> - - - - )} - {loading ? ( ) : ( - + { const { data, loading } = useFetcher(async () => { if (loadAPIKey) { - return fetchServiceAPIKey(); + return fetchProjectAPIKey(); } return null; }, [loadAPIKey]); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts index 2ed8af08891ab..dda1e7ca02a35 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts @@ -18,19 +18,15 @@ export function useFleetPermissions() { const canSaveIntegrations: boolean = Boolean(fleet?.authz.integrations.writeIntegrationPolicies); const canReadAgentPolicies = Boolean(fleet?.authz.fleet.readAgentPolicies); + const canCreateAgentPolicies = Boolean(fleet?.authz.fleet.all); return { canReadAgentPolicies, canSaveIntegrations, + canCreateAgentPolicies, }; } -export function useCanUpdatePrivateMonitor(monitor: EncryptedSyntheticsMonitor) { - const { canSaveIntegrations } = useFleetPermissions(); - - return canUpdatePrivateMonitor(monitor, canSaveIntegrations); -} - export function useCanManagePrivateLocation() { const { canSaveIntegrations, canReadAgentPolicies } = useFleetPermissions(); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts index 7623764232c12..a397e6c0e09da 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts @@ -60,7 +60,7 @@ export const getDecryptedMonitorAPI = async ({ id }: { id: string }): Promise => { return await apiService.get(SYNTHETICS_API_URLS.SYNTHETICS_APIKEY); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts index 50683fdd87a35..c3baa6f21ddc5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts @@ -10,23 +10,8 @@ import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../comm import { apiService } from '../../../../utils/api_service/api_service'; import { AgentPoliciesList } from '.'; -const FLEET_URLS = { - AGENT_POLICIES: '/api/fleet/agent_policies', -}; - export const fetchAgentPolicies = async (): Promise => { - return await apiService.get( - FLEET_URLS.AGENT_POLICIES, - { - page: 1, - perPage: 10000, - sortField: 'name', - sortOrder: 'asc', - full: true, - kuery: 'ingest-agent-policies.is_managed : false', - }, - null - ); + return await apiService.get(SYNTHETICS_API_URLS.AGENT_POLICIES); }; export const addSyntheticsPrivateLocations = async ( diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 50069c95dc443..0e86f8a0ea75e 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { getAgentPoliciesRoute } from './settings/private_locations/get_agent_policies'; import { inspectSyntheticsMonitorRoute } from './monitor_cruds/inspect_monitor'; import { deletePackagePolicyRoute } from './monitor_cruds/delete_integration'; import { createJourneyScreenshotRoute } from './pings/journey_screenshots'; @@ -100,6 +101,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ getPrivateLocationsRoute, getSyntheticsFilters, inspectSyntheticsMonitorRoute, + getAgentPoliciesRoute, ]; export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [ diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts index f5bda409fd84b..1f66fb42df946 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts @@ -31,6 +31,7 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = ( maxBytes: MAX_PAYLOAD_SIZE, }, }, + writeAccess: true, handler: async (routeContext): Promise => { const { request, response, server } = routeContext; const { projectName } = request.params; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts index bd560cb815c47..ed1513b650362 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts @@ -8,7 +8,6 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; -import { deletePermissionError } from '../../synthetics_service/private_location/synthetics_private_location'; import { ConfigKey, EncryptedSyntheticsMonitor, @@ -79,19 +78,12 @@ export const deleteMonitor = async ({ server ); - const { locations } = monitor.attributes; - - const hasPrivateLocation = locations.filter((loc) => !loc.isServiceManaged); - - if (hasPrivateLocation.length > 0) { - await syntheticsMonitorClient.privateLocationAPI.checkPermissions( - request, - deletePermissionError(monitor.attributes.name) - ); - } + let deletePromise; try { const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + deletePromise = savedObjectsClient.delete(syntheticsMonitorType, monitorId); + const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors( [ { @@ -105,7 +97,6 @@ export const deleteMonitor = async ({ savedObjectsClient, spaceId ); - const deletePromise = savedObjectsClient.delete(syntheticsMonitorType, monitorId); const [errors] = await Promise.all([deleteSyncPromise, deletePromise]).catch((e) => { server.logger.error(e); @@ -126,6 +117,9 @@ export const deleteMonitor = async ({ return errors; } catch (e) { + if (deletePromise) { + await deletePromise; + } server.logger.error( `Unable to delete Synthetics monitor ${monitor.attributes[ConfigKey.NAME]}` ); diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor_project.ts index 4769ff5ed8ccf..e04f68dc7201a 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor_project.ts @@ -11,7 +11,6 @@ import { ConfigKey } from '../../../common/runtime_types'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { getMonitors, getKqlFilter } from '../common'; -import { INSUFFICIENT_FLEET_PERMISSIONS } from '../../synthetics_service/project_monitor/project_monitor_formatter'; import { deleteMonitorBulk } from './bulk_cruds/delete_monitor_bulk'; export const deleteSyntheticsMonitorProjectRoute: SyntheticsRestApiRouteFactory = () => ({ @@ -56,22 +55,6 @@ export const deleteSyntheticsMonitorProjectRoute: SyntheticsRestApiRouteFactory { fields: [] } ); - const { - integrations: { writeIntegrationPolicies }, - } = await server.fleet.authz.fromRequest(request); - - const hasPrivateMonitor = monitors.some((monitor) => - monitor.attributes.locations.some((location) => !location.isServiceManaged) - ); - - if (!writeIntegrationPolicies && hasPrivateMonitor) { - return response.forbidden({ - body: { - message: INSUFFICIENT_FLEET_PERMISSIONS, - }, - }); - } - await deleteMonitorBulk({ monitors, server, diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts index d8ca7fd3ebe8d..3a742e3c08e90 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts @@ -42,11 +42,6 @@ describe('syncEditedMonitor', () => { }, }, fleet: { - authz: { - fromRequest: jest - .fn() - .mockReturnValue({ integrations: { writeIntegrationPolicies: true } }), - }, packagePolicyService: { get: jest.fn().mockReturnValue({}), getByIDs: jest.fn().mockReturnValue([]), diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index e2342e174760c..495b236b75c71 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -95,7 +95,6 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( normalizedMonitor: monitorWithRevision, spaceId, }); - if (failedPolicyUpdates && failedPolicyUpdates.length > 0) { const hasError = failedPolicyUpdates.find((update) => update.error); await rollbackUpdate({ diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts index 215790c5f5dcb..6a3dc6b7af23a 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts @@ -16,7 +16,7 @@ export const getAPIKeySyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => const apiKey = await generateAPIKey({ request, server, - uptimePrivileges: true, + projectAPIKey: true, }); return { apiKey }; diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_agent_policies.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_agent_policies.ts new file mode 100644 index 0000000000000..4ac087976c248 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_agent_policies.ts @@ -0,0 +1,28 @@ +/* + * 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 { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; + +export const getAgentPoliciesRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.AGENT_POLICIES, + validate: {}, + handler: async ({ server, context, uptimeEsClient }): Promise => { + const soClient = server.coreStart.savedObjects.createInternalRepository(); + const esClient = server.coreStart.elasticsearch.client.asInternalUser; + + return server.fleet?.agentPolicyService.list(soClient, { + page: 1, + perPage: 10000, + sortField: 'name', + sortOrder: 'asc', + kuery: 'ingest-agent-policies.is_managed : false', + esClient, + }); + }, +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts index 4c1c125c37085..e41b4c0775d00 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts @@ -79,11 +79,11 @@ export const getAPIKeyForSyntheticsService = async ({ export const generateAPIKey = async ({ server, request, - uptimePrivileges = false, + projectAPIKey = false, }: { server: UptimeServerSetup; request: KibanaRequest; - uptimePrivileges?: boolean; + projectAPIKey?: boolean; }) => { const { security } = server; const isApiKeysEnabled = await security.authc.apiKeys?.areAPIKeysEnabled(); @@ -92,7 +92,7 @@ export const generateAPIKey = async ({ throw new Error('Please enable API keys in kibana to use synthetics service.'); } - if (uptimePrivileges) { + if (projectAPIKey) { /* Exposed to the user. Must create directly with the user */ return security.authc.apiKeys?.create(request, { name: 'synthetics-api-key (required for project monitors)', @@ -105,8 +105,6 @@ export const generateAPIKey = async ({ spaces: [ALL_SPACES_ID], feature: { uptime: ['all'], - fleet: ['all'], - fleetv2: ['all'], }, }, ], diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts index 7fd986ed12ca6..96b6b20a3fc32 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts @@ -18,6 +18,7 @@ import { import { SyntheticsPrivateLocation } from './synthetics_private_location'; import { testMonitorPolicy } from './test_policy'; import { formatSyntheticsPolicy } from '../formatters/private_formatters/format_synthetics_policy'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; describe('SyntheticsPrivateLocation', () => { const mockPrivateLocation: PrivateLocation = { @@ -52,15 +53,6 @@ describe('SyntheticsPrivateLocation', () => { username: '', } as unknown as HeartbeatConfig; - const savedObjectsClientMock = { - bulkUpdate: jest.fn(), - get: jest.fn().mockReturnValue({ - attributes: { - locations: [mockPrivateLocation], - }, - }), - } as unknown as SavedObjectsClientContract; - const serverMock: UptimeServerSetup = { uptimeEsClient: { search: jest.fn() }, logger: loggerMock.create(), @@ -72,11 +64,6 @@ describe('SyntheticsPrivateLocation', () => { }, }, fleet: { - authz: { - fromRequest: jest - .fn() - .mockReturnValue({ integrations: { writeIntegrationPolicies: true } }), - }, packagePolicyService: { get: jest.fn().mockReturnValue({}), buildPackagePolicyFromPackage: jest.fn(), @@ -87,84 +74,79 @@ describe('SyntheticsPrivateLocation', () => { getSpaceId: jest.fn().mockReturnValue('nonDefaultSpace'), }, }, + coreStart: { + savedObjects: savedObjectsServiceMock.createStartContract(), + }, } as unknown as UptimeServerSetup; - it.each([ - [true, 'Unable to create Synthetics package policy template for private location'], - [ - false, - 'Unable to create Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.', - ], - ])('throws errors for create monitor', async (writeIntegrationPolicies, error) => { - const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ - ...serverMock, - fleet: { - ...serverMock.fleet, - authz: { - fromRequest: jest.fn().mockReturnValue({ integrations: { writeIntegrationPolicies } }), + it.each([['Unable to create Synthetics package policy template for private location']])( + 'throws errors for create monitor', + async (error) => { + const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ + ...serverMock, + fleet: { + ...serverMock.fleet, }, - }, - }); + }); - try { - await syntheticsPrivateLocation.createPackagePolicies( - [{ config: testConfig, globalParams: {} }], - {} as unknown as KibanaRequest, - savedObjectsClientMock, - [mockPrivateLocation], - 'test-space' - ); - } catch (e) { - expect(e).toEqual(new Error(error)); + try { + await syntheticsPrivateLocation.createPackagePolicies( + [{ config: testConfig, globalParams: {} }], + {} as unknown as KibanaRequest, + [mockPrivateLocation], + 'test-space' + ); + } catch (e) { + expect(e).toEqual(new Error(error)); + } } - }); + ); - it.each([ - [true, 'Unable to create Synthetics package policy template for private location'], - [ - false, - 'Unable to update Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.', - ], - ])('throws errors for edit monitor', async (writeIntegrationPolicies, error) => { - const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ - ...serverMock, - fleet: { - ...serverMock.fleet, - authz: { - fromRequest: jest.fn().mockReturnValue({ integrations: { writeIntegrationPolicies } }), + it.each([['Unable to create Synthetics package policy template for private location']])( + 'throws errors for edit monitor', + async (error) => { + const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ + ...serverMock, + fleet: { + ...serverMock.fleet, }, - }, - }); + }); - try { - await syntheticsPrivateLocation.editMonitors( - [{ config: testConfig, globalParams: {} }], - {} as unknown as KibanaRequest, - savedObjectsClientMock, - [mockPrivateLocation], - 'test-space' - ); - } catch (e) { - expect(e).toEqual(new Error(error)); + try { + await syntheticsPrivateLocation.editMonitors( + [{ config: testConfig, globalParams: {} }], + {} as unknown as KibanaRequest, + [mockPrivateLocation], + 'test-space' + ); + } catch (e) { + expect(e).toEqual(new Error(error)); + } } - }); + ); it.each([ [ - true, 'Unable to delete Synthetics package policy for monitor Test Monitor with private location Test Location', ], [ - false, 'Unable to delete Synthetics package policy for monitor Test Monitor. Fleet write permissions are needed to use Synthetics private locations.', ], - ])('throws errors for delete monitor', async (writeIntegrationPolicies, error) => { + ])('throws errors for delete monitor', async (error) => { const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ ...serverMock, fleet: { ...serverMock.fleet, - authz: { - fromRequest: jest.fn().mockReturnValue({ integrations: { writeIntegrationPolicies } }), + packagePolicyService: { + ...serverMock.fleet.packagePolicyService, + delete( + soClient: SavedObjectsClientContract, + esClient: any, + ids: string[], + options?: any + ): any { + throw new Error(error); + }, }, }, }); @@ -172,7 +154,6 @@ describe('SyntheticsPrivateLocation', () => { await syntheticsPrivateLocation.deleteMonitors( [testConfig], {} as unknown as KibanaRequest, - savedObjectsClientMock, 'test-space' ); } catch (e) { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 5f06839d4bc03..479b1b6f375b0 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; +import { KibanaRequest } from '@kbn/core/server'; import { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import { NewPackagePolicyWithId } from '@kbn/fleet-plugin/server/services/package_policy'; import { cloneDeep } from 'lodash'; @@ -38,9 +38,11 @@ export class SyntheticsPrivateLocation { this.server = _server; } - async buildNewPolicy(savedObjectsClient: SavedObjectsClientContract): Promise { + async buildNewPolicy(): Promise { + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); + const newPolicy = await this.server.fleet.packagePolicyService.buildPackagePolicyFromPackage( - savedObjectsClient, + soClient, 'synthetics', this.server.logger ); @@ -62,15 +64,10 @@ export class SyntheticsPrivateLocation { generateNewPolicy( config: HeartbeatConfig, privateLocation: PrivateLocation, - savedObjectsClient: SavedObjectsClientContract, newPolicyTemplate: NewPackagePolicy, spaceId: string, globalParams: Record ): (NewPackagePolicy & { policy_id: string }) | null { - if (!savedObjectsClient) { - throw new Error('Could not find savedObjectsClient'); - } - const { label: locName } = privateLocation; const newPolicy = cloneDeep(newPolicyTemplate); @@ -106,34 +103,19 @@ export class SyntheticsPrivateLocation { } } - async checkPermissions(request: KibanaRequest, error: string) { - const { - integrations: { writeIntegrationPolicies }, - } = await this.server.fleet.authz.fromRequest(request); - - if (!writeIntegrationPolicies) { - throw new Error(error); - } - } - async createPackagePolicies( configs: PrivateConfig[], request: KibanaRequest, - savedObjectsClient: SavedObjectsClientContract, privateLocations: PrivateLocation[], spaceId: string ) { if (configs.length === 0) { return { created: [], failed: [] }; } - await this.checkPermissions( - request, - `Unable to create Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` - ); const newPolicies: NewPackagePolicyWithId[] = []; - const newPolicyTemplate = await this.buildNewPolicy(savedObjectsClient); + const newPolicyTemplate = await this.buildNewPolicy(); for (const { config, globalParams } of configs) { try { @@ -153,7 +135,6 @@ export class SyntheticsPrivateLocation { const newPolicy = this.generateNewPolicy( config, location, - savedObjectsClient, newPolicyTemplate, spaceId, globalParams @@ -181,7 +162,7 @@ export class SyntheticsPrivateLocation { } try { - return await this.createPolicyBulk(newPolicies, savedObjectsClient); + return await this.createPolicyBulk(newPolicies); } catch (e) { this.server.logger.error(e); throw e; @@ -190,19 +171,18 @@ export class SyntheticsPrivateLocation { async inspectPackagePolicy({ privateConfig, - savedObjectsClient, spaceId, allPrivateLocations, }: { privateConfig?: PrivateConfig; - savedObjectsClient: SavedObjectsClientContract; allPrivateLocations: PrivateLocation[]; spaceId: string; }) { if (!privateConfig) { return null; } - const newPolicyTemplate = await this.buildNewPolicy(savedObjectsClient); + const newPolicyTemplate = await this.buildNewPolicy(); + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); const { config, globalParams } = privateConfig; try { @@ -215,7 +195,6 @@ export class SyntheticsPrivateLocation { const newPolicy = this.generateNewPolicy( config, location, - savedObjectsClient, newPolicyTemplate, spaceId, globalParams @@ -226,7 +205,7 @@ export class SyntheticsPrivateLocation { id: this.getPolicyId(config, location.id, spaceId), } as NewPackagePolicyWithId; - return await this.server.fleet.packagePolicyService.inspect(savedObjectsClient, pkgPolicy); + return await this.server.fleet.packagePolicyService.inspect(soClient, pkgPolicy); } catch (e) { this.server.logger.error(e); return null; @@ -236,7 +215,6 @@ export class SyntheticsPrivateLocation { async editMonitors( configs: Array<{ config: HeartbeatConfig; globalParams: Record }>, request: KibanaRequest, - savedObjectsClient: SavedObjectsClientContract, allPrivateLocations: PrivateLocation[], spaceId: string ) { @@ -244,12 +222,7 @@ export class SyntheticsPrivateLocation { return {}; } - await this.checkPermissions( - request, - `Unable to update Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` - ); - - const newPolicyTemplate = await this.buildNewPolicy(savedObjectsClient); + const newPolicyTemplate = await this.buildNewPolicy(); const policiesToUpdate: NewPackagePolicyWithId[] = []; const policiesToCreate: NewPackagePolicyWithId[] = []; @@ -258,7 +231,6 @@ export class SyntheticsPrivateLocation { const existingPolicies = await this.getExistingPolicies( configs.map(({ config }) => config), allPrivateLocations, - savedObjectsClient, spaceId ); @@ -276,7 +248,6 @@ export class SyntheticsPrivateLocation { const newPolicy = this.generateNewPolicy( config, privateLocation, - savedObjectsClient, newPolicyTemplate, spaceId, globalParams @@ -302,9 +273,9 @@ export class SyntheticsPrivateLocation { } const [_createResponse, failedUpdatesRes, _deleteResponse] = await Promise.all([ - this.createPolicyBulk(policiesToCreate, savedObjectsClient), - this.updatePolicyBulk(policiesToUpdate, savedObjectsClient), - this.deletePolicyBulk(policiesToDelete, savedObjectsClient), + this.createPolicyBulk(policiesToCreate), + this.updatePolicyBulk(policiesToUpdate), + this.deletePolicyBulk(policiesToDelete), ]); const failedUpdates = failedUpdatesRes?.map(({ packagePolicy, error }) => { @@ -332,9 +303,10 @@ export class SyntheticsPrivateLocation { async getExistingPolicies( configs: HeartbeatConfig[], allPrivateLocations: PrivateLocation[], - savedObjectsClient: SavedObjectsClientContract, spaceId: string ) { + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); + const listOfPolicies: string[] = []; for (const config of configs) { for (const privateLocation of allPrivateLocations) { @@ -343,19 +315,16 @@ export class SyntheticsPrivateLocation { } } return ( - (await this.server.fleet.packagePolicyService.getByIDs(savedObjectsClient, listOfPolicies, { + (await this.server.fleet.packagePolicyService.getByIDs(soClient, listOfPolicies, { ignoreMissing: true, })) ?? [] ); } - async createPolicyBulk( - newPolicies: NewPackagePolicyWithId[], - savedObjectsClient: SavedObjectsClientContract - ) { - const soClient = savedObjectsClient; + async createPolicyBulk(newPolicies: NewPackagePolicyWithId[]) { + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient && newPolicies.length > 0) { + if (esClient && newPolicies.length > 0) { return await this.server.fleet.packagePolicyService.bulkCreate( soClient, esClient, @@ -364,11 +333,8 @@ export class SyntheticsPrivateLocation { } } - async updatePolicyBulk( - policiesToUpdate: NewPackagePolicyWithId[], - savedObjectsClient: SavedObjectsClientContract - ) { - const soClient = savedObjectsClient; + async updatePolicyBulk(policiesToUpdate: NewPackagePolicyWithId[]) { + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); const esClient = this.server.uptimeEsClient.baseESClient; if (soClient && esClient && policiesToUpdate.length > 0) { const { failedPolicies } = await this.server.fleet.packagePolicyService.bulkUpdate( @@ -383,11 +349,8 @@ export class SyntheticsPrivateLocation { } } - async deletePolicyBulk( - policyIdsToDelete: string[], - savedObjectsClient: SavedObjectsClientContract - ) { - const soClient = savedObjectsClient; + async deletePolicyBulk(policyIdsToDelete: string[]) { + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); const esClient = this.server.uptimeEsClient.baseESClient; if (soClient && esClient && policyIdsToDelete.length > 0) { return await this.server.fleet.packagePolicyService.delete( @@ -401,12 +364,10 @@ export class SyntheticsPrivateLocation { } } - async deleteMonitors( - configs: HeartbeatConfig[], - request: KibanaRequest, - soClient: SavedObjectsClientContract, - spaceId: string - ) { + async deleteMonitors(configs: HeartbeatConfig[], request: KibanaRequest, spaceId: string) { + const soClient = this.server.coreStart.savedObjects.createInternalRepository(); + const esClient = this.server.uptimeEsClient.baseESClient; + const policyIdsToDelete = []; for (const config of configs) { const { locations } = config; @@ -414,7 +375,6 @@ export class SyntheticsPrivateLocation { const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); for (const privateLocation of monitorPrivateLocations) { - await this.checkPermissions(request, deletePermissionError(config[ConfigKey.NAME])); try { policyIdsToDelete.push(this.getPolicyId(config, privateLocation.id, spaceId)); } catch (e) { @@ -424,8 +384,19 @@ export class SyntheticsPrivateLocation { } } if (policyIdsToDelete.length > 0) { - await this.checkPermissions(request, deletePermissionError()); - await this.deletePolicyBulk(policyIdsToDelete, soClient); + const result = await this.server.fleet.packagePolicyService.delete( + soClient, + esClient, + policyIdsToDelete, + { + force: true, + } + ); + const failedPolicies = result?.filter((policy) => !policy.success); + if (failedPolicies?.length === policyIdsToDelete.length) { + throw new Error(deletePolicyError(configs[0][ConfigKey.NAME])); + } + return result; } } @@ -450,10 +421,6 @@ const throwAddEditError = (hasPolicy: boolean, location?: string, name?: string) ); }; -export const deletePermissionError = (name?: string) => { - return `Unable to delete Synthetics package policy for monitor ${name}. Fleet write permissions are needed to use Synthetics private locations.`; -}; - const deletePolicyError = (name: string, location?: string) => { return `Unable to delete Synthetics package policy for monitor ${name} with private location ${location}`; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts index b812ac1cbea12..c5e04cc795776 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts @@ -5,11 +5,8 @@ * 2.0. */ import { loggerMock } from '@kbn/logging-mocks'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { - INSUFFICIENT_FLEET_PERMISSIONS, - ProjectMonitorFormatter, -} from './project_monitor_formatter'; +import { savedObjectsClientMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; +import { ProjectMonitorFormatter } from './project_monitor_formatter'; import { ConfigKey, DataStream, @@ -123,6 +120,9 @@ describe('ProjectMonitorFormatter', () => { }, }, encryptedSavedObjects: mockEncryptedSO(), + coreStart: { + savedObjects: savedObjectsServiceMock.createStartContract(), + }, } as unknown as UptimeServerSetup; const syntheticsService = new SyntheticsService(serverMock); @@ -164,13 +164,14 @@ describe('ProjectMonitorFormatter', () => { } as any) ); - it('should return errors', async () => { + it('should return validation errors errors', async () => { const pushMonitorFormatter = new ProjectMonitorFormatter({ projectId: 'test-project', - spaceId: 'default-space', + // @ts-ignore + spaceId: 5, routeContext, encryptedSavedObjectsClient, - monitors: testMonitors, + monitors: [testMonitors[0]], }); pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); @@ -185,76 +186,17 @@ describe('ProjectMonitorFormatter', () => { createdMonitors: [], failedMonitors: [ { - details: "Cannot read properties of undefined (reading 'authz')", + details: 'spaceId.replace is not a function', id: 'check if title is present 10 0', payload: testMonitors[0], reason: 'Failed to create or update monitor', }, - { - details: "Cannot read properties of undefined (reading 'authz')", - id: 'check if title is present 10 1', - payload: testMonitors[1], - reason: 'Failed to create or update monitor', - }, - ], - updatedMonitors: [], - }); - }); - - it('throws fleet permission error', async () => { - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: false } }), - }, - } as any; - - const pushMonitorFormatter = new ProjectMonitorFormatter({ - projectId: 'test-project', - spaceId: 'default-space', - encryptedSavedObjectsClient, - routeContext, - monitors: testMonitors, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - }).toStrictEqual({ - createdMonitors: [], - failedMonitors: [ - { - details: INSUFFICIENT_FLEET_PERMISSIONS, - id: 'check if title is present 10 0', - payload: testMonitors[0], - reason: 'Failed to create or update monitor', - }, - { - details: INSUFFICIENT_FLEET_PERMISSIONS, - id: 'check if title is present 10 1', - payload: testMonitors[1], - reason: 'Failed to create or update monitor', - }, ], updatedMonitors: [], }); }); it('catches errors from bulk edit method', async () => { - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - } as any; - soClient.bulkCreate.mockImplementation(async () => { return { saved_objects: [], @@ -282,7 +224,7 @@ describe('ProjectMonitorFormatter', () => { updatedMonitors: [], failedMonitors: [ { - details: "Cannot read properties of undefined (reading 'buildPackagePolicyFromPackage')", + details: "Cannot read properties of undefined (reading 'packagePolicyService')", payload: payloadData, reason: 'Failed to create 2 monitors', }, @@ -291,14 +233,6 @@ describe('ProjectMonitorFormatter', () => { }); it('configures project monitors when there are errors', async () => { - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - } as any; - soClient.bulkCreate = jest.fn().mockResolvedValue({ saved_objects: [] }); const pushMonitorFormatter = new ProjectMonitorFormatter({ @@ -322,7 +256,7 @@ describe('ProjectMonitorFormatter', () => { updatedMonitors: [], failedMonitors: [ { - details: "Cannot read properties of undefined (reading 'buildPackagePolicyFromPackage')", + details: "Cannot read properties of undefined (reading 'packagePolicyService')", payload: payloadData, reason: 'Failed to create 2 monitors', }, @@ -331,15 +265,6 @@ describe('ProjectMonitorFormatter', () => { }); it('shows errors thrown by fleet api', async () => { - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - packagePolicyService: {}, - } as any; - soClient.bulkCreate = jest.fn().mockResolvedValue({ saved_objects: soResult }); const pushMonitorFormatter = new ProjectMonitorFormatter({ @@ -363,8 +288,7 @@ describe('ProjectMonitorFormatter', () => { updatedMonitors: [], failedMonitors: [ { - details: - 'this.server.fleet.packagePolicyService.buildPackagePolicyFromPackage is not a function', + details: "Cannot read properties of undefined (reading 'packagePolicyService')", reason: 'Failed to create 2 monitors', payload: payloadData, }, @@ -373,14 +297,6 @@ describe('ProjectMonitorFormatter', () => { }); it('creates project monitors when no errors', async () => { - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - } as any; - soClient.bulkCreate = jest.fn().mockResolvedValue({ saved_objects: soResult }); monitorClient.addMonitors = jest.fn().mockReturnValue([]); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts index 8128847253bb1..d9540da65ec11 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { KibanaRequest } from '@kbn/core/server'; import pMap from 'p-map'; import { SavedObjectsUpdateResponse, @@ -41,14 +40,6 @@ import { normalizeProjectMonitor } from './normalizers'; type FailedError = Array<{ id?: string; reason: string; details: string; payload?: object }>; -export const INSUFFICIENT_FLEET_PERMISSIONS = i18n.translate( - 'xpack.synthetics.service.projectMonitors.insufficientFleetPermissions', - { - defaultMessage: - 'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.', - } -); - export const CANNOT_UPDATE_MONITOR_TO_DIFFERENT_TYPE = i18n.translate( 'xpack.synthetics.service.projectMonitors.cannotUpdateMonitorToDifferentType', { @@ -77,11 +68,8 @@ export class ProjectMonitorFormatter { private server: UptimeServerSetup; private projectFilter: string; private syntheticsMonitorClient: SyntheticsMonitorClient; - private request: KibanaRequest; private routeContext: RouteContext; - private writeIntegrationPoliciesPermissions?: boolean; - constructor({ encryptedSavedObjectsClient, projectId, @@ -104,7 +92,6 @@ export class ProjectMonitorFormatter { this.monitors = monitors; this.server = routeContext.server; this.projectFilter = `${syntheticsMonitorType}.attributes.${ConfigKey.PROJECT_ID}: "${this.projectId}"`; - this.request = routeContext.request; this.publicLocations = []; this.privateLocations = []; } @@ -186,21 +173,6 @@ export class ProjectMonitorFormatter { ]); }; - validatePermissions = async ({ monitor }: { monitor: ProjectMonitor }) => { - if (this.writeIntegrationPoliciesPermissions || (monitor.privateLocations ?? []).length === 0) { - return; - } - const { - integrations: { writeIntegrationPolicies }, - } = await this.server.fleet.authz.fromRequest(this.request); - - this.writeIntegrationPoliciesPermissions = writeIntegrationPolicies; - - if (!writeIntegrationPolicies) { - throw new Error(INSUFFICIENT_FLEET_PERMISSIONS); - } - }; - validateProjectMonitor = async ({ monitor, publicLocations, @@ -211,8 +183,6 @@ export class ProjectMonitorFormatter { privateLocations: PrivateLocation[]; }) => { try { - await this.validatePermissions({ monitor }); - const { normalizedFields: normalizedMonitor, errors } = normalizeProjectMonitor({ monitor, locations: this.publicLocations, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts index 95169f39d4943..997dfe842507e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts @@ -77,7 +77,6 @@ export class SyntheticsMonitorClient { const newPolicies = this.privateLocationAPI.createPackagePolicies( privateConfigs, request, - savedObjectsClient, allPrivateLocations, spaceId ); @@ -98,7 +97,7 @@ export class SyntheticsMonitorClient { allPrivateLocations: PrivateLocation[], spaceId: string ) { - const { request, savedObjectsClient } = routeContext; + const { request } = routeContext; const privateConfigs: Array<{ config: HeartbeatConfig; globalParams: Record }> = []; @@ -146,7 +145,6 @@ export class SyntheticsMonitorClient { const privateEditPromise = this.privateLocationAPI.editMonitors( privateConfigs, request, - savedObjectsClient, allPrivateLocations, spaceId ); @@ -168,12 +166,7 @@ export class SyntheticsMonitorClient { savedObjectsClient: SavedObjectsClientContract, spaceId: string ) { - const privateDeletePromise = this.privateLocationAPI.deleteMonitors( - monitors, - request, - savedObjectsClient, - spaceId - ); + const privateDeletePromise = this.privateLocationAPI.deleteMonitors(monitors, request, spaceId); const publicDeletePromise = this.syntheticsService.deleteConfigs( monitors.map((monitor) => ({ monitor, configId: monitor.config_id, params: {} })) @@ -256,7 +249,6 @@ export class SyntheticsMonitorClient { await this.privateLocationAPI.editMonitors( privateConfigs, request, - savedObjectsClient, allPrivateLocations, spaceId ); @@ -401,7 +393,6 @@ export class SyntheticsMonitorClient { ); const privatePromise = this.privateLocationAPI.inspectPackagePolicy({ privateConfig: privateConfigs?.[0], - savedObjectsClient, allPrivateLocations, spaceId, }); diff --git a/x-pack/plugins/synthetics/tsconfig.json b/x-pack/plugins/synthetics/tsconfig.json index 6ed6d749b193c..21261820072d0 100644 --- a/x-pack/plugins/synthetics/tsconfig.json +++ b/x-pack/plugins/synthetics/tsconfig.json @@ -83,6 +83,7 @@ "@kbn/core-http-server", "@kbn/core-application-browser", "@kbn/std", + "@kbn/core-saved-objects-server-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index acfab0c82bea1..146dde8511fbb 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -36493,7 +36493,6 @@ "xpack.synthetics.monitorManagement.callout.disabled": "La Gestion des moniteurs est actuellement désactivée", "xpack.synthetics.monitorManagement.callout.disabled.adminContact": "La Gestion des moniteurs sera activée lorsqu'un administrateur visitera l'application Synthetics.", "xpack.synthetics.monitorManagement.cancelLabel": "Annuler", - "xpack.synthetics.monitorManagement.cannotSaveIntegration": "Vous n'êtes pas autorisé à gérer des emplacements privés. Cela nécessite le privilège Kibana \"Tous\" pour Fleet et Integrations.", "xpack.synthetics.monitorManagement.closeButtonLabel": "Fermer", "xpack.synthetics.monitorManagement.completed": "TERMINÉ", "xpack.synthetics.monitorManagement.configurations.label": "Configurations", @@ -36554,7 +36553,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "Statut", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "Impossible de synchroniser les moniteurs avec le service Synthetics", "xpack.synthetics.monitorManagement.nameRequired": "Le nom de l’emplacement est requis", - "xpack.synthetics.monitorManagement.noFleetPermission": "Vous n'êtes pas autorisé à effectuer cette action. Cela nécessite le privilège Kibana \"Tous\" pour Integrations.", "xpack.synthetics.monitorManagement.noSyntheticsPermissions": "Vous ne disposez pas d'autorisations suffisantes pour effectuer cette action.", "xpack.synthetics.monitorManagement.overviewTab.title": "Aperçu", "xpack.synthetics.monitorManagement.param.keyExists": "La clé existe déjà", @@ -36567,7 +36565,6 @@ "xpack.synthetics.monitorManagement.policyHost": "Politique d'agent", "xpack.synthetics.monitorManagement.privateLocations": "Emplacements privés", "xpack.synthetics.monitorManagement.privateLocations.needPermissions": "Il vous manque certains privilèges Kibana pour gérer les emplacements privés", - "xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage": "Vous ne disposez pas d'autorisation pour ajouter des moniteurs dans des emplacements privés. Contactez votre administrateur pour demander des droits d'accès.", "xpack.synthetics.monitorManagement.priviledges.all": "Tous", "xpack.synthetics.monitorManagement.projectDelete.docsLink": "En savoir plus", "xpack.synthetics.monitorManagement.projectPush.label": "Commande push du projet", @@ -36772,7 +36769,6 @@ "xpack.synthetics.server.projectMonitors.locationEmptyError": "Vous devez ajouter au moins un emplacement ou un emplacement privé à ce moniteur.", "xpack.synthetics.service.projectMonitors.cannotUpdateMonitorToDifferentType": "Impossible de mettre à jour le moniteur avec un type différent.", "xpack.synthetics.service.projectMonitors.failedToUpdateMonitor": "Impossible de créer ou de mettre à jour le moniteur", - "xpack.synthetics.service.projectMonitors.insufficientFleetPermissions": "Permissions insuffisantes. Pour configurer les emplacements privés, vous devez disposer d'autorisations d'écriture sur Fleet et sur les intégrations. Pour résoudre ce problème, veuillez générer une nouvelle clé d'API avec un utilisateur disposant des autorisations d'écriture sur Fleet et sur les intégrations.", "xpack.synthetics.settings.alertDefaultForm.requiredEmail": "À : L'e-mail est requis pour le connecteur d'e-mails sélectionné", "xpack.synthetics.settings.applyChanges": "Appliquer les modifications", "xpack.synthetics.settings.blank.error": "Ne peut pas être vide.", @@ -39611,4 +39607,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "Présentation" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 87d4bd9bc7916..120c1ffc8283b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -36474,7 +36474,6 @@ "xpack.synthetics.monitorManagement.callout.disabled": "モニター管理は現在無効です", "xpack.synthetics.monitorManagement.callout.disabled.adminContact": "管理者がSyntheticsアプリにアクセスすると、モニター管理が有効化されます。", "xpack.synthetics.monitorManagement.cancelLabel": "キャンセル", - "xpack.synthetics.monitorManagement.cannotSaveIntegration": "非公開の場所を管理する権限がありません。Fleetと統合の両方で「すべて」Kibana権限が必要です。", "xpack.synthetics.monitorManagement.closeButtonLabel": "閉じる", "xpack.synthetics.monitorManagement.completed": "完了", "xpack.synthetics.monitorManagement.configurations.label": "構成", @@ -36535,7 +36534,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "ステータス", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "モニターをSyntheticsサービスと同期できませんでした", "xpack.synthetics.monitorManagement.nameRequired": "場所名は必須です", - "xpack.synthetics.monitorManagement.noFleetPermission": "このアクションを実行する権限がありません。統合には「すべて」Kibana権限が必要です。", "xpack.synthetics.monitorManagement.noSyntheticsPermissions": "このアクションを実行する十分な権限がありません。", "xpack.synthetics.monitorManagement.overviewTab.title": "概要", "xpack.synthetics.monitorManagement.param.keyExists": "キーがすでに存在します", @@ -36548,7 +36546,6 @@ "xpack.synthetics.monitorManagement.policyHost": "エージェントポリシー", "xpack.synthetics.monitorManagement.privateLocations": "非公開の場所", "xpack.synthetics.monitorManagement.privateLocations.needPermissions": "非公開の場所を管理するための一部のKibana権限がありません", - "xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage": "モニターを非公開の場所に追加する権限がありません。アクセスをリクエストするには、管理者に問い合わせてください。", "xpack.synthetics.monitorManagement.priviledges.all": "すべて", "xpack.synthetics.monitorManagement.projectDelete.docsLink": "詳細", "xpack.synthetics.monitorManagement.projectPush.label": "プロジェクトプッシュコマンド", @@ -36753,7 +36750,6 @@ "xpack.synthetics.server.projectMonitors.locationEmptyError": "1つ以上の場所または非公開の場所をこのモニターに追加する必要があります。", "xpack.synthetics.service.projectMonitors.cannotUpdateMonitorToDifferentType": "モニターを別のタイプに更新できません。", "xpack.synthetics.service.projectMonitors.failedToUpdateMonitor": "モニターを作成または更新できません", - "xpack.synthetics.service.projectMonitors.insufficientFleetPermissions": "パーミッションがありません。非公開の場所を構成するには、Fleetと統合の書き込み権限が必要です。解決するには、Fleetと統合の書き込み権限が割り当てられたユーザーで、新しいAPIキーを生成してください。", "xpack.synthetics.settings.alertDefaultForm.requiredEmail": "終了:選択した電子メールコネクターには電子メールアドレスが必須です", "xpack.synthetics.settings.applyChanges": "変更を適用", "xpack.synthetics.settings.blank.error": "空白にすることはできません。", @@ -39581,4 +39577,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "実地検証" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 473cab9ace496..bc6429e21ec95 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -36468,7 +36468,6 @@ "xpack.synthetics.monitorManagement.callout.disabled": "监测管理当前处于禁用状态", "xpack.synthetics.monitorManagement.callout.disabled.adminContact": "管理员访问 Synthetics 应用时,将启用监测管理。", "xpack.synthetics.monitorManagement.cancelLabel": "取消", - "xpack.synthetics.monitorManagement.cannotSaveIntegration": "您无权管理专用位置。这需要 Fleet 和集成的“全部”Kibana 权限。", "xpack.synthetics.monitorManagement.closeButtonLabel": "关闭", "xpack.synthetics.monitorManagement.completed": "已完成", "xpack.synthetics.monitorManagement.configurations.label": "配置", @@ -36529,7 +36528,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "状态", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "监测无法与 Synthetics 服务同步", "xpack.synthetics.monitorManagement.nameRequired": "“位置名称”必填", - "xpack.synthetics.monitorManagement.noFleetPermission": "您无权执行此操作。这需要集成的“全部”Kibana 权限。", "xpack.synthetics.monitorManagement.noSyntheticsPermissions": "您的权限不足,无法执行此操作。", "xpack.synthetics.monitorManagement.overviewTab.title": "概览", "xpack.synthetics.monitorManagement.param.keyExists": "密钥已存在", @@ -36542,7 +36540,6 @@ "xpack.synthetics.monitorManagement.policyHost": "代理策略", "xpack.synthetics.monitorManagement.privateLocations": "专用位置", "xpack.synthetics.monitorManagement.privateLocations.needPermissions": "您缺少管理专用位置所需的某些 Kibana 权限", - "xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage": "您无权将监测添加到专用位置。请联系管理员请求访问权限。", "xpack.synthetics.monitorManagement.priviledges.all": "全部", "xpack.synthetics.monitorManagement.projectDelete.docsLink": "了解详情", "xpack.synthetics.monitorManagement.projectPush.label": "项目推送命令", @@ -36747,7 +36744,6 @@ "xpack.synthetics.server.projectMonitors.locationEmptyError": "必须至少将一个位置或专用位置添加到此监测。", "xpack.synthetics.service.projectMonitors.cannotUpdateMonitorToDifferentType": "无法将监测更新为不同类型。", "xpack.synthetics.service.projectMonitors.failedToUpdateMonitor": "无法创建或更新监测", - "xpack.synthetics.service.projectMonitors.insufficientFleetPermissions": "权限不足。要配置专用位置,您必须具有 Fleet 和集成写入权限。要解决问题,请通过具有 Fleet 和集成写入权限的用户生成新的 API 密钥。", "xpack.synthetics.settings.alertDefaultForm.requiredEmail": "到:选定的电子邮件连接器需要电子邮件", "xpack.synthetics.settings.applyChanges": "应用更改", "xpack.synthetics.settings.blank.error": "不能为空。", @@ -39575,4 +39571,4 @@ "xpack.painlessLab.title": "Painless 实验室", "xpack.painlessLab.walkthroughButtonLabel": "指导" } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index f56f37d0435e7..f315dd711ff7e 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -324,8 +324,6 @@ export default function ({ getService }: FtrProviderContext) { { feature: { uptime: ['all'], - fleet: ['all'], - fleetv2: ['all'], actions: ['all'], }, spaces: ['*'], diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index 9908c1e576463..bf0b46022f83e 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -1280,7 +1280,7 @@ export default function ({ getService }: FtrProviderContext) { } }); - it('project monitors - returns a failed monitor when user defines a private location without fleet permissions', async () => { + it('project monitors - cannot update project monitors with read only privileges', async () => { const project = `test-project-${uuidv4()}`; const secondMonitor = { @@ -1297,7 +1297,7 @@ export default function ({ getService }: FtrProviderContext) { kibana: [ { feature: { - uptime: ['all'], + uptime: ['read'], }, spaces: ['*'], }, @@ -1309,63 +1309,21 @@ export default function ({ getService }: FtrProviderContext) { full_name: 'a kibana user', }); - const { body } = await supertestWithoutAuth + await supertestWithoutAuth .put( SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project) ) .auth(username, password) .set('kbn-xsrf', 'true') .send({ monitors: testMonitors }) - .expect(200); - - expect(body).eql({ - createdMonitors: [testMonitors[0].id], - updatedMonitors: [], - failedMonitors: [ - { - details: - 'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.', - id: 'test-id-2', - payload: { - content: - 'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA', - filter: { - match: 'check if title is present', - }, - id: 'test-id-2', - locations: ['localhost'], - name: 'check if title is present', - params: {}, - playwrightOptions: { - chromiumSandbox: false, - headless: true, - }, - privateLocations: ['Test private location 0'], - schedule: 10, - tags: [], - throttling: { - download: 5, - latency: 20, - upload: 3, - }, - hash: 'ekrjelkjrelkjre', - }, - reason: 'Failed to create or update monitor', - }, - ], - }); + .expect(403); } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor(monitor.id, project, 'default'); - }), - ]); await security.user.delete(username); await security.role.delete(roleName); } }); - it('project monitors - returns a successful monitor when user defines a private location with fleet permissions', async () => { + it('project monitors - returns a successful monitor when user defines a private location, even without fleet permissions', async () => { const project = `test-project-${uuidv4()}`; const secondMonitor = { ...projectMonitors.monitors[0], @@ -1382,8 +1340,6 @@ export default function ({ getService }: FtrProviderContext) { { feature: { uptime: ['all'], - fleetv2: ['all'], - fleet: ['all'], }, spaces: ['*'], }, @@ -1394,10 +1350,11 @@ export default function ({ getService }: FtrProviderContext) { roles: [roleName], full_name: 'a kibana user', }); - const { body } = await supertest + const { body } = await supertestWithoutAuth .put( SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project) ) + .auth(username, password) .set('kbn-xsrf', 'true') .send({ monitors: testMonitors }) .expect(200); diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts index 54650a5d53cbf..f7c5f99e81f66 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts @@ -8,6 +8,8 @@ import { v4 as uuidv4 } from 'uuid'; import { HTTPFields, MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; @@ -115,13 +117,9 @@ export default function ({ getService }: FtrProviderContext) { const username = 'admin'; const roleName = `synthetics_admin`; const password = `${username}-password`; - const SPACE_ID = `test-space-${uuidv4()}`; - const SPACE_NAME = `test-space-name ${uuidv4()}`; let monitorId = ''; try { - await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - // use a user without fleet permissions to cause an error await security.role.create(roleName, { kibana: [ @@ -140,6 +138,10 @@ export default function ({ getService }: FtrProviderContext) { }); const { id } = await saveMonitor(newMonitor as MonitorFields); monitorId = id; + + // delete the integration policy to cause an error + await kibanaServer.savedObjects.clean({ types: [PACKAGE_POLICY_SAVED_OBJECT_TYPE] }); + await supertestWithoutAuth .delete(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) .auth(username, password) @@ -153,10 +155,7 @@ export default function ({ getService }: FtrProviderContext) { } finally { await security.user.delete(username); await security.role.delete(roleName); - await supertest - .delete(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) - .set('kbn-xsrf', 'true') - .expect(200); + await kibanaServer.savedObjects.clean({ types: [syntheticsMonitorType] }); } }); }); diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts index 58f9299cf41f2..ecd824a1052e7 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts @@ -6,7 +6,6 @@ */ import { v4 as uuidv4 } from 'uuid'; import { ConfigKey, ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types'; -import { INSUFFICIENT_FLEET_PERMISSIONS } from '@kbn/synthetics-plugin/server/synthetics_service/project_monitor/project_monitor_formatter'; import { REQUEST_TOO_LARGE } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/delete_monitor_project'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; @@ -505,17 +504,14 @@ export default function ({ getService }: FtrProviderContext) { const { total } = savedObjectsResponse.body; expect(total).to.eql(2); - const { - body: { message }, - } = await supertestWithoutAuth + await supertestWithoutAuth .delete( SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project) ) .set('kbn-xsrf', 'true') .auth(username, password) .send({ monitors: monitorsToDelete }) - .expect(403); - expect(message).to.eql(INSUFFICIENT_FLEET_PERMISSIONS); + .expect(200); } finally { await supertest .delete( diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index eb3b19aa4b8a8..1334579d46d2c 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -311,6 +311,7 @@ export default function ({ getService }: FtrProviderContext) { label: 'Europe West', isServiceManaged: true, }, + { id: testPolicyId, label: 'Private location', isServiceManaged: false }, ], }; @@ -344,10 +345,7 @@ export default function ({ getService }: FtrProviderContext) { monitorId = id; const toUpdate = { ...savedMonitor, - locations: [ - ...savedMonitor.locations, - { id: testPolicyId, label: 'Private location', isServiceManaged: false }, - ], + name: '!@#$%^&*()_++[\\-\\]- wow', urls: 'https://google.com', }; await supertestWithoutAuth @@ -360,6 +358,7 @@ export default function ({ getService }: FtrProviderContext) { const response = await monitorTestService.getMonitor(monitorId); // ensure monitor was not updated + expect(response.body.urls).not.eql(toUpdate.urls); expect(response.body.urls).eql(newMonitor.urls); expect(response.body.locations).eql(newMonitor.locations); } finally {