"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureRawRequest = exports.CoreKibanaRequest = void 0; exports.isKibanaRequest = isKibanaRequest; exports.isRealRequest = isRealRequest; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _url = require("url"); var _uuid = require("uuid"); var _rxjs = require("rxjs"); var _operators = require("rxjs/operators"); var _std = require("@kbn/std"); var _coreHttpCommon = require("@kbn/core-http-common"); var _validator = require("./validator"); var _route = require("./route"); var _socket = require("./socket"); /* * 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. */ const requestSymbol = Symbol('request'); /** * Core internal implementation of {@link KibanaRequest} * @internal * @remarks Only publicly exposed for consumers that need to forge requests using {@link CoreKibanaRequest.from}. * All other usages should import and use the {@link KibanaRequest} interface instead. */ class CoreKibanaRequest { /** * Factory for creating requests. Validates the request before creating an * instance of a KibanaRequest. * @internal */ static from(req, routeSchemas = {}, withoutSecretHeaders = true) { const routeValidator = _validator.RouteValidator.from(routeSchemas); let requestParts; if (isFakeRawRequest(req)) { requestParts = { query: {}, params: {}, body: {} }; } else { const rawParts = CoreKibanaRequest.sanitizeRequest(req); requestParts = CoreKibanaRequest.validate(rawParts, routeValidator); } return new CoreKibanaRequest(req, requestParts.params, requestParts.query, requestParts.body, withoutSecretHeaders); } /** * 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. */ static sanitizeRequest(req) { var _req$query; const { [_coreHttpCommon.ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM]: __, ...query } = (_req$query = req.query) !== null && _req$query !== void 0 ? _req$query : {}; return { query, params: req.params, body: req.payload }; } /** * 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 * received in the route handler. * @internal */ static validate(raw, routeValidator) { const params = routeValidator.getParams(raw.params, 'request params'); const query = routeValidator.getQuery(raw.query, 'request query'); const body = routeValidator.getBody(raw.body, 'request body'); return { query, params, body }; } /** {@inheritDoc KibanaRequest.id} */ constructor(request, params, query, body, // @ts-expect-error we will use this flag as soon as http request proxy is supported in the core // until that time we have to expose all the headers withoutSecretHeaders) { var _appState$requestId, _appState$requestUuid, _request$url, _this$url, _this$url$searchParam, _request$auth$isAuthe, _request$auth; (0, _defineProperty2.default)(this, "id", void 0); /** {@inheritDoc KibanaRequest.uuid} */ (0, _defineProperty2.default)(this, "uuid", void 0); /** {@inheritDoc KibanaRequest.url} */ (0, _defineProperty2.default)(this, "url", void 0); /** {@inheritDoc KibanaRequest.route} */ (0, _defineProperty2.default)(this, "route", void 0); /** {@inheritDoc KibanaRequest.headers} */ (0, _defineProperty2.default)(this, "headers", void 0); /** {@inheritDoc KibanaRequest.isSystemRequest} */ (0, _defineProperty2.default)(this, "isSystemRequest", void 0); /** {@inheritDoc KibanaRequest.socket} */ (0, _defineProperty2.default)(this, "socket", void 0); /** {@inheritDoc KibanaRequest.events} */ (0, _defineProperty2.default)(this, "events", void 0); /** {@inheritDoc KibanaRequest.auth} */ (0, _defineProperty2.default)(this, "auth", void 0); /** {@inheritDoc KibanaRequest.isFakeRequest} */ (0, _defineProperty2.default)(this, "isFakeRequest", void 0); /** {@inheritDoc KibanaRequest.isInternalApiRequest} */ (0, _defineProperty2.default)(this, "isInternalApiRequest", void 0); /** {@inheritDoc KibanaRequest.rewrittenUrl} */ (0, _defineProperty2.default)(this, "rewrittenUrl", void 0); /** @internal */ (0, _defineProperty2.default)(this, requestSymbol, void 0); this.params = params; this.query = query; this.body = body; this.withoutSecretHeaders = withoutSecretHeaders; // The `requestId` and `requestUuid` properties will not be populated for requests that are 'faked' by internal systems that leverage // KibanaRequest in conjunction with scoped Elasticsearch and SavedObjectsClient in order to pass credentials. // In these cases, the ids default to a newly generated UUID. const appState = request.app; this.id = (_appState$requestId = appState === null || appState === void 0 ? void 0 : appState.requestId) !== null && _appState$requestId !== void 0 ? _appState$requestId : (0, _uuid.v4)(); this.uuid = (_appState$requestUuid = appState === null || appState === void 0 ? void 0 : appState.requestUuid) !== null && _appState$requestUuid !== void 0 ? _appState$requestUuid : (0, _uuid.v4)(); this.rewrittenUrl = appState === null || appState === void 0 ? void 0 : appState.rewrittenUrl; this.url = (_request$url = request.url) !== null && _request$url !== void 0 ? _request$url : new _url.URL('https://fake-request/url'); this.headers = isRealRawRequest(request) ? (0, _std.deepFreeze)({ ...request.headers }) : request.headers; this.isSystemRequest = this.headers['kbn-system-request'] === 'true'; this.isFakeRequest = isFakeRawRequest(request); this.isInternalApiRequest = _coreHttpCommon.X_ELASTIC_INTERNAL_ORIGIN_REQUEST in this.headers || Boolean((_this$url = this.url) === null || _this$url === void 0 ? void 0 : (_this$url$searchParam = _this$url.searchParams) === null || _this$url$searchParam === void 0 ? void 0 : _this$url$searchParam.has(_coreHttpCommon.ELASTIC_INTERNAL_ORIGIN_QUERY_PARAM)); // prevent Symbol exposure via Object.getOwnPropertySymbols() Object.defineProperty(this, requestSymbol, { value: request, enumerable: false }); this.route = (0, _std.deepFreeze)(this.getRouteInfo(request)); this.socket = isRealRawRequest(request) ? new _socket.KibanaSocket(request.raw.req.socket) : _socket.KibanaSocket.getFakeSocket(); this.events = this.getEvents(request); this.auth = { // missing in fakeRequests, so we cast to false isAuthenticated: (_request$auth$isAuthe = (_request$auth = request.auth) === null || _request$auth === void 0 ? void 0 : _request$auth.isAuthenticated) !== null && _request$auth$isAuthe !== void 0 ? _request$auth$isAuthe : false }; } getEvents(request) { if (isFakeRawRequest(request)) { return { aborted$: _rxjs.NEVER, completed$: _rxjs.NEVER }; } const completed$ = (0, _rxjs.fromEvent)(request.raw.res, 'close').pipe((0, _operators.shareReplay)(1), (0, _operators.first)()); // the response's underlying connection was terminated prematurely const aborted$ = completed$.pipe((0, _operators.filter)(() => !isCompleted(request))); return { aborted$, completed$ }; } getRouteInfo(request) { var _ref, _request$route, _request$route$settin, _request$raw$req$sock, _xsrfRequired, _request$route2, _request$route2$setti, _request$route2$setti2, _request$route3, _request$route3$setti, _request$path; const method = (_ref = request.method) !== null && _ref !== void 0 ? _ref : 'get'; const { parse, maxBytes, allow, output, timeout: payloadTimeout } = ((_request$route = request.route) === null || _request$route === void 0 ? void 0 : (_request$route$settin = _request$route.settings) === null || _request$route$settin === void 0 ? void 0 : _request$route$settin.payload) || {}; // the socket is undefined when using @hapi/shot, or when a "fake request" is used const socketTimeout = isRealRawRequest(request) ? (_request$raw$req$sock = request.raw.req.socket) === null || _request$raw$req$sock === void 0 ? void 0 : _request$raw$req$sock.timeout : undefined; const options = { authRequired: this.getAuthRequired(request), // TypeScript note: Casting to `RouterOptions` to fix the following error: // // Property 'app' does not exist on type 'RouteSettings' // // In @types/hapi__hapi v18, `request.route.settings` is of type // `RouteSettings`, which doesn't have an `app` property. I think this is // a mistake. In v19, the `RouteSettings` interface does have an `app` // property. xsrfRequired: (_xsrfRequired = (_request$route2 = request.route) === null || _request$route2 === void 0 ? void 0 : (_request$route2$setti = _request$route2.settings) === null || _request$route2$setti === void 0 ? void 0 : (_request$route2$setti2 = _request$route2$setti.app) === null || _request$route2$setti2 === void 0 ? void 0 : _request$route2$setti2.xsrfRequired) !== null && _xsrfRequired !== void 0 ? _xsrfRequired : true, // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 access: this.getAccess(request), tags: ((_request$route3 = request.route) === null || _request$route3 === void 0 ? void 0 : (_request$route3$setti = _request$route3.settings) === null || _request$route3$setti === void 0 ? void 0 : _request$route3$setti.tags) || [], timeout: { payload: payloadTimeout, idleSocket: socketTimeout === 0 ? undefined : socketTimeout }, body: (0, _route.isSafeMethod)(method) ? undefined : { parse, maxBytes, accepts: allow, output: output // We do not support all the HAPI-supported outputs and TS complains } }; // TS does not understand this is OK so I'm enforced to do this enforced casting return { path: (_request$path = request.path) !== null && _request$path !== void 0 ? _request$path : '/', method, options }; } /** set route access to internal if not declared */ getAccess(request) { var _access, _request$route4, _request$route4$setti, _request$route4$setti2; return (_access = (_request$route4 = request.route) === null || _request$route4 === void 0 ? void 0 : (_request$route4$setti = _request$route4.settings) === null || _request$route4$setti === void 0 ? void 0 : (_request$route4$setti2 = _request$route4$setti.app) === null || _request$route4$setti2 === void 0 ? void 0 : _request$route4$setti2.access) !== null && _access !== void 0 ? _access : 'internal'; } getAuthRequired(request) { if (isFakeRawRequest(request)) { return true; } const authOptions = request.route.settings.auth; if (typeof authOptions === 'object') { // 'try' is used in the legacy platform if (authOptions.mode === 'optional' || authOptions.mode === 'try') { return 'optional'; } if (authOptions.mode === 'required') { return true; } } // legacy platform routes if (authOptions === undefined) { return true; } // @ts-expect-error According to @types/hapi__hapi, `route.settings` should be of type `RouteSettings`, but it seems that it's actually `RouteOptions` (https://github.com/hapijs/hapi/blob/v18.4.2/lib/route.js#L139) if (authOptions === false) { return false; } throw new Error(`unexpected authentication options: ${JSON.stringify(authOptions)} for route: ${this.url.pathname}${this.url.search}`); } } /** * Returns underlying Hapi Request * @internal */ exports.CoreKibanaRequest = CoreKibanaRequest; const ensureRawRequest = request => isKibanaRequest(request) ? request[requestSymbol] : request; /** * Checks if an incoming request is a {@link KibanaRequest} * @internal */ exports.ensureRawRequest = ensureRawRequest; function isKibanaRequest(request) { return request instanceof CoreKibanaRequest; } function isRealRawRequest(request) { try { return request.raw.req && typeof request.raw.req === 'object' && request.raw.res && typeof request.raw.res === 'object'; } catch { return false; } } function isFakeRawRequest(request) { return !isRealRawRequest(request); } /** * Checks if an incoming request either KibanaRequest or Hapi.Request * @internal */ function isRealRequest(request) { return isKibanaRequest(request) || isRealRawRequest(request); } function isCompleted(request) { return request.raw.res.writableFinished; }