[data / search session] BWCA all the routes (#158981)

### Summary

All routes
- Use versioned router
- Validate responses
- All responses are typed with response types, separate from internal
api types. This is to help prevent unacknowledged changes to the api.

Closes https://github.com/elastic/kibana/issues/158944

---------

Co-authored-by: Lukas Olson <lukas@elastic.co>
This commit is contained in:
Matthew Kime 2023-06-16 00:28:29 -05:00 committed by GitHub
parent 4e1a4b2d07
commit 5c5add2d7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 410 additions and 150 deletions

View file

@ -10,7 +10,6 @@ import { PublicContract } from '@kbn/utility-types';
import { HttpSetup } from '@kbn/core/public';
import type {
SavedObject,
SavedObjectsFindResponse,
SavedObjectsUpdateResponse,
SavedObjectsFindOptions,
} from '@kbn/core/server';
@ -24,6 +23,9 @@ export interface SessionsClientDeps {
http: HttpSetup;
}
const version = '1';
const options = { version };
/**
* CRUD Search Session SO
*/
@ -35,7 +37,7 @@ export class SessionsClient {
}
public get(sessionId: string): Promise<SearchSessionSavedObject> {
return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`);
return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`, options);
}
public create({
@ -54,6 +56,7 @@ export class SessionsClient {
sessionId: string;
}): Promise<SearchSessionSavedObject> {
return this.http.post(`/internal/session`, {
version,
body: JSON.stringify({
name,
appId,
@ -65,9 +68,10 @@ export class SessionsClient {
});
}
public find(options: Omit<SavedObjectsFindOptions, 'type'>): Promise<SearchSessionsFindResponse> {
public find(opts: Omit<SavedObjectsFindOptions, 'type'>): Promise<SearchSessionsFindResponse> {
return this.http!.post(`/internal/session/_find`, {
body: JSON.stringify(options),
version,
body: JSON.stringify(opts),
});
}
@ -76,27 +80,23 @@ export class SessionsClient {
attributes: unknown
): Promise<SavedObjectsUpdateResponse<SearchSessionSavedObjectAttributes>> {
return this.http!.put(`/internal/session/${encodeURIComponent(sessionId)}`, {
version,
body: JSON.stringify(attributes),
});
}
public rename(
sessionId: string,
newName: string
): Promise<SavedObjectsUpdateResponse<Pick<SearchSessionSavedObjectAttributes, 'name'>>> {
return this.update(sessionId, { name: newName });
public async rename(sessionId: string, newName: string): Promise<void> {
await this.update(sessionId, { name: newName });
}
public extend(
sessionId: string,
expires: string
): Promise<SavedObjectsFindResponse<SearchSessionSavedObjectAttributes>> {
return this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/_extend`, {
public async extend(sessionId: string, expires: string): Promise<void> {
await this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/_extend`, {
version,
body: JSON.stringify({ expires }),
});
}
public delete(sessionId: string): Promise<void> {
return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`);
return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`, options);
}
}

View file

@ -71,7 +71,12 @@ export type {
DataRequestHandlerContext,
AsyncSearchStatusResponse,
} from './search';
export { shimHitsTotal, SearchSessionService, NoSearchIdInSessionError } from './search';
export {
shimHitsTotal,
SearchSessionService,
NoSearchIdInSessionError,
INITIAL_SEARCH_SESSION_REST_VERSION,
} from './search';
// Search namespace
export const search = {

View file

@ -15,3 +15,4 @@ export { usageProvider, searchUsageObserver } from './collectors/search';
export * from './aggs';
export * from './session';
export * from './errors/no_search_id_in_session';
export { INITIAL_SEARCH_SESSION_REST_VERSION } from './routes/session';

View file

@ -0,0 +1,72 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { schema } from '@kbn/config-schema';
const searchSessionRequestInfoSchema = schema.object({
id: schema.string(),
strategy: schema.string(),
});
const serializeableSchema = schema.mapOf(schema.string(), schema.any());
const searchSessionAttrSchema = schema.object({
sessionId: schema.string(),
name: schema.maybe(schema.string()),
appId: schema.maybe(schema.string()),
created: schema.string(),
expires: schema.string(),
locatorId: schema.maybe(schema.string()),
initialState: schema.maybe(serializeableSchema),
restoreState: schema.maybe(serializeableSchema),
idMapping: schema.mapOf(schema.string(), searchSessionRequestInfoSchema),
realmType: schema.maybe(schema.string()),
realmName: schema.maybe(schema.string()),
username: schema.maybe(schema.string()),
version: schema.string(),
isCanceled: schema.maybe(schema.boolean()),
});
export const searchSessionSchema = schema.object({
id: schema.string(),
attributes: searchSessionAttrSchema,
});
export const searchSessionStatusSchema = schema.object({
status: schema.oneOf([
schema.literal('in_progress'),
schema.literal('error'),
schema.literal('complete'),
schema.literal('cancelled'),
schema.literal('expired'),
]),
errors: schema.maybe(schema.arrayOf(schema.string())),
});
export const searchSessionsFindSchema = schema.object({
total: schema.number(),
saved_objects: schema.arrayOf(searchSessionSchema),
statuses: schema.recordOf(schema.string(), searchSessionStatusSchema),
});
const referencesSchema = schema.arrayOf(
schema.object({ id: schema.string(), type: schema.string(), name: schema.string() })
);
export const searchSessionsUpdateSchema = schema.object({
id: schema.string(),
type: schema.string(),
updated_at: schema.maybe(schema.string()),
version: schema.maybe(schema.string()),
namespaces: schema.maybe(schema.arrayOf(schema.string())),
references: schema.maybe(referencesSchema),
attributes: schema.object({
name: schema.maybe(schema.string()),
expires: schema.maybe(schema.string()),
}),
});

View file

@ -0,0 +1,65 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SerializableRecord } from '@kbn/utility-types';
interface SearchSessionAttrRestResponse {
sessionId: string;
name?: string;
appId?: string;
created: string;
expires: string;
locatorId?: string;
initialState?: SerializableRecord;
restoreState?: SerializableRecord;
idMapping: Record<string, SearchSessionRequestInfoRestResponse>;
realmType?: string;
realmName?: string;
username?: string;
version: string;
isCanceled?: boolean;
}
interface SearchSessionRequestInfoRestResponse {
id: string;
strategy: string;
}
export interface SearchSessionRestResponse {
id: string;
attributes: SearchSessionAttrRestResponse;
}
export interface SearchSessionStatusRestResponse {
status: StatusRestRespone;
errors?: string[];
}
type StatusRestRespone = 'in_progress' | 'error' | 'complete' | 'cancelled' | 'expired';
export interface SearchSessionsFindRestResponse {
saved_objects: SearchSessionRestResponse[];
total: number;
/**
* Map containing calculated statuses of search sessions from the find response
*/
statuses: Record<string, SearchSessionStatusRestResponse>;
}
export interface SearchSessionsUpdateRestResponse {
id: string;
type: string;
updated_at?: string;
version?: string;
namespaces?: string[];
references?: Array<{ id: string; type: string; name: string }>;
attributes: {
name?: string;
expires?: string;
};
}

View file

@ -15,6 +15,11 @@ import { dataPluginMock } from '../../mocks';
import { registerSessionRoutes } from './session';
enum GetHandlerIndex {
ID,
STATUS,
}
enum PostHandlerIndex {
SAVE,
FIND,
@ -43,7 +48,8 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, saveHandler] = mockRouter.post.mock.calls[PostHandlerIndex.SAVE];
const [[, saveHandler]] =
mockRouter.versioned.post.mock.results[PostHandlerIndex.SAVE].value.addVersion.mock.calls;
await saveHandler(mockContext, mockRequest, mockResponse);
@ -58,7 +64,7 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [[, getHandler]] = mockRouter.get.mock.calls;
const [[, getHandler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls;
await getHandler(mockContext, mockRequest, mockResponse);
@ -73,10 +79,12 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [[], [, statusHandler]] = mockRouter.get.mock.calls;
const [[, statusHandler]] =
mockRouter.versioned.get.mock.results[GetHandlerIndex.STATUS].value.addVersion.mock.calls;
await statusHandler(mockContext, mockRequest, mockResponse);
expect(mockContext.search!.getSessionStatus).toHaveBeenCalled();
expect(mockContext.search!.getSessionStatus).toHaveBeenCalledWith(id);
});
@ -92,7 +100,8 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, findHandler] = mockRouter.post.mock.calls[PostHandlerIndex.FIND];
const [[, findHandler]] =
mockRouter.versioned.post.mock.results[PostHandlerIndex.FIND].value.addVersion.mock.calls;
await findHandler(mockContext, mockRequest, mockResponse);
@ -110,7 +119,8 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, updateHandler] = mockRouter.put.mock.calls[0];
const [[, updateHandler]] =
mockRouter.versioned.put.mock.results[0].value.addVersion.mock.calls;
await updateHandler(mockContext, mockRequest, mockResponse);
@ -125,7 +135,8 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, cancelHandler] = mockRouter.post.mock.calls[PostHandlerIndex.CANCEL];
const [[, cancelHandler]] =
mockRouter.versioned.post.mock.results[PostHandlerIndex.CANCEL].value.addVersion.mock.calls;
await cancelHandler(mockContext, mockRequest, mockResponse);
@ -140,7 +151,8 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, deleteHandler] = mockRouter.delete.mock.calls[0];
const [[, deleteHandler]] =
mockRouter.versioned.delete.mock.results[0].value.addVersion.mock.calls;
await deleteHandler(mockContext, mockRequest, mockResponse);
@ -157,7 +169,8 @@ describe('registerSessionRoutes', () => {
const mockResponse = httpServerMock.createResponseFactory();
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, extendHandler] = mockRouter.post.mock.calls[PostHandlerIndex.EXTEND];
const [[, extendHandler]] =
mockRouter.versioned.post.mock.results[PostHandlerIndex.EXTEND].value.addVersion.mock.calls;
await extendHandler(mockContext, mockRequest, mockResponse);

View file

@ -10,26 +10,52 @@ import { schema } from '@kbn/config-schema';
import { Logger } from '@kbn/core/server';
import { reportServerError } from '@kbn/kibana-utils-plugin/server';
import { DataPluginRouter } from '../types';
import {
SearchSessionRestResponse,
SearchSessionStatusRestResponse,
SearchSessionsFindRestResponse,
SearchSessionsUpdateRestResponse,
} from './response_types';
import {
searchSessionSchema,
searchSessionStatusSchema,
searchSessionsFindSchema,
searchSessionsUpdateSchema,
} from './response_schema';
const STORE_SEARCH_SESSIONS_ROLE_TAG = `access:store_search_session`;
const access = 'internal';
const options = {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
};
const pathPrefix = '/internal/session';
export const INITIAL_SEARCH_SESSION_REST_VERSION = '1';
const version = INITIAL_SEARCH_SESSION_REST_VERSION;
const idAndAttrsOnly = (so?: SearchSessionRestResponse) =>
so && { id: so.id, attributes: so.attributes };
export function registerSessionRoutes(router: DataPluginRouter, logger: Logger): void {
router.post(
router.versioned.post({ path: pathPrefix, access, options }).addVersion(
{
path: '/internal/session',
version,
validate: {
body: schema.object({
sessionId: schema.string(),
name: schema.string(),
appId: schema.string(),
expires: schema.maybe(schema.string()),
locatorId: schema.string(),
initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })),
restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
body: schema.object({
sessionId: schema.string(),
name: schema.string(),
appId: schema.string(),
expires: schema.maybe(schema.string()),
locatorId: schema.string(),
initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })),
restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })),
}),
},
response: {
200: {
body: schema.maybe(searchSessionSchema),
},
},
},
},
async (context, request, res) => {
@ -38,6 +64,7 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
try {
const searchContext = await context.search;
const response = await searchContext.saveSession(sessionId, {
name,
appId,
@ -47,9 +74,9 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
restoreState,
});
return res.ok({
body: response,
});
const body: SearchSessionRestResponse | undefined = idAndAttrsOnly(response);
return res.ok({ body });
} catch (err) {
logger.error(err);
return reportServerError(res, err);
@ -57,23 +84,59 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
}
);
router.get(
router.versioned.get({ path: `${pathPrefix}/{id}`, access, options }).addVersion(
{
path: '/internal/session/{id}',
version,
validate: {
params: schema.object({
id: schema.string(),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
params: schema.object({
id: schema.string(),
}),
},
response: {
200: {
body: searchSessionSchema,
},
},
},
},
async (context, request, res) => {
const { id } = request.params;
try {
const searchContext = await context.search;
const response = await searchContext!.getSession(id);
const response: SearchSessionRestResponse = await searchContext!.getSession(id);
const body = idAndAttrsOnly(response);
return res.ok({ body });
} catch (e) {
const err = e.output?.payload || e;
logger.error(err);
return reportServerError(res, err);
}
}
);
router.versioned.get({ path: `${pathPrefix}/{id}/status`, access, options }).addVersion(
{
version,
validate: {
request: {
params: schema.object({
id: schema.string(),
}),
},
response: {
200: {
body: searchSessionStatusSchema,
},
},
},
},
async (context, request, res) => {
const { id } = request.params;
try {
const searchContext = await context.search;
const response: SearchSessionStatusRestResponse = await searchContext!.getSessionStatus(id);
return res.ok({
body: response,
@ -86,58 +149,33 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
}
);
router.get(
router.versioned.post({ path: `${pathPrefix}/_find`, access, options }).addVersion(
{
path: '/internal/session/{id}/status',
version,
validate: {
params: schema.object({
id: schema.string(),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
},
},
async (context, request, res) => {
const { id } = request.params;
try {
const searchContext = await context.search;
const response = await searchContext!.getSessionStatus(id);
return res.ok({
body: response,
});
} catch (e) {
const err = e.output?.payload || e;
logger.error(err);
return reportServerError(res, err);
}
}
);
router.post(
{
path: '/internal/session/_find',
validate: {
body: schema.object({
page: schema.maybe(schema.number()),
perPage: schema.maybe(schema.number()),
sortField: schema.maybe(schema.string()),
sortOrder: schema.maybe(schema.oneOf([schema.literal('desc'), schema.literal('asc')])),
filter: schema.maybe(schema.string()),
searchFields: schema.maybe(schema.arrayOf(schema.string())),
search: schema.maybe(schema.string()),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
body: schema.object({
page: schema.maybe(schema.number()),
perPage: schema.maybe(schema.number()),
sortField: schema.maybe(schema.string()),
sortOrder: schema.maybe(schema.oneOf([schema.literal('desc'), schema.literal('asc')])),
filter: schema.maybe(schema.string()),
searchFields: schema.maybe(schema.arrayOf(schema.string())),
search: schema.maybe(schema.string()),
}),
},
response: {
200: {
body: searchSessionsFindSchema,
},
},
},
},
async (context, request, res) => {
const { page, perPage, sortField, sortOrder, filter, searchFields, search } = request.body;
try {
const searchContext = await context.search;
const response = await searchContext!.findSessions({
const response: SearchSessionsFindRestResponse = await searchContext!.findSessions({
page,
perPage,
sortField,
@ -147,9 +185,13 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
search,
});
return res.ok({
body: response,
});
const body = {
total: response.total,
saved_objects: response.saved_objects.map(idAndAttrsOnly),
statuses: response.statuses,
};
return res.ok({ body });
} catch (err) {
logger.error(err);
return reportServerError(res, err);
@ -157,16 +199,15 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
}
);
router.delete(
router.versioned.delete({ path: `${pathPrefix}/{id}`, access, options }).addVersion(
{
path: '/internal/session/{id}',
version,
validate: {
params: schema.object({
id: schema.string(),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
params: schema.object({
id: schema.string(),
}),
},
},
},
async (context, request, res) => {
@ -184,16 +225,15 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
}
);
router.post(
router.versioned.post({ path: `${pathPrefix}/{id}/cancel`, access, options }).addVersion(
{
path: '/internal/session/{id}/cancel',
version,
validate: {
params: schema.object({
id: schema.string(),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
params: schema.object({
id: schema.string(),
}),
},
},
},
async (context, request, res) => {
@ -211,20 +251,24 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
}
);
router.put(
router.versioned.put({ path: `${pathPrefix}/{id}`, access, options }).addVersion(
{
path: '/internal/session/{id}',
version,
validate: {
params: schema.object({
id: schema.string(),
}),
body: schema.object({
name: schema.maybe(schema.string()),
expires: schema.maybe(schema.string()),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
params: schema.object({
id: schema.string(),
}),
body: schema.object({
name: schema.maybe(schema.string()),
expires: schema.maybe(schema.string()),
}),
},
response: {
200: {
body: searchSessionsUpdateSchema,
},
},
},
},
async (context, request, res) => {
@ -232,8 +276,10 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
const { name, expires } = request.body;
try {
const searchContext = await context.search;
const response = await searchContext.updateSession(id, { name, expires });
const response: SearchSessionsUpdateRestResponse = await searchContext.updateSession(id, {
name,
expires,
});
return res.ok({
body: response,
});
@ -244,19 +290,23 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
}
);
router.post(
router.versioned.post({ path: `${pathPrefix}/{id}/_extend`, access, options }).addVersion(
{
path: '/internal/session/{id}/_extend',
version,
validate: {
params: schema.object({
id: schema.string(),
}),
body: schema.object({
expires: schema.string(),
}),
},
options: {
tags: [STORE_SEARCH_SESSIONS_ROLE_TAG],
request: {
params: schema.object({
id: schema.string(),
}),
body: schema.object({
expires: schema.string(),
}),
},
response: {
200: {
body: searchSessionsUpdateSchema,
},
},
},
},
async (context, request, res) => {
@ -264,7 +314,10 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger):
const { expires } = request.body;
try {
const searchContext = await context.search;
const response = await searchContext.extendSession(id, new Date(expires));
const response: SearchSessionsUpdateRestResponse = await searchContext.extendSession(
id,
new Date(expires)
);
return res.ok({
body: response,

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { INITIAL_SEARCH_SESSION_REST_VERSION } from '@kbn/data-plugin/server';
import expect from '@kbn/expect';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
@ -23,6 +24,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -37,6 +39,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -47,17 +50,26 @@ export default function ({ getService }: FtrProviderContext) {
})
.expect(200);
await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200);
await supertest
.get(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
});
it('should fail to delete an unknown session', async () => {
await supertest.delete(`/internal/session/123`).set('kbn-xsrf', 'foo').expect(404);
await supertest
.delete(`/internal/session/123`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(404);
});
it('should create and delete a session', async () => {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -68,15 +80,24 @@ export default function ({ getService }: FtrProviderContext) {
})
.expect(200);
await supertest.delete(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200);
await supertest
.delete(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(404);
await supertest
.get(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(404);
});
it('should create and cancel a session', async () => {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -89,11 +110,13 @@ export default function ({ getService }: FtrProviderContext) {
await supertest
.post(`/internal/session/${sessionId}/cancel`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
const resp = await supertest
.get(`/internal/session/${sessionId}/status`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
@ -109,6 +132,7 @@ export default function ({ getService }: FtrProviderContext) {
body: { attributes: originalSession },
} = await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -123,6 +147,7 @@ export default function ({ getService }: FtrProviderContext) {
body: { attributes: updatedSession },
} = await supertest
.put(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
name: newName,
@ -139,7 +164,7 @@ export default function ({ getService }: FtrProviderContext) {
// run search, this will not be persisted because session is not saved yet
const searchRes1 = await supertest
.post(`/internal/search/ese`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -161,6 +186,7 @@ export default function ({ getService }: FtrProviderContext) {
// save session
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -174,7 +200,7 @@ export default function ({ getService }: FtrProviderContext) {
// run search
const searchRes2 = await supertest
.post(`/internal/search/ese`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -195,6 +221,7 @@ export default function ({ getService }: FtrProviderContext) {
await retry.waitFor('a search persisted into session', async () => {
const resp = await supertest
.get(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
@ -213,6 +240,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -225,6 +253,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertest
.post(`/internal/session/${sessionId}/_extend`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
expires: '2021-02-26T21:02:43.742Z',
@ -236,6 +265,7 @@ export default function ({ getService }: FtrProviderContext) {
it('should fail to extend a nonexistent session', async () => {
await supertest
.post(`/internal/session/123/_extend`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
expires: '2021-02-26T21:02:43.742Z',
@ -259,7 +289,7 @@ export default function ({ getService }: FtrProviderContext) {
// run search
const searchRes = await supertest
.post(`/internal/search/ese`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -272,6 +302,7 @@ export default function ({ getService }: FtrProviderContext) {
// persist session
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -285,7 +316,7 @@ export default function ({ getService }: FtrProviderContext) {
// run search to persist into a session
await supertest
.post(`/internal/search/ese/${id}`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -297,6 +328,7 @@ export default function ({ getService }: FtrProviderContext) {
await retry.waitFor('searches persisted into session', async () => {
const resp = await supertest
.get(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
@ -313,6 +345,7 @@ export default function ({ getService }: FtrProviderContext) {
async () => {
const resp = await supertest
.get(`/internal/session/${sessionId}/status`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
@ -342,6 +375,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -354,6 +388,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertestWithoutAuth
.get(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.auth('other_user', 'password')
.expect(404);
@ -363,6 +398,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -375,6 +411,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertestWithoutAuth
.delete(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.auth('other_user', 'password')
.expect(404);
@ -384,6 +421,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -396,6 +434,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertestWithoutAuth
.post(`/internal/session/${sessionId}/cancel`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.auth('other_user', 'password')
.expect(404);
@ -405,6 +444,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertest
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -417,6 +457,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertestWithoutAuth
.post(`/internal/session/${sessionId}/_extend`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.auth('other_user', 'password')
.send({
@ -429,6 +470,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertestWithoutAuth
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -469,6 +511,7 @@ export default function ({ getService }: FtrProviderContext) {
const sessionId = `my-session-${Math.random()}`;
await supertestWithoutAuth
.post(`/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.auth('analyst', 'analyst-password')
.set('kbn-xsrf', 'foo')
.send({
@ -482,6 +525,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertestWithoutAuth
.get(`/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.auth('analyst', 'analyst-password')
.set('kbn-xsrf', 'foo')
.expect(403);
@ -522,7 +566,7 @@ export default function ({ getService }: FtrProviderContext) {
// run search
const searchRes = await supertest
.post(`/s/${spaceId}/internal/search/ese`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -535,6 +579,7 @@ export default function ({ getService }: FtrProviderContext) {
// persist session
await supertest
.post(`/s/${spaceId}/internal/session`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -548,7 +593,7 @@ export default function ({ getService }: FtrProviderContext) {
// run search to persist into a session
await supertest
.post(`/s/${spaceId}/internal/search/ese/${id}`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.send({
sessionId,
@ -560,6 +605,7 @@ export default function ({ getService }: FtrProviderContext) {
await retry.waitFor('searches persisted into session', async () => {
const resp = await supertest
.get(`/s/${spaceId}/internal/session/${sessionId}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);
@ -576,6 +622,7 @@ export default function ({ getService }: FtrProviderContext) {
async () => {
const resp = await supertest
.get(`/s/${spaceId}/internal/session/${sessionId}/status`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'foo')
.expect(200);

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { INITIAL_SEARCH_SESSION_REST_VERSION } from '@kbn/data-plugin/server';
import expect from '@kbn/expect';
import { SavedObjectsFindResponse } from '@kbn/core/server';
import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
@ -145,6 +147,7 @@ export class SearchSessionsService extends FtrService {
await this.retry.tryForTime(10000, async () => {
const { body } = await this.security.testUserSupertest
.post('/internal/session/_find')
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set('kbn-xsrf', 'anything')
.set('kbn-system-request', 'true')
.send({
@ -169,6 +172,7 @@ export class SearchSessionsService extends FtrService {
this.log.debug(`Deleting search session: ${so.id}`);
await this.security.testUserSupertest
.delete(`/internal/session/${so.id}`)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_SEARCH_SESSION_REST_VERSION)
.set(`kbn-xsrf`, `anything`)
.expect(200);
})