Skip to content

Commit

Permalink
[8.x] [Search] Handle insufficient privileges nicely on Serverless (e…
Browse files Browse the repository at this point in the history
…lastic#196160) (elastic#196383)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Search] Handle insufficient privileges nicely on Serverless
(elastic#196160)](elastic#196160)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Sander
Philipse","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-15T16:14:23Z","message":"[Search]
Handle insufficient privileges nicely on Serverless (elastic#196160)\n\n##
Summary\r\nThis adds a couple of callouts and disables unprivileged
actions, so we\r\ndon't bombard the user with ugly error messages when
they click buttons\r\nor navigate to pages.\r\n\r\n\r\nIt also:\r\n -
Fixes a couple of TODO docLinks that were broken (oops)\r\n- Adds an
errorhandler on all serverless search API routes so we surface\r\nissues
to the user\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"1f9bff8af16633b0f2921bea1522962ab40e737a","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","v8.16.0","backport:version"],"title":"[Search]
Handle insufficient privileges nicely on
Serverless","number":196160,"url":"https://github.com/elastic/kibana/pull/196160","mergeCommit":{"message":"[Search]
Handle insufficient privileges nicely on Serverless (elastic#196160)\n\n##
Summary\r\nThis adds a couple of callouts and disables unprivileged
actions, so we\r\ndon't bombard the user with ugly error messages when
they click buttons\r\nor navigate to pages.\r\n\r\n\r\nIt also:\r\n -
Fixes a couple of TODO docLinks that were broken (oops)\r\n- Adds an
errorhandler on all serverless search API routes so we surface\r\nissues
to the user\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"1f9bff8af16633b0f2921bea1522962ab40e737a"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196160","number":196160,"mergeCommit":{"message":"[Search]
Handle insufficient privileges nicely on Serverless (elastic#196160)\n\n##
Summary\r\nThis adds a couple of callouts and disables unprivileged
actions, so we\r\ndon't bombard the user with ugly error messages when
they click buttons\r\nor navigate to pages.\r\n\r\n\r\nIt also:\r\n -
Fixes a couple of TODO docLinks that were broken (oops)\r\n- Adds an
errorhandler on all serverless search API routes so we surface\r\nissues
to the user\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"1f9bff8af16633b0f2921bea1522962ab40e737a"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Sander Philipse <[email protected]>
  • Loading branch information
kibanamachine and sphilipse authored Oct 15, 2024
1 parent b2ba109 commit 51b8359
Show file tree
Hide file tree
Showing 37 changed files with 400 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function entryToDisplaylistItem(entry: ConfigEntryView): { description: string;
interface ConnectorConfigurationProps {
connector: Connector;
hasPlatinumLicense: boolean;
isDisabled?: boolean;
isLoading: boolean;
saveConfig: (configuration: Record<string, string | number | boolean | null>) => void;
saveAndSync?: (configuration: Record<string, string | number | boolean | null>) => void;
Expand Down Expand Up @@ -89,6 +90,7 @@ export const ConnectorConfigurationComponent: FC<
children,
connector,
hasPlatinumLicense,
isDisabled,
isLoading,
saveConfig,
saveAndSync,
Expand Down Expand Up @@ -207,6 +209,7 @@ export const ConnectorConfigurationComponent: FC<
data-test-subj="entSearchContent-connector-configuration-editConfiguration"
data-telemetry-id="entSearchContent-connector-overview-configuration-editConfiguration"
onClick={() => setIsEditing(!isEditing)}
isDisabled={isDisabled}
>
{i18n.translate(
'searchConnectors.configurationConnector.config.editButton.title',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ interface ConnectorContentSchedulingProps {
hasPlatinumLicense: boolean;
hasChanges: boolean;
hasIngestionError: boolean;
isDisabled?: boolean;
setHasChanges: (changes: boolean) => void;
shouldShowAccessControlSync: boolean;
shouldShowIncrementalSync: boolean;
Expand All @@ -81,6 +82,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr
hasChanges,
hasIngestionError,
hasPlatinumLicense,
isDisabled,
setHasChanges,
shouldShowAccessControlSync,
shouldShowIncrementalSync,
Expand Down Expand Up @@ -140,6 +142,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr
updateConnectorStatus={updateConnectorStatus}
updateScheduling={updateScheduling}
dataTelemetryIdPrefix={dataTelemetryIdPrefix}
isDisabled={isDisabled}
/>
</EuiFlexItem>
{shouldShowIncrementalSync && (
Expand All @@ -153,6 +156,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr
updateConnectorStatus={updateConnectorStatus}
updateScheduling={updateScheduling}
dataTelemetryIdPrefix={dataTelemetryIdPrefix}
isDisabled={isDisabled}
/>
</EuiFlexItem>
)}
Expand Down Expand Up @@ -186,6 +190,7 @@ export const ConnectorSchedulingComponent: React.FC<ConnectorContentSchedulingPr
updateConnectorStatus={updateConnectorStatus}
updateScheduling={updateScheduling}
dataTelemetryIdPrefix={dataTelemetryIdPrefix}
isDisabled={isDisabled}
/>
</SchedulePanel>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface ConnectorContentSchedulingProps {
dataTelemetryIdPrefix: string;
hasPlatinumLicense?: boolean;
hasSyncTypeChanges: boolean;
isDisabled?: boolean;
setHasChanges: (hasChanges: boolean) => void;
setHasSyncTypeChanges: (state: boolean) => void;
type: SyncJobType;
Expand Down Expand Up @@ -104,6 +105,7 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp
setHasSyncTypeChanges,
hasPlatinumLicense = false,
hasSyncTypeChanges,
isDisabled,
type,
updateConnectorStatus,
updateScheduling,
Expand All @@ -120,7 +122,9 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp
!connector.configuration.use_document_level_security?.value;

const isEnableSwitchDisabled =
type === SyncJobType.ACCESS_CONTROL && (!hasPlatinumLicense || isDocumentLevelSecurityDisabled);
(type === SyncJobType.ACCESS_CONTROL &&
(!hasPlatinumLicense || isDocumentLevelSecurityDisabled)) ||
Boolean(isDisabled);

return (
<>
Expand Down Expand Up @@ -217,7 +221,7 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp
<ConnectorCronEditor
hasSyncTypeChanges={hasSyncTypeChanges}
setHasSyncTypeChanges={setHasSyncTypeChanges}
disabled={isGated}
disabled={isGated || Boolean(isDisabled)}
scheduling={scheduling[type]}
onReset={() => {
setScheduling({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
import { ApiKey } from '@kbn/security-plugin-types-common';
import { useQuery } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
import { ApiKeySelectableTokenField } from '@kbn/security-api-key-management';
import {
Expand All @@ -32,6 +30,7 @@ import { useKibanaServices } from '../../hooks/use_kibana';
import { MANAGEMENT_API_KEYS } from '../../../../common/routes';
import { CreateApiKeyFlyout } from './create_api_key_flyout';
import './api_key.scss';
import { useGetApiKeys } from '../../hooks/api/use_api_key';

function isCreatedResponse(
value: SecurityCreateApiKeyResponse | SecurityUpdateApiKeyResponse
Expand All @@ -45,15 +44,16 @@ function isCreatedResponse(
export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: string) => void }) => {
const { http, user } = useKibanaServices();
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
const { data } = useQuery({
queryKey: ['apiKey'],
queryFn: () => http.fetch<{ apiKeys: ApiKey[] }>('/internal/serverless_search/api_keys'),
});
const { data } = useGetApiKeys();

const [apiKey, setApiKey] = useState<SecurityCreateApiKeyResponse | undefined>(undefined);
const saveApiKey = (value: SecurityCreateApiKeyResponse) => {
setApiKey(value);
};

// Prevent flickering in the most common case of having access to manage api keys
const canManageOwnApiKey = !data || data.canManageOwnApiKey;

useEffect(() => {
if (apiKey) {
setClientApiKey(apiKey.encoded);
Expand Down Expand Up @@ -101,7 +101,7 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
</EuiStep>
</EuiPanel>
) : (
<EuiPanel>
<EuiPanel color={'plain'}>
<EuiTitle size="xs">
<h3>
{i18n.translate('xpack.serverlessSearch.apiKey.panel.title', {
Expand All @@ -117,6 +117,16 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
})}
</EuiText>
<EuiSpacer size="l" />
{!canManageOwnApiKey && (
<>
<EuiBadge iconType="warningFilled">
{i18n.translate('xpack.serverlessSearch.apiKey.panel.noUserPrivileges', {
defaultMessage: "You don't have access to manage API keys",
})}
</EuiBadge>
<EuiSpacer size="m" />
</>
)}
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="m">
Expand All @@ -127,6 +137,7 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
size="s"
fill
onClick={() => setIsFlyoutOpen(true)}
disabled={!canManageOwnApiKey}
data-test-subj="new-api-key-button"
aria-label={i18n.translate(
'xpack.serverlessSearch.apiKey.newButton.ariaLabel',
Expand All @@ -143,24 +154,29 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
</EuiButton>
</span>
</EuiFlexItem>
<EuiFlexItem>
<span>
<EuiButton
iconType="popout"
size="s"
href={http.basePath.prepend(MANAGEMENT_API_KEYS)}
target="_blank"
data-test-subj="manage-api-keys-button"
aria-label={i18n.translate('xpack.serverlessSearch.apiKey.manage.ariaLabel', {
defaultMessage: 'Manage API keys',
})}
>
{i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', {
defaultMessage: 'Manage',
})}
</EuiButton>
</span>
</EuiFlexItem>
{canManageOwnApiKey && (
<EuiFlexItem>
<span>
<EuiButton
iconType="popout"
size="s"
href={http.basePath.prepend(MANAGEMENT_API_KEYS)}
target="_blank"
data-test-subj="manage-api-keys-button"
aria-label={i18n.translate(
'xpack.serverlessSearch.apiKey.manage.ariaLabel',
{
defaultMessage: 'Manage API keys',
}
)}
>
{i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', {
defaultMessage: 'Manage',
})}
</EuiButton>
</span>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,33 @@ import { ConnectorSchedulingComponent } from '@kbn/search-connectors/components/
import { useConnectorScheduling } from '../../../hooks/api/use_update_connector_scheduling';

interface ConnectorSchedulingPanels {
canManageConnectors: boolean;
connector: Connector;
}
export const ConnectorScheduling: React.FC<ConnectorSchedulingPanels> = ({ connector }) => {
export const ConnectorScheduling: React.FC<ConnectorSchedulingPanels> = ({
canManageConnectors,
connector,
}) => {
const [hasChanges, setHasChanges] = useState<boolean>(false);
const { isLoading, mutate } = useConnectorScheduling(connector.id);
const hasIncrementalSyncFeature = connector?.features?.incremental_sync ?? false;
const shouldShowIncrementalSync =
hasIncrementalSyncFeature && (connector?.features?.incremental_sync?.enabled ?? false);
return (
<ConnectorSchedulingComponent
connector={connector}
dataTelemetryIdPrefix="serverlessSearch"
hasChanges={hasChanges}
hasIngestionError={connector?.status === ConnectorStatus.ERROR}
hasPlatinumLicense={false}
setHasChanges={setHasChanges}
shouldShowAccessControlSync={false}
shouldShowIncrementalSync={shouldShowIncrementalSync}
updateConnectorStatus={isLoading}
updateScheduling={mutate}
/>
<>
<ConnectorSchedulingComponent
connector={connector}
isDisabled={!canManageConnectors}
dataTelemetryIdPrefix="serverlessSearch"
hasChanges={hasChanges}
hasIngestionError={connector?.status === ConnectorStatus.ERROR}
hasPlatinumLicense={false}
setHasChanges={setHasChanges}
shouldShowAccessControlSync={false}
shouldShowIncrementalSync={shouldShowIncrementalSync}
updateConnectorStatus={isLoading}
updateScheduling={mutate}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { OPTIONAL_LABEL } from '../../../../../common/i18n_string';
import { useCreateApiKey } from '../../../hooks/api/use_create_api_key';
import { useGetApiKeys } from '../../../hooks/api/use_api_key';
interface ApiKeyPanelProps {
connector: Connector;
}
export const ApiKeyPanel: React.FC<ApiKeyPanelProps> = ({ connector }) => {
const { data, isLoading, mutate } = useCreateApiKey();
const { data: apiKeysData } = useGetApiKeys();
return (
<EuiPanel hasBorder>
<EuiFlexGroup direction="row" justifyContent="spaceBetween" alignItems="center">
Expand Down Expand Up @@ -59,7 +61,7 @@ export const ApiKeyPanel: React.FC<ApiKeyPanelProps> = ({ connector }) => {
<span>
<EuiButton
data-test-subj="serverlessSearchApiKeyPanelNewApiKeyButton"
isDisabled={!connector.index_name}
isDisabled={!connector.index_name || !apiKeysData?.canManageOwnApiKey}
isLoading={isLoading}
iconType="plusInCircle"
color="primary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import { useEditConnectorConfiguration } from '../../../hooks/api/use_connector_

interface ConnectorConfigFieldsProps {
connector: Connector;
isDisabled: boolean;
}

export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({ connector }) => {
export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({
connector,
isDisabled,
}) => {
const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id);
const { queryKey } = useConnector(connector.id);
const queryClient = useQueryClient();
Expand Down Expand Up @@ -53,6 +57,7 @@ export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({ co
<ConnectorConfigurationComponent
connector={connector}
hasPlatinumLicense={false}
isDisabled={isDisabled}
isLoading={isLoading}
saveConfig={mutate}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import { ConnectionDetails } from './connection_details_panel';
import { ConnectorIndexnamePanel } from './connector_index_name_panel';

interface ConnectorConfigurationPanels {
canManageConnectors: boolean;
connector: Connector;
}

export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels> = ({
canManageConnectors,
connector,
}) => {
const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id);
Expand All @@ -37,6 +39,7 @@ export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels
<>
<EuiPanel hasBorder>
<ConnectorConfigurationComponent
isDisabled={!canManageConnectors}
connector={connector}
hasPlatinumLicense={false}
isLoading={isLoading}
Expand All @@ -46,7 +49,7 @@ export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels
</EuiPanel>
<EuiSpacer />
<EuiPanel hasBorder>
<ConnectorIndexnamePanel connector={connector} />
<ConnectorIndexnamePanel canManageConnectors={canManageConnectors} connector={connector} />
</EuiPanel>
<EuiSpacer />
<ConnectionDetails
Expand Down
Loading

0 comments on commit 51b8359

Please sign in to comment.