[Ingest] Address #59376 feedback (#59961)

* Disable create/destroy CTAs if no write capability

Use `core.application.capabilities.ingestManager.write` to test user permissions

* Add -all & -read tags for HTTP routes

* Update test .expect() to match description

* Add useCapabilities hook. Fix two issues with hiding/disabling CTA.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
John Schulz 2020-03-12 10:48:14 -04:00 committed by GitHub
parent 27d85cf492
commit e12a8ad8a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 123 additions and 65 deletions

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export { useCapabilities } from './use_capabilities';
export { useCore, CoreContext } from './use_core'; export { useCore, CoreContext } from './use_core';
export { useConfig, ConfigContext } from './use_config'; export { useConfig, ConfigContext } from './use_config';
export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps';

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useCore } from './';
export function useCapabilities() {
const core = useCore();
return core.application.capabilities.ingestManager;
}

View file

@ -18,7 +18,7 @@ import {
} from '@elastic/eui'; } from '@elastic/eui';
import { Error } from '../../../components'; import { Error } from '../../../components';
import { AGENT_CONFIG_PATH } from '../../../constants'; import { AGENT_CONFIG_PATH } from '../../../constants';
import { useLink } from '../../../hooks'; import { useCapabilities, useLink } from '../../../hooks';
import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types'; import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types';
import { useGetPackageInfoByKey, useGetAgentConfigs, sendGetOneAgentConfig } from '../../../hooks'; import { useGetPackageInfoByKey, useGetAgentConfigs, sendGetOneAgentConfig } from '../../../hooks';
@ -30,6 +30,7 @@ export const StepSelectConfig: React.FunctionComponent<{
cancelUrl: string; cancelUrl: string;
onNext: () => void; onNext: () => void;
}> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, cancelUrl, onNext }) => { }> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, cancelUrl, onNext }) => {
const hasWriteCapabilites = useCapabilities().write;
// Selected config state // Selected config state
const [selectedConfigId, setSelectedConfigId] = useState<string | undefined>( const [selectedConfigId, setSelectedConfigId] = useState<string | undefined>(
agentConfig ? agentConfig.id : undefined agentConfig ? agentConfig.id : undefined
@ -134,7 +135,12 @@ export const StepSelectConfig: React.FunctionComponent<{
</EuiTitle> </EuiTitle>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="plusInCircle" href={CREATE_NEW_CONFIG_URI} size="s"> <EuiButtonEmpty
isDisabled={!hasWriteCapabilites}
iconType="plusInCircle"
href={CREATE_NEW_CONFIG_URI}
size="s"
>
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.createDatasource.StepSelectConfig.createNewConfigButtonText" id="xpack.ingestManager.createDatasource.StepSelectConfig.createNewConfigButtonText"
defaultMessage="Create new configuration" defaultMessage="Create new configuration"

View file

@ -24,7 +24,7 @@ import {
} from '@elastic/eui'; } from '@elastic/eui';
import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
import styled from 'styled-components'; import styled from 'styled-components';
import { useGetOneAgentConfig } from '../../../hooks'; import { useCapabilities, useGetOneAgentConfig } from '../../../hooks';
import { Datasource } from '../../../types'; import { Datasource } from '../../../types';
import { Loading } from '../../../components'; import { Loading } from '../../../components';
import { WithHeaderLayout } from '../../../layouts'; import { WithHeaderLayout } from '../../../layouts';
@ -57,6 +57,7 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
const { const {
params: { configId, tabId = '' }, params: { configId, tabId = '' },
} = useRouteMatch<{ configId: string; tabId?: string }>(); } = useRouteMatch<{ configId: string; tabId?: string }>();
const hasWriteCapabilites = useCapabilities().write;
const agentConfigRequest = useGetOneAgentConfig(configId); const agentConfigRequest = useGetOneAgentConfig(configId);
const agentConfig = agentConfigRequest.data ? agentConfigRequest.data.item : null; const agentConfig = agentConfigRequest.data ? agentConfigRequest.data.item : null;
const { isLoading, error, sendRequest: refreshAgentConfig } = agentConfigRequest; const { isLoading, error, sendRequest: refreshAgentConfig } = agentConfigRequest;
@ -318,7 +319,12 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
</h2> </h2>
} }
actions={ actions={
<EuiButton fill iconType="plusInCircle" href={URI.ADD_DATASOURCE}> <EuiButton
isDisabled={!hasWriteCapabilites}
fill
iconType="plusInCircle"
href={URI.ADD_DATASOURCE}
>
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.configDetails.addDatasourceButtonText" id="xpack.ingestManager.configDetails.addDatasourceButtonText"
defaultMessage="Create data source" defaultMessage="Create data source"
@ -330,7 +336,11 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
} }
search={{ search={{
toolsRight: [ toolsRight: [
<EuiButton iconType="plusInCircle" href={URI.ADD_DATASOURCE}> <EuiButton
isDisabled={!hasWriteCapabilites}
iconType="plusInCircle"
href={URI.ADD_DATASOURCE}
>
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.configDetails.addDatasourceButtonText" id="xpack.ingestManager.configDetails.addDatasourceButtonText"
defaultMessage="Create data source" defaultMessage="Create data source"

View file

@ -27,7 +27,7 @@ interface Props {
} }
export const CreateAgentConfigFlyout: React.FunctionComponent<Props> = ({ onClose }) => { export const CreateAgentConfigFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
const { notifications } = useCore(); const { application, notifications } = useCore();
const [agentConfig, setAgentConfig] = useState<NewAgentConfig>({ const [agentConfig, setAgentConfig] = useState<NewAgentConfig>({
name: '', name: '',
@ -93,7 +93,11 @@ export const CreateAgentConfigFlyout: React.FunctionComponent<Props> = ({ onClos
<EuiButton <EuiButton
fill fill
isLoading={isLoading} isLoading={isLoading}
disabled={isLoading || Object.keys(validation).length > 0} isDisabled={
!application.capabilities.ingestManager.write ||
isLoading ||
Object.keys(validation).length > 0
}
onClick={async () => { onClick={async () => {
setIsLoading(true); setIsLoading(true);
try { try {

View file

@ -33,6 +33,7 @@ import {
} from '../../../constants'; } from '../../../constants';
import { WithHeaderLayout } from '../../../layouts'; import { WithHeaderLayout } from '../../../layouts';
import { import {
useCapabilities,
useGetAgentConfigs, useGetAgentConfigs,
usePagination, usePagination,
useLink, useLink,
@ -87,6 +88,7 @@ const DangerEuiContextMenuItem = styled(EuiContextMenuItem)`
const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>( const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>(
({ config, onDelete }) => { ({ config, onDelete }) => {
const hasWriteCapabilites = useCapabilities().write;
const DETAILS_URI = useLink(`${AGENT_CONFIG_DETAILS_PATH}${config.id}`); const DETAILS_URI = useLink(`${AGENT_CONFIG_DETAILS_PATH}${config.id}`);
const ADD_DATASOURCE_URI = `${DETAILS_URI}/add-datasource`; const ADD_DATASOURCE_URI = `${DETAILS_URI}/add-datasource`;
@ -120,6 +122,7 @@ const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>(
</EuiContextMenuItem>, </EuiContextMenuItem>,
<EuiContextMenuItem <EuiContextMenuItem
disabled={!hasWriteCapabilites}
icon="plusInCircle" icon="plusInCircle"
href={ADD_DATASOURCE_URI} href={ADD_DATASOURCE_URI}
key="createDataSource" key="createDataSource"
@ -130,7 +133,7 @@ const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>(
/> />
</EuiContextMenuItem>, </EuiContextMenuItem>,
<EuiContextMenuItem icon="copy" disabled={true} key="copyConfig"> <EuiContextMenuItem disabled={true} icon="copy" key="copyConfig">
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.agentConfigList.copyConfigActionText" id="xpack.ingestManager.agentConfigList.copyConfigActionText"
defaultMessage="Copy configuration" defaultMessage="Copy configuration"
@ -162,6 +165,7 @@ const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>(
export const AgentConfigListPage: React.FunctionComponent<{}> = () => { export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
// Config information // Config information
const hasWriteCapabilites = useCapabilities().write;
const { const {
fleet: { enabled: isFleetEnabled }, fleet: { enabled: isFleetEnabled },
} = useConfig(); } = useConfig();
@ -309,6 +313,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
<EuiButton <EuiButton
fill fill
iconType="plusInCircle" iconType="plusInCircle"
isDisabled={!hasWriteCapabilites}
onClick={() => setIsCreateAgentConfigFlyoutOpen(true)} onClick={() => setIsCreateAgentConfigFlyoutOpen(true)}
> >
<FormattedMessage <FormattedMessage
@ -317,7 +322,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
/> />
</EuiButton> </EuiButton>
), ),
[setIsCreateAgentConfigFlyoutOpen] [hasWriteCapabilites, setIsCreateAgentConfigFlyoutOpen]
); );
const emptyPrompt = useMemo( const emptyPrompt = useMemo(
@ -331,10 +336,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
/> />
</h2> </h2>
} }
actions={createAgentConfigButton} actions={hasWriteCapabilites ?? createAgentConfigButton}
/> />
), ),
[createAgentConfigButton] [hasWriteCapabilites, createAgentConfigButton]
); );
return ( return (

View file

@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiTitle, IconType, EuiButton } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiTitle, IconType, EuiButton } from '@elastic/eui';
import { PackageInfo } from '../../../../types'; import { PackageInfo } from '../../../../types';
import { EPM_PATH } from '../../../../constants'; import { EPM_PATH } from '../../../../constants';
import { useLink } from '../../../../hooks'; import { useCapabilities, useLink } from '../../../../hooks';
import { IconPanel } from '../../components/icon_panel'; import { IconPanel } from '../../components/icon_panel';
import { NavButtonBack } from '../../components/nav_button_back'; import { NavButtonBack } from '../../components/nav_button_back';
import { Version } from '../../components/version'; import { Version } from '../../components/version';
@ -34,6 +34,7 @@ type HeaderProps = PackageInfo & { iconType?: IconType };
export function Header(props: HeaderProps) { export function Header(props: HeaderProps) {
const { iconType, name, title, version } = props; const { iconType, name, title, version } = props;
const hasWriteCapabilites = useCapabilities().write;
const { toListView } = useLinks(); const { toListView } = useLinks();
// useBreadcrumbs([{ text: PLUGIN.TITLE, href: toListView() }, { text: title }]); // useBreadcrumbs([{ text: PLUGIN.TITLE, href: toListView() }, { text: title }]);
@ -61,7 +62,11 @@ export function Header(props: HeaderProps) {
<RightColumn> <RightColumn>
<EuiFlexGroup justifyContent="flexEnd"> <EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButton iconType="plusInCircle" href={ADD_DATASOURCE_URI}> <EuiButton
isDisabled={!hasWriteCapabilites}
iconType="plusInCircle"
href={ADD_DATASOURCE_URI}
>
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.epm.addDatasourceButtonText" id="xpack.ingestManager.epm.addDatasourceButtonText"
defaultMessage="Create data source" defaultMessage="Create data source"

View file

@ -6,6 +6,7 @@
import { EuiButton } from '@elastic/eui'; import { EuiButton } from '@elastic/eui';
import React, { Fragment, useCallback, useMemo, useState } from 'react'; import React, { Fragment, useCallback, useMemo, useState } from 'react';
import { PackageInfo, InstallStatus } from '../../../../types'; import { PackageInfo, InstallStatus } from '../../../../types';
import { useCapabilities } from '../../../../hooks';
import { useDeletePackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks'; import { useDeletePackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks';
import { ConfirmPackageDelete } from './confirm_package_delete'; import { ConfirmPackageDelete } from './confirm_package_delete';
import { ConfirmPackageInstall } from './confirm_package_install'; import { ConfirmPackageInstall } from './confirm_package_install';
@ -16,6 +17,7 @@ interface InstallationButtonProps {
export function InstallationButton(props: InstallationButtonProps) { export function InstallationButton(props: InstallationButtonProps) {
const { assets, name, title, version } = props.package; const { assets, name, title, version } = props.package;
const hasWriteCapabilites = useCapabilities().write;
const installPackage = useInstallPackage(); const installPackage = useInstallPackage();
const deletePackage = useDeletePackage(); const deletePackage = useDeletePackage();
const getPackageInstallStatus = useGetPackageInstallStatus(); const getPackageInstallStatus = useGetPackageInstallStatus();
@ -86,10 +88,10 @@ export function InstallationButton(props: InstallationButtonProps) {
/> />
); );
return ( return hasWriteCapabilites ? (
<Fragment> <Fragment>
{isInstalled ? installedButton : installButton} {isInstalled ? installedButton : installButton}
{isModalVisible && (isInstalled ? deletionModal : installationModal)} {isModalVisible && (isInstalled ? deletionModal : installationModal)}
</Fragment> </Fragment>
); ) : null;
} }

View file

@ -24,7 +24,7 @@ import { useAgentRefresh } from '../hooks';
import { AgentMetadataFlyout } from './metadata_flyout'; import { AgentMetadataFlyout } from './metadata_flyout';
import { Agent } from '../../../../types'; import { Agent } from '../../../../types';
import { AgentHealth } from '../../components/agent_health'; import { AgentHealth } from '../../components/agent_health';
import { useGetOneAgentConfig } from '../../../../hooks'; import { useCapabilities, useGetOneAgentConfig } from '../../../../hooks';
import { Loading } from '../../../../components'; import { Loading } from '../../../../components';
import { ConnectedLink } from '../../components'; import { ConnectedLink } from '../../components';
import { AgentUnenrollProvider } from '../../components/agent_unenroll_provider'; import { AgentUnenrollProvider } from '../../components/agent_unenroll_provider';
@ -53,6 +53,7 @@ interface Props {
agent: Agent; agent: Agent;
} }
export const AgentDetailSection: React.FunctionComponent<Props> = ({ agent }) => { export const AgentDetailSection: React.FunctionComponent<Props> = ({ agent }) => {
const hasWriteCapabilites = useCapabilities().write;
const metadataFlyout = useFlyout(); const metadataFlyout = useFlyout();
const refreshAgent = useAgentRefresh(); const refreshAgent = useAgentRefresh();
@ -125,7 +126,7 @@ export const AgentDetailSection: React.FunctionComponent<Props> = ({ agent }) =>
<AgentUnenrollProvider> <AgentUnenrollProvider>
{unenrollAgentsPrompt => ( {unenrollAgentsPrompt => (
<EuiButton <EuiButton
disabled={!agent.active} disabled={!hasWriteCapabilites || !agent.active}
onClick={() => { onClick={() => {
unenrollAgentsPrompt([agent.id], 1, refreshAgent); unenrollAgentsPrompt([agent.id], 1, refreshAgent);
}} }}

View file

@ -12,6 +12,7 @@ import { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks';
import { ConfirmDeleteModal } from './confirm_delete_modal'; import { ConfirmDeleteModal } from './confirm_delete_modal';
import { CreateApiKeyForm } from './create_api_key_form'; import { CreateApiKeyForm } from './create_api_key_form';
import { EnrollmentAPIKey } from '../../../../../types'; import { EnrollmentAPIKey } from '../../../../../types';
import { useCapabilities } from '../../../../../hooks';
import { enrollmentAPIKeyRouteService } from '../../../../../services'; import { enrollmentAPIKeyRouteService } from '../../../../../services';
export { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks'; export { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks';
@ -98,13 +99,14 @@ export const EnrollmentApiKeysTable: React.FunctionComponent<{
export const CreateApiKeyButton: React.FunctionComponent<{ onChange: () => void }> = ({ export const CreateApiKeyButton: React.FunctionComponent<{ onChange: () => void }> = ({
onChange, onChange,
}) => { }) => {
const hasWriteCapabilites = useCapabilities().write;
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
return ( return (
<EuiPopover <EuiPopover
ownFocus ownFocus
button={ button={
<EuiLink onClick={() => setIsOpen(true)} color="primary"> <EuiLink disabled={!hasWriteCapabilites} onClick={() => setIsOpen(true)} color="primary">
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.enrollmentApiKeyList.createNewButton" id="xpack.ingestManager.enrollmentApiKeyList.createNewButton"
defaultMessage="Create a new key" defaultMessage="Create a new key"

View file

@ -34,7 +34,7 @@ import { WithHeaderLayout } from '../../../layouts';
import { Agent } from '../../../types'; import { Agent } from '../../../types';
import { import {
usePagination, usePagination,
useCore, useCapabilities,
useGetAgentConfigs, useGetAgentConfigs,
useGetAgents, useGetAgents,
useUrlParams, useUrlParams,
@ -85,6 +85,7 @@ const statusFilters = [
] as Array<{ label: string; status: string }>; ] as Array<{ label: string; status: string }>;
const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, refresh }) => { const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, refresh }) => {
const hasWriteCapabilites = useCapabilities().write;
const DETAILS_URI = useLink(FLEET_AGENT_DETAIL_PATH); const DETAILS_URI = useLink(FLEET_AGENT_DETAIL_PATH);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]);
@ -118,6 +119,7 @@ const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, r
<AgentUnenrollProvider> <AgentUnenrollProvider>
{unenrollAgentsPrompt => ( {unenrollAgentsPrompt => (
<EuiContextMenuItem <EuiContextMenuItem
disabled={!hasWriteCapabilites}
icon="cross" icon="cross"
onClick={() => { onClick={() => {
unenrollAgentsPrompt([agent.id], 1, () => { unenrollAgentsPrompt([agent.id], 1, () => {
@ -140,7 +142,7 @@ const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, r
export const AgentListPage: React.FunctionComponent<{}> = () => { export const AgentListPage: React.FunctionComponent<{}> = () => {
const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || '';
const core = useCore(); const hasWriteCapabilites = useCapabilities().write;
// Agent data states // Agent data states
const [showInactive, setShowInactive] = useState<boolean>(false); const [showInactive, setShowInactive] = useState<boolean>(false);
@ -363,7 +365,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
</h2> </h2>
} }
actions={ actions={
core.application.capabilities.ingestManager.write ? ( hasWriteCapabilites ? (
<EuiButton fill iconType="plusInCircle" onClick={() => setIsEnrollmentFlyoutOpen(true)}> <EuiButton fill iconType="plusInCircle" onClick={() => setIsEnrollmentFlyoutOpen(true)}>
<FormattedMessage <FormattedMessage
id="xpack.ingestManager.agentList.addButton" id="xpack.ingestManager.agentList.addButton"
@ -432,7 +434,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
})} })}
/> />
</EuiFlexItem> </EuiFlexItem>
{core.application.capabilities.ingestManager.write && ( {hasWriteCapabilites && (
<> <>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<Divider /> <Divider />

View file

@ -18,6 +18,10 @@ import { SecurityPluginSetup } from '../../security/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { import {
PLUGIN_ID, PLUGIN_ID,
OUTPUT_SAVED_OBJECT_TYPE,
AGENT_CONFIG_SAVED_OBJECT_TYPE,
DATASOURCE_SAVED_OBJECT_TYPE,
PACKAGES_SAVED_OBJECT_TYPE,
AGENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
@ -49,6 +53,16 @@ export interface IngestManagerAppContext {
savedObjects: SavedObjectsServiceStart; savedObjects: SavedObjectsServiceStart;
} }
const allSavedObjectTypes = [
OUTPUT_SAVED_OBJECT_TYPE,
AGENT_CONFIG_SAVED_OBJECT_TYPE,
DATASOURCE_SAVED_OBJECT_TYPE,
PACKAGES_SAVED_OBJECT_TYPE,
AGENT_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
];
export class IngestManagerPlugin implements Plugin { export class IngestManagerPlugin implements Plugin {
private config$: Observable<IngestManagerConfigType>; private config$: Observable<IngestManagerConfigType>;
private security: SecurityPluginSetup | undefined; private security: SecurityPluginSetup | undefined;
@ -73,26 +87,18 @@ export class IngestManagerPlugin implements Plugin {
app: [PLUGIN_ID, 'kibana'], app: [PLUGIN_ID, 'kibana'],
privileges: { privileges: {
all: { all: {
api: [PLUGIN_ID], api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`],
savedObject: { savedObject: {
all: [ all: allSavedObjectTypes,
AGENT_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
],
read: [], read: [],
}, },
ui: ['show', 'read', 'write'], ui: ['show', 'read', 'write'],
}, },
read: { read: {
api: [PLUGIN_ID], api: [`${PLUGIN_ID}-read`],
savedObject: { savedObject: {
all: [], all: [],
read: [ read: allSavedObjectTypes,
AGENT_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
],
}, },
ui: ['show', 'read'], ui: ['show', 'read'],
}, },

View file

@ -42,7 +42,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.INFO_PATTERN, path: AGENT_API_ROUTES.INFO_PATTERN,
validate: GetOneAgentRequestSchema, validate: GetOneAgentRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getAgentHandler getAgentHandler
); );
@ -51,7 +51,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.UPDATE_PATTERN, path: AGENT_API_ROUTES.UPDATE_PATTERN,
validate: UpdateAgentRequestSchema, validate: UpdateAgentRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
updateAgentHandler updateAgentHandler
); );
@ -60,7 +60,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.DELETE_PATTERN, path: AGENT_API_ROUTES.DELETE_PATTERN,
validate: DeleteAgentRequestSchema, validate: DeleteAgentRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
deleteAgentHandler deleteAgentHandler
); );
@ -69,7 +69,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.LIST_PATTERN, path: AGENT_API_ROUTES.LIST_PATTERN,
validate: GetAgentsRequestSchema, validate: GetAgentsRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getAgentsHandler getAgentsHandler
); );
@ -108,7 +108,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.UNENROLL_PATTERN, path: AGENT_API_ROUTES.UNENROLL_PATTERN,
validate: PostAgentUnenrollRequestSchema, validate: PostAgentUnenrollRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
postAgentsUnenrollHandler postAgentsUnenrollHandler
); );
@ -118,7 +118,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.EVENTS_PATTERN, path: AGENT_API_ROUTES.EVENTS_PATTERN,
validate: GetOneAgentEventsRequestSchema, validate: GetOneAgentEventsRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getAgentEventsHandler getAgentEventsHandler
); );
@ -128,7 +128,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_API_ROUTES.STATUS_PATTERN, path: AGENT_API_ROUTES.STATUS_PATTERN,
validate: GetAgentStatusRequestSchema, validate: GetAgentStatusRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getAgentStatusForConfigHandler getAgentStatusForConfigHandler
); );

View file

@ -28,7 +28,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_CONFIG_API_ROUTES.LIST_PATTERN, path: AGENT_CONFIG_API_ROUTES.LIST_PATTERN,
validate: GetAgentConfigsRequestSchema, validate: GetAgentConfigsRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getAgentConfigsHandler getAgentConfigsHandler
); );
@ -38,7 +38,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_CONFIG_API_ROUTES.INFO_PATTERN, path: AGENT_CONFIG_API_ROUTES.INFO_PATTERN,
validate: GetOneAgentConfigRequestSchema, validate: GetOneAgentConfigRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getOneAgentConfigHandler getOneAgentConfigHandler
); );
@ -48,7 +48,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_CONFIG_API_ROUTES.CREATE_PATTERN, path: AGENT_CONFIG_API_ROUTES.CREATE_PATTERN,
validate: CreateAgentConfigRequestSchema, validate: CreateAgentConfigRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
createAgentConfigHandler createAgentConfigHandler
); );
@ -58,7 +58,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN, path: AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN,
validate: UpdateAgentConfigRequestSchema, validate: UpdateAgentConfigRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
updateAgentConfigHandler updateAgentConfigHandler
); );
@ -68,7 +68,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_CONFIG_API_ROUTES.DELETE_PATTERN, path: AGENT_CONFIG_API_ROUTES.DELETE_PATTERN,
validate: DeleteAgentConfigsRequestSchema, validate: DeleteAgentConfigsRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
deleteAgentConfigsHandler deleteAgentConfigsHandler
); );
@ -78,7 +78,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN, path: AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN,
validate: GetFullAgentConfigRequestSchema, validate: GetFullAgentConfigRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getFullAgentConfig getFullAgentConfig
); );

View file

@ -24,7 +24,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: DATASOURCE_API_ROUTES.LIST_PATTERN, path: DATASOURCE_API_ROUTES.LIST_PATTERN,
validate: GetDatasourcesRequestSchema, validate: GetDatasourcesRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getDatasourcesHandler getDatasourcesHandler
); );
@ -34,7 +34,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: DATASOURCE_API_ROUTES.INFO_PATTERN, path: DATASOURCE_API_ROUTES.INFO_PATTERN,
validate: GetOneDatasourceRequestSchema, validate: GetOneDatasourceRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getOneDatasourceHandler getOneDatasourceHandler
); );
@ -44,7 +44,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: DATASOURCE_API_ROUTES.CREATE_PATTERN, path: DATASOURCE_API_ROUTES.CREATE_PATTERN,
validate: CreateDatasourceRequestSchema, validate: CreateDatasourceRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
createDatasourceHandler createDatasourceHandler
); );
@ -54,7 +54,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: DATASOURCE_API_ROUTES.UPDATE_PATTERN, path: DATASOURCE_API_ROUTES.UPDATE_PATTERN,
validate: UpdateDatasourceRequestSchema, validate: UpdateDatasourceRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
updateDatasourceHandler updateDatasourceHandler
); );

View file

@ -23,7 +23,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: ENROLLMENT_API_KEY_ROUTES.INFO_PATTERN, path: ENROLLMENT_API_KEY_ROUTES.INFO_PATTERN,
validate: GetOneEnrollmentAPIKeyRequestSchema, validate: GetOneEnrollmentAPIKeyRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getOneEnrollmentApiKeyHandler getOneEnrollmentApiKeyHandler
); );
@ -32,7 +32,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: ENROLLMENT_API_KEY_ROUTES.DELETE_PATTERN, path: ENROLLMENT_API_KEY_ROUTES.DELETE_PATTERN,
validate: DeleteEnrollmentAPIKeyRequestSchema, validate: DeleteEnrollmentAPIKeyRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
deleteEnrollmentApiKeyHandler deleteEnrollmentApiKeyHandler
); );
@ -41,7 +41,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN, path: ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN,
validate: GetEnrollmentAPIKeysRequestSchema, validate: GetEnrollmentAPIKeysRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getEnrollmentApiKeysHandler getEnrollmentApiKeysHandler
); );
@ -50,7 +50,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN, path: ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN,
validate: PostEnrollmentAPIKeyRequestSchema, validate: PostEnrollmentAPIKeyRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
postEnrollmentApiKeyHandler postEnrollmentApiKeyHandler
); );

View file

@ -26,7 +26,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: EPM_API_ROUTES.CATEGORIES_PATTERN, path: EPM_API_ROUTES.CATEGORIES_PATTERN,
validate: false, validate: false,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getCategoriesHandler getCategoriesHandler
); );
@ -35,7 +35,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: EPM_API_ROUTES.LIST_PATTERN, path: EPM_API_ROUTES.LIST_PATTERN,
validate: GetPackagesRequestSchema, validate: GetPackagesRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getListHandler getListHandler
); );
@ -44,7 +44,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: EPM_API_ROUTES.FILEPATH_PATTERN, path: EPM_API_ROUTES.FILEPATH_PATTERN,
validate: GetFileRequestSchema, validate: GetFileRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getFileHandler getFileHandler
); );
@ -53,7 +53,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: EPM_API_ROUTES.INFO_PATTERN, path: EPM_API_ROUTES.INFO_PATTERN,
validate: GetInfoRequestSchema, validate: GetInfoRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getInfoHandler getInfoHandler
); );
@ -62,7 +62,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: EPM_API_ROUTES.INSTALL_PATTERN, path: EPM_API_ROUTES.INSTALL_PATTERN,
validate: InstallPackageRequestSchema, validate: InstallPackageRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
installPackageHandler installPackageHandler
); );
@ -71,7 +71,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: EPM_API_ROUTES.DELETE_PATTERN, path: EPM_API_ROUTES.DELETE_PATTERN,
validate: DeletePackageRequestSchema, validate: DeletePackageRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
deletePackageHandler deletePackageHandler
); );

View file

@ -18,7 +18,9 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: SETUP_API_ROUTE, path: SETUP_API_ROUTE,
validate: false, validate: false,
options: { tags: [`access:${PLUGIN_ID}`] }, // if this route is set to `-all`, a read-only user get a 404 for this route
// and will see `Unable to initialize Ingest Manager` in the UI
options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
ingestManagerSetupHandler ingestManagerSetupHandler
); );
@ -27,7 +29,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: FLEET_SETUP_API_ROUTES.INFO_PATTERN, path: FLEET_SETUP_API_ROUTES.INFO_PATTERN,
validate: GetFleetSetupRequestSchema, validate: GetFleetSetupRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-read`] },
}, },
getFleetSetupHandler getFleetSetupHandler
); );
@ -37,7 +39,7 @@ export const registerRoutes = (router: IRouter) => {
{ {
path: FLEET_SETUP_API_ROUTES.CREATE_PATTERN, path: FLEET_SETUP_API_ROUTES.CREATE_PATTERN,
validate: CreateFleetSetupRequestSchema, validate: CreateFleetSetupRequestSchema,
options: { tags: [`access:${PLUGIN_ID}`] }, options: { tags: [`access:${PLUGIN_ID}-all`] },
}, },
createFleetSetupHandler createFleetSetupHandler
); );

View file

@ -65,7 +65,7 @@ export default function({ getService }: FtrProviderContext) {
.delete(`/api/ingest_manager/fleet/agents/agent1`) .delete(`/api/ingest_manager/fleet/agents/agent1`)
.auth(users.fleet_user.username, users.fleet_user.password) .auth(users.fleet_user.username, users.fleet_user.password)
.set('kbn-xsrf', 'xx') .set('kbn-xsrf', 'xx')
.expect(403); .expect(404);
expect(apiResponse).not.to.eql({ expect(apiResponse).not.to.eql({
success: true, success: true,