From 518ae4e6db332c0e899fdefa81dd92cff3e2c490 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 11 Mar 2020 16:24:10 -0400 Subject: [PATCH 1/4] Disable create/destroy CTAs if no write capability Use `core.application.capabilities.ingestManager.write` to test user permissions --- .../step_select_config.tsx | 10 ++++++++-- .../sections/agent_config/details_page/index.tsx | 16 +++++++++++++--- .../list_page/components/create_config.tsx | 8 ++++++-- .../sections/agent_config/list_page/index.tsx | 11 ++++++++--- .../sections/epm/screens/detail/header.tsx | 9 +++++++-- .../epm/screens/detail/installation_button.tsx | 6 ++++-- .../components/details_section.tsx | 5 +++-- .../components/enrollment_api_keys/index.tsx | 8 +++++++- .../sections/fleet/agent_list_page/index.tsx | 2 ++ x-pack/plugins/ingest_manager/server/plugin.ts | 12 ++++++++++++ 10 files changed, 70 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx index e3209c8c12640..8b802b08a921d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { Error } from '../../../components'; import { AGENT_CONFIG_PATH } from '../../../constants'; -import { useLink } from '../../../hooks'; +import { useCore, useLink } from '../../../hooks'; import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types'; import { useGetPackageInfoByKey, useGetAgentConfigs, sendGetOneAgentConfig } from '../../../hooks'; @@ -30,6 +30,7 @@ export const StepSelectConfig: React.FunctionComponent<{ cancelUrl: string; onNext: () => void; }> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, cancelUrl, onNext }) => { + const core = useCore(); // Selected config state const [selectedConfigId, setSelectedConfigId] = useState( agentConfig ? agentConfig.id : undefined @@ -134,7 +135,12 @@ export const StepSelectConfig: React.FunctionComponent<{ - + { const { params: { configId, tabId = '' }, } = useRouteMatch<{ configId: string; tabId?: string }>(); + const core = useCore(); const agentConfigRequest = useGetOneAgentConfig(configId); const agentConfig = agentConfigRequest.data ? agentConfigRequest.data.item : null; const { isLoading, error, sendRequest: refreshAgentConfig } = agentConfigRequest; @@ -318,7 +319,12 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => { } actions={ - + { } search={{ toolsRight: [ - + = ({ onClose }) => { - const { notifications } = useCore(); + const { application, notifications } = useCore(); const [agentConfig, setAgentConfig] = useState({ name: '', @@ -93,7 +93,11 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ onClos 0} + isDisabled={ + !application.capabilities.ingestManager.write || + isLoading || + Object.keys(validation).length > 0 + } onClick={async () => { setIsLoading(true); try { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index be32972622618..5b905047486fb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx @@ -33,6 +33,7 @@ import { } from '../../../constants'; import { WithHeaderLayout } from '../../../layouts'; import { + useCore, useGetAgentConfigs, usePagination, useLink, @@ -87,6 +88,7 @@ const DangerEuiContextMenuItem = styled(EuiContextMenuItem)` const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>( ({ config, onDelete }) => { + const core = useCore(); const DETAILS_URI = useLink(`${AGENT_CONFIG_DETAILS_PATH}${config.id}`); const ADD_DATASOURCE_URI = `${DETAILS_URI}/add-datasource`; @@ -120,6 +122,7 @@ const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>( , void }>( /> , - + void }>( export const AgentConfigListPage: React.FunctionComponent<{}> = () => { // Config information + const core = useCore(); const { fleet: { enabled: isFleetEnabled }, } = useConfig(); @@ -309,6 +313,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { setIsCreateAgentConfigFlyoutOpen(true)} > = () => { /> ), - [setIsCreateAgentConfigFlyoutOpen] + [core.application.capabilities.ingestManager.write, setIsCreateAgentConfigFlyoutOpen] ); const emptyPrompt = useMemo( @@ -331,7 +336,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { /> } - actions={createAgentConfigButton} + actions={false ?? createAgentConfigButton} /> ), [createAgentConfigButton] diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx index c1fded46eeedb..611db6f812c1d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiTitle, IconType, EuiButton } from '@elastic/eui'; import { PackageInfo } from '../../../../types'; import { EPM_PATH } from '../../../../constants'; -import { useLink } from '../../../../hooks'; +import { useCore, useLink } from '../../../../hooks'; import { IconPanel } from '../../components/icon_panel'; import { NavButtonBack } from '../../components/nav_button_back'; import { Version } from '../../components/version'; @@ -34,6 +34,7 @@ type HeaderProps = PackageInfo & { iconType?: IconType }; export function Header(props: HeaderProps) { const { iconType, name, title, version } = props; + const core = useCore(); const { toListView } = useLinks(); // useBreadcrumbs([{ text: PLUGIN.TITLE, href: toListView() }, { text: title }]); @@ -61,7 +62,11 @@ export function Header(props: HeaderProps) { - + ); - return ( + return core.application.capabilities.ingestManager.write ? ( {isInstalled ? installedButton : installButton} {isModalVisible && (isInstalled ? deletionModal : installationModal)} - ); + ) : null; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx index e6edeb76f183c..866a01f801f33 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx @@ -24,7 +24,7 @@ import { useAgentRefresh } from '../hooks'; import { AgentMetadataFlyout } from './metadata_flyout'; import { Agent } from '../../../../types'; import { AgentHealth } from '../../components/agent_health'; -import { useGetOneAgentConfig } from '../../../../hooks'; +import { useCore, useGetOneAgentConfig } from '../../../../hooks'; import { Loading } from '../../../../components'; import { ConnectedLink } from '../../components'; import { AgentUnenrollProvider } from '../../components/agent_unenroll_provider'; @@ -53,6 +53,7 @@ interface Props { agent: Agent; } export const AgentDetailSection: React.FunctionComponent = ({ agent }) => { + const core = useCore(); const metadataFlyout = useFlyout(); const refreshAgent = useAgentRefresh(); @@ -125,7 +126,7 @@ export const AgentDetailSection: React.FunctionComponent = ({ agent }) => {unenrollAgentsPrompt => ( { unenrollAgentsPrompt([agent.id], 1, refreshAgent); }} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx index ed564b6f80bf8..e2972819976d5 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx @@ -12,6 +12,7 @@ import { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks'; import { ConfirmDeleteModal } from './confirm_delete_modal'; import { CreateApiKeyForm } from './create_api_key_form'; import { EnrollmentAPIKey } from '../../../../../types'; +import { useCore } from '../../../../../hooks'; import { enrollmentAPIKeyRouteService } from '../../../../../services'; export { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks'; @@ -98,13 +99,18 @@ export const EnrollmentApiKeysTable: React.FunctionComponent<{ export const CreateApiKeyButton: React.FunctionComponent<{ onChange: () => void }> = ({ onChange, }) => { + const core = useCore(); const [isOpen, setIsOpen] = React.useState(false); return ( setIsOpen(true)} color="primary"> + setIsOpen(true)} + color="primary" + > ; const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, refresh }) => { + const core = useCore(); const DETAILS_URI = useLink(FLEET_AGENT_DETAIL_PATH); const [isOpen, setIsOpen] = useState(false); const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); @@ -118,6 +119,7 @@ const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, r {unenrollAgentsPrompt => ( { unenrollAgentsPrompt([agent.id], 1, () => { diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 2bd7395135433..bd1efce8fe118 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -18,6 +18,10 @@ import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { PLUGIN_ID, + 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, @@ -76,6 +80,10 @@ export class IngestManagerPlugin implements Plugin { api: [PLUGIN_ID], savedObject: { all: [ + 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, @@ -89,6 +97,10 @@ export class IngestManagerPlugin implements Plugin { savedObject: { all: [], read: [ + 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, From 53d0ce345659662b71d30053ba79ba11c3807b6c Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 11 Mar 2020 17:30:52 -0400 Subject: [PATCH 2/4] Add -all & -read tags for HTTP routes --- .../plugins/ingest_manager/server/plugin.ts | 34 ++++++++----------- .../server/routes/agent/index.ts | 14 ++++---- .../server/routes/agent_config/index.ts | 12 +++---- .../server/routes/datasource/index.ts | 8 ++--- .../server/routes/enrollment_api_key/index.ts | 8 ++--- .../ingest_manager/server/routes/epm/index.ts | 12 +++---- .../server/routes/setup/index.ts | 8 +++-- 7 files changed, 46 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index bd1efce8fe118..c162ea5fadabe 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -53,6 +53,16 @@ export interface IngestManagerAppContext { 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 { private config$: Observable; private security: SecurityPluginSetup | undefined; @@ -77,34 +87,18 @@ export class IngestManagerPlugin implements Plugin { app: [PLUGIN_ID, 'kibana'], privileges: { all: { - api: [PLUGIN_ID], + api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`], savedObject: { - all: [ - 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, - ], + all: allSavedObjectTypes, read: [], }, ui: ['show', 'read', 'write'], }, read: { - api: [PLUGIN_ID], + api: [`${PLUGIN_ID}-read`], savedObject: { all: [], - read: [ - 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, - ], + read: allSavedObjectTypes, }, ui: ['show', 'read'], }, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index a23a9fa62adbe..8a65fa9c50e8b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -42,7 +42,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.INFO_PATTERN, validate: GetOneAgentRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getAgentHandler ); @@ -51,7 +51,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.UPDATE_PATTERN, validate: UpdateAgentRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, updateAgentHandler ); @@ -60,7 +60,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.DELETE_PATTERN, validate: DeleteAgentRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, deleteAgentHandler ); @@ -69,7 +69,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.LIST_PATTERN, validate: GetAgentsRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getAgentsHandler ); @@ -108,7 +108,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.UNENROLL_PATTERN, validate: PostAgentUnenrollRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, postAgentsUnenrollHandler ); @@ -118,7 +118,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.EVENTS_PATTERN, validate: GetOneAgentEventsRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getAgentEventsHandler ); @@ -128,7 +128,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_API_ROUTES.STATUS_PATTERN, validate: GetAgentStatusRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getAgentStatusForConfigHandler ); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts index 9481adbbac195..c3b3c00a9574c 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -28,7 +28,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_CONFIG_API_ROUTES.LIST_PATTERN, validate: GetAgentConfigsRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getAgentConfigsHandler ); @@ -38,7 +38,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_CONFIG_API_ROUTES.INFO_PATTERN, validate: GetOneAgentConfigRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getOneAgentConfigHandler ); @@ -48,7 +48,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_CONFIG_API_ROUTES.CREATE_PATTERN, validate: CreateAgentConfigRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, createAgentConfigHandler ); @@ -58,7 +58,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN, validate: UpdateAgentConfigRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, updateAgentConfigHandler ); @@ -68,7 +68,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_CONFIG_API_ROUTES.DELETE_PATTERN, validate: DeleteAgentConfigsRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, deleteAgentConfigsHandler ); @@ -78,7 +78,7 @@ export const registerRoutes = (router: IRouter) => { { path: AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN, validate: GetFullAgentConfigRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getFullAgentConfig ); diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts index 70ab0027a6907..412eb17c6d45a 100644 --- a/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts @@ -24,7 +24,7 @@ export const registerRoutes = (router: IRouter) => { { path: DATASOURCE_API_ROUTES.LIST_PATTERN, validate: GetDatasourcesRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getDatasourcesHandler ); @@ -34,7 +34,7 @@ export const registerRoutes = (router: IRouter) => { { path: DATASOURCE_API_ROUTES.INFO_PATTERN, validate: GetOneDatasourceRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getOneDatasourceHandler ); @@ -44,7 +44,7 @@ export const registerRoutes = (router: IRouter) => { { path: DATASOURCE_API_ROUTES.CREATE_PATTERN, validate: CreateDatasourceRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, createDatasourceHandler ); @@ -54,7 +54,7 @@ export const registerRoutes = (router: IRouter) => { { path: DATASOURCE_API_ROUTES.UPDATE_PATTERN, validate: UpdateDatasourceRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, updateDatasourceHandler ); diff --git a/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts b/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts index 8d04333b4a542..6df5299d30bd4 100644 --- a/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts @@ -23,7 +23,7 @@ export const registerRoutes = (router: IRouter) => { { path: ENROLLMENT_API_KEY_ROUTES.INFO_PATTERN, validate: GetOneEnrollmentAPIKeyRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getOneEnrollmentApiKeyHandler ); @@ -32,7 +32,7 @@ export const registerRoutes = (router: IRouter) => { { path: ENROLLMENT_API_KEY_ROUTES.DELETE_PATTERN, validate: DeleteEnrollmentAPIKeyRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, deleteEnrollmentApiKeyHandler ); @@ -41,7 +41,7 @@ export const registerRoutes = (router: IRouter) => { { path: ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN, validate: GetEnrollmentAPIKeysRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getEnrollmentApiKeysHandler ); @@ -50,7 +50,7 @@ export const registerRoutes = (router: IRouter) => { { path: ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN, validate: PostEnrollmentAPIKeyRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, postEnrollmentApiKeyHandler ); diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts b/x-pack/plugins/ingest_manager/server/routes/epm/index.ts index 49d9242e67f72..cb9ec5cc532c4 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/index.ts @@ -26,7 +26,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.CATEGORIES_PATTERN, validate: false, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getCategoriesHandler ); @@ -35,7 +35,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.LIST_PATTERN, validate: GetPackagesRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getListHandler ); @@ -44,7 +44,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.FILEPATH_PATTERN, validate: GetFileRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getFileHandler ); @@ -53,7 +53,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.INFO_PATTERN, validate: GetInfoRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getInfoHandler ); @@ -62,7 +62,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.INSTALL_PATTERN, validate: InstallPackageRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, installPackageHandler ); @@ -71,7 +71,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.DELETE_PATTERN, validate: DeletePackageRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, deletePackageHandler ); diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts b/x-pack/plugins/ingest_manager/server/routes/setup/index.ts index a335265c01160..7e09d8dbef1f6 100644 --- a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/setup/index.ts @@ -18,7 +18,9 @@ export const registerRoutes = (router: IRouter) => { { path: SETUP_API_ROUTE, 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 ); @@ -27,7 +29,7 @@ export const registerRoutes = (router: IRouter) => { { path: FLEET_SETUP_API_ROUTES.INFO_PATTERN, validate: GetFleetSetupRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getFleetSetupHandler ); @@ -37,7 +39,7 @@ export const registerRoutes = (router: IRouter) => { { path: FLEET_SETUP_API_ROUTES.CREATE_PATTERN, validate: CreateFleetSetupRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, }, createFleetSetupHandler ); From 45717e2ed48c6ee2381a1a669965540ce1824701 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 11 Mar 2020 20:35:14 -0400 Subject: [PATCH 3/4] Update test .expect() to match description --- x-pack/test/api_integration/apis/fleet/delete_agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/fleet/delete_agent.ts b/x-pack/test/api_integration/apis/fleet/delete_agent.ts index 5cb069ab22aba..bddfcfbf6715e 100644 --- a/x-pack/test/api_integration/apis/fleet/delete_agent.ts +++ b/x-pack/test/api_integration/apis/fleet/delete_agent.ts @@ -65,7 +65,7 @@ export default function({ getService }: FtrProviderContext) { .delete(`/api/ingest_manager/fleet/agents/agent1`) .auth(users.fleet_user.username, users.fleet_user.password) .set('kbn-xsrf', 'xx') - .expect(403); + .expect(404); expect(apiResponse).not.to.eql({ success: true, From 42a7604fbcaaabe3b936643c48c2b734ea17ba1d Mon Sep 17 00:00:00 2001 From: John Schulz Date: Thu, 12 Mar 2020 10:26:14 -0400 Subject: [PATCH 4/4] Add useCapabilities hook. Fix two issues with hiding/disabling CTA. --- .../applications/ingest_manager/hooks/index.ts | 1 + .../ingest_manager/hooks/use_capabilities.ts | 12 ++++++++++++ .../step_select_config.tsx | 6 +++--- .../agent_config/details_page/index.tsx | 8 ++++---- .../sections/agent_config/list_page/index.tsx | 18 +++++++++--------- .../sections/epm/screens/detail/header.tsx | 6 +++--- .../epm/screens/detail/installation_button.tsx | 6 +++--- .../components/details_section.tsx | 6 +++--- .../components/enrollment_api_keys/index.tsx | 10 +++------- .../sections/fleet/agent_list_page/index.tsx | 12 ++++++------ 10 files changed, 47 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index 4bae22bbed368..5e0695bd3e305 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -4,6 +4,7 @@ * 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 { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts new file mode 100644 index 0000000000000..0a16c4a62a7d1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts @@ -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; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx index 8b802b08a921d..2ddfc170069a1 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { Error } from '../../../components'; import { AGENT_CONFIG_PATH } from '../../../constants'; -import { useCore, useLink } from '../../../hooks'; +import { useCapabilities, useLink } from '../../../hooks'; import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types'; import { useGetPackageInfoByKey, useGetAgentConfigs, sendGetOneAgentConfig } from '../../../hooks'; @@ -30,7 +30,7 @@ export const StepSelectConfig: React.FunctionComponent<{ cancelUrl: string; onNext: () => void; }> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, cancelUrl, onNext }) => { - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; // Selected config state const [selectedConfigId, setSelectedConfigId] = useState( agentConfig ? agentConfig.id : undefined @@ -136,7 +136,7 @@ export const StepSelectConfig: React.FunctionComponent<{ { const { params: { configId, tabId = '' }, } = useRouteMatch<{ configId: string; tabId?: string }>(); - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const agentConfigRequest = useGetOneAgentConfig(configId); const agentConfig = agentConfigRequest.data ? agentConfigRequest.data.item : null; const { isLoading, error, sendRequest: refreshAgentConfig } = agentConfigRequest; @@ -320,7 +320,7 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => { } actions={ { search={{ toolsRight: [ diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index 5b905047486fb..49399be092a89 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx @@ -33,7 +33,7 @@ import { } from '../../../constants'; import { WithHeaderLayout } from '../../../layouts'; import { - useCore, + useCapabilities, useGetAgentConfigs, usePagination, useLink, @@ -88,7 +88,7 @@ const DangerEuiContextMenuItem = styled(EuiContextMenuItem)` const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>( ({ config, onDelete }) => { - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const DETAILS_URI = useLink(`${AGENT_CONFIG_DETAILS_PATH}${config.id}`); const ADD_DATASOURCE_URI = `${DETAILS_URI}/add-datasource`; @@ -122,7 +122,7 @@ const RowActions = React.memo<{ config: AgentConfig; onDelete: () => void }>( , void }>( /> , - + void }>( export const AgentConfigListPage: React.FunctionComponent<{}> = () => { // Config information - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const { fleet: { enabled: isFleetEnabled }, } = useConfig(); @@ -313,7 +313,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { setIsCreateAgentConfigFlyoutOpen(true)} > = () => { /> ), - [core.application.capabilities.ingestManager.write, setIsCreateAgentConfigFlyoutOpen] + [hasWriteCapabilites, setIsCreateAgentConfigFlyoutOpen] ); const emptyPrompt = useMemo( @@ -336,10 +336,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { /> } - actions={false ?? createAgentConfigButton} + actions={hasWriteCapabilites ?? createAgentConfigButton} /> ), - [createAgentConfigButton] + [hasWriteCapabilites, createAgentConfigButton] ); return ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx index 611db6f812c1d..5a51515d49486 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiTitle, IconType, EuiButton } from '@elastic/eui'; import { PackageInfo } from '../../../../types'; import { EPM_PATH } from '../../../../constants'; -import { useCore, useLink } from '../../../../hooks'; +import { useCapabilities, useLink } from '../../../../hooks'; import { IconPanel } from '../../components/icon_panel'; import { NavButtonBack } from '../../components/nav_button_back'; import { Version } from '../../components/version'; @@ -34,7 +34,7 @@ type HeaderProps = PackageInfo & { iconType?: IconType }; export function Header(props: HeaderProps) { const { iconType, name, title, version } = props; - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const { toListView } = useLinks(); // useBreadcrumbs([{ text: PLUGIN.TITLE, href: toListView() }, { text: title }]); @@ -63,7 +63,7 @@ export function Header(props: HeaderProps) { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx index 8285afa641e8a..8a8afed5570ed 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx @@ -6,7 +6,7 @@ import { EuiButton } from '@elastic/eui'; import React, { Fragment, useCallback, useMemo, useState } from 'react'; import { PackageInfo, InstallStatus } from '../../../../types'; -import { useCore } from '../../../../hooks'; +import { useCapabilities } from '../../../../hooks'; import { useDeletePackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks'; import { ConfirmPackageDelete } from './confirm_package_delete'; import { ConfirmPackageInstall } from './confirm_package_install'; @@ -17,7 +17,7 @@ interface InstallationButtonProps { export function InstallationButton(props: InstallationButtonProps) { const { assets, name, title, version } = props.package; - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const installPackage = useInstallPackage(); const deletePackage = useDeletePackage(); const getPackageInstallStatus = useGetPackageInstallStatus(); @@ -88,7 +88,7 @@ export function InstallationButton(props: InstallationButtonProps) { /> ); - return core.application.capabilities.ingestManager.write ? ( + return hasWriteCapabilites ? ( {isInstalled ? installedButton : installButton} {isModalVisible && (isInstalled ? deletionModal : installationModal)} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx index 866a01f801f33..0844368dc214b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx @@ -24,7 +24,7 @@ import { useAgentRefresh } from '../hooks'; import { AgentMetadataFlyout } from './metadata_flyout'; import { Agent } from '../../../../types'; import { AgentHealth } from '../../components/agent_health'; -import { useCore, useGetOneAgentConfig } from '../../../../hooks'; +import { useCapabilities, useGetOneAgentConfig } from '../../../../hooks'; import { Loading } from '../../../../components'; import { ConnectedLink } from '../../components'; import { AgentUnenrollProvider } from '../../components/agent_unenroll_provider'; @@ -53,7 +53,7 @@ interface Props { agent: Agent; } export const AgentDetailSection: React.FunctionComponent = ({ agent }) => { - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const metadataFlyout = useFlyout(); const refreshAgent = useAgentRefresh(); @@ -126,7 +126,7 @@ export const AgentDetailSection: React.FunctionComponent = ({ agent }) => {unenrollAgentsPrompt => ( { unenrollAgentsPrompt([agent.id], 1, refreshAgent); }} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx index e2972819976d5..19957e7827680 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/enrollment_api_keys/index.tsx @@ -12,7 +12,7 @@ import { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks'; import { ConfirmDeleteModal } from './confirm_delete_modal'; import { CreateApiKeyForm } from './create_api_key_form'; import { EnrollmentAPIKey } from '../../../../../types'; -import { useCore } from '../../../../../hooks'; +import { useCapabilities } from '../../../../../hooks'; import { enrollmentAPIKeyRouteService } from '../../../../../services'; export { useEnrollmentApiKeys, useEnrollmentApiKey } from './hooks'; @@ -99,18 +99,14 @@ export const EnrollmentApiKeysTable: React.FunctionComponent<{ export const CreateApiKeyButton: React.FunctionComponent<{ onChange: () => void }> = ({ onChange, }) => { - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const [isOpen, setIsOpen] = React.useState(false); return ( setIsOpen(true)} - color="primary" - > + setIsOpen(true)} color="primary"> ; const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, refresh }) => { - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; const DETAILS_URI = useLink(FLEET_AGENT_DETAIL_PATH); const [isOpen, setIsOpen] = useState(false); const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); @@ -119,7 +119,7 @@ const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, r {unenrollAgentsPrompt => ( { unenrollAgentsPrompt([agent.id], 1, () => { @@ -142,7 +142,7 @@ const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, r export const AgentListPage: React.FunctionComponent<{}> = () => { const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; - const core = useCore(); + const hasWriteCapabilites = useCapabilities().write; // Agent data states const [showInactive, setShowInactive] = useState(false); @@ -365,7 +365,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { } actions={ - core.application.capabilities.ingestManager.write ? ( + hasWriteCapabilites ? ( setIsEnrollmentFlyoutOpen(true)}> = () => { })} /> - {core.application.capabilities.ingestManager.write && ( + {hasWriteCapabilites && ( <>