Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Synthetics] Remove fleet permission requirement for private location monitor cruds #159378

Merged
merged 42 commits into from Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
91d7ce6
simplify fleet permissions
shahzad31 Jun 9, 2023
bd6cf6d
update i18n
shahzad31 Jun 9, 2023
9df0fd1
update test
shahzad31 Jun 9, 2023
a977b74
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 9, 2023
a96eaef
update test
shahzad31 Jun 9, 2023
81f711c
Merge branch 'internal-system-user' of https://github.com/shahzad31/k…
shahzad31 Jun 9, 2023
2bc2adb
update test
shahzad31 Jun 9, 2023
88c1745
update test
shahzad31 Jun 9, 2023
b7400b9
update
shahzad31 Jun 11, 2023
07ef9cb
Merge branch 'main' of https://github.com/elastic/kibana into interna…
shahzad31 Jun 12, 2023
57d28cd
update test
shahzad31 Jun 12, 2023
20ed98e
Merge branch 'main' into internal-system-user
shahzad31 Jun 12, 2023
42aa963
update test
shahzad31 Jun 12, 2023
9bb78dc
Merge branch 'internal-system-user' of https://github.com/shahzad31/k…
shahzad31 Jun 12, 2023
8d40bc4
update test
shahzad31 Jun 12, 2023
45a1a2e
update test
shahzad31 Jun 12, 2023
a4a03fa
update test
shahzad31 Jun 12, 2023
b890ae4
update test
shahzad31 Jun 12, 2023
b7d6b8e
Merge branch 'main' into internal-system-user
kibanamachine Jun 12, 2023
a630634
update test
shahzad31 Jun 13, 2023
f51f7cf
Merge branch 'main' of https://github.com/elastic/kibana into interna…
shahzad31 Jun 13, 2023
008dc6e
Merge branch 'internal-system-user' of https://github.com/shahzad31/k…
shahzad31 Jun 13, 2023
b2a969a
PR feedback
shahzad31 Jun 13, 2023
17ee23d
update types
shahzad31 Jun 13, 2023
54f052e
Merge branch 'main' of https://github.com/elastic/kibana into interna…
shahzad31 Jun 13, 2023
d8dbdc3
adjust test for project monitors
dominiqueclarke Jun 13, 2023
e36bb52
remove fleet privileges requirement from project monitors
dominiqueclarke Jun 13, 2023
0f2cf88
Merge branch 'main' of https://github.com/elastic/kibana into interna…
shahzad31 Jun 14, 2023
3adbffd
update types
shahzad31 Jun 14, 2023
d9647f0
update test
shahzad31 Jun 14, 2023
df99606
update permission
shahzad31 Jun 14, 2023
5928aba
update types
shahzad31 Jun 14, 2023
44b7fdb
type
shahzad31 Jun 14, 2023
6cb9946
Merge branch 'main' into internal-system-user
shahzad31 Jun 14, 2023
1e0020d
Update x-pack/plugins/synthetics/public/apps/synthetics/components/co…
shahzad31 Jun 14, 2023
8b0b381
Update x-pack/plugins/synthetics/public/apps/synthetics/components/se…
shahzad31 Jun 14, 2023
8acc11a
disable agent policy needed prompt when user does not have fleet priv…
dominiqueclarke Jun 14, 2023
27f8774
Update x-pack/plugins/synthetics/public/apps/synthetics/components/co…
shahzad31 Jun 14, 2023
ef8a46c
Update x-pack/plugins/synthetics/public/apps/synthetics/components/co…
shahzad31 Jun 14, 2023
746d286
Merge branch 'main' of https://github.com/elastic/kibana into interna…
shahzad31 Jun 14, 2023
56fa244
update test
shahzad31 Jun 14, 2023
6450cf9
Merge branch 'main' of https://github.com/elastic/kibana into interna…
shahzad31 Jun 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 3 additions & 6 deletions x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
Expand Up @@ -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[]
Expand Down Expand Up @@ -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 = {
Expand All @@ -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 });
Expand Down
21 changes: 13 additions & 8 deletions x-pack/plugins/fleet/server/services/agent_policy.ts
Expand Up @@ -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 {
Expand Down Expand Up @@ -370,6 +372,8 @@ class AgentPolicyService {
options: ListWithKuery & {
withPackagePolicies?: boolean;
fields?: string[];
esClient?: ElasticsearchClient;
withAgentCount?: boolean;
}
): Promise<{
items: AgentPolicy[];
Expand All @@ -385,6 +389,8 @@ class AgentPolicyService {
kuery,
withPackagePolicies = false,
fields,
esClient,
withAgentCount = false,
} = options;

const baseFindParams = {
Expand Down Expand Up @@ -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;
},
Expand All @@ -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,
Expand Down
Expand Up @@ -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`,
Expand Down
Expand Up @@ -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."`
shahzad31 marked this conversation as resolved.
Show resolved Hide resolved
);
const createLocationBtn = await page.getByRole('button', { name: 'Create location' });
expect(await createLocationBtn.getAttribute('disabled')).toEqual('');
Expand Down
Expand Up @@ -14,30 +14,28 @@ export const FleetPermissionsCallout = () => {
return (
<EuiCallOut title={NEED_PERMISSIONS_PRIVATE_LOCATIONS} color="warning" iconType="help">
<p>{NEED_PRIVATE_LOCATIONS_PERMISSION}</p>
<p>
<FormattedMessage
id="xpack.synthetics.privateLocations.needFleetPermission.description"
defaultMessage="Once there is an agent policy available, you'll be able to add manage private locations and monitors with the regular Synthetics app privileges."
shahzad31 marked this conversation as resolved.
Show resolved Hide resolved
/>
</p>
</EuiCallOut>
);
};

/**
* 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 (
<EuiToolTip content={disabledMessage}>
Expand All @@ -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(
Expand All @@ -77,40 +65,16 @@ export const ALL = i18n.translate('xpack.synthetics.monitorManagement.priviledge
export const NEED_PRIVATE_LOCATIONS_PERMISSION = (
<FormattedMessage
id="xpack.synthetics.monitorManagement.privateLocations.needFleetPermission"
defaultMessage="You are not authorized to manage private locations. It requires the {all} Kibana privilege for both Fleet and Integrations."
defaultMessage="In order to create private location, you need an agent policy. You are not authorized to create fleet agent policies. It requires the {all} Kibana privilege for Fleet."
shahzad31 marked this conversation as resolved.
Show resolved Hide resolved
values={{
all: <EuiCode>{`"${ALL}"`}</EuiCode>,
}}
/>
);

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.',
}
);
Expand Up @@ -57,22 +57,22 @@ describe('JourneyScreenshotDialog', () => {
});

it('shows loading indicator when image is loading', async () => {
const { queryByTestId } = render(<JourneyScreenshotDialog {...testProps} />);
const { getByTestId, queryByTestId } = render(<JourneyScreenshotDialog {...testProps} />);

expect(queryByTestId('screenshotImageLoadingProgress')).not.toBeInTheDocument();
userEvent.click(queryByTestId('screenshotImageNextButton'));
userEvent.click(getByTestId('screenshotImageNextButton'));
});

it('respects maxSteps', () => {
const { queryByTestId } = render(<JourneyScreenshotDialog {...testProps} />);
const { getByTestId, queryByTestId } = render(<JourneyScreenshotDialog {...testProps} />);

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(<JourneyScreenshotDialog {...testProps} />);
const { getByText, queryByTestId } = render(<JourneyScreenshotDialog {...testProps} />);
expect(queryByTestId('stepScreenshotThumbnail')).toHaveProperty(
'src',
'http://localhost/test-img-url-1'
Expand Down
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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(<GettingStartedPage />, {
state: {
serviceLocations: {
Expand Down Expand Up @@ -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(<GettingStartedPage />, {
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(
<GettingStartedPage />,
{
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(<GettingStartedPage />, {
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();
});
});
Expand Up @@ -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 {
Expand All @@ -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

Expand Down