diff --git a/.eslintignore b/.eslintignore index 331cd654f7..84433bf3bb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -97,3 +97,27 @@ lib/types/config/ServicesConfiguration.js lib/types/config/StorageService/StorageServiceElasticsearchConfiguration.js lib/types/config/internalCache/InternalCacheRedisConfiguration.js lib/types/config/publicCache/PublicCacheRedisConfiguration.js +lib/api/openapi/OpenApiManager.js +lib/api/openapi/components/document/index.js +lib/api/openapi/components/index.js +lib/api/openapi/openApiGenerator.js +lib/core/backend/backendErrors.js +lib/core/backend/backendOpenApi.js +lib/core/security/profileRepository.js +lib/core/shared/sdk/funnelProtocol.js +lib/kerror/index.js +lib/model/security/profile.js +lib/model/security/role.js +lib/model/security/user.js +lib/types/ControllerRights.js +lib/types/HttpStream.js +lib/types/OpenApiDefinition.js +lib/types/Policy.js +lib/types/PolicyRestrictions.js +lib/types/Target.js +lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.js +lib/types/errors/ErrorDefinition.js +lib/types/errors/ErrorDomains.js +lib/util/array.js +lib/util/bufferedPassThrough.js +lib/util/dump-collection.js \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7711c0add9..aca12d085f 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,27 @@ lib/types/config/ServicesConfiguration.js lib/types/config/StorageService/StorageServiceElasticsearchConfiguration.js lib/types/config/internalCache/InternalCacheRedisConfiguration.js lib/types/config/publicCache/PublicCacheRedisConfiguration.js +lib/api/openapi/OpenApiManager.js +lib/api/openapi/components/document/index.js +lib/api/openapi/components/index.js +lib/api/openapi/openApiGenerator.js +lib/core/backend/backendErrors.js +lib/core/backend/backendOpenApi.js +lib/core/security/profileRepository.js +lib/core/shared/sdk/funnelProtocol.js +lib/kerror/index.js +lib/model/security/profile.js +lib/model/security/role.js +lib/model/security/user.js +lib/types/ControllerRights.js +lib/types/HttpStream.js +lib/types/OpenApiDefinition.js +lib/types/Policy.js +lib/types/PolicyRestrictions.js +lib/types/Target.js +lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.js +lib/types/errors/ErrorDefinition.js +lib/types/errors/ErrorDomains.js +lib/util/array.js +lib/util/bufferedPassThrough.js +lib/util/dump-collection.js \ No newline at end of file diff --git a/lib/core/network/entryPoint.js b/lib/core/network/entryPoint.js index 465eab8295..79f92f4a61 100644 --- a/lib/core/network/entryPoint.js +++ b/lib/core/network/entryPoint.js @@ -33,7 +33,7 @@ const MqttProtocol = require('./protocols/mqttProtocol'); const InternalProtocol = require('./protocols/internalProtocol'); const HttpWsProtocol = require('./protocols/httpwsProtocol'); const Manifest = require('./protocolManifest'); -const removeErrorStack = require('./removeErrorStack'); +const { removeStacktrace } = require('../../util/stackTrace'); const kerror = require('../../kerror'); const { AccessLogger } = require('./accessLogger'); @@ -256,7 +256,7 @@ class EntryPoint { const response = _res.response.toJSON(); - cb(removeErrorStack(response)); + cb(removeStacktrace(response)); }); } @@ -298,7 +298,7 @@ class EntryPoint { // -------------------------------------------------------------------- _broadcast (data) { - const sanitized = removeErrorStack(data); + const sanitized = removeStacktrace(data); debug('[server] broadcasting data through all protocols: %a', sanitized); @@ -316,7 +316,7 @@ class EntryPoint { request.setError(networkError.get('shutting_down')); this.logAccess(connection, request); - cb(removeErrorStack(request.response.toJSON())); + cb(removeStacktrace(request.response.toJSON())); } _notify (data) { @@ -332,7 +332,7 @@ class EntryPoint { } try { - this.protocols.get(client.protocol).notify(removeErrorStack(data)); + this.protocols.get(client.protocol).notify(removeStacktrace(data)); } catch (e) { global.kuzzle.log.error(`[notify] protocol ${client.protocol} failed: ${e.message}`); diff --git a/lib/core/network/protocols/httpwsProtocol.js b/lib/core/network/protocols/httpwsProtocol.js index 59b6e05f78..dace30a760 100644 --- a/lib/core/network/protocols/httpwsProtocol.js +++ b/lib/core/network/protocols/httpwsProtocol.js @@ -32,7 +32,7 @@ const { Request } = require('../../../api/request'); const { KuzzleError } = require('../../../kerror/errors'); const Protocol = require('./protocol'); const ClientConnection = require('../clientConnection'); -const removeErrorStack = require('../removeErrorStack'); +const { removeStacktrace } = require('../../../util/stackTrace'); const debug = require('../../../util/debug'); const kerror = require('../../../kerror'); const HttpMessage = require('./httpMessage'); @@ -412,7 +412,7 @@ class HttpWsProtocol extends Protocol { // If a requestId is provided we use it instead of the generated one request.id = requestId || request.id; - const sanitized = removeErrorStack(request.response.toJSON()).content; + const sanitized = removeStacktrace(request.response.toJSON()).content; this.wsSend(socket, Buffer.from(JSON.stringify(sanitized))); } @@ -831,7 +831,7 @@ class HttpWsProtocol extends Protocol { ? error : kerrorHTTP.getFrom(error, 'unexpected_error', error.message); - const content = Buffer.from(JSON.stringify(removeErrorStack(kerr))); + const content = Buffer.from(JSON.stringify(removeStacktrace(kerr))); debugHTTP('[%s] httpSendError: %a', message.connection.id, kerr); @@ -884,7 +884,7 @@ class HttpWsProtocol extends Protocol { * @returns {Buffer} */ httpRequestToResponse (request, message) { - let data = removeErrorStack(request.response.toJSON()); + let data = removeStacktrace(request.response.toJSON()); if (message.requestId !== data.requestId) { data.requestId = message.requestId; diff --git a/lib/core/network/protocols/mqttProtocol.js b/lib/core/network/protocols/mqttProtocol.js index 8bbf3faa9d..3ae3b34a64 100644 --- a/lib/core/network/protocols/mqttProtocol.js +++ b/lib/core/network/protocols/mqttProtocol.js @@ -22,12 +22,13 @@ 'use strict'; const net = require('net'); + const aedes = require('aedes'); const ClientConnection = require('../clientConnection'); const Protocol = require('./protocol'); const { Request } = require('../../../api/request'); -const removeErrorStack = require('../removeErrorStack'); +const { removeStacktrace } = require('../../../util/stackTrace'); const kerror = require('../../../kerror').wrap('network', 'mqtt'); const debug = require('../../../util/debug')('kuzzle:network:protocols:mqtt'); @@ -238,7 +239,7 @@ class MqttProtocol extends Protocol { connection, error: kerror.getFrom(error, 'unexpected_error', error.message) }); - this._respond(client, removeErrorStack(errReq.response.toJSON())); + this._respond(client, removeStacktrace(errReq.response.toJSON())); } _authorizePublish (client, packet, callback) { diff --git a/lib/core/network/removeErrorStack.js b/lib/core/network/removeErrorStack.js deleted file mode 100644 index 7d782cee75..0000000000 --- a/lib/core/network/removeErrorStack.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Kuzzle, a backend software, self-hostable and ready to use - * to power modern apps - * - * Copyright 2015-2020 Kuzzle - * mailto: support AT kuzzle.io - * website: http://kuzzle.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict'; - -const util = require('util'); - -const { hilightUserCode } = require('../../util/stackTrace'); - -/** - * utility method: must be invoked by all protocols to remove stack traces - * from payloads before sending them - * @param {Error|Object} data - expected: plain error object or serialized - * request response - * @returns {*} return the data minus the stack trace - */ -module.exports = data => { - if (util.isError(data)) { - if (global.NODE_ENV !== 'development') { - data.stack = undefined; - } - else { - data.stack = data.stack.split('\n').map(hilightUserCode).join('\n'); - } - } - else if (data && data.content && data.content.error) { - if (global.NODE_ENV !== 'development') { - data.content.error.stack = undefined; - } - else { - data.content.error.stack = data.content.error.stack - ? data.content.error.stack.split('\n').map(hilightUserCode).join('\n') - : undefined; - } - } - - return data; -}; diff --git a/lib/core/network/router.js b/lib/core/network/router.js index 3ef752a0ef..e84e5dc66d 100644 --- a/lib/core/network/router.js +++ b/lib/core/network/router.js @@ -24,7 +24,7 @@ const { Request } = require('../../api/request'); const kerror = require('../../kerror'); const HttpRouter = require('./httpRouter'); -const removeErrorStack = require('./removeErrorStack'); +const { removeStacktrace } = require('../../util/stackTrace'); /** * @class Router @@ -197,7 +197,7 @@ class Router { _res.setError(err); } - cb(removeErrorStack(_res)); + cb(removeStacktrace(_res)); }); } }); diff --git a/lib/core/shared/sdk/embeddedSdk.ts b/lib/core/shared/sdk/embeddedSdk.ts index cef378dada..d4aa477697 100644 --- a/lib/core/shared/sdk/embeddedSdk.ts +++ b/lib/core/shared/sdk/embeddedSdk.ts @@ -29,7 +29,7 @@ import { } from 'kuzzle-sdk'; import { RequestPayload, ResponsePayload } from '../../../types'; -import FunnelProtocol from './funnelProtocol'; +import { FunnelProtocol } from './funnelProtocol'; import { isPlainObject } from '../../../util/safeObject'; import * as kerror from '../../../kerror'; import ImpersonatedSDK from './impersonatedSdk'; @@ -94,7 +94,8 @@ export class EmbeddedSDK extends Kuzzle { realtime: EmbeddedRealtime; constructor () { - super(new FunnelProtocol(), { autoResubscribe: false }); + // FunnelProtocol is not technically a valid SDK protocol + super(new FunnelProtocol() as any, { autoResubscribe: false }); } /** diff --git a/lib/core/shared/sdk/funnelProtocol.js b/lib/core/shared/sdk/funnelProtocol.ts similarity index 65% rename from lib/core/shared/sdk/funnelProtocol.js rename to lib/core/shared/sdk/funnelProtocol.ts index e4e2780e1b..77bdf4f65b 100644 --- a/lib/core/shared/sdk/funnelProtocol.js +++ b/lib/core/shared/sdk/funnelProtocol.ts @@ -19,21 +19,19 @@ * limitations under the License. */ -'use strict'; +import { KuzzleEventEmitter } from 'kuzzle-sdk'; -const { KuzzleEventEmitter } = require('kuzzle-sdk'); +import { RequestPayload } from '../../../types'; +import { Request } from '../../../api/request'; +import * as kerror from '../../../kerror'; -const { Request } = require('../../../api/request'); -const kerror = require('../../../kerror'); -const { hilightUserCode } = require('../../../util/stackTrace'); +export class FunnelProtocol extends KuzzleEventEmitter { + private id = 'funnel'; + private connectionId: string = null; -class FunnelProtocol extends KuzzleEventEmitter { constructor () { super(); - this.id = 'funnel'; - this.connectionId = null; - /** * Realtime notifications are sent by the InternalProtocol * through the internal event system. @@ -51,7 +49,7 @@ class FunnelProtocol extends KuzzleEventEmitter { /** * Hydrate the user and execute SDK query */ - async query (request) { + async query (request: RequestPayload) { if (! this.connectionId) { this.connectionId = await global.kuzzle.ask('core:network:internal:connectionId:get'); } @@ -75,31 +73,21 @@ class FunnelProtocol extends KuzzleEventEmitter { const kuzzleRequest = new Request(request, requestOptions); - try { - if (requestOptions.user && request.__checkRights__ - && ! await requestOptions.user.isActionAllowed(kuzzleRequest) - ) { - throw kerror.get( - 'security', - 'rights', - 'forbidden', - kuzzleRequest.input.controller, - kuzzleRequest.input.action, - requestOptions.user._id); - } - - const result = await global.kuzzle.funnel.executePluginRequest(kuzzleRequest); - - return { result }; + if ( requestOptions.user + && request.__checkRights__ + && ! await requestOptions.user.isActionAllowed(kuzzleRequest) + ) { + throw kerror.get( + 'security', + 'rights', + 'forbidden', + kuzzleRequest.input.controller, + kuzzleRequest.input.action, + requestOptions.user._id); } - catch (error) { - if (error.stack) { - error.stack = error.stack.split('\n').map(hilightUserCode).join('\n'); - } - throw error; - } + const result = await global.kuzzle.funnel.executePluginRequest(kuzzleRequest); + + return { result }; } } - -module.exports = FunnelProtocol; diff --git a/lib/kerror/index.ts b/lib/kerror/index.ts index 6c2171773d..f4be0abf61 100644 --- a/lib/kerror/index.ts +++ b/lib/kerror/index.ts @@ -26,7 +26,6 @@ import { JSONObject } from 'kuzzle-sdk'; import { domains as internalDomains } from './codes'; import * as errors from './errors'; -import { hilightUserCode } from '../util/stackTrace'; import { KuzzleError } from './errors'; import { ErrorDefinition, ErrorDomains } from '../types'; @@ -130,8 +129,7 @@ function cleanStackTrace (error: KuzzleError): void { // filter all lines related to the kerror object return ! line.includes(currentFileName); - }) - .map(hilightUserCode); + }); // insert a deletion message in place of the new error instantiation line newStack[messageLength] = ' [...Kuzzle internal calls deleted...]'; diff --git a/lib/types/KuzzleDocument.ts b/lib/types/KuzzleDocument.ts index 2838034d97..69dfbac820 100644 --- a/lib/types/KuzzleDocument.ts +++ b/lib/types/KuzzleDocument.ts @@ -1,6 +1,8 @@ import { JSONObject } from 'kuzzle-sdk'; -// Should be in the SDK instead +/** + * @deprecated Use KDocument instead (See https://docs.kuzzle.io/sdk/js/7/essentials/strong-typing/) + */ export interface KuzzleDocument { _id: string; diff --git a/lib/util/stackTrace.js b/lib/util/stackTrace.js index 0b9b4ab3a6..502ca243ed 100644 --- a/lib/util/stackTrace.js +++ b/lib/util/stackTrace.js @@ -21,33 +21,72 @@ 'use strict'; +const util = require('util'); + +const MARKER = '>'; +const PADDING = ' '; + /** - * Hilight user code - * - * e.g. - * at BackendController._add (/home/kuzzle/lib/core/application/backend.ts:261:28) - * at BackendController.register (/home/kuzzle/lib/core/application/backend.ts:187:10) - * > at registerFoo (/home/aschen/projets/app/test.ts:12:18) - * > at init (/home/aschen/projets/app/test.ts:8:3) - * at Module._compile (internal/modules/cjs/loader.js:1133:30) - */ + * Hilight user code + * + * e.g. + * at BackendController._add (/home/kuzzle/lib/core/application/backend.ts:261:28) + * at BackendController.register (/home/kuzzle/lib/core/application/backend.ts:187:10) + * >>>> at registerFoo (/home/aschen/projets/app/test.ts:12:18) + * >>>> at init (/home/aschen/projets/app/test.ts:8:3) + * at Module._compile (internal/modules/cjs/loader.js:1133:30) + */ function hilightUserCode (line) { // ignore first line (error message) or already enhanced - if (! line.includes(' at ') || line.startsWith('>')) { + if (! line.includes(' at ') || line.startsWith(MARKER)) { return line; } - if ( line.includes('kuzzle/lib/') // ignore kuzzle code - || (line.indexOf('at /') === -1 && line.charAt(line.indexOf('(') + 1) !== '/') // ignore node internal - || line.includes('node_modules') // ignore dependencies - ) { - return ' ' + line; + const isKuzzleCode = line.includes('kuzzle/lib/'); + const isNodeCode = ! line.includes('at /') + && ! line.includes('at async /') + && line.charAt(line.indexOf('(') + 1) !== '/'; + const isModuleCode = line.includes('node_modules'); + if (isKuzzleCode || isNodeCode || isModuleCode) { + return PADDING + line; } // hilight user code - return '>' + line; + return MARKER + line; +} + +/** + * utility method: must be invoked by all protocols to remove stack traces + * from payloads before sending them + * @param {Error|Object} data - expected: plain error object or serialized + * request response + * @returns {*} return the data minus the stack trace + */ +function removeStacktrace (data) { + if (util.types.isNativeError(data)) { + if (global.NODE_ENV !== 'development') { + data.stack = undefined; + } + else { + data.stack = data.stack.split('\n').map(hilightUserCode).join('\n'); + } + } + else if (data && data.content && data.content.error) { + // @todo v3: stack should be removed only for "production" env + if (global.NODE_ENV !== 'development') { + data.content.error.stack = undefined; + } + else { + data.content.error.stack = data.content.error.stack + ? data.content.error.stack.split('\n').map(hilightUserCode).join('\n') + : undefined; + } + } + + return data; } module.exports = { hilightUserCode, + removeStacktrace, }; diff --git a/package.json b/package.json index 2d8e43ee3b..f88a3e3cc7 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "koncorde": "^4.0.2", "kuzzle-plugin-auth-passport-local": "^6.3.6", "kuzzle-plugin-logger": "^3.0.3", - "kuzzle-sdk": "7.9.1", + "kuzzle-sdk": "^7.9.1", "kuzzle-vault": "^2.0.4", "lodash": "4.17.21", "long": "^5.2.0", diff --git a/test/core/shared/sdk/funnelProtocol.test.js b/test/core/shared/sdk/funnelProtocol.test.js index 0a24939d98..d237ebac03 100644 --- a/test/core/shared/sdk/funnelProtocol.test.js +++ b/test/core/shared/sdk/funnelProtocol.test.js @@ -10,7 +10,7 @@ const { ForbiddenError } = require('../../../../index'); const { User } = require('../../../../lib/model/security/user'); -const FunnelProtocol = require('../../../../lib/core/shared/sdk/funnelProtocol'); +const { FunnelProtocol } = require('../../../../lib/core/shared/sdk/funnelProtocol'); describe('Test: sdk/funnelProtocol', () => { let request; @@ -55,12 +55,6 @@ describe('Test: sdk/funnelProtocol', () => { }); }); - describe('#isReady', () => { - it('should return true', () => { - should(funnelProtocol.isReady()).be.true(); - }); - }); - describe('#query', () => { beforeEach(() => { kuzzle.ask diff --git a/test/util/stacktrace.test.js b/test/util/stacktrace.test.js new file mode 100644 index 0000000000..f84a05e952 --- /dev/null +++ b/test/util/stacktrace.test.js @@ -0,0 +1,56 @@ +'use strict'; + +const should = require('should'); + +const { hilightUserCode } = require('../../lib/util/stackTrace'); + +describe('#hilightUserCode', () => { + it('should ignore the error message', () => { + const line = 'Something is wrong with people'; + + const stack = hilightUserCode(line); + + should(stack).be.eql(line); + }); + + it('should ignore already enhanced lines', () => { + const line = '> at BackendController._add (/home/kuzzle/lib/core/application/backend.ts:261:28)'; + + const stack = hilightUserCode(line); + + should(stack).be.eql(line); + }); + + it('should add padding for line about kuzzle code', () => { + const line = ' at BackendController._add (/home/kuzzle/lib/core/application/backend.ts:261:28)'; + + const stack = hilightUserCode(line); + + should(stack).be.eql(' ' + line); + }); + + it('should add padding for line about node internal', () => { + const line = ' at processImmediate (internal/timers.js:462:21)'; + + const stack = hilightUserCode(line); + + should(stack).be.eql(' ' + line); + }); + + it('should add padding for line about module', () => { + const line = ' at Assertion.value (node_modules/should/cjs/should.js:356:19)'; + + const stack = hilightUserCode(line); + + should(stack).be.eql(' ' + line); + }); + + it('should hilight user code', () => { + const line = ' at registerFoo (/home/aschen/projets/app/test.ts:12:18)'; + + const stack = hilightUserCode(line); + + should(stack).be.eql('>' + line); + }); + +});