[ML] Versioning transforms APIs (#158273)

Adds versioning to all of the transform APIs.
Versions are added to the server side routes and to the client side
functions which call the routes.
Updates API tests to add the API version to the request headers.

All of the APIs are internal and have been given the version '1'.

**Internal APIs**

`/internal/transform/privileges`
`/internal/transform/field_histograms/{dataViewTitle}`
`/internal/transform/privileges`
`/internal/transform/transforms/{transformId}/messages`
`/internal/transform/transforms/_nodes`
`/internal/transform/transforms`
`/internal/transform/transforms/{transformId}`
`/internal/transform/transforms/_stats`
`/internal/transform/transforms/{transformId}/_stats`
`/internal/transform/transforms/{transformId}`
`/internal/transform/transforms/{transformId}/_update`
`/internal/transform/reauthorize_transforms`
`/internal/transform/delete_transforms`
`/internal/transform/reset_transforms`
`/internal/transform/transforms/_preview`
`/internal/transform/start_transforms`
`/internal/transform/stop_transforms`
`/internal/transform/schedule_now_transforms`

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2023-05-25 13:12:10 +01:00 committed by GitHub
parent c5eee26671
commit 7d93816d98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 823 additions and 658 deletions

View file

@ -18,6 +18,7 @@ export interface SendRequestConfig {
* HttpFetchOptions#asSystemRequest.
*/
asSystemRequest?: boolean;
version?: string;
}
export interface SendRequestResponse<D = any, E = any> {
@ -27,13 +28,14 @@ export interface SendRequestResponse<D = any, E = any> {
export const sendRequest = async <D = any, E = any>(
httpClient: HttpSetup,
{ path, method, body, query, asSystemRequest }: SendRequestConfig
{ path, method, body, query, version, asSystemRequest }: SendRequestConfig
): Promise<SendRequestResponse<D, E>> => {
try {
const stringifiedBody = typeof body === 'string' ? body : JSON.stringify(body);
const response = await httpClient[method]<{ data?: D } & D>(path, {
body: stringifiedBody,
query,
version,
asSystemRequest,
});

View file

@ -27,7 +27,16 @@ export interface UseRequestResponse<D = any, E = Error> {
export const useRequest = <D = any, E = Error>(
httpClient: HttpSetup,
{ path, method, query, body, pollIntervalMs, initialData, deserializer }: UseRequestConfig
{
path,
method,
query,
body,
pollIntervalMs,
initialData,
deserializer,
version,
}: UseRequestConfig
): UseRequestResponse<D, E> => {
const isMounted = useRef(false);
@ -60,10 +69,11 @@ export const useRequest = <D = any, E = Error>(
method,
query: queryStringified ? query : undefined,
body: bodyStringified ? body : undefined,
version,
};
// queryStringified and bodyStringified stand in for query and body as dependencies.
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [path, method, queryStringified, bodyStringified]);
}, [path, method, queryStringified, bodyStringified, version]);
const resendRequest = useCallback(
async (asSystemRequest?: boolean) => {

View file

@ -26,7 +26,11 @@ export const PLUGIN = {
},
};
export const API_BASE_PATH = '/api/transform/';
const INTERNAL_API_BASE_PATH = '/internal/transform/';
const EXTERNAL_API_BASE_PATH = '/api/transform/';
export const addInternalBasePath = (uri: string): string => `${INTERNAL_API_BASE_PATH}${uri}`;
export const addExternalBasePath = (uri: string): string => `${EXTERNAL_API_BASE_PATH}${uri}`;
// In order to create a transform, the API requires the following privileges:
// - transform_admin (builtin)

View file

@ -18,7 +18,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { API_BASE_PATH } from '../../common/constants';
import { addInternalBasePath } from '../../common/constants';
import { SectionError } from './components';
import { SECTION_SLUG } from './constants';
@ -69,7 +69,9 @@ export const renderApp = (element: HTMLElement, appDependencies: AppDependencies
<EuiErrorBoundary>
<KibanaThemeProvider theme$={appDependencies.theme.theme$}>
<KibanaContextProvider services={appDependencies}>
<AuthorizationProvider privilegesEndpoint={`${API_BASE_PATH}privileges`}>
<AuthorizationProvider
privilegesEndpoint={{ path: addInternalBasePath(`privileges`), version: '1' }}
>
<I18nContext>
<App history={appDependencies.history} />
</I18nContext>

View file

@ -54,7 +54,7 @@ import type {
} from '../../../common/api_schemas/update_transforms';
import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats';
import type { TransformId } from '../../../common/types/transform';
import { API_BASE_PATH } from '../../../common/constants';
import { addInternalBasePath } from '../../../common/constants';
import type { EsIndex } from '../../../common/types/es_index';
import type { EsIngestPipeline } from '../../../common/types/es_ingest_pipeline';
@ -81,7 +81,7 @@ export const useApi = () => {
() => ({
async getTransformNodes(): Promise<GetTransformNodesResponseSchema | IHttpFetchError> {
try {
return await http.get(`${API_BASE_PATH}transforms/_nodes`);
return await http.get(addInternalBasePath(`transforms/_nodes`), { version: '1' });
} catch (e) {
return e;
}
@ -90,7 +90,9 @@ export const useApi = () => {
transformId: TransformId
): Promise<GetTransformsResponseSchema | IHttpFetchError> {
try {
return await http.get(`${API_BASE_PATH}transforms/${transformId}`);
return await http.get(addInternalBasePath(`transforms/${transformId}`), {
version: '1',
});
} catch (e) {
return e;
}
@ -99,7 +101,10 @@ export const useApi = () => {
fetchOptions: FetchOptions = {}
): Promise<GetTransformsResponseSchema | IHttpFetchError> {
try {
return await http.get(`${API_BASE_PATH}transforms`, fetchOptions);
return await http.get(addInternalBasePath(`transforms`), {
...fetchOptions,
version: '1',
});
} catch (e) {
return e;
}
@ -108,7 +113,9 @@ export const useApi = () => {
transformId: TransformId
): Promise<GetTransformsStatsResponseSchema | IHttpFetchError> {
try {
return await http.get(`${API_BASE_PATH}transforms/${transformId}/_stats`);
return await http.get(addInternalBasePath(`transforms/${transformId}/_stats`), {
version: '1',
});
} catch (e) {
return e;
}
@ -117,7 +124,10 @@ export const useApi = () => {
fetchOptions: FetchOptions = {}
): Promise<GetTransformsStatsResponseSchema | IHttpFetchError> {
try {
return await http.get(`${API_BASE_PATH}transforms/_stats`, fetchOptions);
return await http.get(addInternalBasePath(`transforms/_stats`), {
...fetchOptions,
version: '1',
});
} catch (e) {
return e;
}
@ -127,8 +137,9 @@ export const useApi = () => {
transformConfig: PutTransformsRequestSchema
): Promise<PutTransformsResponseSchema | IHttpFetchError> {
try {
return await http.put(`${API_BASE_PATH}transforms/${transformId}`, {
return await http.put(addInternalBasePath(`transforms/${transformId}`), {
body: JSON.stringify(transformConfig),
version: '1',
});
} catch (e) {
return e;
@ -139,8 +150,9 @@ export const useApi = () => {
transformConfig: PostTransformsUpdateRequestSchema
): Promise<PostTransformsUpdateResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}transforms/${transformId}/_update`, {
return await http.post(addInternalBasePath(`transforms/${transformId}/_update`), {
body: JSON.stringify(transformConfig),
version: '1',
});
} catch (e) {
return e;
@ -150,8 +162,9 @@ export const useApi = () => {
reqBody: DeleteTransformsRequestSchema
): Promise<DeleteTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}delete_transforms`, {
return await http.post(addInternalBasePath(`delete_transforms`), {
body: JSON.stringify(reqBody),
version: '1',
});
} catch (e) {
return e;
@ -161,8 +174,9 @@ export const useApi = () => {
obj: PostTransformsPreviewRequestSchema
): Promise<PostTransformsPreviewResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}transforms/_preview`, {
return await http.post(addInternalBasePath(`transforms/_preview`), {
body: JSON.stringify(obj),
version: '1',
});
} catch (e) {
return e;
@ -172,8 +186,9 @@ export const useApi = () => {
reqBody: ReauthorizeTransformsRequestSchema
): Promise<ReauthorizeTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}reauthorize_transforms`, {
return await http.post(addInternalBasePath(`reauthorize_transforms`), {
body: JSON.stringify(reqBody),
version: '1',
});
} catch (e) {
return e;
@ -184,8 +199,9 @@ export const useApi = () => {
reqBody: ResetTransformsRequestSchema
): Promise<ResetTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}reset_transforms`, {
return await http.post(addInternalBasePath(`reset_transforms`), {
body: JSON.stringify(reqBody),
version: '1',
});
} catch (e) {
return e;
@ -195,8 +211,9 @@ export const useApi = () => {
reqBody: StartTransformsRequestSchema
): Promise<StartTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}start_transforms`, {
return await http.post(addInternalBasePath(`start_transforms`), {
body: JSON.stringify(reqBody),
version: '1',
});
} catch (e) {
return e;
@ -206,8 +223,9 @@ export const useApi = () => {
transformsInfo: StopTransformsRequestSchema
): Promise<StopTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}stop_transforms`, {
return await http.post(addInternalBasePath(`stop_transforms`), {
body: JSON.stringify(transformsInfo),
version: '1',
});
} catch (e) {
return e;
@ -217,8 +235,9 @@ export const useApi = () => {
transformsInfo: ScheduleNowTransformsRequestSchema
): Promise<ScheduleNowTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}schedule_now_transforms`, {
return await http.post(addInternalBasePath(`schedule_now_transforms`), {
body: JSON.stringify(transformsInfo),
version: '1',
});
} catch (e) {
return e;
@ -232,11 +251,12 @@ export const useApi = () => {
{ messages: GetTransformsAuditMessagesResponseSchema; total: number } | IHttpFetchError
> {
try {
return await http.get(`${API_BASE_PATH}transforms/${transformId}/messages`, {
return await http.get(addInternalBasePath(`transforms/${transformId}/messages`), {
query: {
sortField,
sortDirection,
},
version: '1',
});
} catch (e) {
return e;
@ -244,14 +264,14 @@ export const useApi = () => {
},
async getEsIndices(): Promise<EsIndex[] | IHttpFetchError> {
try {
return await http.get(`/api/index_management/indices`);
return await http.get(`/api/index_management/indices`, { version: '1' });
} catch (e) {
return e;
}
},
async getEsIngestPipelines(): Promise<EsIngestPipeline[] | IHttpFetchError> {
try {
return await http.get('/api/ingest_pipelines');
return await http.get('/api/ingest_pipelines', { version: '1' });
} catch (e) {
return e;
}
@ -264,13 +284,14 @@ export const useApi = () => {
samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE
): Promise<FieldHistogramsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}field_histograms/${dataViewTitle}`, {
return await http.post(addInternalBasePath(`field_histograms/${dataViewTitle}`), {
body: JSON.stringify({
query,
fields,
samplerShardSize,
...(runtimeMappings !== undefined ? { runtimeMappings } : {}),
}),
version: '1',
});
} catch (e) {
return e;

View file

@ -36,17 +36,19 @@ const initialValue: Authorization = {
export const AuthorizationContext = createContext<Authorization>({ ...initialValue });
interface Props {
privilegesEndpoint: string;
privilegesEndpoint: { path: string; version: string };
children: React.ReactNode;
}
export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => {
const { path, version } = privilegesEndpoint;
const {
isLoading,
error,
data: privilegesData,
} = useRequest({
path: privilegesEndpoint,
path,
version,
method: 'get',
});

View file

@ -7,11 +7,14 @@
import type { HttpSetup } from '@kbn/core/public';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { API_BASE_PATH } from '../../../common/constants';
import { addInternalBasePath } from '../../../common/constants';
export class IndexService {
async canDeleteIndex(http: HttpSetup) {
const privilege = await http.get<{ hasAllPrivileges: boolean }>(`${API_BASE_PATH}privileges`);
const privilege = await http.get<{ hasAllPrivileges: boolean }>(
addInternalBasePath(`privileges`),
{ version: '1' }
);
if (!privilege) {
return false;
}

View file

@ -6,6 +6,7 @@
*/
import { fetchHistogramsForFields } from '@kbn/ml-agg-utils';
import { addInternalBasePath } from '../../../common/constants';
import { dataViewTitleSchema, DataViewTitleSchema } from '../../../common/api_schemas/common';
import {
@ -14,40 +15,45 @@ import {
} from '../../../common/api_schemas/field_histograms';
import { RouteDependencies } from '../../types';
import { addBasePath } from '..';
import { wrapError, wrapEsError } from './error_utils';
export function registerFieldHistogramsRoutes({ router, license }: RouteDependencies) {
router.post<DataViewTitleSchema, undefined, FieldHistogramsRequestSchema>(
{
path: addBasePath('field_histograms/{dataViewTitle}'),
validate: {
params: dataViewTitleSchema,
body: fieldHistogramsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('field_histograms/{dataViewTitle}'),
access: 'internal',
})
.addVersion<DataViewTitleSchema, undefined, FieldHistogramsRequestSchema>(
{
version: '1',
validate: {
request: {
params: dataViewTitleSchema,
body: fieldHistogramsRequestSchema,
},
},
},
},
license.guardApiRoute<DataViewTitleSchema, undefined, FieldHistogramsRequestSchema>(
async (ctx, req, res) => {
const { dataViewTitle } = req.params;
const { query, fields, runtimeMappings, samplerShardSize } = req.body;
license.guardApiRoute<DataViewTitleSchema, undefined, FieldHistogramsRequestSchema>(
async (ctx, req, res) => {
const { dataViewTitle } = req.params;
const { query, fields, runtimeMappings, samplerShardSize } = req.body;
try {
const esClient = (await ctx.core).elasticsearch.client;
const resp = await fetchHistogramsForFields(
esClient.asCurrentUser,
dataViewTitle,
query,
fields,
samplerShardSize,
runtimeMappings
);
try {
const esClient = (await ctx.core).elasticsearch.client;
const resp = await fetchHistogramsForFields(
esClient.asCurrentUser,
dataViewTitle,
query,
fields,
samplerShardSize,
runtimeMappings
);
return res.ok({ body: resp });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
return res.ok({ body: resp });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
}
)
);
)
);
}

View file

@ -8,65 +8,80 @@
import type { IScopedClusterClient } from '@kbn/core/server';
import type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types';
import { getPrivilegesAndCapabilities } from '../../../common/privilege/has_privilege_factory';
import { APP_CLUSTER_PRIVILEGES, APP_INDEX_PRIVILEGES } from '../../../common/constants';
import {
addInternalBasePath,
APP_CLUSTER_PRIVILEGES,
APP_INDEX_PRIVILEGES,
} from '../../../common/constants';
import type { Privileges } from '../../../common/types/privileges';
import type { RouteDependencies } from '../../types';
import { addBasePath } from '..';
export function registerPrivilegesRoute({ router, license }: RouteDependencies) {
router.get(
{ path: addBasePath('privileges'), validate: {} },
license.guardApiRoute(async (ctx, req, res) => {
const privilegesResult: Privileges = {
hasAllPrivileges: true,
missingPrivileges: {
cluster: [],
index: [],
},
};
if (license.getStatus().isSecurityEnabled === false) {
// If security isn't enabled, let the user use app.
return res.ok({ body: privilegesResult });
}
const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client;
const esClusterPrivilegesReq: Promise<SecurityHasPrivilegesResponse> =
esClient.asCurrentUser.security.hasPrivileges({
body: {
// @ts-expect-error SecurityClusterPrivilege doesnt contain all the priviledges
cluster: APP_CLUSTER_PRIVILEGES,
},
});
const [esClusterPrivileges, userPrivileges] = await Promise.all([
// Get cluster privileges
esClusterPrivilegesReq,
// // Get all index privileges the user has
esClient.asCurrentUser.security.getUserPrivileges(),
]);
const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges;
const { indices } = userPrivileges;
// Check if they have all the required index privileges for at least one index
const hasOneIndexWithAllPrivileges =
indices.find(({ privileges }: { privileges: string[] }) => {
if (privileges.includes('all')) {
return true;
}
const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) =>
privileges.includes(privilege)
);
return indexHasAllPrivileges;
}) !== undefined;
return res.ok({
body: getPrivilegesAndCapabilities(cluster, hasOneIndexWithAllPrivileges, hasAllPrivileges),
});
router.versioned
.get({
path: addInternalBasePath('privileges'),
access: 'internal',
})
);
.addVersion(
{
version: '1',
validate: false,
},
license.guardApiRoute(async (ctx, req, res) => {
const privilegesResult: Privileges = {
hasAllPrivileges: true,
missingPrivileges: {
cluster: [],
index: [],
},
};
if (license.getStatus().isSecurityEnabled === false) {
// If security isn't enabled, let the user use app.
return res.ok({ body: privilegesResult });
}
const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client;
const esClusterPrivilegesReq: Promise<SecurityHasPrivilegesResponse> =
esClient.asCurrentUser.security.hasPrivileges({
body: {
// @ts-expect-error SecurityClusterPrivilege doesnt contain all the priviledges
cluster: APP_CLUSTER_PRIVILEGES,
},
});
const [esClusterPrivileges, userPrivileges] = await Promise.all([
// Get cluster privileges
esClusterPrivilegesReq,
// // Get all index privileges the user has
esClient.asCurrentUser.security.getUserPrivileges(),
]);
const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges;
const { indices } = userPrivileges;
// Check if they have all the required index privileges for at least one index
const hasOneIndexWithAllPrivileges =
indices.find(({ privileges }: { privileges: string[] }) => {
if (privileges.includes('all')) {
return true;
}
const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) =>
privileges.includes(privilege)
);
return indexHasAllPrivileges;
}) !== undefined;
return res.ok({
body: getPrivilegesAndCapabilities(
cluster,
hasOneIndexWithAllPrivileges,
hasAllPrivileges
),
});
})
);
}

View file

@ -22,7 +22,7 @@ import {
ReauthorizeTransformsRequestSchema,
ReauthorizeTransformsResponseSchema,
} from '../../../common/api_schemas/reauthorize_transforms';
import { TRANSFORM_STATE } from '../../../common/constants';
import { addInternalBasePath, TRANSFORM_STATE } from '../../../common/constants';
import {
transformIdParamSchema,
ResponseStatus,
@ -67,8 +67,6 @@ import {
import { RouteDependencies } from '../../types';
import { addBasePath } from '..';
import { isRequestTimeout, fillResultsWithTimeouts, wrapError, wrapEsError } from './error_utils';
import { registerTransformsAuditMessagesRoutes } from './transforms_audit_messages';
import { registerTransformNodesRoutes } from './transforms_nodes';
@ -90,88 +88,154 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
/**
* @apiGroup Transforms
*
* @api {get} /api/transform/transforms Get transforms
* @api {get} /internal/transform/transforms Get transforms
* @apiName GetTransforms
* @apiDescription Returns transforms
*
* @apiSchema (params) jobAuditMessagesJobIdSchema
* @apiSchema (query) jobAuditMessagesQuerySchema
*/
router.get(
{ path: addBasePath('transforms'), validate: false },
license.guardApiRoute<estypes.TransformGetTransformRequest, undefined, undefined>(
async (ctx, req, res) => {
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransform({
size: 1000,
...req.params,
});
const alerting = await ctx.alerting;
if (alerting) {
const transformHealthService = transformHealthServiceProvider({
esClient: esClient.asCurrentUser,
rulesClient: alerting.getRulesClient(),
router.versioned
.get({
path: addInternalBasePath('transforms'),
access: 'internal',
})
.addVersion(
{
version: '1',
validate: false,
},
license.guardApiRoute<estypes.TransformGetTransformRequest, undefined, undefined>(
async (ctx, req, res) => {
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransform({
size: 1000,
...req.params,
});
// @ts-ignore
await transformHealthService.populateTransformsWithAssignedRules(body.transforms);
}
const alerting = await ctx.alerting;
if (alerting) {
const transformHealthService = transformHealthServiceProvider({
esClient: esClient.asCurrentUser,
rulesClient: alerting.getRulesClient(),
});
return res.ok({ body });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
// @ts-ignore
await transformHealthService.populateTransformsWithAssignedRules(body.transforms);
}
return res.ok({ body });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
}
)
);
)
);
/**
* @apiGroup Transforms
*
* @api {get} /api/transform/transforms/:transformId Get transform
* @api {get} /internal/transform/transforms/:transformId Get transform
* @apiName GetTransform
* @apiDescription Returns a single transform
*
* @apiSchema (params) transformIdParamSchema
*/
router.get<TransformIdParamSchema, undefined, undefined>(
{
path: addBasePath('transforms/{transformId}'),
validate: { params: transformIdParamSchema },
},
license.guardApiRoute<TransformIdParamSchema, undefined, undefined>(async (ctx, req, res) => {
const { transformId } = req.params;
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransform({
transform_id: transformId,
});
return res.ok({ body });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
router.versioned
.get({
path: addInternalBasePath('transforms/{transformId}'),
access: 'internal',
})
);
.addVersion<TransformIdParamSchema, undefined, undefined>(
{
version: '1',
validate: {
request: {
params: transformIdParamSchema,
},
},
},
license.guardApiRoute<TransformIdParamSchema, undefined, undefined>(async (ctx, req, res) => {
const { transformId } = req.params;
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransform({
transform_id: transformId,
});
return res.ok({ body });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
})
);
/**
* @apiGroup Transforms
*
* @api {get} /api/transform/transforms/_stats Get transforms stats
* @api {get} /internal/transform/transforms/_stats Get transforms stats
* @apiName GetTransformsStats
* @apiDescription Returns transforms stats
*/
router.get(
{ path: addBasePath('transforms/_stats'), validate: false },
license.guardApiRoute<estypes.TransformGetTransformStatsResponse, undefined, undefined>(
async (ctx, req, res) => {
router.versioned
.get({
path: addInternalBasePath('transforms/_stats'),
access: 'internal',
})
.addVersion(
{
version: '1',
validate: false,
},
license.guardApiRoute<estypes.TransformGetTransformStatsResponse, undefined, undefined>(
async (ctx, req, res) => {
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransformStats(
{
size: 1000,
transform_id: '_all',
},
{ maxRetries: 0 }
);
return res.ok({ body });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
)
);
/**
* @apiGroup Transforms
*
* @api {get} /internal/transform/transforms/:transformId/_stats Get transform stats
* @apiName GetTransformStats
* @apiDescription Returns stats for a single transform
*
* @apiSchema (params) transformIdParamSchema
*/
router.versioned
.get({
path: addInternalBasePath('transforms/{transformId}/_stats'),
access: 'internal',
})
.addVersion<TransformIdParamSchema, undefined, undefined>(
{
version: '1',
validate: {
request: {
params: transformIdParamSchema,
},
},
},
license.guardApiRoute<TransformIdParamSchema, undefined, undefined>(async (ctx, req, res) => {
const { transformId } = req.params;
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransformStats(
{
size: 1000,
transform_id: '_all',
transform_id: transformId,
},
{ maxRetries: 0 }
);
@ -179,354 +243,385 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
)
);
})
);
/**
* @apiGroup Transforms
*
* @api {get} /api/transform/transforms/:transformId/_stats Get transform stats
* @apiName GetTransformStats
* @apiDescription Returns stats for a single transform
*
* @apiSchema (params) transformIdParamSchema
*/
router.get<TransformIdParamSchema, undefined, undefined>(
{
path: addBasePath('transforms/{transformId}/_stats'),
validate: { params: transformIdParamSchema },
},
license.guardApiRoute<TransformIdParamSchema, undefined, undefined>(async (ctx, req, res) => {
const { transformId } = req.params;
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.getTransformStats(
{
transform_id: transformId,
},
{ maxRetries: 0 }
);
return res.ok({ body });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
})
);
/**
* @apiGroup Transforms
*
* @api {put} /api/transform/transforms/:transformId Put transform
* @api {put} /internal/transform/transforms/:transformId Put transform
* @apiName PutTransform
* @apiDescription Creates a transform
*
* @apiSchema (params) transformIdParamSchema
* @apiSchema (body) putTransformsRequestSchema
*/
router.put<TransformIdParamSchema, undefined, PutTransformsRequestSchema>(
{
path: addBasePath('transforms/{transformId}'),
validate: {
params: transformIdParamSchema,
body: putTransformsRequestSchema,
router.versioned
.put({
path: addInternalBasePath('transforms/{transformId}'),
access: 'internal',
})
.addVersion<TransformIdParamSchema, undefined, PutTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
params: transformIdParamSchema,
body: putTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<TransformIdParamSchema, undefined, PutTransformsRequestSchema>(
async (ctx, req, res) => {
const { transformId } = req.params;
license.guardApiRoute<TransformIdParamSchema, undefined, PutTransformsRequestSchema>(
async (ctx, req, res) => {
const { transformId } = req.params;
const response: PutTransformsResponseSchema = {
transformsCreated: [],
errors: [],
};
const response: PutTransformsResponseSchema = {
transformsCreated: [],
errors: [],
};
const esClient = (await ctx.core).elasticsearch.client;
await esClient.asCurrentUser.transform
.putTransform({
// @ts-expect-error @elastic/elasticsearch group_by is expected to be optional in TransformPivot
body: req.body,
transform_id: transformId,
})
.then(() => {
response.transformsCreated.push({ transform: transformId });
})
.catch((e) =>
response.errors.push({
id: transformId,
error: wrapEsError(e),
const esClient = (await ctx.core).elasticsearch.client;
await esClient.asCurrentUser.transform
.putTransform({
// @ts-expect-error @elastic/elasticsearch group_by is expected to be optional in TransformPivot
body: req.body,
transform_id: transformId,
})
);
.then(() => {
response.transformsCreated.push({ transform: transformId });
})
.catch((e) =>
response.errors.push({
id: transformId,
error: wrapEsError(e),
})
);
return res.ok({ body: response });
}
)
);
return res.ok({ body: response });
}
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/transforms/:transformId/_update Post transform update
* @api {post} /internal/transform/transforms/:transformId/_update Post transform update
* @apiName PostTransformUpdate
* @apiDescription Updates a transform
*
* @apiSchema (params) transformIdParamSchema
* @apiSchema (body) postTransformsUpdateRequestSchema
*/
router.post<TransformIdParamSchema, undefined, PostTransformsUpdateRequestSchema>(
{
path: addBasePath('transforms/{transformId}/_update'),
validate: {
params: transformIdParamSchema,
body: postTransformsUpdateRequestSchema,
router.versioned
.post({
path: addInternalBasePath('transforms/{transformId}/_update'),
access: 'internal',
})
.addVersion<TransformIdParamSchema, undefined, PostTransformsUpdateRequestSchema>(
{
version: '1',
validate: {
request: {
params: transformIdParamSchema,
body: postTransformsUpdateRequestSchema,
},
},
},
},
license.guardApiRoute<TransformIdParamSchema, undefined, PostTransformsUpdateRequestSchema>(
async (ctx, req, res) => {
const { transformId } = req.params;
license.guardApiRoute<TransformIdParamSchema, undefined, PostTransformsUpdateRequestSchema>(
async (ctx, req, res) => {
const { transformId } = req.params;
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.updateTransform({
// @ts-expect-error query doesn't satisfy QueryDslQueryContainer from @elastic/elasticsearch
body: req.body,
transform_id: transformId,
});
return res.ok({
body,
});
} catch (e) {
return res.customError(wrapError(e));
try {
const esClient = (await ctx.core).elasticsearch.client;
const body = await esClient.asCurrentUser.transform.updateTransform({
// @ts-expect-error query doesn't satisfy QueryDslQueryContainer from @elastic/elasticsearch
body: req.body,
transform_id: transformId,
});
return res.ok({
body,
});
} catch (e) {
return res.customError(wrapError(e));
}
}
}
)
);
)
);
/**
* @apiGroup Reauthorize transforms with API key generated from currently logged in user
* @api {post} /api/transform/reauthorize_transforms Post reauthorize transforms
* @api {post} /internal/transform/reauthorize_transforms Post reauthorize transforms
* @apiName Reauthorize Transforms
* @apiDescription Reauthorize transforms by generating an API Key for current user
* and update transform's es-secondary-authorization headers with the generated key,
* then start the transform.
* @apiSchema (body) reauthorizeTransformsRequestSchema
*/
router.post<undefined, undefined, StartTransformsRequestSchema>(
{
path: addBasePath('reauthorize_transforms'),
validate: {
body: reauthorizeTransformsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('reauthorize_transforms'),
access: 'internal',
})
.addVersion<undefined, undefined, StartTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
body: reauthorizeTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, StartTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const transformsInfo = req.body;
const { elasticsearch } = coreStart;
const esClient = elasticsearch.client.asScoped(req).asCurrentUser;
license.guardApiRoute<undefined, undefined, StartTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const transformsInfo = req.body;
const { elasticsearch } = coreStart;
const esClient = elasticsearch.client.asScoped(req).asCurrentUser;
let apiKeyWithCurrentUserPermission;
let apiKeyWithCurrentUserPermission;
// If security is not enabled or available, user should not have the need to reauthorize
// in that case, start anyway
if (securityStart) {
apiKeyWithCurrentUserPermission = await securityStart.authc.apiKeys.grantAsInternalUser(
req,
// If security is not enabled or available, user should not have the need to reauthorize
// in that case, start anyway
if (securityStart) {
apiKeyWithCurrentUserPermission =
await securityStart.authc.apiKeys.grantAsInternalUser(req, {
name: `auto-generated-transform-api-key`,
role_descriptors: {},
});
}
const secondaryAuth = generateTransformSecondaryAuthHeaders(
apiKeyWithCurrentUserPermission
);
const authorizedTransforms = await reauthorizeAndStartTransforms(
transformsInfo,
esClient,
{
name: `auto-generated-transform-api-key`,
role_descriptors: {},
...(secondaryAuth ? secondaryAuth : {}),
}
);
return res.ok({ body: authorizedTransforms });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
const secondaryAuth = generateTransformSecondaryAuthHeaders(
apiKeyWithCurrentUserPermission
);
const authorizedTransforms = await reauthorizeAndStartTransforms(
transformsInfo,
esClient,
{
...(secondaryAuth ? secondaryAuth : {}),
}
);
return res.ok({ body: authorizedTransforms });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
)
);
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/delete_transforms Post delete transforms
* @api {post} /internal/transform/delete_transforms Post delete transforms
* @apiName DeleteTransforms
* @apiDescription Deletes transforms
*
* @apiSchema (body) deleteTransformsRequestSchema
*/
router.post<undefined, undefined, DeleteTransformsRequestSchema>(
{
path: addBasePath('delete_transforms'),
validate: {
body: deleteTransformsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('delete_transforms'),
access: 'internal',
})
.addVersion<undefined, undefined, DeleteTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
body: deleteTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, DeleteTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const { savedObjects, elasticsearch } = coreStart;
const savedObjectsClient = savedObjects.getScopedClient(req);
const esClient = elasticsearch.client.asScoped(req).asCurrentUser;
license.guardApiRoute<undefined, undefined, DeleteTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const { savedObjects, elasticsearch } = coreStart;
const savedObjectsClient = savedObjects.getScopedClient(req);
const esClient = elasticsearch.client.asScoped(req).asCurrentUser;
const dataViewsService = await dataViews.dataViewsServiceFactory(
savedObjectsClient,
esClient,
req
);
const body = await deleteTransforms(req.body, ctx, res, dataViewsService);
const dataViewsService = await dataViews.dataViewsServiceFactory(
savedObjectsClient,
esClient,
req
);
const body = await deleteTransforms(req.body, ctx, res, dataViewsService);
if (body && body.status) {
if (body.status === 404) {
return res.notFound();
}
if (body.status === 403) {
return res.forbidden();
if (body && body.status) {
if (body.status === 404) {
return res.notFound();
}
if (body.status === 403) {
return res.forbidden();
}
}
return res.ok({
body,
});
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
return res.ok({
body,
});
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
)
);
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/reset_transforms Post reset transforms
* @api {post} /internal/transform/reset_transforms Post reset transforms
* @apiName ResetTransforms
* @apiDescription resets transforms
*
* @apiSchema (body) resetTransformsRequestSchema
*/
router.post<undefined, undefined, ResetTransformsRequestSchema>(
{
path: addBasePath('reset_transforms'),
validate: {
body: resetTransformsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('reset_transforms'),
access: 'internal',
})
.addVersion<undefined, undefined, ResetTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
body: resetTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, ResetTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const body = await resetTransforms(req.body, ctx, res);
license.guardApiRoute<undefined, undefined, ResetTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const body = await resetTransforms(req.body, ctx, res);
if (body && body.status) {
if (body.status === 404) {
return res.notFound();
}
if (body.status === 403) {
return res.forbidden();
if (body && body.status) {
if (body.status === 404) {
return res.notFound();
}
if (body.status === 403) {
return res.forbidden();
}
}
return res.ok({
body,
});
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
return res.ok({
body,
});
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
)
);
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/transforms/_preview Preview transform
* @api {post} /internal/transform/transforms/_preview Preview transform
* @apiName PreviewTransform
* @apiDescription Previews transform
*
* @apiSchema (body) postTransformsPreviewRequestSchema
*/
router.post<undefined, undefined, PostTransformsPreviewRequestSchema>(
{
path: addBasePath('transforms/_preview'),
validate: {
body: postTransformsPreviewRequestSchema,
router.versioned
.post({
path: addInternalBasePath('transforms/_preview'),
access: 'internal',
})
.addVersion<undefined, undefined, PostTransformsPreviewRequestSchema>(
{
version: '1',
validate: {
request: {
body: postTransformsPreviewRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, PostTransformsPreviewRequestSchema>(
previewTransformHandler
)
);
license.guardApiRoute<undefined, undefined, PostTransformsPreviewRequestSchema>(
previewTransformHandler
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/start_transforms Start transforms
* @api {post} /internal/transform/start_transforms Start transforms
* @apiName PostStartTransforms
* @apiDescription Starts transform
*
* @apiSchema (body) startTransformsRequestSchema
*/
router.post<undefined, undefined, StartTransformsRequestSchema>(
{
path: addBasePath('start_transforms'),
validate: {
body: startTransformsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('start_transforms'),
access: 'internal',
})
.addVersion<undefined, undefined, StartTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
body: startTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, StartTransformsRequestSchema>(
startTransformsHandler
)
);
license.guardApiRoute<undefined, undefined, StartTransformsRequestSchema>(
startTransformsHandler
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/stop_transforms Stop transforms
* @api {post} /internal/transform/stop_transforms Stop transforms
* @apiName PostStopTransforms
* @apiDescription Stops transform
*
* @apiSchema (body) stopTransformsRequestSchema
*/
router.post<undefined, undefined, StopTransformsRequestSchema>(
{
path: addBasePath('stop_transforms'),
validate: {
body: stopTransformsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('stop_transforms'),
access: 'internal',
})
.addVersion<undefined, undefined, StopTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
body: stopTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, StopTransformsRequestSchema>(stopTransformsHandler)
);
license.guardApiRoute<undefined, undefined, StopTransformsRequestSchema>(
stopTransformsHandler
)
);
/**
* @apiGroup Transforms
*
* @api {post} /api/transform/schedule_now_transforms Schedules transforms now
* @api {post} /internal/transform/schedule_now_transforms Schedules transforms now
* @apiName PostScheduleNowTransforms
* @apiDescription Schedules transforms now
*
* @apiSchema (body) scheduleNowTransformsRequestSchema
*/
router.post<undefined, undefined, ScheduleNowTransformsRequestSchema>(
{
path: addBasePath('schedule_now_transforms'),
validate: {
body: scheduleNowTransformsRequestSchema,
router.versioned
.post({
path: addInternalBasePath('schedule_now_transforms'),
access: 'internal',
})
.addVersion<undefined, undefined, ScheduleNowTransformsRequestSchema>(
{
version: '1',
validate: {
request: {
body: scheduleNowTransformsRequestSchema,
},
},
},
},
license.guardApiRoute<undefined, undefined, ScheduleNowTransformsRequestSchema>(
scheduleNowTransformsHandler
)
);
license.guardApiRoute<undefined, undefined, ScheduleNowTransformsRequestSchema>(
scheduleNowTransformsHandler
)
);
registerTransformsAuditMessagesRoutes(routeDependencies);
registerTransformNodesRoutes(routeDependencies);

View file

@ -6,14 +6,12 @@
*/
import { schema } from '@kbn/config-schema';
import { DEFAULT_MAX_AUDIT_MESSAGE_SIZE } from '../../../common/constants';
import { addInternalBasePath, DEFAULT_MAX_AUDIT_MESSAGE_SIZE } from '../../../common/constants';
import { transformIdParamSchema, TransformIdParamSchema } from '../../../common/api_schemas/common';
import { AuditMessage } from '../../../common/types/messages';
import { RouteDependencies } from '../../types';
import { addBasePath } from '..';
import { wrapError, wrapEsError } from './error_utils';
export const ML_DF_NOTIFICATION_INDEX_PATTERN = '.transform-notifications-read';
@ -31,98 +29,105 @@ export function registerTransformsAuditMessagesRoutes({ router, license }: Route
/**
* @apiGroup Transforms Audit Messages
*
* @api {get} /api/transform/transforms/:transformId/messages Transforms Messages
* @api {get} /internal/transform/transforms/:transformId/messages Transforms Messages
* @apiName GetTransformsMessages
* @apiDescription Get transforms audit messages
*
* @apiSchema (params) transformIdParamSchema
*/
router.get<TransformIdParamSchema, TransformMessageQuery, undefined>(
{
path: addBasePath('transforms/{transformId}/messages'),
validate: {
params: transformIdParamSchema,
query: schema.object({
sortField: schema.string(),
sortDirection: schema.oneOf([schema.literal('asc'), schema.literal('desc')]),
}),
router.versioned
.get({
path: addInternalBasePath('transforms/{transformId}/messages'),
access: 'internal',
})
.addVersion<TransformIdParamSchema, TransformMessageQuery, undefined>(
{
version: '1',
validate: {
request: {
params: transformIdParamSchema,
query: schema.object({
sortField: schema.string(),
sortDirection: schema.oneOf([schema.literal('asc'), schema.literal('desc')]),
}),
},
},
},
},
license.guardApiRoute<TransformIdParamSchema, TransformMessageQuery, undefined>(
async (ctx, req, res) => {
const { transformId } = req.params;
const sortField = req.query?.sortField ?? 'timestamp';
const sortDirection = req.query?.sortDirection ?? 'desc';
license.guardApiRoute<TransformIdParamSchema, TransformMessageQuery, undefined>(
async (ctx, req, res) => {
const { transformId } = req.params;
const sortField = req.query?.sortField ?? 'timestamp';
const sortDirection = req.query?.sortDirection ?? 'desc';
// search for audit messages,
// transformId is optional. without it, all transforms will be listed.
const query: BoolQuery = {
bool: {
filter: [
{
bool: {
must_not: {
term: {
level: 'activity',
// search for audit messages,
// transformId is optional. without it, all transforms will be listed.
const query: BoolQuery = {
bool: {
filter: [
{
bool: {
must_not: {
term: {
level: 'activity',
},
},
},
},
],
},
};
// if no transformId specified, load all of the messages
if (transformId !== undefined) {
query.bool.filter.push({
bool: {
should: [
{
term: {
transform_id: '', // catch system messages
},
},
{
term: {
transform_id: transformId, // messages for specified transformId
},
},
],
},
],
},
};
// if no transformId specified, load all of the messages
if (transformId !== undefined) {
query.bool.filter.push({
bool: {
should: [
{
term: {
transform_id: '', // catch system messages
},
},
{
term: {
transform_id: transformId, // messages for specified transformId
},
},
],
},
});
}
try {
const esClient = (await ctx.core).elasticsearch.client;
const resp = await esClient.asCurrentUser.search<AuditMessage>({
index: ML_DF_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
size: DEFAULT_MAX_AUDIT_MESSAGE_SIZE,
body: {
sort: [
{ [sortField]: { order: sortDirection } },
{ transform_id: { order: 'asc' as const } },
],
query,
},
track_total_hits: true,
});
const totalHits =
typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total!.value;
let messages: AuditMessage[] = [];
// TODO: remove typeof checks when appropriate overloading is added for the `search` API
if (
(typeof resp.hits.total === 'number' && resp.hits.total > 0) ||
(typeof resp.hits.total === 'object' && resp.hits.total.value > 0)
) {
messages = resp.hits.hits.map((hit) => hit._source!);
});
}
try {
const esClient = (await ctx.core).elasticsearch.client;
const resp = await esClient.asCurrentUser.search<AuditMessage>({
index: ML_DF_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
size: DEFAULT_MAX_AUDIT_MESSAGE_SIZE,
body: {
sort: [
{ [sortField]: { order: sortDirection } },
{ transform_id: { order: 'asc' as const } },
],
query,
},
track_total_hits: true,
});
const totalHits =
typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total!.value;
let messages: AuditMessage[] = [];
// TODO: remove typeof checks when appropriate overloading is added for the `search` API
if (
(typeof resp.hits.total === 'number' && resp.hits.total > 0) ||
(typeof resp.hits.total === 'object' && resp.hits.total.value > 0)
) {
messages = resp.hits.hits.map((hit) => hit._source!);
}
return res.ok({ body: { messages, total: totalHits } });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
return res.ok({ body: { messages, total: totalHits } });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
}
)
);
)
);
}

View file

@ -9,12 +9,10 @@ import Boom from '@hapi/boom';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { NODES_INFO_PRIVILEGES } from '../../../common/constants';
import { addInternalBasePath, NODES_INFO_PRIVILEGES } from '../../../common/constants';
import { RouteDependencies } from '../../types';
import { addBasePath } from '..';
import { wrapError, wrapEsError } from './error_utils';
const NODE_ROLES = 'roles';
@ -38,51 +36,56 @@ export function registerTransformNodesRoutes({ router, license }: RouteDependenc
/**
* @apiGroup Transform Nodes
*
* @api {get} /api/transforms/_nodes Transform Nodes
* @api {get} /internal/transforms/_nodes Transform Nodes
* @apiName GetTransformNodes
* @apiDescription Get transform nodes
*/
router.get<undefined, undefined, undefined>(
{
path: addBasePath('transforms/_nodes'),
validate: false,
},
license.guardApiRoute<undefined, undefined, undefined>(async (ctx, req, res) => {
try {
const esClient = (await ctx.core).elasticsearch.client;
// If security is enabled, check that the user has at least permission to
// view transforms before calling the _nodes endpoint with the internal user.
if (license.getStatus().isSecurityEnabled === true) {
const { has_all_requested: hasAllPrivileges } =
await esClient.asCurrentUser.security.hasPrivileges({
body: {
// @ts-expect-error SecurityClusterPrivilege doesnt contain all the priviledges
cluster: NODES_INFO_PRIVILEGES,
},
});
router.versioned
.get({
path: addInternalBasePath('transforms/_nodes'),
access: 'internal',
})
.addVersion<undefined, undefined, undefined>(
{
version: '1',
validate: false,
},
license.guardApiRoute<undefined, undefined, undefined>(async (ctx, req, res) => {
try {
const esClient = (await ctx.core).elasticsearch.client;
// If security is enabled, check that the user has at least permission to
// view transforms before calling the _nodes endpoint with the internal user.
if (license.getStatus().isSecurityEnabled === true) {
const { has_all_requested: hasAllPrivileges } =
await esClient.asCurrentUser.security.hasPrivileges({
body: {
// @ts-expect-error SecurityClusterPrivilege doesnt contain all the priviledges
cluster: NODES_INFO_PRIVILEGES,
},
});
if (!hasAllPrivileges) {
return res.customError(wrapError(new Boom.Boom('Forbidden', { statusCode: 403 })));
}
}
const { nodes } = await esClient.asInternalUser.nodes.info({
filter_path: `nodes.*.${NODE_ROLES}`,
});
let count = 0;
if (isNodes(nodes)) {
for (const { roles } of Object.values(nodes)) {
if (roles.includes('transform')) {
count++;
if (!hasAllPrivileges) {
return res.customError(wrapError(new Boom.Boom('Forbidden', { statusCode: 403 })));
}
}
}
return res.ok({ body: { count } });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
})
);
const { nodes } = await esClient.asInternalUser.nodes.info({
filter_path: `nodes.*.${NODE_ROLES}`,
});
let count = 0;
if (isNodes(nodes)) {
for (const { roles } of Object.values(nodes)) {
if (roles.includes('transform')) {
count++;
}
}
}
return res.ok({ body: { count } });
} catch (e) {
return res.customError(wrapError(wrapEsError(e)));
}
})
);
}

View file

@ -11,10 +11,6 @@ import { registerFieldHistogramsRoutes } from './api/field_histograms';
import { registerPrivilegesRoute } from './api/privileges';
import { registerTransformsRoutes } from './api/transforms';
import { API_BASE_PATH } from '../../common/constants';
export const addBasePath = (uri: string): string => `${API_BASE_PATH}${uri}`;
export function registerRoutes(dependencies: RouteDependencies) {
registerFieldHistogramsRoutes(dependencies);
registerPrivilegesRoute(dependencies);

View file

@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext) => {
await transform.api.createTransform(transformId, config);
}
describe('/api/transform/delete_transforms', function () {
describe('/internal/transform/delete_transforms', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -55,12 +55,12 @@ export default ({ getService }: FtrProviderContext) => {
transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }],
};
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -76,12 +76,12 @@ export default ({ getService }: FtrProviderContext) => {
transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }],
};
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(403, status, body);
@ -96,12 +96,12 @@ export default ({ getService }: FtrProviderContext) => {
transformsInfo: [{ id: 'invalid_transform_id', state: TRANSFORM_STATE.STOPPED }],
};
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -134,12 +134,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should delete multiple transforms by transformIds', async () => {
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -158,12 +158,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should delete multiple transforms by transformIds, even if one of the transformIds is invalid', async () => {
const invalidTransformId = 'invalid_transform_id';
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send({
...reqBody,
transformsInfo: [
@ -208,12 +208,12 @@ export default ({ getService }: FtrProviderContext) => {
deleteDestIndex: true,
};
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -247,12 +247,12 @@ export default ({ getService }: FtrProviderContext) => {
deleteDestDataView: true,
};
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -287,12 +287,12 @@ export default ({ getService }: FtrProviderContext) => {
deleteDestDataView: true,
};
const { body, status } = await supertest
.post(`/api/transform/delete_transforms`)
.post(`/internal/transform/delete_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);

View file

@ -25,9 +25,9 @@ export default ({ getService }: FtrProviderContext) => {
return `transform-by-${username}`;
}
function generateHeaders(apiKey: SecurityCreateApiKeyResponse) {
function generateHeaders(apiKey: SecurityCreateApiKeyResponse, version?: string) {
return {
...getCommonRequestHeader(),
...getCommonRequestHeader(version),
'es-secondary-authorization': `ApiKey ${apiKey.encoded}`,
};
}
@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => {
// If transform was created with sufficient permissions -> should create and start
// If transform was created with insufficient permissions -> should create but not start
describe('/api/transform/reauthorize_transforms', function () {
describe('/internal/transform/reauthorize_transforms', function () {
const apiKeysForTransformUsers = new Map<USER, SecurityCreateApiKeyResponse>();
async function expectUnauthorizedTransform(transformId: string, createdByUser: USER) {
@ -122,7 +122,7 @@ export default ({ getService }: FtrProviderContext) => {
beforeEach(async () => {
await createTransform(
transformCreatedByViewerId,
generateHeaders(apiKeysForTransformUsers.get(USER.TRANSFORM_VIEWER)!)
generateHeaders(apiKeysForTransformUsers.get(USER.TRANSFORM_VIEWER)!, '1')
);
});
afterEach(async () => {
@ -132,12 +132,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should not reauthorize transform created by transform_viewer for transform_unauthorized', async () => {
const reqBody: ReauthorizeTransformsRequestSchema = [{ id: transformCreatedByViewerId }];
const { body, status } = await supertest
.post(`/api/transform/reauthorize_transforms`)
.post(`/internal/transform/reauthorize_transforms`)
.auth(
USER.TRANSFORM_UNAUTHORIZED,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_UNAUTHORIZED)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
expect(body[transformCreatedByViewerId].success).to.eql(
@ -152,12 +152,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should not reauthorize transform created by transform_viewer for transform_viewer', async () => {
const reqBody: ReauthorizeTransformsRequestSchema = [{ id: transformCreatedByViewerId }];
const { body, status } = await supertest
.post(`/api/transform/reauthorize_transforms`)
.post(`/internal/transform/reauthorize_transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
expect(body[transformCreatedByViewerId].success).to.eql(
@ -172,12 +172,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should reauthorize transform created by transform_viewer with new api key of poweruser and start the transform', async () => {
const reqBody: ReauthorizeTransformsRequestSchema = [{ id: transformCreatedByViewerId }];
const { body, status } = await supertest
.post(`/api/transform/reauthorize_transforms`)
.post(`/internal/transform/reauthorize_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
expect(body[transformCreatedByViewerId].success).to.eql(
@ -201,12 +201,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 200 with error in response if invalid transformId', async () => {
const reqBody: ReauthorizeTransformsRequestSchema = [{ id: 'invalid_transform_id' }];
const { body, status } = await supertest
.post(`/api/transform/reauthorize_transforms`)
.post(`/internal/transform/reauthorize_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -227,7 +227,7 @@ export default ({ getService }: FtrProviderContext) => {
[USER.TRANSFORM_VIEWER, USER.TRANSFORM_POWERUSER].map((user) =>
createTransform(
getTransformIdByUser(user),
generateHeaders(apiKeysForTransformUsers.get(user)!)
generateHeaders(apiKeysForTransformUsers.get(user)!, '1')
)
)
);
@ -247,12 +247,12 @@ export default ({ getService }: FtrProviderContext) => {
const invalidTransformId = 'invalid_transform_id';
const { body, status } = await supertest
.post(`/api/transform/reauthorize_transforms`)
.post(`/internal/transform/reauthorize_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send([...reqBody, { id: invalidTransformId }]);
transform.api.assertResponseStatusCode(200, status, body);

View file

@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext) => {
},
};
describe('/api/transform/reset_transforms', function () {
describe('/internal/transform/reset_transforms', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -73,12 +73,12 @@ export default ({ getService }: FtrProviderContext) => {
transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }],
};
const { body, status } = await supertest
.post(`/api/transform/reset_transforms`)
.post(`/internal/transform/reset_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -103,12 +103,12 @@ export default ({ getService }: FtrProviderContext) => {
transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }],
};
const { body, status } = await supertest
.post(`/api/transform/reset_transforms`)
.post(`/internal/transform/reset_transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(403, status, body);
@ -127,12 +127,12 @@ export default ({ getService }: FtrProviderContext) => {
transformsInfo: [{ id: 'invalid_transform_id', state: TRANSFORM_STATE.STOPPED }],
};
const { body, status } = await supertest
.post(`/api/transform/reset_transforms`)
.post(`/internal/transform/reset_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -175,12 +175,12 @@ export default ({ getService }: FtrProviderContext) => {
);
const { body, status } = await supertest
.post(`/api/transform/reset_transforms`)
.post(`/internal/transform/reset_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -214,12 +214,12 @@ export default ({ getService }: FtrProviderContext) => {
const invalidTransformId = 'invalid_transform_id';
const { body, status } = await supertest
.post(`/api/transform/reset_transforms`)
.post(`/internal/transform/reset_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send({
...reqBody,
transformsInfo: [

View file

@ -26,7 +26,7 @@ export default ({ getService }: FtrProviderContext) => {
await transform.api.createTransform(transformId, config);
}
describe('/api/transform/schedule_now_transforms', function () {
describe('/internal/transform/schedule_now_transforms', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -50,12 +50,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should schedule the transform by transformId', async () => {
const reqBody: ScheduleNowTransformsRequestSchema = [{ id: transformId }];
const { body, status } = await supertest
.post(`/api/transform/schedule_now_transforms`)
.post(`/internal/transform/schedule_now_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -66,12 +66,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 200 with success:false for unauthorized user', async () => {
const reqBody: ScheduleNowTransformsRequestSchema = [{ id: transformId }];
const { body, status } = await supertest
.post(`/api/transform/schedule_now_transforms`)
.post(`/internal/transform/schedule_now_transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -84,12 +84,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 200 with error in response if invalid transformId', async () => {
const reqBody: ScheduleNowTransformsRequestSchema = [{ id: 'invalid_transform_id' }];
const { body, status } = await supertest
.post(`/api/transform/schedule_now_transforms`)
.post(`/internal/transform/schedule_now_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -124,12 +124,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should schedule multiple transforms by transformIds', async () => {
const { body, status } = await supertest
.post(`/api/transform/schedule_now_transforms`)
.post(`/internal/transform/schedule_now_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -141,12 +141,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should schedule multiple transforms by transformIds, even if one of the transformIds is invalid', async () => {
const invalidTransformId = 'invalid_transform_id';
const { body, status } = await supertest
.post(`/api/transform/schedule_now_transforms`)
.post(`/internal/transform/schedule_now_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send([{ id: reqBody[0].id }, { id: invalidTransformId }, { id: reqBody[1].id }]);
transform.api.assertResponseStatusCode(200, status, body);

View file

@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext) => {
await transform.api.createTransform(transformId, config);
}
describe('/api/transform/start_transforms', function () {
describe('/internal/transform/start_transforms', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -49,12 +49,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should start the transform by transformId', async () => {
const reqBody: StartTransformsRequestSchema = [{ id: transformId }];
const { body, status } = await supertest
.post(`/api/transform/start_transforms`)
.post(`/internal/transform/start_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -67,12 +67,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 200 with success:false for unauthorized user', async () => {
const reqBody: StartTransformsRequestSchema = [{ id: transformId }];
const { body, status } = await supertest
.post(`/api/transform/start_transforms`)
.post(`/internal/transform/start_transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -88,12 +88,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 200 with error in response if invalid transformId', async () => {
const reqBody: StartTransformsRequestSchema = [{ id: 'invalid_transform_id' }];
const { body, status } = await supertest
.post(`/api/transform/start_transforms`)
.post(`/internal/transform/start_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -124,12 +124,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should start multiple transforms by transformIds', async () => {
const { body, status } = await supertest
.post(`/api/transform/start_transforms`)
.post(`/internal/transform/start_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -143,12 +143,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should start multiple transforms by transformIds, even if one of the transformIds is invalid', async () => {
const invalidTransformId = 'invalid_transform_id';
const { body, status } = await supertest
.post(`/api/transform/start_transforms`)
.post(`/internal/transform/start_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send([{ id: reqBody[0].id }, { id: invalidTransformId }, { id: reqBody[1].id }]);
transform.api.assertResponseStatusCode(200, status, body);

View file

@ -43,7 +43,7 @@ export default ({ getService }: FtrProviderContext) => {
await transform.api.createAndRunTransform(transformId, config);
}
describe('/api/transform/stop_transforms', function () {
describe('/internal/transform/stop_transforms', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -67,12 +67,12 @@ export default ({ getService }: FtrProviderContext) => {
{ id: transformId, state: TRANSFORM_STATE.STARTED },
];
const { body, status } = await supertest
.post(`/api/transform/stop_transforms`)
.post(`/internal/transform/stop_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -88,12 +88,12 @@ export default ({ getService }: FtrProviderContext) => {
{ id: transformId, state: TRANSFORM_STATE.STARTED },
];
const { body, status } = await supertest
.post(`/api/transform/stop_transforms`)
.post(`/internal/transform/stop_transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -112,12 +112,12 @@ export default ({ getService }: FtrProviderContext) => {
{ id: 'invalid_transform_id', state: TRANSFORM_STATE.STARTED },
];
const { body, status } = await supertest
.post(`/api/transform/stop_transforms`)
.post(`/internal/transform/stop_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -149,12 +149,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should stop multiple transforms by transformIds', async () => {
const { body, status } = await supertest
.post(`/api/transform/stop_transforms`)
.post(`/internal/transform/stop_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(reqBody);
transform.api.assertResponseStatusCode(200, status, body);
@ -170,12 +170,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should stop multiple transforms by transformIds, even if one of the transformIds is invalid', async () => {
const invalidTransformId = 'invalid_transform_id';
const { body, status } = await supertest
.post(`/api/transform/stop_transforms`)
.post(`/internal/transform/stop_transforms`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send([
{ id: reqBody[0].id, state: reqBody[0].state },
{ id: invalidTransformId, state: TRANSFORM_STATE.STOPPED },

View file

@ -80,7 +80,7 @@ export default ({ getService }: FtrProviderContext) => {
);
}
describe('/api/transform/transforms', function () {
describe('/internal/transform/transforms', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -95,12 +95,12 @@ export default ({ getService }: FtrProviderContext) => {
describe('/transforms', function () {
it('should return a list of transforms for super-user', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms')
.get('/internal/transform/transforms')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -109,12 +109,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return a list of transforms for transform view-only user', async () => {
const { body, status } = await supertest
.get(`/api/transform/transforms`)
.get(`/internal/transform/transforms`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -125,12 +125,12 @@ export default ({ getService }: FtrProviderContext) => {
describe('/transforms/{transformId}', function () {
it('should return a specific transform configuration for super-user', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms/transform-test-get-1')
.get('/internal/transform/transforms/transform-test-get-1')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -139,12 +139,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return a specific transform configuration transform view-only user', async () => {
const { body, status } = await supertest
.get(`/api/transform/transforms/transform-test-get-1`)
.get(`/internal/transform/transforms/transform-test-get-1`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -153,12 +153,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should report 404 for a non-existing transform', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms/the-non-existing-transform')
.get('/internal/transform/transforms/the-non-existing-transform')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(404, status, body);
});

View file

@ -17,7 +17,7 @@ export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertestWithoutAuth');
const transform = getService('transform');
describe('/api/transform/transforms/{transformId}/ create', function () {
describe('/internal/transform/transforms/{transformId}/ create', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -31,12 +31,12 @@ export default ({ getService }: FtrProviderContext) => {
const transformId = 'test_transform_id';
const { body, status } = await supertest
.put(`/api/transform/transforms/${transformId}`)
.put(`/internal/transform/transforms/${transformId}`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send({
...generateTransformConfig(transformId),
latest: {
@ -55,12 +55,12 @@ export default ({ getService }: FtrProviderContext) => {
const { pivot, ...config } = generateTransformConfig(transformId);
const { body, status } = await supertest
.put(`/api/transform/transforms/${transformId}`)
.put(`/internal/transform/transforms/${transformId}`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(config);
transform.api.assertResponseStatusCode(400, status, body);

View file

@ -30,15 +30,15 @@ export default ({ getService }: FtrProviderContext) => {
expect(body.count).to.not.be.lessThan(expected.apiTransformTransformsNodes.minCount);
}
describe('/api/transform/transforms/_nodes', function () {
describe('/internal/transform/transforms/_nodes', function () {
it('should return the number of available transform nodes for a power user', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms/_nodes')
.get('/internal/transform/transforms/_nodes')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -47,12 +47,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return the number of available transform nodes for a viewer user', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms/_nodes')
.get('/internal/transform/transforms/_nodes')
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -61,12 +61,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should not return the number of available transform nodes for an unauthorized user', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms/_nodes')
.get('/internal/transform/transforms/_nodes')
.auth(
USER.TRANSFORM_UNAUTHORIZED,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_UNAUTHORIZED)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(403, status, body);
});

View file

@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext) => {
return config as PostTransformsPreviewRequestSchema;
}
describe('/api/transform/transforms/_preview', function () {
describe('/internal/transform/transforms/_preview', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -44,12 +44,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return a transform preview', async () => {
const { body, status } = await supertest
.post('/api/transform/transforms/_preview')
.post('/internal/transform/transforms/_preview')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(getTransformPreviewConfig());
transform.api.assertResponseStatusCode(200, status, body);
@ -61,12 +61,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return a correct error for transform preview', async () => {
const { body, status } = await supertest
.post(`/api/transform/transforms/_preview`)
.post(`/internal/transform/transforms/_preview`)
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send({
...getTransformPreviewConfig(),
pivot: {
@ -85,12 +85,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 403 for transform view-only user', async () => {
const { body, status } = await supertest
.post(`/api/transform/transforms/_preview`)
.post(`/internal/transform/transforms/_preview`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(getTransformPreviewConfig());
transform.api.assertResponseStatusCode(403, status, body);
});

View file

@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => {
);
}
describe('/api/transform/transforms/_stats', function () {
describe('/internal/transform/transforms/_stats', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -74,12 +74,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return a list of transforms statistics for super-user', async () => {
const { body, status } = await supertest
.get('/api/transform/transforms/_stats')
.get('/internal/transform/transforms/_stats')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);
@ -88,12 +88,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return a list of transforms statistics view-only user', async () => {
const { body, status } = await supertest
.get(`/api/transform/transforms/_stats`)
.get(`/internal/transform/transforms/_stats`)
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, status, body);

View file

@ -57,7 +57,7 @@ export default ({ getService }: FtrProviderContext) => {
};
}
describe('/api/transform/transforms/{transformId}/_update', function () {
describe('/internal/transform/transforms/{transformId}/_update', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await transform.testResources.setKibanaTimeZoneToUTC();
@ -71,12 +71,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should update a transform', async () => {
// assert the original transform for comparison
const { body: transformOriginalBody, status: transformOriginalStatus } = await supertest
.get('/api/transform/transforms/transform-test-update-1')
.get('/internal/transform/transforms/transform-test-update-1')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, transformOriginalStatus, transformOriginalBody);
@ -94,12 +94,12 @@ export default ({ getService }: FtrProviderContext) => {
// update the transform and assert the response
const { body: transformUpdateResponseBody, status: transformUpdatedResponseStatus } =
await supertest
.post('/api/transform/transforms/transform-test-update-1/_update')
.post('/internal/transform/transforms/transform-test-update-1/_update')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(getTransformUpdateConfig());
transform.api.assertResponseStatusCode(
200,
@ -118,12 +118,12 @@ export default ({ getService }: FtrProviderContext) => {
// assert the updated transform for comparison
const { body: transformUpdatedBody, status: transformUpdatedStatus } = await supertest
.get('/api/transform/transforms/transform-test-update-1')
.get('/internal/transform/transforms/transform-test-update-1')
.auth(
USER.TRANSFORM_POWERUSER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send();
transform.api.assertResponseStatusCode(200, transformUpdatedStatus, transformUpdatedBody);
@ -144,12 +144,12 @@ export default ({ getService }: FtrProviderContext) => {
it('should return 403 for transform view-only user', async () => {
const { body, status } = await supertest
.post('/api/transform/transforms/transform-test-update-1/_update')
.post('/internal/transform/transforms/transform-test-update-1/_update')
.auth(
USER.TRANSFORM_VIEWER,
transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER)
)
.set(getCommonRequestHeader())
.set(getCommonRequestHeader('1'))
.send(getTransformUpdateConfig());
transform.api.assertResponseStatusCode(403, status, body);
});

View file

@ -46,9 +46,9 @@ interface TestDataLatest {
type TestData = TestDataPivot | TestDataLatest;
function generateHeaders(apiKey: SecurityCreateApiKeyResponse) {
function generateHeaders(apiKey: SecurityCreateApiKeyResponse, version?: string) {
return {
...getCommonRequestHeader(),
...getCommonRequestHeader(version),
'es-secondary-authorization': `ApiKey ${apiKey.encoded}`,
};
}
@ -144,7 +144,7 @@ export default function ({ getService }: FtrProviderContext) {
await transform.api.createTransform(testData.originalConfig.id, testData.originalConfig, {
deferValidation: true,
// Create transforms with secondary authorization headers
headers: generateHeaders(apiKeysForTransformUsers.get(testData.created_by_user)!),
headers: generateHeaders(apiKeysForTransformUsers.get(testData.created_by_user)!, '1'),
});
// For transforms created with insufficient permissions, they can be created but not started
// so we should not assert that the api call is successful here

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import { ProvidedType } from '@kbn/test';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import type { ProvidedType } from '@kbn/test';
import { FtrProviderContext } from '../../ftr_provider_context';
import type { FtrProviderContext } from '../../ftr_provider_context';
const COMMON_REQUEST_HEADERS = {
'kbn-xsrf': 'some-xsrf-token',
@ -26,5 +27,5 @@ export function getCommonRequestHeader(apiVersion?: string) {
return COMMON_REQUEST_HEADERS;
}
return Object.assign(COMMON_REQUEST_HEADERS, { 'elastic-api-version': apiVersion });
return Object.assign(COMMON_REQUEST_HEADERS, { [ELASTIC_HTTP_VERSION_HEADER]: apiVersion });
}