Skip to content

Commit

Permalink
[ML] Add option to Reauthorize transform in Management page (#154736)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
qn895 and kibanamachine authored Apr 20, 2023
1 parent 2f8a729 commit dd46350
Show file tree
Hide file tree
Showing 27 changed files with 1,160 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { TypeOf } from '@kbn/config-schema';

import { transformIdsSchema, CommonResponseStatusSchema } from './common';

export const reauthorizeTransformsRequestSchema = transformIdsSchema;
export type ReauthorizeTransformsRequestSchema = TypeOf<typeof reauthorizeTransformsRequestSchema>;
export type ReauthorizeTransformsResponseSchema = CommonResponseStatusSchema;
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('has_privilege_factory', () => {
canDeleteTransform: true,
canGetTransform: true,
canPreviewTransform: true,
canReauthorizeTransform: true,
canResetTransform: true,
canScheduleNowTransform: true,
canStartStopTransform: true,
Expand All @@ -96,6 +97,7 @@ describe('has_privilege_factory', () => {
canDeleteTransform: false,
canGetTransform: true,
canPreviewTransform: false,
canReauthorizeTransform: false,
canResetTransform: false,
canScheduleNowTransform: false,
canStartStopTransform: false,
Expand Down Expand Up @@ -124,6 +126,7 @@ describe('has_privilege_factory', () => {
canGetTransform: false,
canPreviewTransform: false,
canResetTransform: false,
canReauthorizeTransform: false,
canScheduleNowTransform: false,
canStartStopTransform: false,
canUseTransformAlerts: false,
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/transform/common/privilege/has_privilege_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface TransformCapabilities {
canDeleteTransform: boolean;
canPreviewTransform: boolean;
canCreateTransform: boolean;
canReauthorizeTransform: boolean;
canScheduleNowTransform: boolean;
canStartStopTransform: boolean;
canCreateTransformAlerts: boolean;
Expand All @@ -30,6 +31,7 @@ export const INITIAL_CAPABILITIES = Object.freeze<Capabilities>({
canDeleteTransform: false,
canPreviewTransform: false,
canCreateTransform: false,
canReauthorizeTransform: false,
canScheduleNowTransform: false,
canStartStopTransform: false,
canCreateTransformAlerts: false,
Expand Down Expand Up @@ -130,6 +132,8 @@ export const getPrivilegesAndCapabilities = (

capabilities.canScheduleNowTransform = capabilities.canStartStopTransform;

capabilities.canReauthorizeTransform = capabilities.canStartStopTransform;

return { privileges: privilegesResult, capabilities };
};
// create the text for button's tooltips if the user
Expand Down Expand Up @@ -170,6 +174,16 @@ export function createCapabilityFailureMessage(
}
);
break;

case 'canReauthorizeTransform':
message = i18n.translate(
'xpack.transform.capability.noPermission.reauthorizeTransformTooltip',
{
defaultMessage: 'You do not have permission to reauthorize transforms.',
}
);
break;

case 'canDeleteTransform':
message = i18n.translate('xpack.transform.capability.noPermission.deleteTransformTooltip', {
defaultMessage: 'You do not have permission to delete transforms.',
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/transform/common/types/transform_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { type TransformHealth, type TransformState, TRANSFORM_STATE } from '../c
import { TransformId } from './transform';

export interface TransformHealthIssue {
type: string;
issue: string;
details?: string;
count: number;
Expand Down
41 changes: 41 additions & 0 deletions x-pack/plugins/transform/common/utils/transform_api_key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { GrantAPIKeyResult } from '@kbn/security-plugin/server';
import type { SecurityCreateApiKeyResponse } from '@elastic/elasticsearch/lib/api/types';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';

export interface TransformAPIKey extends GrantAPIKeyResult {
/**
* Generated encoded API key used for headers
*/
encoded: string;
}

export interface SecondaryAuthorizationHeader {
headers?: { 'es-secondary-authorization': string | string[] };
}

export function isTransformApiKey(arg: any): arg is TransformAPIKey {
return isPopulatedObject(arg, ['api_key', 'encoded']) && typeof arg.encoded === 'string';
}

export function generateTransformSecondaryAuthHeaders(
apiKeyWithCurrentUserPermission:
| GrantAPIKeyResult
| null
| undefined
| SecurityCreateApiKeyResponse
): SecondaryAuthorizationHeader | undefined {
return isTransformApiKey(apiKeyWithCurrentUserPermission)
? {
headers: {
'es-secondary-authorization': `ApiKey ${apiKeyWithCurrentUserPermission.encoded}`,
},
}
: undefined;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { TransformHealthIssue } from '../../../common/types/transform_stats';
import { TRANSFORM_HEALTH } from '../../../common/constants';
import type { TransformListRow } from './transform_list';

export const needsReauthorization = (transform: Partial<TransformListRow>) => {
return (
isPopulatedObject(transform.config?.authorization, ['api_key']) &&
isPopulatedObject(transform.stats) &&
transform.stats.health.status === TRANSFORM_HEALTH.red &&
transform.stats.health.issues?.find(
(issue) => (issue as TransformHealthIssue).issue === 'Privileges check failed'
) !== undefined
);
};
16 changes: 16 additions & 0 deletions x-pack/plugins/transform/public/app/hooks/use_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import type { IHttpFetchError } from '@kbn/core-http-browser';

import { KBN_FIELD_TYPES } from '@kbn/field-types';

import {
ReauthorizeTransformsRequestSchema,
ReauthorizeTransformsResponseSchema,
} from '../../../common/api_schemas/reauthorize_transforms';
import type { GetTransformsAuditMessagesResponseSchema } from '../../../common/api_schemas/audit_messages';
import type {
DeleteTransformsRequestSchema,
Expand Down Expand Up @@ -166,6 +170,18 @@ export const useApi = () => {
return e;
}
},
async reauthorizeTransforms(
reqBody: ReauthorizeTransformsRequestSchema
): Promise<ReauthorizeTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}reauthorize_transforms`, {
body: JSON.stringify(reqBody),
});
} catch (e) {
return e;
}
},

async resetTransforms(
reqBody: ResetTransformsRequestSchema
): Promise<ResetTransformsResponseSchema | IHttpFetchError> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { i18n } from '@kbn/i18n';

import { toMountPoint } from '@kbn/kibana-react-plugin/public';

import type { StartTransformsRequestSchema } from '../../../common/api_schemas/start_transforms';
import { isStartTransformsResponseSchema } from '../../../common/api_schemas/type_guards';

import { getErrorMessage } from '../../../common/utils/errors';

import { useAppDependencies, useToastNotifications } from '../app_dependencies';
import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
import { ToastNotificationText } from '../components';

import { useApi } from './use_api';

export const useReauthorizeTransforms = () => {
const { overlays, theme } = useAppDependencies();
const toastNotifications = useToastNotifications();
const api = useApi();

return async (transformsInfo: StartTransformsRequestSchema) => {
const results = await api.reauthorizeTransforms(transformsInfo);

if (!isStartTransformsResponseSchema(results)) {
toastNotifications.addDanger({
title: i18n.translate(
'xpack.transform.stepCreateForm.reauthorizeTransformResponseSchemaErrorMessage',
{
defaultMessage: 'An error occurred calling the reauthorize transforms request.',
}
),
text: toMountPoint(
<ToastNotificationText
overlays={overlays}
theme={theme}
text={getErrorMessage(results)}
/>,
{ theme$: theme.theme$ }
),
});
return;
}

for (const transformId in results) {
// hasOwnProperty check to ensure only properties on object itself, and not its prototypes
if (results.hasOwnProperty(transformId)) {
const result = results[transformId];
if (result.success === true) {
toastNotifications.addSuccess(
i18n.translate('xpack.transform.transformList.reauthorizeTransformSuccessMessage', {
defaultMessage: 'Request to reauthorize transform {transformId} acknowledged.',
values: { transformId },
})
);
} else {
toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), {
title: i18n.translate(
'xpack.transform.transformList.reauthorizeTransformErrorMessage',
{
defaultMessage: 'An error occurred reauthorizing the transform {transformId}',
values: { transformId },
}
),
toastMessage: result.error!.reason,
});
}
}
}

refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH);
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export { useReauthorizeAction } from './use_reauthorize_action';
export { ReauthorizeActionModal } from './reauthorize_action_modal';
export { isReauthorizeActionDisabled, ReauthorizeActionName } from './reauthorize_action_name';
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui';
import type { ReauthorizeAction } from './use_reauthorize_action';

export const ReauthorizeActionModal: FC<ReauthorizeAction> = ({
closeModal,
items,
reauthorizeAndCloseModal,
}) => {
const isBulkAction = items.length > 1;

const bulkReauthorizeModalTitle = i18n.translate(
'xpack.transform.transformList.bulkReauthorizeModalTitle',
{
defaultMessage: 'Reauthorize {count} {count, plural, one {transform} other {transforms}}?',
values: { count: items && items.length },
}
);
const reauthorizeModalTitle = i18n.translate(
'xpack.transform.transformList.reauthorizeModalTitle',
{
defaultMessage: 'Reauthorize {transformId}?',
values: { transformId: items[0] && items[0].config.id },
}
);

return (
<EuiConfirmModal
data-test-subj="transformReauthorizeModal"
title={isBulkAction === true ? bulkReauthorizeModalTitle : reauthorizeModalTitle}
onCancel={closeModal}
onConfirm={reauthorizeAndCloseModal}
cancelButtonText={i18n.translate(
'xpack.transform.transformList.reauthorizeModalCancelButton',
{
defaultMessage: 'Cancel',
}
)}
confirmButtonText={i18n.translate(
'xpack.transform.transformList.reauthorizeModalConfirmButton',
{
defaultMessage: 'Reauthorize',
}
)}
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
buttonColor="primary"
>
<p>
{i18n.translate('xpack.transform.transformList.reauthorizeModalBody', {
defaultMessage:
'Your current roles are used to update and start the transform. Starting a transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.',
})}
</p>
</EuiConfirmModal>
);
};
Loading

0 comments on commit dd46350

Please sign in to comment.