[HTTP] Allow for internal requests to also specify special query param elasticInternalOrigin (#163796)

## Summary

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

* Raise the notion of "internal" into `CoreKibanaRequest`. This enables
us to share this with lifecycle handlers and control validation of query
params
* Added new `isInternalRequest` alongside `isSystemRequest` and
`isFakeRequest`
* Slight simplification to existing internal restriction check
* Some other chores and minor fixes

## Test

* Start ES with `yarn es serverless` and Kibana with `yarn start
--serverless --server.restrictInternalApis=true`
* Add the service account token to `kibana.dev.yml`:
`elasticsearch.serviceAccountToken: <SAT>`
* Send a request to an internal endpoint like: `curl -XPOST
-uelastic:changeme http://localhost:5601/<base-path>/api/files/find -H
'kbn-xsrf: foo' -H 'content-type: application/json' -d '{}'`
    * Should give you a 400 result
* message like `{"statusCode":400,"error":"Bad Request","message":"uri
[http://localhost:5603/api/files/find] with method [post] exists but is
not available with the current configuration"}`
* Send the same request, but include the query param:
`elasticInternalOrigin=true`
   *  Should give you a 200 result

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jean-Louis Leysens 2023-08-21 11:55:33 +02:00 committed by GitHub
parent bc988f22c6
commit 23d39555e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 242 additions and 171 deletions

View file

@ -12,5 +12,6 @@ export type { ApiVersion } from './src/versioning';
export { export {
ELASTIC_HTTP_VERSION_HEADER, ELASTIC_HTTP_VERSION_HEADER,
ELASTIC_HTTP_VERSION_QUERY_PARAM, ELASTIC_HTTP_VERSION_QUERY_PARAM,
ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST, X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from './src/constants'; } from './src/constants';

View file

@ -9,5 +9,5 @@
/** @public */ /** @public */
export const ELASTIC_HTTP_VERSION_HEADER = 'elastic-api-version' as const; export const ELASTIC_HTTP_VERSION_HEADER = 'elastic-api-version' as const;
export const ELASTIC_HTTP_VERSION_QUERY_PARAM = 'apiVersion' as const; export const ELASTIC_HTTP_VERSION_QUERY_PARAM = 'apiVersion' as const;
export const ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM = 'elasticInternalOrigin' as const;
export const X_ELASTIC_INTERNAL_ORIGIN_REQUEST = 'x-elastic-internal-origin' as const; export const X_ELASTIC_INTERNAL_ORIGIN_REQUEST = 'x-elastic-internal-origin' as const;

View file

@ -15,6 +15,10 @@ import { hapiMocks } from '@kbn/hapi-mocks';
import type { FakeRawRequest } from '@kbn/core-http-server'; import type { FakeRawRequest } from '@kbn/core-http-server';
import { CoreKibanaRequest } from './request'; import { CoreKibanaRequest } from './request';
import { schema } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema';
import {
ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
describe('CoreKibanaRequest', () => { describe('CoreKibanaRequest', () => {
describe('using real requests', () => { describe('using real requests', () => {
@ -145,6 +149,58 @@ describe('CoreKibanaRequest', () => {
}); });
}); });
describe('isInternalApiRequest property', () => {
it('is true when header is set', () => {
const request = hapiMocks.createRequest({
headers: { [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'true' },
});
const kibanaRequest = CoreKibanaRequest.from(request);
expect(kibanaRequest.isInternalApiRequest).toBe(true);
});
it('is true when query param is set', () => {
const request = hapiMocks.createRequest({
query: { [ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM]: 'true' },
});
const kibanaRequest = CoreKibanaRequest.from(request);
expect(kibanaRequest.isInternalApiRequest).toBe(true);
});
it('is true when both header and query param is set', () => {
const request = hapiMocks.createRequest({
headers: { [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'true' },
query: { [ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM]: 'true' },
});
const kibanaRequest = CoreKibanaRequest.from(request);
expect(kibanaRequest.isInternalApiRequest).toBe(true);
});
it('is false when neither header nor query param is set', () => {
const request = hapiMocks.createRequest();
const kibanaRequest = CoreKibanaRequest.from(request);
expect(kibanaRequest.isInternalApiRequest).toBe(false);
});
});
describe('sanitize input', () => {
it('does not pass the reserved query parameter to consumers', () => {
const request = hapiMocks.createRequest({
query: { [ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM]: 'true', myCoolValue: 'cool!' },
});
const kibanaRequest = CoreKibanaRequest.from(request, {
query: schema.object({ myCoolValue: schema.string() }),
});
expect(kibanaRequest.query).toEqual({ myCoolValue: 'cool!' });
});
it('pass nothing if only the reserved query param is present', () => {
const request = hapiMocks.createRequest({
query: { [ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM]: 'true' },
});
expect(() =>
CoreKibanaRequest.from(request, {
query: schema.object({}, { unknowns: 'forbid' }), // we require an empty object
})
).not.toThrow();
});
});
describe('route.options.authRequired property', () => { describe('route.options.authRequired property', () => {
it('handles required auth: undefined', () => { it('handles required auth: undefined', () => {
const auth: RouteOptions['auth'] = undefined; const auth: RouteOptions['auth'] = undefined;

View file

@ -29,6 +29,10 @@ import {
RawRequest, RawRequest,
FakeRawRequest, FakeRawRequest,
} from '@kbn/core-http-server'; } from '@kbn/core-http-server';
import {
ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { RouteValidator } from './validator'; import { RouteValidator } from './validator';
import { isSafeMethod } from './route'; import { isSafeMethod } from './route';
import { KibanaSocket } from './socket'; import { KibanaSocket } from './socket';
@ -59,7 +63,13 @@ export class CoreKibanaRequest<
withoutSecretHeaders: boolean = true withoutSecretHeaders: boolean = true
) { ) {
const routeValidator = RouteValidator.from<P, Q, B>(routeSchemas); const routeValidator = RouteValidator.from<P, Q, B>(routeSchemas);
const requestParts = CoreKibanaRequest.validate(req, routeValidator); let requestParts: { params: P; query: Q; body: B };
if (isFakeRawRequest(req)) {
requestParts = { query: {} as Q, params: {} as P, body: {} as B };
} else {
const rawParts = CoreKibanaRequest.sanitizeRequest(req);
requestParts = CoreKibanaRequest.validate(rawParts, routeValidator);
}
return new CoreKibanaRequest( return new CoreKibanaRequest(
req, req,
requestParts.params, requestParts.params,
@ -69,6 +79,22 @@ export class CoreKibanaRequest<
); );
} }
/**
* We have certain values that may be passed via query params that we want to
* exclude from further processing like validation. This method removes those
* internal values.
*/
private static sanitizeRequest<P, Q, B>(
req: Request
): { query: unknown; params: unknown; body: unknown } {
const { [ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM]: __, ...query } = req.query ?? {};
return {
query,
params: req.params,
body: req.payload,
};
}
/** /**
* Validates the different parts of a request based on the schemas defined for * Validates the different parts of a request based on the schemas defined for
* the route. Builds up the actual params, query and body object that will be * the route. Builds up the actual params, query and body object that will be
@ -76,43 +102,42 @@ export class CoreKibanaRequest<
* @internal * @internal
*/ */
private static validate<P, Q, B>( private static validate<P, Q, B>(
req: RawRequest, raw: { params: unknown; query: unknown; body: unknown },
routeValidator: RouteValidator<P, Q, B> routeValidator: RouteValidator<P, Q, B>
): { ): {
params: P; params: P;
query: Q; query: Q;
body: B; body: B;
} { } {
if (isFakeRawRequest(req)) { const params = routeValidator.getParams(raw.params, 'request params');
return { query: {} as Q, params: {} as P, body: {} as B }; const query = routeValidator.getQuery(raw.query, 'request query');
} const body = routeValidator.getBody(raw.body, 'request body');
const params = routeValidator.getParams(req.params, 'request params');
const query = routeValidator.getQuery(req.query, 'request query');
const body = routeValidator.getBody(req.payload, 'request body');
return { query, params, body }; return { query, params, body };
} }
/** {@inheritDoc IKibanaRequest.id} */ /** {@inheritDoc KibanaRequest.id} */
public readonly id: string; public readonly id: string;
/** {@inheritDoc IKibanaRequest.uuid} */ /** {@inheritDoc KibanaRequest.uuid} */
public readonly uuid: string; public readonly uuid: string;
/** {@inheritDoc IKibanaRequest.url} */ /** {@inheritDoc KibanaRequest.url} */
public readonly url: URL; public readonly url: URL;
/** {@inheritDoc IKibanaRequest.route} */ /** {@inheritDoc KibanaRequest.route} */
public readonly route: RecursiveReadonly<KibanaRequestRoute<Method>>; public readonly route: RecursiveReadonly<KibanaRequestRoute<Method>>;
/** {@inheritDoc IKibanaRequest.headers} */ /** {@inheritDoc KibanaRequest.headers} */
public readonly headers: Headers; public readonly headers: Headers;
/** {@inheritDoc IKibanaRequest.isSystemRequest} */ /** {@inheritDoc KibanaRequest.isSystemRequest} */
public readonly isSystemRequest: boolean; public readonly isSystemRequest: boolean;
/** {@inheritDoc IKibanaRequest.socket} */ /** {@inheritDoc KibanaRequest.socket} */
public readonly socket: IKibanaSocket; public readonly socket: IKibanaSocket;
/** {@inheritDoc IKibanaRequest.events} */ /** {@inheritDoc KibanaRequest.events} */
public readonly events: KibanaRequestEvents; public readonly events: KibanaRequestEvents;
/** {@inheritDoc IKibanaRequest.auth} */ /** {@inheritDoc KibanaRequest.auth} */
public readonly auth: KibanaRequestAuth; public readonly auth: KibanaRequestAuth;
/** {@inheritDoc IKibanaRequest.isFakeRequest} */ /** {@inheritDoc KibanaRequest.isFakeRequest} */
public readonly isFakeRequest: boolean; public readonly isFakeRequest: boolean;
/** {@inheritDoc IKibanaRequest.rewrittenUrl} */ /** {@inheritDoc KibanaRequest.isInternalApiRequest} */
public readonly isInternalApiRequest: boolean;
/** {@inheritDoc KibanaRequest.rewrittenUrl} */
public readonly rewrittenUrl?: URL; public readonly rewrittenUrl?: URL;
/** @internal */ /** @internal */
@ -139,7 +164,9 @@ export class CoreKibanaRequest<
this.headers = isRealRawRequest(request) ? deepFreeze({ ...request.headers }) : request.headers; this.headers = isRealRawRequest(request) ? deepFreeze({ ...request.headers }) : request.headers;
this.isSystemRequest = this.headers['kbn-system-request'] === 'true'; this.isSystemRequest = this.headers['kbn-system-request'] === 'true';
this.isFakeRequest = isFakeRawRequest(request); this.isFakeRequest = isFakeRawRequest(request);
this.isInternalApiRequest =
X_ELASTIC_INTERNAL_ORIGIN_REQUEST in this.headers ||
Boolean(this.url?.searchParams?.has(ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM));
// prevent Symbol exposure via Object.getOwnPropertySymbols() // prevent Symbol exposure via Object.getOwnPropertySymbols()
Object.defineProperty(this, requestSymbol, { Object.defineProperty(this, requestSymbol, {
value: request, value: request,

View file

@ -39,11 +39,13 @@ const createToolkit = (): ToolkitMock => {
const forgeRequest = ({ const forgeRequest = ({
headers = {}, headers = {},
query = {},
path = '/', path = '/',
method = 'get', method = 'get',
kibanaRouteOptions, kibanaRouteOptions,
}: Partial<{ }: Partial<{
headers: Record<string, string>; headers: Record<string, string>;
query: Record<string, string>;
path: string; path: string;
method: RouteMethod; method: RouteMethod;
kibanaRouteOptions: KibanaRouteOptions; kibanaRouteOptions: KibanaRouteOptions;
@ -51,6 +53,7 @@ const forgeRequest = ({
return mockRouter.createKibanaRequest({ return mockRouter.createKibanaRequest({
headers, headers,
path, path,
query,
method, method,
kibanaRouteOptions, kibanaRouteOptions,
}); });
@ -259,11 +262,13 @@ describe('restrictInternal post-auth handler', () => {
}); });
const createForgeRequest = ( const createForgeRequest = (
access: 'internal' | 'public', access: 'internal' | 'public',
headers: Record<string, string> | undefined = {} headers: Record<string, string> | undefined = {},
query: Record<string, string> | undefined = {}
) => { ) => {
return forgeRequest({ return forgeRequest({
method: 'get', method: 'get',
headers, headers,
query,
path: `/${access}/some-path`, path: `/${access}/some-path`,
kibanaRouteOptions: { kibanaRouteOptions: {
xsrfRequired: false, xsrfRequired: false,
@ -318,6 +323,24 @@ describe('restrictInternal post-auth handler', () => {
const request = createForgeRequest('public'); const request = createForgeRequest('public');
createForwardSuccess(handler, request); createForwardSuccess(handler, request);
}); });
it('forward the request to the next interceptor if called with internal origin query param for internal API', () => {
const handler = createRestrictInternalRoutesPostAuthHandler(config as HttpConfig);
const request = createForgeRequest('internal', undefined, { elasticInternalOrigin: 'true' });
createForwardSuccess(handler, request);
});
it('forward the request to the next interceptor if called with internal origin query param for public APIs', () => {
const handler = createRestrictInternalRoutesPostAuthHandler(config as HttpConfig);
const request = createForgeRequest('internal', undefined, { elasticInternalOrigin: 'true' });
createForwardSuccess(handler, request);
});
it('forward the request to the next interceptor if called without internal origin query param for public APIs', () => {
const handler = createRestrictInternalRoutesPostAuthHandler(config as HttpConfig);
const request = createForgeRequest('public');
createForwardSuccess(handler, request);
});
}); });
describe('when restriction is not enabled', () => { describe('when restriction is not enabled', () => {

View file

@ -8,7 +8,6 @@
import type { OnPostAuthHandler, OnPreResponseHandler } from '@kbn/core-http-server'; import type { OnPostAuthHandler, OnPreResponseHandler } from '@kbn/core-http-server';
import { isSafeMethod } from '@kbn/core-http-router-server-internal'; import { isSafeMethod } from '@kbn/core-http-router-server-internal';
import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common/src/constants';
import { HttpConfig } from './http_config'; import { HttpConfig } from './http_config';
const VERSION_HEADER = 'kbn-version'; const VERSION_HEADER = 'kbn-version';
@ -45,11 +44,7 @@ export const createRestrictInternalRoutesPostAuthHandler = (
return (request, response, toolkit) => { return (request, response, toolkit) => {
const isInternalRoute = request.route.options.access === 'internal'; const isInternalRoute = request.route.options.access === 'internal';
if (isRestrictionEnabled && isInternalRoute && !request.isInternalApiRequest) {
// only check if the header is present, not it's content.
const hasInternalKibanaRequestHeader = X_ELASTIC_INTERNAL_ORIGIN_REQUEST in request.headers;
if (isRestrictionEnabled && isInternalRoute && !hasInternalKibanaRequestHeader) {
// throw 400 // throw 400
return response.badRequest({ return response.badRequest({
body: `uri [${request.url}] with method [${request.route.method}] exists but is not available with the current configuration`, body: `uri [${request.url}] with method [${request.route.method}] exists but is not available with the current configuration`,
@ -75,7 +70,6 @@ export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPost
}, },
}); });
} }
return toolkit.next(); return toolkit.next();
}; };
}; };

View file

@ -135,6 +135,12 @@ export interface KibanaRequest<
*/ */
readonly isFakeRequest: boolean; readonly isFakeRequest: boolean;
/**
* An internal request has access to internal routes.
* @note See the {@link KibanaRequestRouteOptions#access} route option.
*/
readonly isInternalApiRequest: boolean;
/** /**
* The socket associated with this request. * The socket associated with this request.
* See {@link IKibanaSocket}. * See {@link IKibanaSocket}.

View file

@ -18,6 +18,11 @@ export const createRequestMock = (customization: DeepPartial<Request> = {}): Req
formatUrl(Object.assign({ pathname, path, href: path }, customization.url)), formatUrl(Object.assign({ pathname, path, href: path }, customization.url)),
'http://localhost' 'http://localhost'
); );
if (customization.query) {
Object.entries(customization.query).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
}
return merge( return merge(
{}, {},

View file

@ -13,6 +13,7 @@ import { contextServiceMock } from '@kbn/core-http-context-server-mocks';
import { createConfigService, createHttpServer } from '@kbn/core-http-server-mocks'; import { createConfigService, createHttpServer } from '@kbn/core-http-server-mocks';
import { HttpService, HttpServerSetup } from '@kbn/core-http-server-internal'; import { HttpService, HttpServerSetup } from '@kbn/core-http-server-internal';
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
import { schema } from '@kbn/config-schema';
const actualVersion = kibanaPackageJson.version; const actualVersion = kibanaPackageJson.version;
const versionHeader = 'kbn-version'; const versionHeader = 'kbn-version';
@ -22,37 +23,39 @@ const allowlistedTestPath = '/xsrf/test/route/whitelisted';
const xsrfDisabledTestPath = '/xsrf/test/route/disabled'; const xsrfDisabledTestPath = '/xsrf/test/route/disabled';
const kibanaName = 'my-kibana-name'; const kibanaName = 'my-kibana-name';
const internalProductHeader = 'x-elastic-internal-origin'; const internalProductHeader = 'x-elastic-internal-origin';
const internalProductQueryParam = 'elasticInternalOrigin';
const setupDeps = { const setupDeps = {
context: contextServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(),
executionContext: executionContextServiceMock.createInternalSetupContract(), executionContext: executionContextServiceMock.createInternalSetupContract(),
}; };
const testConfig: Parameters<typeof createConfigService>[0] = {
server: {
name: kibanaName,
securityResponseHeaders: {
// reflects default config
strictTransportSecurity: null,
xContentTypeOptions: 'nosniff',
referrerPolicy: 'strict-origin-when-cross-origin',
permissionsPolicy: null,
crossOriginOpenerPolicy: 'same-origin',
} as any,
customResponseHeaders: {
'some-header': 'some-value',
'referrer-policy': 'strict-origin', // overrides a header that is defined by securityResponseHeaders
},
xsrf: { disableProtection: false, allowlist: [allowlistedTestPath] },
},
};
describe('core lifecycle handlers', () => { describe('core lifecycle handlers', () => {
let server: HttpService; let server: HttpService;
let innerServer: HttpServerSetup['server']; let innerServer: HttpServerSetup['server'];
let router: IRouter; let router: IRouter;
beforeEach(async () => { beforeEach(async () => {
const configService = createConfigService({ const configService = createConfigService(testConfig);
server: {
name: kibanaName,
securityResponseHeaders: {
// reflects default config
strictTransportSecurity: null,
xContentTypeOptions: 'nosniff',
referrerPolicy: 'strict-origin-when-cross-origin',
permissionsPolicy: null,
crossOriginOpenerPolicy: 'same-origin',
} as any,
customResponseHeaders: {
'some-header': 'some-value',
'referrer-policy': 'strict-origin', // overrides a header that is defined by securityResponseHeaders
},
xsrf: { disableProtection: false, allowlist: [allowlistedTestPath] },
},
});
server = createHttpServer({ configService }); server = createHttpServer({ configService });
await server.preboot({ context: contextServiceMock.createPrebootContract() }); await server.preboot({ context: contextServiceMock.createPrebootContract() });
const serverSetup = await server.setup(setupDeps); const serverSetup = await server.setup(setupDeps);
router = serverSetup.createRouter('/'); router = serverSetup.createRouter('/');
@ -217,15 +220,36 @@ describe('core lifecycle handlers', () => {
describe('restrictInternalRoutes post-auth handler', () => { describe('restrictInternalRoutes post-auth handler', () => {
const testInternalRoute = '/restrict_internal_routes/test/route_internal'; const testInternalRoute = '/restrict_internal_routes/test/route_internal';
const testPublicRoute = '/restrict_internal_routes/test/route_public'; const testPublicRoute = '/restrict_internal_routes/test/route_public';
beforeEach(async () => { beforeEach(async () => {
await server?.stop();
const configService = createConfigService({
server: {
...testConfig.server,
restrictInternalApis: true,
},
});
server = createHttpServer({ configService });
await server.preboot({ context: contextServiceMock.createPrebootContract() });
const serverSetup = await server.setup(setupDeps);
router = serverSetup.createRouter('/');
innerServer = serverSetup.server;
router.get( router.get(
{ path: testInternalRoute, validate: false, options: { access: 'internal' } }, {
path: testInternalRoute,
validate: { query: schema.object({ myValue: schema.string() }) },
options: { access: 'internal' },
},
(context, req, res) => { (context, req, res) => {
return res.ok({ body: 'ok()' }); return res.ok({ body: 'ok()' });
} }
); );
router.get( router.get(
{ path: testPublicRoute, validate: false, options: { access: 'public' } }, {
path: testPublicRoute,
validate: { query: schema.object({ myValue: schema.string() }) },
options: { access: 'public' },
},
(context, req, res) => { (context, req, res) => {
return res.ok({ body: 'ok()' }); return res.ok({ body: 'ok()' });
} }
@ -233,10 +257,18 @@ describe('core lifecycle handlers', () => {
await server.start(); await server.start();
}); });
it('rejects requests to internal routes without special values', async () => {
await supertest(innerServer.listener)
.get(testInternalRoute)
.query({ myValue: 'test' })
.expect(400);
});
it('accepts requests with the internal product header to internal routes', async () => { it('accepts requests with the internal product header to internal routes', async () => {
await supertest(innerServer.listener) await supertest(innerServer.listener)
.get(testInternalRoute) .get(testInternalRoute)
.set(internalProductHeader, 'anything') .set(internalProductHeader, 'anything')
.query({ myValue: 'test' })
.expect(200, 'ok()'); .expect(200, 'ok()');
}); });
@ -244,6 +276,21 @@ describe('core lifecycle handlers', () => {
await supertest(innerServer.listener) await supertest(innerServer.listener)
.get(testPublicRoute) .get(testPublicRoute)
.set(internalProductHeader, 'anything') .set(internalProductHeader, 'anything')
.query({ myValue: 'test' })
.expect(200, 'ok()');
});
it('accepts requests with the internal product query param to internal routes', async () => {
await supertest(innerServer.listener)
.get(testInternalRoute)
.query({ [internalProductQueryParam]: 'anything', myValue: 'test' })
.expect(200, 'ok()');
});
it('accepts requests with the internal product query param to public routes', async () => {
await supertest(innerServer.listener)
.get(testInternalRoute)
.query({ [internalProductQueryParam]: 'anything', myValue: 'test' })
.expect(200, 'ok()'); .expect(200, 'ok()');
}); });
}); });

View file

@ -4,11 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import { mockRouter } from '@kbn/core-http-router-server-mocks';
import { Request } from '@hapi/hapi';
import { ruleTypeRegistryMock } from './rule_type_registry.mock'; import { ruleTypeRegistryMock } from './rule_type_registry.mock';
import { CoreKibanaRequest } from '@kbn/core/server';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import { securityMock } from '@kbn/security-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks';
import { import {
AlertingAuthorizationClientFactory, AlertingAuthorizationClientFactory,
@ -18,7 +15,6 @@ import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
jest.mock('./authorization/alerting_authorization'); jest.mock('./authorization/alerting_authorization');
const savedObjectsClient = savedObjectsClientMock.create();
const features = featuresPluginMock.createStart(); const features = featuresPluginMock.createStart();
const securityPluginSetup = securityMock.createSetup(); const securityPluginSetup = securityMock.createSetup();
@ -32,23 +28,6 @@ const alertingAuthorizationClientFactoryParams: jest.Mocked<AlertingAuthorizatio
features, features,
}; };
const fakeRequest = {
app: {},
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
} as unknown as Request;
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
@ -60,7 +39,7 @@ test('creates an alerting authorization client with proper constructor arguments
securityPluginStart, securityPluginStart,
...alertingAuthorizationClientFactoryParams, ...alertingAuthorizationClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
factory.create(request); factory.create(request);
@ -78,7 +57,7 @@ test('creates an alerting authorization client with proper constructor arguments
test('creates an alerting authorization client with proper constructor arguments', async () => { test('creates an alerting authorization client with proper constructor arguments', async () => {
const factory = new AlertingAuthorizationClientFactory(); const factory = new AlertingAuthorizationClientFactory();
factory.initialize(alertingAuthorizationClientFactoryParams); factory.initialize(alertingAuthorizationClientFactoryParams);
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
factory.create(request); factory.create(request);

View file

@ -4,9 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import { mockRouter } from '@kbn/core-http-router-server-mocks';
import { Request } from '@hapi/hapi';
import { CoreKibanaRequest } from '@kbn/core/server';
import { import {
MaintenanceWindowClientFactory, MaintenanceWindowClientFactory,
MaintenanceWindowClientFactoryOpts, MaintenanceWindowClientFactoryOpts,
@ -33,23 +31,6 @@ const maintenanceWindowClientFactoryParams: jest.Mocked<MaintenanceWindowClientF
savedObjectsService, savedObjectsService,
}; };
const fakeRequest = {
app: {},
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
} as unknown as Request;
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
@ -60,7 +41,7 @@ test('creates a maintenance window client with proper constructor arguments when
securityPluginStart, securityPluginStart,
...maintenanceWindowClientFactoryParams, ...maintenanceWindowClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
@ -82,7 +63,7 @@ test('creates a maintenance window client with proper constructor arguments when
test('creates a maintenance window client with proper constructor arguments', async () => { test('creates a maintenance window client with proper constructor arguments', async () => {
const factory = new MaintenanceWindowClientFactory(); const factory = new MaintenanceWindowClientFactory();
factory.initialize(maintenanceWindowClientFactoryParams); factory.initialize(maintenanceWindowClientFactoryParams);
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
@ -107,7 +88,7 @@ test('creates an unauthorized maintenance window client', async () => {
securityPluginStart, securityPluginStart,
...maintenanceWindowClientFactoryParams, ...maintenanceWindowClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
@ -130,7 +111,7 @@ test('creates an unauthorized maintenance window client', async () => {
test('getUserName() returns null when security is disabled', async () => { test('getUserName() returns null when security is disabled', async () => {
const factory = new MaintenanceWindowClientFactory(); const factory = new MaintenanceWindowClientFactory();
factory.initialize(maintenanceWindowClientFactoryParams); factory.initialize(maintenanceWindowClientFactoryParams);
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
factory.createWithAuthorization(request); factory.createWithAuthorization(request);
const constructorCall = jest.requireMock('./maintenance_window_client').MaintenanceWindowClient const constructorCall = jest.requireMock('./maintenance_window_client').MaintenanceWindowClient
@ -146,7 +127,7 @@ test('getUserName() returns a name when security is enabled', async () => {
securityPluginStart, securityPluginStart,
...maintenanceWindowClientFactoryParams, ...maintenanceWindowClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
factory.createWithAuthorization(request); factory.createWithAuthorization(request);

View file

@ -5,11 +5,9 @@
* 2.0. * 2.0.
*/ */
import { Request } from '@hapi/hapi';
import { RulesClientFactory, RulesClientFactoryOpts } from './rules_client_factory'; import { RulesClientFactory, RulesClientFactoryOpts } from './rules_client_factory';
import { ruleTypeRegistryMock } from './rule_type_registry.mock'; import { ruleTypeRegistryMock } from './rule_type_registry.mock';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { CoreKibanaRequest } from '@kbn/core/server';
import { import {
savedObjectsClientMock, savedObjectsClientMock,
savedObjectsServiceMock, savedObjectsServiceMock,
@ -26,6 +24,7 @@ import { alertingAuthorizationClientFactoryMock } from './alerting_authorization
import { AlertingAuthorization } from './authorization'; import { AlertingAuthorization } from './authorization';
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
import { mockRouter } from '@kbn/core-http-router-server-mocks';
jest.mock('./rules_client'); jest.mock('./rules_client');
jest.mock('./authorization/alerting_authorization'); jest.mock('./authorization/alerting_authorization');
@ -54,23 +53,6 @@ const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
alertingAuthorizationClientFactory as unknown as AlertingAuthorizationClientFactory, alertingAuthorizationClientFactory as unknown as AlertingAuthorizationClientFactory,
}; };
const fakeRequest = {
app: {},
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
} as unknown as Request;
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
beforeEach(() => { beforeEach(() => {
@ -86,7 +68,7 @@ beforeEach(() => {
test('creates a rules client with proper constructor arguments when security is enabled', async () => { test('creates a rules client with proper constructor arguments when security is enabled', async () => {
const factory = new RulesClientFactory(); const factory = new RulesClientFactory();
factory.initialize({ securityPluginSetup, securityPluginStart, ...rulesClientFactoryParams }); factory.initialize({ securityPluginSetup, securityPluginStart, ...rulesClientFactoryParams });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
alertingAuthorizationClientFactory.create.mockReturnValue( alertingAuthorizationClientFactory.create.mockReturnValue(
@ -130,7 +112,7 @@ test('creates a rules client with proper constructor arguments when security is
test('creates a rules client with proper constructor arguments', async () => { test('creates a rules client with proper constructor arguments', async () => {
const factory = new RulesClientFactory(); const factory = new RulesClientFactory();
factory.initialize(rulesClientFactoryParams); factory.initialize(rulesClientFactoryParams);
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
alertingAuthorizationClientFactory.create.mockReturnValue( alertingAuthorizationClientFactory.create.mockReturnValue(
@ -170,7 +152,7 @@ test('creates a rules client with proper constructor arguments', async () => {
test('getUserName() returns null when security is disabled', async () => { test('getUserName() returns null when security is disabled', async () => {
const factory = new RulesClientFactory(); const factory = new RulesClientFactory();
factory.initialize(rulesClientFactoryParams); factory.initialize(rulesClientFactoryParams);
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
const userNameResult = await constructorCall.getUserName(); const userNameResult = await constructorCall.getUserName();
@ -184,7 +166,7 @@ test('getUserName() returns a name when security is enabled', async () => {
securityPluginSetup, securityPluginSetup,
securityPluginStart, securityPluginStart,
}); });
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
securityPluginStart.authc.getCurrentUser.mockReturnValueOnce({ securityPluginStart.authc.getCurrentUser.mockReturnValueOnce({
@ -197,7 +179,7 @@ test('getUserName() returns a name when security is enabled', async () => {
test('getActionsClient() returns ActionsClient', async () => { test('getActionsClient() returns ActionsClient', async () => {
const factory = new RulesClientFactory(); const factory = new RulesClientFactory();
factory.initialize(rulesClientFactoryParams); factory.initialize(rulesClientFactoryParams);
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
const actionsClient = await constructorCall.getActionsClient(); const actionsClient = await constructorCall.getActionsClient();
@ -207,7 +189,7 @@ test('getActionsClient() returns ActionsClient', async () => {
test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled', async () => { test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled', async () => {
const factory = new RulesClientFactory(); const factory = new RulesClientFactory();
factory.initialize(rulesClientFactoryParams); factory.initialize(rulesClientFactoryParams);
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
const createAPIKeyResult = await constructorCall.createAPIKey(); const createAPIKeyResult = await constructorCall.createAPIKey();
@ -217,7 +199,7 @@ test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled
test('createAPIKey() returns { apiKeysEnabled: false } when security is enabled but ES security is disabled', async () => { test('createAPIKey() returns { apiKeysEnabled: false } when security is enabled but ES security is disabled', async () => {
const factory = new RulesClientFactory(); const factory = new RulesClientFactory();
factory.initialize(rulesClientFactoryParams); factory.initialize(rulesClientFactoryParams);
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
securityPluginStart.authc.apiKeys.grantAsInternalUser.mockResolvedValueOnce(null); securityPluginStart.authc.apiKeys.grantAsInternalUser.mockResolvedValueOnce(null);
@ -232,7 +214,7 @@ test('createAPIKey() returns an API key when security is enabled', async () => {
securityPluginSetup, securityPluginSetup,
securityPluginStart, securityPluginStart,
}); });
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
securityPluginStart.authc.apiKeys.grantAsInternalUser.mockResolvedValueOnce({ securityPluginStart.authc.apiKeys.grantAsInternalUser.mockResolvedValueOnce({
@ -254,7 +236,7 @@ test('createAPIKey() throws when security plugin createAPIKey throws an error',
securityPluginSetup, securityPluginSetup,
securityPluginStart, securityPluginStart,
}); });
factory.create(CoreKibanaRequest.from(fakeRequest), savedObjectsService); factory.create(mockRouter.createKibanaRequest(), savedObjectsService);
const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0];
securityPluginStart.authc.apiKeys.grantAsInternalUser.mockRejectedValueOnce( securityPluginStart.authc.apiKeys.grantAsInternalUser.mockRejectedValueOnce(

View file

@ -5,8 +5,7 @@
* 2.0. * 2.0.
*/ */
import { Request } from '@hapi/hapi'; import { mockRouter } from '@kbn/core-http-router-server-mocks';
import { CoreKibanaRequest } from '@kbn/core/server';
import { import {
RulesSettingsClientFactory, RulesSettingsClientFactory,
RulesSettingsClientFactoryOpts, RulesSettingsClientFactoryOpts,
@ -33,23 +32,6 @@ const rulesSettingsClientFactoryParams: jest.Mocked<RulesSettingsClientFactoryOp
savedObjectsService, savedObjectsService,
}; };
const fakeRequest = {
app: {},
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
} as unknown as Request;
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
@ -60,7 +42,7 @@ test('creates a rules settings client with proper constructor arguments when sec
securityPluginStart, securityPluginStart,
...rulesSettingsClientFactoryParams, ...rulesSettingsClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
@ -82,7 +64,7 @@ test('creates a rules settings client with proper constructor arguments when sec
test('creates a rules settings client with proper constructor arguments', async () => { test('creates a rules settings client with proper constructor arguments', async () => {
const factory = new RulesSettingsClientFactory(); const factory = new RulesSettingsClientFactory();
factory.initialize(rulesSettingsClientFactoryParams); factory.initialize(rulesSettingsClientFactoryParams);
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
@ -107,7 +89,7 @@ test('creates an unauthorized rules settings client', async () => {
securityPluginStart, securityPluginStart,
...rulesSettingsClientFactoryParams, ...rulesSettingsClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
@ -130,7 +112,7 @@ test('creates an unauthorized rules settings client', async () => {
test('getUserName() returns null when security is disabled', async () => { test('getUserName() returns null when security is disabled', async () => {
const factory = new RulesSettingsClientFactory(); const factory = new RulesSettingsClientFactory();
factory.initialize(rulesSettingsClientFactoryParams); factory.initialize(rulesSettingsClientFactoryParams);
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
factory.createWithAuthorization(request); factory.createWithAuthorization(request);
const constructorCall = const constructorCall =
@ -146,7 +128,7 @@ test('getUserName() returns a name when security is enabled', async () => {
securityPluginStart, securityPluginStart,
...rulesSettingsClientFactoryParams, ...rulesSettingsClientFactoryParams,
}); });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest();
factory.createWithAuthorization(request); factory.createWithAuthorization(request);

View file

@ -56,6 +56,7 @@
"@kbn/core-capabilities-common", "@kbn/core-capabilities-common",
"@kbn/unified-search-plugin", "@kbn/unified-search-plugin",
"@kbn/core-http-server-mocks", "@kbn/core-http-server-mocks",
"@kbn/core-http-router-server-mocks",
], ],
"exclude": ["target/**/*"] "exclude": ["target/**/*"]
} }

View file

@ -5,10 +5,9 @@
* 2.0. * 2.0.
*/ */
import { Request } from '@hapi/hapi'; import { mockRouter } from '@kbn/core-http-router-server-mocks';
import { AlertsClientFactory, AlertsClientFactoryProps } from './alerts_client_factory'; import { AlertsClientFactory, AlertsClientFactoryProps } from './alerts_client_factory';
import { ElasticsearchClient, KibanaRequest, CoreKibanaRequest } from '@kbn/core/server'; import { ElasticsearchClient, KibanaRequest } from '@kbn/core/server';
import { loggingSystemMock } from '@kbn/core/server/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks';
import { securityMock } from '@kbn/security-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
@ -29,22 +28,6 @@ const alertsClientFactoryParams: AlertsClientFactoryProps = {
getRuleType: jest.fn(), getRuleType: jest.fn(),
}; };
const fakeRequest = {
app: {},
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
} as unknown as Request;
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
describe('AlertsClientFactory', () => { describe('AlertsClientFactory', () => {
@ -57,7 +40,10 @@ describe('AlertsClientFactory', () => {
test('creates an alerts client with proper constructor arguments', async () => { test('creates an alerts client with proper constructor arguments', async () => {
const factory = new AlertsClientFactory(); const factory = new AlertsClientFactory();
factory.initialize({ ...alertsClientFactoryParams }); factory.initialize({ ...alertsClientFactoryParams });
const request = CoreKibanaRequest.from(fakeRequest); const request = mockRouter.createKibanaRequest({
headers: {},
path: '/',
});
await factory.create(request); await factory.create(request);
expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({ expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({

View file

@ -33,6 +33,7 @@
"@kbn/share-plugin", "@kbn/share-plugin",
"@kbn/alerting-state-types", "@kbn/alerting-state-types",
"@kbn/alerts-as-data-utils", "@kbn/alerts-as-data-utils",
"@kbn/core-http-router-server-mocks",
], ],
"exclude": [ "exclude": [
"target/**/*", "target/**/*",