@contrast/assess 1.46.1 → 1.46.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/package.json +14 -11
  2. package/lib/crypto-analysis/install/crypto.test.js +0 -146
  3. package/lib/crypto-analysis/install/math.test.js +0 -65
  4. package/lib/dataflow/index.test.js +0 -36
  5. package/lib/dataflow/propagation/index.test.js +0 -103
  6. package/lib/dataflow/propagation/install/JSON/index.test.js +0 -50
  7. package/lib/dataflow/propagation/install/JSON/parse-fn.test.js +0 -232
  8. package/lib/dataflow/propagation/install/JSON/parse.test.js +0 -968
  9. package/lib/dataflow/propagation/install/JSON/stringify.test.js +0 -265
  10. package/lib/dataflow/propagation/install/array-prototype-join.test.js +0 -106
  11. package/lib/dataflow/propagation/install/buffer.test.js +0 -112
  12. package/lib/dataflow/propagation/install/contrast-methods/add.test.js +0 -94
  13. package/lib/dataflow/propagation/install/contrast-methods/index.test.js +0 -49
  14. package/lib/dataflow/propagation/install/contrast-methods/number.test.js +0 -50
  15. package/lib/dataflow/propagation/install/contrast-methods/string.test.js +0 -148
  16. package/lib/dataflow/propagation/install/contrast-methods/tag.test.js +0 -145
  17. package/lib/dataflow/propagation/install/decode-uri-component.test.js +0 -78
  18. package/lib/dataflow/propagation/install/ejs/escape-xml.test.js +0 -69
  19. package/lib/dataflow/propagation/install/ejs/template.test.js +0 -62
  20. package/lib/dataflow/propagation/install/encode-uri.test.js +0 -83
  21. package/lib/dataflow/propagation/install/escape-html.test.js +0 -71
  22. package/lib/dataflow/propagation/install/escape.test.js +0 -73
  23. package/lib/dataflow/propagation/install/fastify-send.test.js +0 -42
  24. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.test.js +0 -71
  25. package/lib/dataflow/propagation/install/isnumeric-0.test.js +0 -58
  26. package/lib/dataflow/propagation/install/joi/any.test.js +0 -270
  27. package/lib/dataflow/propagation/install/joi/array.test.js +0 -912
  28. package/lib/dataflow/propagation/install/joi/boolean.test.js +0 -103
  29. package/lib/dataflow/propagation/install/joi/expression.test.js +0 -76
  30. package/lib/dataflow/propagation/install/joi/index.test.js +0 -39
  31. package/lib/dataflow/propagation/install/joi/number.test.js +0 -103
  32. package/lib/dataflow/propagation/install/joi/object.test.js +0 -119
  33. package/lib/dataflow/propagation/install/joi/ref.test.js +0 -607
  34. package/lib/dataflow/propagation/install/joi/string-schema.test.js +0 -513
  35. package/lib/dataflow/propagation/install/mongoose/index.test.js +0 -42
  36. package/lib/dataflow/propagation/install/mongoose/schema-map.test.js +0 -348
  37. package/lib/dataflow/propagation/install/mongoose/schema-mixed.test.js +0 -511
  38. package/lib/dataflow/propagation/install/mongoose/schema-string.test.js +0 -199
  39. package/lib/dataflow/propagation/install/mustache-escape.test.js +0 -62
  40. package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +0 -74
  41. package/lib/dataflow/propagation/install/parse-int.test.js +0 -48
  42. package/lib/dataflow/propagation/install/path/basename.test.js +0 -143
  43. package/lib/dataflow/propagation/install/path/dirname.test.js +0 -167
  44. package/lib/dataflow/propagation/install/path/extname.test.js +0 -141
  45. package/lib/dataflow/propagation/install/path/format.test.js +0 -250
  46. package/lib/dataflow/propagation/install/path/index.test.js +0 -45
  47. package/lib/dataflow/propagation/install/path/join-and-resolve.test.js +0 -485
  48. package/lib/dataflow/propagation/install/path/normalize.test.js +0 -176
  49. package/lib/dataflow/propagation/install/path/parse.test.js +0 -238
  50. package/lib/dataflow/propagation/install/path/relative.test.js +0 -239
  51. package/lib/dataflow/propagation/install/path/toNamespacedPath.test.js +0 -158
  52. package/lib/dataflow/propagation/install/pug/index.test.js +0 -55
  53. package/lib/dataflow/propagation/install/pug-runtime-escape.test.js +0 -69
  54. package/lib/dataflow/propagation/install/querystring/escape.test.js +0 -63
  55. package/lib/dataflow/propagation/install/querystring/index.test.js +0 -40
  56. package/lib/dataflow/propagation/install/querystring/parse.test.js +0 -272
  57. package/lib/dataflow/propagation/install/querystring/stringify.test.js +0 -301
  58. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.test.js +0 -283
  59. package/lib/dataflow/propagation/install/send.test.js +0 -63
  60. package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +0 -74
  61. package/lib/dataflow/propagation/install/sequelize/sql-string.test.js +0 -119
  62. package/lib/dataflow/propagation/install/sql-template-strings.test.js +0 -100
  63. package/lib/dataflow/propagation/install/string/concat.test.js +0 -145
  64. package/lib/dataflow/propagation/install/string/format-methods.test.js +0 -74
  65. package/lib/dataflow/propagation/install/string/html-methods.test.js +0 -177
  66. package/lib/dataflow/propagation/install/string/index.test.js +0 -103
  67. package/lib/dataflow/propagation/install/string/match-all.test.js +0 -412
  68. package/lib/dataflow/propagation/install/string/match.test.js +0 -374
  69. package/lib/dataflow/propagation/install/string/replace.test.js +0 -601
  70. package/lib/dataflow/propagation/install/string/slice.test.js +0 -278
  71. package/lib/dataflow/propagation/install/string/split.test.js +0 -513
  72. package/lib/dataflow/propagation/install/string/substring.test.js +0 -251
  73. package/lib/dataflow/propagation/install/string/trim.test.js +0 -135
  74. package/lib/dataflow/propagation/install/unescape.test.js +0 -78
  75. package/lib/dataflow/propagation/install/url/domain-parsers.test.js +0 -63
  76. package/lib/dataflow/propagation/install/url/parse.test.js +0 -391
  77. package/lib/dataflow/propagation/install/url/searchParams.test.js +0 -538
  78. package/lib/dataflow/propagation/install/url/url.test.js +0 -466
  79. package/lib/dataflow/propagation/install/util-format.test.js +0 -336
  80. package/lib/dataflow/propagation/install/validator/hooks.test.js +0 -211
  81. package/lib/dataflow/sinks/index.test.js +0 -78
  82. package/lib/dataflow/sinks/install/child-process.test.js +0 -338
  83. package/lib/dataflow/sinks/install/eval.test.js +0 -95
  84. package/lib/dataflow/sinks/install/express/index.test.js +0 -33
  85. package/lib/dataflow/sinks/install/express/reflected-xss.test.js +0 -109
  86. package/lib/dataflow/sinks/install/express/unvalidated-redirect.test.js +0 -144
  87. package/lib/dataflow/sinks/install/fastify/index.test.js +0 -32
  88. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.test.js +0 -130
  89. package/lib/dataflow/sinks/install/fs.test.js +0 -138
  90. package/lib/dataflow/sinks/install/function.test.js +0 -103
  91. package/lib/dataflow/sinks/install/hapi/index.test.js +0 -32
  92. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.test.js +0 -130
  93. package/lib/dataflow/sinks/install/http/index.test.js +0 -33
  94. package/lib/dataflow/sinks/install/http/request.test.js +0 -184
  95. package/lib/dataflow/sinks/install/http/server-response.test.js +0 -160
  96. package/lib/dataflow/sinks/install/koa/index.test.js +0 -32
  97. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.test.js +0 -200
  98. package/lib/dataflow/sinks/install/libxmljs.test.js +0 -158
  99. package/lib/dataflow/sinks/install/marsdb.test.js +0 -166
  100. package/lib/dataflow/sinks/install/mongodb.test.js +0 -617
  101. package/lib/dataflow/sinks/install/mssql.test.js +0 -134
  102. package/lib/dataflow/sinks/install/mysql.test.js +0 -224
  103. package/lib/dataflow/sinks/install/node-serialize.test.js +0 -83
  104. package/lib/dataflow/sinks/install/postgres.test.js +0 -152
  105. package/lib/dataflow/sinks/install/restify.test.js +0 -140
  106. package/lib/dataflow/sinks/install/sequelize.test.js +0 -100
  107. package/lib/dataflow/sinks/install/sqlite3.test.js +0 -118
  108. package/lib/dataflow/sinks/install/vm.test.js +0 -326
  109. package/lib/dataflow/sources/handler.test.js +0 -501
  110. package/lib/dataflow/sources/index.test.js +0 -60
  111. package/lib/dataflow/sources/install/body-parser1.test.js +0 -244
  112. package/lib/dataflow/sources/install/busboy.test.js +0 -152
  113. package/lib/dataflow/sources/install/cookie-parser1.test.js +0 -141
  114. package/lib/dataflow/sources/install/express/params.test.js +0 -110
  115. package/lib/dataflow/sources/install/express/parsedUrl.test.js +0 -107
  116. package/lib/dataflow/sources/install/fastify/fastify.test.js +0 -207
  117. package/lib/dataflow/sources/install/fastify/index.test.js +0 -33
  118. package/lib/dataflow/sources/install/formidable1.test.js +0 -119
  119. package/lib/dataflow/sources/install/graphql-http.test.js +0 -133
  120. package/lib/dataflow/sources/install/hapi/hapi.test.js +0 -171
  121. package/lib/dataflow/sources/install/hapi/index.test.js +0 -33
  122. package/lib/dataflow/sources/install/http.test.js +0 -149
  123. package/lib/dataflow/sources/install/koa/index.test.js +0 -40
  124. package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +0 -160
  125. package/lib/dataflow/sources/install/koa/koa-multer.test.js +0 -197
  126. package/lib/dataflow/sources/install/koa/koa-routers.test.js +0 -146
  127. package/lib/dataflow/sources/install/koa/koa2.test.js +0 -145
  128. package/lib/dataflow/sources/install/multer1.test.js +0 -143
  129. package/lib/dataflow/sources/install/qs6.test.js +0 -131
  130. package/lib/dataflow/sources/install/querystring.test.js +0 -82
  131. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +0 -86
  132. package/lib/dataflow/sources/install/restify/index.test.js +0 -38
  133. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +0 -140
  134. package/lib/dataflow/sources/install/restify/router.test.js +0 -81
  135. package/lib/dataflow/tag-utils-complete.test.js +0 -27
  136. package/lib/dataflow/tag-utils.test.js +0 -192
  137. package/lib/dataflow/tracker.test.js +0 -216
  138. package/lib/dataflow/utils/is-safe-content-type.test.js +0 -16
  139. package/lib/dataflow/utils/is-vulnerable.test.js +0 -115
  140. package/lib/event-factory.test.js +0 -326
  141. package/lib/get-policy.test.js +0 -194
  142. package/lib/get-source-context.test.js +0 -161
  143. package/lib/index.test.js +0 -45
  144. package/lib/make-source-context.test.js +0 -50
  145. package/lib/response-scanning/handlers/index.test.js +0 -419
  146. package/lib/response-scanning/handlers/utils.test.js +0 -380
  147. package/lib/response-scanning/index.test.js +0 -41
  148. package/lib/response-scanning/install/http.test.js +0 -175
  149. package/lib/rule-scopes.test.js +0 -27
  150. package/lib/sampler/common.test.js +0 -101
  151. package/lib/sampler/index.test.js +0 -313
  152. package/lib/session-configuration/handlers.test.js +0 -84
  153. package/lib/session-configuration/index.test.js +0 -36
  154. package/lib/session-configuration/install/express-session.test.js +0 -218
  155. package/lib/session-configuration/install/fastify-cookie.test.js +0 -63
  156. package/lib/session-configuration/install/hapi.test.js +0 -269
  157. package/lib/session-configuration/install/koa.test.js +0 -92
@@ -1,912 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const util = require('util');
5
- const { initAssessFixture } = require('@contrast/test/fixtures');
6
- const {
7
- DataflowTag: {
8
- UNTRUSTED,
9
- STRING_TYPE_CHECKED,
10
- CUSTOM_VALIDATED
11
- }
12
- } = require('@contrast/common');
13
-
14
- function makeTag(string, tag = UNTRUSTED) {
15
- return { [tag]: [0, string.length - 1] };
16
- }
17
-
18
- function makeTags(string, ...tags) {
19
- const ret = {};
20
-
21
- tags.forEach((t) => {
22
- ret[t] = [0, string.length - 1];
23
- });
24
-
25
- return ret;
26
- }
27
-
28
- function makeSchema(chain, base) {
29
- for (let i = 0; i < chain.length; i++) {
30
- const { fn, args = [] } = chain[i];
31
- base = base[fn](...(typeof args === 'function' ? args() : args));
32
- }
33
- return base;
34
- }
35
-
36
- describe('assess dataflow propagation joi array schema validation', function() {
37
- let core, joi, tracker, trackString, simulateRequestScope, makeArraySchema;
38
-
39
- beforeEach(function() {
40
- ({ core, simulateRequestScope, trackString } = initAssessFixture());
41
- tracker = core.assess.dataflow.tracker;
42
- core.config.assess.trust_custom_validators = true;
43
-
44
- core.depHooks.resolve.withArgs({ name: 'joi', file: 'lib/types/string.js', version: '>=17 <18' }).yields(require('joi-17/lib/types/string'));
45
- core.depHooks.resolve.withArgs({ name: 'joi', file: 'lib/types/keys.js', version: '>=17 <18' }).yields(require('joi-17/lib/types/keys'));
46
- core.depHooks.resolve.withArgs({ name: 'joi', file: 'lib/types/any', version: '>=17 <18' }).yields(require('joi-17/lib/types/any'));
47
- core.depHooks.resolve.withArgs({ name: 'joi', file: 'lib/validator', version: '>=17 <18' }).yields(require('joi-17/lib/validator'));
48
- core.depHooks.resolve.withArgs({ name: 'joi', file: 'lib/values.js', version: '>=17 <18' }).yields(require('joi-17/lib/values'));
49
-
50
- require('./index')(core).install();
51
- joi = require('joi-17');
52
- makeArraySchema = function(chain) {
53
- return makeSchema([{ fn: 'array' }, ...chain], joi);
54
- };
55
- });
56
-
57
- afterEach(function() {
58
- Object.keys(require.cache).forEach((key) => {
59
- if (key.includes('joi')) {
60
- delete require.cache[key];
61
- }
62
- });
63
- });
64
-
65
- describe('.items()', function() {
66
- const xyzzy = 'xyzzy';
67
- const plover = 22;
68
- const twisty = 'twisty-passages';
69
- const colossal = 44;
70
- const refData = [xyzzy, plover, twisty, colossal];
71
-
72
- const itemsPositionTests = [
73
- // as soon as the first element is validated as a string the
74
- // remaining elements don't get validated in any way, so they
75
- // won't get tagged. a single string satisfies both .has() and
76
- // .items(joi.string().required()).
77
- {
78
- chain: [
79
- { fn: 'items', args: () => [joi.string().required(), joi.any()] },
80
- { fn: 'max', args: [4] },
81
- { fn: 'min', args: [2] },
82
- { fn: 'length', args: [4] },
83
- { fn: 'has', args: () => [joi.string()] },
84
- { fn: 'single', args: [] }
85
- ],
86
- data: refData,
87
- tags: [
88
- { ...makeTag(xyzzy), ...makeTag(xyzzy, STRING_TYPE_CHECKED) },
89
- { ...makeTag(plover) },
90
- { ...makeTag(twisty) },
91
- { ...makeTag(colossal) }
92
- ]
93
- }
94
- ];
95
-
96
- // build additional tests with .items() in each position of the chain
97
- const refTest = itemsPositionTests[0];
98
- for (let i = 1; i < refTest.chain.length; i++) {
99
- itemsPositionTests[i] = Object.assign({}, refTest);
100
- itemsPositionTests[i].chain = rotate(refTest.chain, i);
101
- }
102
-
103
- itemsPositionTests.forEach((t) => {
104
- const reducer = (acc, i) => {
105
- acc.push(i.fn);
106
- return acc;
107
- };
108
- const text = t.chain.reduce(reducer, []).join(', ');
109
-
110
- it(`works in ${text} order`, function() {
111
- simulateRequestScope(() => {
112
- const schema = makeArraySchema(t.chain);
113
- const data = t.data.map((d) => trackString(d));
114
- const { value, error } = schema.validate(data);
115
-
116
- // check each element of the array
117
- for (let i = 0; i < value.length; i++) {
118
- const v = value[i];
119
- const td = tracker.getData(v);
120
- expect(error).equal(undefined, `${t.fn} for ${v}`);
121
- if (typeof v === 'string') {
122
- expect(td).to.not.be.null;
123
- const tags = t.tags[i];
124
- expect(td.tags).eql(tags, `${t.fn} for ${v}(${i})`);
125
- }
126
- }
127
- });
128
- });
129
- });
130
-
131
- const dataPositionTests = [
132
- // as soon as the first element is validated as a string the
133
- // remaining elements don't get validated in any way, so they
134
- // won't get tagged. a single string satisfies both .has() and
135
- // .items(joi.string().required()).
136
- {
137
- name: 'string required',
138
- chain: [
139
- { fn: 'items', args: () => [joi.string().required(), joi.any()] }
140
- ],
141
- data: undefined,
142
- tags: undefined
143
- },
144
- {
145
- name: 'string and number required',
146
- chain: [
147
- {
148
- fn: 'items',
149
- args: () => [
150
- joi.string().required(),
151
- joi.number().required(),
152
- joi.any()
153
- ]
154
- }
155
- ],
156
- data: undefined,
157
- tags: undefined
158
- }
159
- ];
160
-
161
- //
162
- // for each test generate a test for each permutation of data order
163
- //
164
- for (const t of dataPositionTests) {
165
- const indexPermutations = permutations(Object.keys(refData));
166
- let data;
167
- let dataText;
168
- let tags;
169
- for (const indexes of indexPermutations) {
170
- data = [];
171
- dataText = [];
172
- tags = [];
173
- let firstString = true;
174
- for (let i = 0; i < indexes.length; i++) {
175
- if (typeof refData[indexes[i]] === 'string') {
176
- data[i] = () => trackString(refData[indexes[i]]);
177
- if (firstString) {
178
- tags[i] = {
179
- ...makeTag(refData[indexes[i]]),
180
- ...makeTag(refData[indexes[i]], STRING_TYPE_CHECKED)
181
- };
182
- firstString = false;
183
- } else {
184
- tags[i] = { ...makeTag(refData[indexes[i]]) };
185
- }
186
- } else {
187
- data[i] = refData[indexes[i]];
188
- tags[i] = [];
189
- }
190
- dataText[i] = refData[indexes[i]];
191
- }
192
-
193
- const text = `${t.name} in ${util.format(dataText)}`;
194
-
195
- it(`${text}`, function() {
196
- simulateRequestScope(() => {
197
- data = data.map(i => {
198
- if (typeof i === 'function') {
199
- return i();
200
- }
201
-
202
- return i;
203
- });
204
- // run the test we just constructed
205
- const schema = makeArraySchema(t.chain);
206
- const { value, error } = schema.validate(data);
207
-
208
- // check each element of the array
209
- for (let i = 0; i < value.length; i++) {
210
- const v = value[i];
211
- let td = tracker.getData(v);
212
- expect(error).equal(undefined, `${t.name} for ${v}`);
213
- if (typeof v === 'string') {
214
- expect(td).to.not.be.null;
215
- expect(td.tags).eql(tags[i], `${v}(${i})`);
216
- // and the tags should be correct in the original array too
217
- td = tracker.getData(data[i]);
218
- expect(td).to.not.be.null;
219
- expect(td.tags).eql(tags[i], `${v}(${i})`);
220
- }
221
- }
222
- });
223
- });
224
- }
225
- }
226
- });
227
-
228
- describe('.has()', function() {
229
- it('.has() tags a single string when using .valid()', function() {
230
- simulateRequestScope(() => {
231
- // 'b' is valid so tracking should be removed from it
232
- const schema = joi.string().valid('b');
233
- const objSchema = joi.object().valid({ prop: 'c' });
234
- const data = [trackString('a'), trackString('b'), { prop: trackString('c') }];
235
-
236
- const j = joi.array().has(schema)
237
- .has(objSchema);
238
- const { value, error } = j.validate(data);
239
-
240
- expect(error).undefined;
241
-
242
- const td = tracker.getData(value[0]);
243
- expect(td).to.not.be.null;
244
- expect(td)
245
- .property('tags')
246
- .eql({ ...makeTag('b') });
247
-
248
- expect(tracker.getData(value[1])).to.be.null;
249
- expect(tracker.getData(value[2].prop)).to.be.null;
250
- });
251
- });
252
-
253
- it('.has() tags a single string when using .string()', function() {
254
- simulateRequestScope(() => {
255
- const schema = joi.string();
256
- const a = trackString('a');
257
- const b = trackString('b');
258
- const data = [a, b];
259
-
260
- const j = joi.array().has(schema);
261
- const { value, error } = j.validate(data);
262
-
263
- expect(error).undefined;
264
-
265
- const tagA = makeTags(a, UNTRUSTED, STRING_TYPE_CHECKED);
266
- const tagB = makeTags(b, UNTRUSTED);
267
-
268
- // only the first value that passes is needed by .has()
269
- const checks = [
270
- { name: 'value[0]', value: value[0], tags: tagA },
271
- { name: 'value[1]', value: value[1], tags: tagB },
272
- { name: 'a', value: a, tags: tagA },
273
- { name: 'b', value: b, tags: tagB }
274
- ];
275
- checks.forEach(({ name, value, tags }) => {
276
- const td = tracker.getData(value);
277
- expect(td).to.not.be.null;
278
- expect(td.tags).eql(tags, `for ${name}`);
279
- });
280
- });
281
- });
282
-
283
- it('.has() tags a string embedded in an object', function() {
284
- simulateRequestScope(() => {
285
- const schema = joi.object({ xyzzy: joi.string() });
286
- const a = trackString('a');
287
- const b = trackString('b');
288
- const data = [{ xyzzy: a }, b];
289
-
290
- const j = joi.array().has(schema);
291
- const { value, error } = j.validate(data);
292
-
293
- const tags1 = makeTags('a', UNTRUSTED, STRING_TYPE_CHECKED);
294
- const tags2 = makeTags('b', UNTRUSTED);
295
-
296
- expect(error).undefined;
297
-
298
- let td = tracker.getData(a);
299
- expect(td).to.not.be.null;
300
- expect(td.tags).eql(tags1, 'a should be untrusted');
301
- td = tracker.getData(value[0].xyzzy);
302
- expect(td).to.not.be.null;
303
- expect(td.tags).eql(tags1, 'xyzzy.a should be string-type-checked');
304
-
305
- td = tracker.getData(b);
306
- expect(td).to.not.be.null;
307
- expect(td.tags).eql(tags2, 'b should be untrusted');
308
- td = tracker.getData(value[1]);
309
- expect(td).to.not.be.null;
310
- expect(td.tags).eql(tags2);
311
- });
312
- });
313
- });
314
-
315
- describe('array length constraints', function() {
316
- const lengthTests = [
317
- { fn: 'length', args: [4] },
318
- { fn: 'max', args: [4] },
319
- { fn: 'min', args: [2] },
320
- { fn: 'single', args: [] }
321
- ];
322
-
323
- for (const t of lengthTests) {
324
- const xyzzy = () => trackString('xyzzy');
325
- const plover = () => trackString('plover');
326
- const dragon = () => trackString('dragon');
327
- const twisty = () => trackString('twisty');
328
- let data = [xyzzy, plover, dragon, twisty];
329
-
330
- it(`${t.fn} propagates tags`, function() {
331
- simulateRequestScope(() => {
332
- data = data.map(i => i());
333
- const schema = makeArraySchema([t]);
334
- const { value, error } = schema.validate(data);
335
- expect(error).undefined;
336
-
337
- for (let i = 0; i < value.length; i++) {
338
- const v = value[i];
339
- const tags = data.map((d) => makeTags(d, UNTRUSTED));
340
-
341
- let td = tracker.getData(v);
342
- expect(td).to.not.be.null;
343
- expect(td.tags).eql(tags[i], `for ${v}`);
344
-
345
- td = tracker.getData(data[i]);
346
- expect(td).to.not.be.null;
347
- expect(td.tags).eql(tags[i], `for ${v}`);
348
- }
349
- });
350
- });
351
- }
352
-
353
- it('.single() has no effect on an array element', function() {
354
- simulateRequestScope(() => {
355
- const chain = [
356
- { fn: 'has', args: [joi.string()] },
357
- { fn: 'single', args: [] }
358
- ];
359
-
360
- const xyzzy = trackString('xyzzy');
361
- const tags = makeTags('xyzzy', UNTRUSTED, STRING_TYPE_CHECKED);
362
-
363
- const schema = makeArraySchema(chain);
364
- const { value, error } = schema.validate([xyzzy]);
365
- expect(error).undefined;
366
-
367
- let td = tracker.getData(xyzzy);
368
- expect(td).to.not.be.null;
369
- expect(td.tags).eql(tags);
370
-
371
- td = tracker.getData(value[0]);
372
- expect(td).to.not.be.null;
373
- expect(td.tags).eql(tags);
374
- });
375
- });
376
-
377
- it('.single() propagates tags on a string promoted to an array element', function() {
378
- simulateRequestScope(() => {
379
- const chain = [
380
- { fn: 'has', args: [joi.string()] },
381
- { fn: 'single', args: [] }
382
- ];
383
-
384
- const xyzzy = trackString('xyzzy');
385
- const tags = makeTags('xyzzy', UNTRUSTED, STRING_TYPE_CHECKED);
386
-
387
- const schema = makeArraySchema(chain);
388
- const { value, error } = schema.validate(xyzzy);
389
- expect(error).undefined;
390
-
391
- let td = tracker.getData(xyzzy);
392
- expect(td).to.not.be.null;
393
- expect(td.tags).eql(tags);
394
-
395
- td = tracker.getData(value[0]);
396
- expect(td).to.not.be.null;
397
- expect(td.tags).eql(tags);
398
- });
399
- });
400
- });
401
-
402
- describe('.ordered()', function() {
403
- const xyzzy = 'xyzzy';
404
- const plover = 22;
405
- const twisty = 'twisty-passages';
406
- const colossal = 44;
407
- const refData = [xyzzy, plover, twisty, colossal];
408
-
409
- const orderedPositionTests = [
410
- // as soon as the first element is validated as a string the
411
- // remaining elements don't get validated in any way, so they
412
- // won't get tagged. a single string satisfies both .has() and
413
- // .items(joi.string().required()).
414
- {
415
- chain: [
416
- {
417
- fn: 'ordered',
418
- args: () => [
419
- joi.string().required(),
420
- joi.number().required(),
421
- joi.string(),
422
- joi.any()
423
- ]
424
- },
425
- { fn: 'max', args: [4] },
426
- { fn: 'min', args: [2] },
427
- { fn: 'length', args: [4] },
428
- { fn: 'has', args: () => [joi.string()] },
429
- { fn: 'single', args: [] }
430
- ],
431
- data: refData,
432
- tags: [
433
- makeTags(xyzzy, UNTRUSTED, STRING_TYPE_CHECKED),
434
- [],
435
- makeTags(twisty, UNTRUSTED, STRING_TYPE_CHECKED),
436
- []
437
- ]
438
- }
439
- ];
440
-
441
- // test .ordered() in each chain position by making additional tests.
442
- const refTest = orderedPositionTests[0];
443
- for (let i = 1; i < refTest.chain.length; i++) {
444
- orderedPositionTests[i] = Object.assign({}, refTest);
445
- orderedPositionTests[i].chain = rotate(refTest.chain, i);
446
- }
447
-
448
- for (const t of orderedPositionTests) {
449
- const reducer = (acc, i) => {
450
- acc.push(i.fn);
451
- return acc;
452
- };
453
- const text = t.chain.reduce(reducer, []).join(', ');
454
-
455
- it(`works in ${text} order`, function() {
456
- simulateRequestScope(() => {
457
- const schema = makeArraySchema(t.chain);
458
- const data = t.data.map((d) => trackString(d));
459
- const { value, error } = schema.validate(data);
460
-
461
- // check each element of the array
462
- for (let i = 0; i < value.length; i++) {
463
- const v = value[i];
464
- const td = tracker.getData(v);
465
- expect(error).equal(undefined, `${t.fn} for ${v}`);
466
- if (typeof v === 'string') {
467
- expect(td).to.not.be.null;
468
- const tags = t.tags[i];
469
- expect(td.tags).eql(tags, `${t.fn} for ${v}(${i})`);
470
- }
471
- }
472
- });
473
- });
474
- }
475
-
476
- const orderedErrorTests = [
477
- // as soon as the first element is validated as a string the
478
- // remaining elements don't get validated in any way, so they
479
- // won't get tagged. a single string satisfies both .has() and
480
- // .items(joi.string().required()).
481
- {
482
- name: 'first element fails validation',
483
- chain: [
484
- {
485
- fn: 'ordered',
486
- args: () => [
487
- joi.number().required(),
488
- joi.string().required(),
489
- joi.string(),
490
- joi.any()
491
- ]
492
- }
493
- ],
494
- data: [() => trackString(xyzzy), () => trackString(twisty), () => 1, () => 1],
495
- tags: [
496
- makeTags(xyzzy, UNTRUSTED),
497
- makeTags(twisty, UNTRUSTED),
498
- [],
499
- []
500
- ]
501
- },
502
- {
503
- name: 'first element passes, second element fails',
504
- chain: [
505
- {
506
- fn: 'ordered',
507
- args: () => [
508
- joi.string().required(),
509
- joi.number().required(),
510
- joi.string(),
511
- joi.any()
512
- ]
513
- }
514
- ],
515
- data: [() => trackString(xyzzy), () => trackString(twisty), () => 1, () => 1],
516
- tags: [
517
- makeTags(xyzzy, UNTRUSTED, STRING_TYPE_CHECKED),
518
- makeTags(twisty, UNTRUSTED),
519
- [],
520
- []
521
- ]
522
- },
523
- {
524
- name: 'string, any, number, string, then fail',
525
- chain: [
526
- {
527
- fn: 'ordered',
528
- args: () => [
529
- joi.string().required(),
530
- joi.any(),
531
- joi.number().required(),
532
- joi.string().required(),
533
- joi.number()
534
- ]
535
- }
536
- ],
537
- data: [() => trackString(xyzzy), () => trackString(twisty), () => 12, () => trackString('z'), () => true],
538
- tags: [
539
- makeTags(xyzzy, UNTRUSTED, STRING_TYPE_CHECKED),
540
- makeTags(twisty, UNTRUSTED),
541
- [],
542
- makeTags('z', UNTRUSTED, STRING_TYPE_CHECKED)
543
- ]
544
- }
545
- ];
546
-
547
- for (const t of orderedErrorTests) {
548
- it(`works when ${t.name}`, function() {
549
- simulateRequestScope(() => {
550
- t.data = t.data.map((i) => i());
551
- const schema = makeArraySchema(t.chain);
552
- const { value, error } = schema.validate(t.data);
553
-
554
- expect(error).instanceof(Error, `${t.name}`);
555
- for (let i = 0; i < t.data.length; i++) {
556
- const v = value[i];
557
- const td = tracker.getData(v);
558
- if (typeof v === 'string') {
559
- expect(td).to.not.be.null;
560
- expect(td.tags).eql(t.tags[i], `${t.name} for ${v}(${i})`);
561
- }
562
- }
563
- });
564
- });
565
- }
566
- });
567
-
568
- describe('.sort', function() {
569
- const a = 'a';
570
- const b = 'bb';
571
- const c = 'ccc';
572
- const d = 'dddd';
573
- const e = 'eeeee';
574
- const z = 'z'.repeat(26);
575
-
576
- const sortedRefData = [a, b, c, d, e, z];
577
-
578
- const sortedTests = [
579
- {
580
- name: 'propagates with no validation',
581
- chain: [{ fn: 'sort', args: [] }],
582
- tags: sortedRefData
583
- .map((s) => makeTags(s, UNTRUSTED))
584
- },
585
- {
586
- name: 'validates and propagates with .items(joi.string())',
587
- chain: [
588
- { fn: 'sort', args: [] },
589
- { fn: 'items', args: () => [joi.string()] }
590
- ],
591
- tags: sortedRefData
592
- .map((s) => makeTags(s, UNTRUSTED, STRING_TYPE_CHECKED))
593
- }
594
- ];
595
-
596
- for (const t of sortedTests) {
597
- // build schema and execute the test
598
- it(`${t.name} for sorted arrays`, function() {
599
- simulateRequestScope(() => {
600
- const schema = makeArraySchema(t.chain);
601
- const { value, error } = schema.validate(sortedRefData.map(i => trackString(i)));
602
-
603
- expect(error).undefined;
604
-
605
- for (let i = 0; i < sortedRefData.length; i++) {
606
- const v = value[i];
607
- const td = tracker.getData(v);
608
- expect(td).to.not.be.null;
609
- expect(td.tags).eql(t.tags[i], `${t.name} for ${v}`);
610
- }
611
- });
612
- });
613
- }
614
-
615
- const unsortedRefData = [e, d, c, b, a, z];
616
- const unsortedTests = [
617
- {
618
- name: 'propagates with no validation',
619
- chain: [
620
- {
621
- fn: 'sort',
622
- args: []
623
- }
624
- ],
625
- tags: unsortedRefData.map((s) => makeTags(s, UNTRUSTED))
626
- },
627
- {
628
- name: 'validates and propagates',
629
- chain: [
630
- { fn: 'sort', args: [] },
631
- { fn: 'items', args: () => [joi.string()] }
632
- ],
633
- tags: unsortedRefData.map((s) =>
634
- makeTags(s, UNTRUSTED, STRING_TYPE_CHECKED)
635
- )
636
- }
637
- ];
638
-
639
- for (const t of unsortedTests) {
640
- it(`${t.name} for unsorted arrays`, function() {
641
- simulateRequestScope(() => {
642
- const schema = makeArraySchema(t.chain);
643
- const data = unsortedRefData.map(i => trackString(i));
644
- const { value, error } = schema.validate(data);
645
-
646
- expect(error).undefined;
647
-
648
- // get sorted tags to check output array
649
- const sortedTags = t.tags.slice().sort((a, b) => a[UNTRUSTED][1] - b[UNTRUSTED][1]);
650
-
651
- for (let i = 0; i < data.length; i++) {
652
- // check the sorted values
653
- const v = value[i];
654
- let td = tracker.getData(v);
655
- expect(td).to.not.be.null;
656
- expect(td.tags).eql(sortedTags[i], `${t.name} for ${v}`);
657
- // check the original unsorted data
658
- td = tracker.getData(data[i]);
659
- expect(td).to.not.be.null;
660
- expect(td.tags).eql(t.tags[i], `${t.name} source ${data[i]}`);
661
- }
662
- });
663
- });
664
- }
665
- });
666
-
667
- describe('nested arrays', function() {
668
- const a = 'a';
669
- const b = 'bb';
670
- const c = 'ccc';
671
- const d = 'dddd';
672
- const e = 'eeeee';
673
- const refData = [a, [b, c, [d, e]]];
674
-
675
- function tagger(item, fn = trackString) {
676
- return (function execute(item) {
677
- if (Array.isArray(item)) {
678
- return item.map(execute);
679
- }
680
- return fn(item);
681
- })(item);
682
- }
683
-
684
- const tests = [
685
- {
686
- name: 'explicit schema',
687
- chain: [
688
- {
689
- fn: 'items',
690
- args: () => [
691
- joi.string(),
692
- joi.array().items(joi.string(), joi.array().items(joi.string()))
693
- ]
694
- }
695
- ],
696
- data: () => tagger(refData)
697
- }
698
- ];
699
-
700
- for (const t of tests) {
701
- it(`tags all elements with ${t.name}`, function() {
702
- simulateRequestScope(() => {
703
- t.data = t.data();
704
- const schema = makeArraySchema(t.chain);
705
- const { value, error } = schema.validate(t.data);
706
-
707
- expect(error).undefined;
708
-
709
- const check = (item) => {
710
- const td = tracker.getData(item);
711
- expect(td).to.not.be.null;
712
- expect(td.tags).eql(
713
- makeTags(item, UNTRUSTED, STRING_TYPE_CHECKED)
714
- );
715
- };
716
- // use tagger to check too
717
- tagger(value, check);
718
- });
719
- });
720
- }
721
- });
722
-
723
- describe('errors', function() {
724
- const xyzzy = 'xyzzy';
725
- const twisty = 'twisty-passages';
726
- const cave = 'cave';
727
- const blowup = 42;
728
- const refData = [xyzzy, twisty, cave, blowup];
729
-
730
- const tests = [
731
- // as soon as the first element is validated as a string the
732
- // remaining elements don't get validated in any way, so they
733
- // won't get tagged. a single string satisfies both .has() and
734
- // .items(joi.string().required()).
735
- {
736
- chain: [
737
- { fn: 'items', args: () => [joi.number().forbidden(), joi.string()] }
738
- ],
739
- data: refData
740
- },
741
- {
742
- chain: [
743
- { fn: 'items', args: () => [joi.string(), joi.number().forbidden()] }
744
- ]
745
- }
746
- ];
747
-
748
- // make additional tests, moving the number that causes failure.
749
- const refTest = tests[0];
750
- for (let i = 1; i < refData.length; i++) {
751
- tests[i] = {
752
- chain: refTest.chain,
753
- data: rotate(refTest.data, i)
754
- };
755
- }
756
-
757
- for (const t of tests) {
758
- const failIndex = t.data.findIndex((d) => typeof d === 'number');
759
-
760
- it(`should validate strings before index ${failIndex}`, function() {
761
- simulateRequestScope(() => {
762
- const data = t.data.map((d) => trackString(d));
763
- const schema = makeArraySchema(t.chain);
764
- const { value, error } = schema.validate(data);
765
-
766
- expect(error).not.undefined;
767
-
768
- // strings before the number should have 'string-type-checked'
769
- for (let i = 0; i < failIndex; i++) {
770
- const v = value[i];
771
- const tags = makeTags(v, UNTRUSTED, STRING_TYPE_CHECKED);
772
- let td = tracker.getData(v);
773
- expect(td).to.not.be.null;
774
- expect(td.tags).eql(tags);
775
-
776
- td = tracker.getData(data[i]);
777
- expect(td).to.not.be.null;
778
- expect(td.tags).eql(tags);
779
- }
780
-
781
- // strings after the number should have only 'untrusted'
782
- for (let i = failIndex + 1; i < data.length; i++) {
783
- const v = value[i];
784
- const tags = makeTags(v, UNTRUSTED);
785
- let td = tracker.getData(v);
786
- expect(td).to.not.be.null;
787
- expect(td.tags).eql(tags, `for ${v}`);
788
-
789
- td = tracker.getData(data[i]);
790
- expect(td).to.not.be.null;
791
- expect(td.tags).eql(tags, `for ${data[i]}`);
792
- }
793
- });
794
- });
795
- }
796
- });
797
-
798
- describe('async', function() {
799
- const a = 'a';
800
- const b = 'bb';
801
- const c = 'ccc';
802
- const d = 'dddd';
803
- const e = 'eeeee';
804
- const unnestedRefData = [a, b, c, d, e];
805
- const nestedRefData = [a, [b, c, [d, e]]];
806
-
807
- function tagger(item, fn = trackString) {
808
- return (function execute(item) {
809
- if (Array.isArray(item)) {
810
- return item.map(execute);
811
- }
812
-
813
- return fn(item);
814
- })(item);
815
- }
816
-
817
- function syncValidator() {
818
- return;
819
- }
820
-
821
- async function asyncValidator() {
822
- return new Promise((resolve) => {
823
- setTimeout(() => {
824
- resolve();
825
- }, 10);
826
- });
827
- }
828
-
829
- const tests = [
830
- {
831
- name: 'unnested without modification (sync)',
832
- chain: [
833
- { fn: 'items', args: () => [joi.string().external(syncValidator)] }
834
- ],
835
- data: () => tagger(unnestedRefData),
836
- tags: [UNTRUSTED, STRING_TYPE_CHECKED, CUSTOM_VALIDATED]
837
- },
838
- {
839
- name: 'pass without modification (async)',
840
- chain: [
841
- {
842
- fn: 'items',
843
- args: () => [joi.any().external(asyncValidator)]
844
- }
845
- ],
846
- data: () => tagger(nestedRefData),
847
- tags: [UNTRUSTED, CUSTOM_VALIDATED]
848
- },
849
- {
850
- name: 'pass with modification (sync)',
851
- chain: [
852
- {
853
- fn: 'items',
854
- args: () => [joi.any().external(syncValidator)]
855
- }
856
- ],
857
- data: () => tagger(nestedRefData),
858
- tags: [UNTRUSTED, CUSTOM_VALIDATED]
859
- }
860
- ];
861
-
862
- for (const t of tests) {
863
- it(`tags all elements with ${t.name}`, function(done) {
864
- simulateRequestScope(() => {
865
- t.data = t.data();
866
- const schema = makeArraySchema(t.chain);
867
- schema.validateAsync(t.data)
868
- .then((value) => {
869
- const check = (item) => {
870
- const td = tracker.getData(item);
871
- expect(td).to.not.be.null;
872
- expect(td.tags).to.deep.equal(makeTags(item, ...t.tags));
873
- };
874
- // use tagger to check too
875
- tagger(value, check);
876
- tagger(t.data, check);
877
- done();
878
- })
879
- .catch(err => done(err));
880
- });
881
- });
882
- }
883
- });
884
-
885
- //
886
- // helpers
887
- //
888
- function rotate(a, n) {
889
- if (a.length === 0 || n === 0) {
890
- return a;
891
- }
892
- n = n % a.length;
893
- return [...a.slice(n), ...a.slice(0, n)];
894
- }
895
-
896
- function permutations(arr, n = arr.length, out = []) {
897
- for (let i = 0; i < n; i++) {
898
- // if down to one remaing just use it
899
- if (n === 2) {
900
- out.push(arr.slice());
901
- } else {
902
- permutations(arr, n - 1, out);
903
- }
904
- const j = n % 2 == 0 ? i : 0;
905
- const t = arr[n - 1];
906
- arr[n - 1] = arr[j];
907
- arr[j] = t;
908
- }
909
- return out;
910
- }
911
- });
912
-