Skip to content

Commit 696d418

Browse files
authored
Fix erroneous requests due to HTTP-specific settings (#497)
# Description The new `verb` HTTP specific option was handled by the SDK controllers, impacting all protocols, including WebSocket and MQTT Impacted routes: `document:mGet` and `security:mGetUsers` Those 2 routes send the `ids` argument to Kuzzle as a string, instead sending it as an array. Fortunately, Kuzzle makes no difference between requests sent using HTTP or WebSocket or any other protocol, and it sanitizes the request in such a way that this bug is invisible to common users. But documents aren't sanitized the same way when the API route is invoked by a plugin. And when that happens, the two routes mentioned earlier make any plugin invoking them crash. This PR removes any specificities from the SDK controllers and, instead, makes the HTTP protocol handle the new `verb` option itself. To do that, it simply converts any `body` object provided to a querystring if the selected API route uses the `GET` method. The conversion code was already there, it just needed a small fix when converting arrays (that conversion was never used before). # Other changes * Dependencies update * Migrate the deprecate `mocha.opts` file to `.mocharc.json` * Fix documentation deadlinks # How to reproduce Create a small plugin, and invoke `document:mGet` using `this.context.sdk.document.mGet` The following error ensues: `Wrong type for argument "ids" (expected: array)`
1 parent 3d1e7c0 commit 696d418

File tree

12 files changed

+2650
-2669
lines changed

12 files changed

+2650
-2669
lines changed

.mocharc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"reporter": "dot",
3+
"recursive": true,
4+
"slow": 2000,
5+
"timeout": 10000,
6+
"require": ["should-sinon"]
7+
}

doc/7/core-classes/kuzzle-error/introduction/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ order: 0
1010

1111
Inherits from the standard `Error` class.
1212

13-
The KuzzleError class represents an [error response from Kuzzle API](/core/2/api/essentials/errors).
13+
The KuzzleError class represents an [error response from Kuzzle API](/core/2/api/essentials/error-handling).

doc/7/essentials/error-handling/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ All SDK methods return a promise, that can be rejected with a `KuzzleError` valu
1717
| `status` | <pre>number</pre> | Status following [HTTP Standards](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) |
1818
| `stack` | <pre>string</pre> | Error stacktrace (Only in development mode) |
1919

20-
You can find a detailed list of possible errors messages and statuses in the [documentation API](/core/2/api/essentials/errors).
20+
You can find a detailed list of possible errors messages and statuses in the [documentation API](/core/2/api/essentials/error-handling).
2121

2222
#### Example with a promise chain
2323

package-lock.json

Lines changed: 2548 additions & 2570 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "kuzzle-sdk",
3-
"version": "7.1.0",
3+
"version": "7.1.1",
44
"description": "Official Javascript SDK for Kuzzle",
55
"author": "The Kuzzle Team <[email protected]>",
66
"repository": {
@@ -39,32 +39,32 @@
3939
"main": "index.js",
4040
"license": "Apache-2.0",
4141
"dependencies": {
42-
"@babel/core": "^7.8.4",
43-
"@babel/preset-env": "^7.8.4",
42+
"@babel/core": "^7.9.0",
43+
"@babel/preset-env": "^7.9.0",
4444
"audit": "0.0.6",
45-
"babel-loader": "^8.0.6",
45+
"babel-loader": "^8.1.0",
4646
"kuzdoc": "^1.2.2",
4747
"min-req-promise": "^1.0.1",
48-
"ora": "^3.4.0",
49-
"webpack": "^4.41.6",
50-
"ws": "^6.2.1"
48+
"ora": "^4.0.3",
49+
"webpack": "^4.42.1",
50+
"ws": "^7.2.3"
5151
},
5252
"devDependencies": {
5353
"codecov": "^3.6.5",
54-
"cucumber": "^5.1.0",
55-
"eslint": "^5.16.0",
54+
"cucumber": "^6.0.5",
55+
"eslint": "^6.8.0",
5656
"eslint-friendly-formatter": "^4.0.1",
57-
"eslint-loader": "^2.2.1",
58-
"lolex": "^5.1.2",
59-
"mocha": "6.2.0",
57+
"eslint-loader": "^3.0.3",
58+
"lolex": "^6.0.0",
59+
"mocha": "7.1.1",
6060
"mock-require": "^3.0.3",
61-
"nyc": "^14.1.1",
61+
"nyc": "^15.0.0",
6262
"proxyquire": "^2.1.3",
6363
"retry": "^0.12.0",
64-
"rewire": "^4.0.1",
64+
"rewire": "^5.0.0",
6565
"should": "13.2.3",
6666
"should-sinon": "0.0.6",
67-
"sinon": "^7.5.0"
67+
"sinon": "^9.0.1"
6868
},
6969
"engines": {
7070
"node": ">= 10.13.0"

src/Kuzzle.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ class Kuzzle extends KuzzleEventEmitter {
382382
}
383383

384384
/**
385-
* This is a low-level method, exposed to allow advanced SDK users to bypass high-level methods.
385+
* This is a low-level method, exposed to allow advanced SDK users to bypass
386+
* high-level methods.
386387
* Base method used to send read queries to Kuzzle
387388
*
388389
* Takes an optional argument object with the following properties:

src/controllers/document.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,10 @@ class DocumentController extends BaseController {
136136
const request = {
137137
index,
138138
collection,
139-
action: 'mGet'
139+
action: 'mGet',
140+
body: {ids}
140141
};
141142

142-
if (options.verb === 'POST') {
143-
request.body = {ids};
144-
}
145-
else {
146-
request.ids = ids.join();
147-
}
148143
return this.query(request, options)
149144
.then(response => response.result);
150145
}

src/controllers/security/index.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,15 +357,9 @@ class SecurityController extends BaseController {
357357

358358
mGetUsers (ids, options = {}) {
359359
const request = {
360-
action: 'mGetUsers'
360+
action: 'mGetUsers',
361+
body: {ids}
361362
};
362-
363-
if (options.verb === 'POST') {
364-
request.body = { ids };
365-
}
366-
else {
367-
request.ids = ids.join();
368-
}
369363

370364
return this.query(request, options)
371365
.then(response => response.result.hits.map(hit => new User(this.kuzzle, hit._id, hit._source)));

src/protocols/http.js

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -136,26 +136,41 @@ class HttpWrapper extends KuzzleAbstractProtocol {
136136
* @returns {Promise<any>}
137137
*/
138138
send (data, options = {}) {
139-
const
140-
payload = {
141-
action: undefined,
142-
body: undefined,
143-
collection: undefined,
144-
controller: undefined,
145-
headers: {
146-
'Content-Type': 'application/json'
147-
},
148-
index: undefined,
149-
meta: undefined,
150-
requestId: undefined,
139+
const route = this.routes[data.controller]
140+
&& this.routes[data.controller][data.action];
141+
142+
if (! route) {
143+
const error = new Error(`No URL found for "${data.controller}:${data.action}".`);
144+
this.emit(data.requestId, { status: 400, error });
145+
return;
146+
}
147+
148+
const method = options.verb || route.verb;
149+
150+
const payload = {
151+
action: undefined,
152+
body: undefined,
153+
collection: undefined,
154+
controller: undefined,
155+
headers: {
156+
'Content-Type': 'application/json'
151157
},
152-
queryArgs = {};
158+
index: undefined,
159+
meta: undefined,
160+
requestId: undefined,
161+
};
162+
const queryArgs = {};
153163

154164
for (const key of Object.keys(data)) {
155165
const value = data[key];
156166

157167
if (key === 'body') {
158-
payload.body = JSON.stringify(value);
168+
if (method === 'GET') {
169+
Object.assign(queryArgs, value);
170+
}
171+
else {
172+
payload.body = JSON.stringify(value);
173+
}
159174
}
160175
else if (key === 'jwt') {
161176
payload.headers.authorization = 'Bearer ' + value;
@@ -171,24 +186,10 @@ class HttpWrapper extends KuzzleAbstractProtocol {
171186
}
172187
}
173188

174-
const route = this.routes[payload.controller]
175-
&& this.routes[payload.controller][payload.action];
189+
const regex = /\/:([^/]*)/;
176190

177-
if (! route) {
178-
const error = new Error(`No URL found for "${payload.controller}:${payload.action}".`);
179-
180-
this.emit(payload.requestId, { status: 400, error });
181-
182-
return;
183-
}
184-
185-
const
186-
method = options.verb || route.verb,
187-
regex = /\/:([^/]*)/;
188-
189-
let
190-
url = route.url,
191-
matches = regex.exec(url);
191+
let url = route.url;
192+
let matches = regex.exec(url);
192193

193194
while (matches) {
194195
const urlParam = data[ matches[1] ];
@@ -214,8 +215,7 @@ class HttpWrapper extends KuzzleAbstractProtocol {
214215
const value = queryArgs[key];
215216

216217
if (Array.isArray(value)) {
217-
queryString.push(...value.map(v => `${key}=${v}`));
218-
218+
queryString.push(`${key}=${value.join()}`);
219219
}
220220
else if (typeof value === 'boolean') {
221221
// In Kuzzle, an optional boolean option is set to true if present in

test/controllers/document.test.js

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -256,38 +256,23 @@ describe('Document Controller', () => {
256256
],
257257
total: 2
258258
};
259-
it('should call document/mGet POST query and return a Promise which resolves the list of documents', () => {
259+
it('should call document/mGet query and return a Promise which resolves to the list of documents', () => {
260260
kuzzle.query.resolves({result});
261-
options.verb = 'POST';
262-
return kuzzle.document.mGet('index', 'collection', ['document1', 'document2'], options)
263-
.then(res => {
264-
should(kuzzle.query)
265-
.be.calledOnce()
266-
.be.calledWith({
267-
controller: 'document',
268-
action: 'mGet',
269-
index: 'index',
270-
collection: 'collection',
271-
body: {ids: ['document1', 'document2']}
272-
}, options);
273261

274-
should(res).be.equal(result);
275-
});
276-
});
277-
it('should call document/mGet GET query and return a Promise which resolves the list of documents', () => {
278-
kuzzle.query.resolves({ result });
279-
options.verb = undefined;
280-
return kuzzle.document.mGet('index', 'collection', ['document1', 'document2'], options)
262+
return kuzzle.document
263+
.mGet('index', 'collection', ['document1', 'document2'], options)
281264
.then(res => {
282265
should(kuzzle.query)
283266
.be.calledOnce()
284-
.be.calledWith({
285-
controller: 'document',
286-
action: 'mGet',
287-
index: 'index',
288-
collection: 'collection',
289-
ids: 'document1,document2'
290-
}, options);
267+
.be.calledWith(
268+
{
269+
controller: 'document',
270+
action: 'mGet',
271+
index: 'index',
272+
collection: 'collection',
273+
body: {ids: ['document1', 'document2']}
274+
},
275+
options);
291276

292277
should(res).be.equal(result);
293278
});

0 commit comments

Comments
 (0)