ueberdb2 3.0.1 → 4.0.1

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.
package/CHANGELOG.md CHANGED
@@ -1,11 +1,49 @@
1
1
  # Notable Changes
2
2
 
3
+ ## v4.0.1
4
+
5
+ Security fix:
6
+
7
+ * `getSub()` now returns `null` when it encounters a non-"own" property
8
+ (including `__proto__`) or any non-object while walking the given property
9
+ path. This should make it easier to avoid accidental prototype pollution
10
+ vulnerabilities.
11
+
12
+ ## v4.0.0
13
+
14
+ Compatibility changes:
15
+
16
+ * `redis`: The `socket` and `client_options` settings, deprecated since
17
+ v1.3.1, have been removed.
18
+ * `redis`: The client configuration object has changed with the new version of
19
+ the `redis` client library. See the [`redis` client library
20
+ documentation](https://github.com/redis/node-redis/blob/redis%404.1.0/docs/client-configuration.md)
21
+ for details.
22
+
23
+ Bug fixes:
24
+
25
+ * `redis`: Several `findKeys()` fixes.
26
+
27
+ Updated database dependencies:
28
+
29
+ * `redis`: Updated `redis` from 3.1.2 to 4.1.0.
30
+
31
+ ## v3.0.2
32
+
33
+ Security fix:
34
+
35
+ * `getSub()` now returns `null` when it encounters a non-"own" property
36
+ (including `__proto__`) or any non-object while walking the given property
37
+ path. This should make it easier to avoid accidental prototype pollution
38
+ vulnerabilities.
39
+
3
40
  ## v3.0.1
4
41
 
5
42
  Bug fixes:
6
43
 
7
44
  * Fixed `findKeys()` calls containing special regular expression characters
8
- for those database drivers that use the glob-to-regex helper function.
45
+ (applicable to the database drivers that use the glob-to-regex helper
46
+ function).
9
47
 
10
48
  ## v3.0.0
11
49
 
@@ -17,8 +55,12 @@ Compatibility changes:
17
55
  the `migrate_to_newer_schema` option to `true`.
18
56
  * As mentioned in the v2.2.0 changes, passing callbacks to the database
19
57
  methods is deprecated. Use the returned Promises instead.
20
- * As mentioned in the v1.4.15 changes, `postgrespool` is deprecated. Use
21
- `postgres` instead.
58
+ * `postgrespool`: As mentioned in the v1.4.15 changes, `postgrespool` is
59
+ deprecated. Use `postgres` instead.
60
+ * `redis`: As mentioned in the v1.3.1 changes, the `socket` and
61
+ `client_options` settings are deprecated. Pass the [client options
62
+ object](https://www.npmjs.com/package/redis/v/3.1.2#options-object-properties)
63
+ directly.
22
64
 
23
65
  Bug fixes:
24
66
 
@@ -33,6 +75,15 @@ Updated database dependencies:
33
75
  * `postgres`: Updated `pg` to 8.7.3.
34
76
  * `sqlite`: Updated `sqlite3` to 5.0.6.
35
77
 
78
+ ## v2.2.4
79
+
80
+ Security fix:
81
+
82
+ * `getSub()` now returns `null` when it encounters a non-"own" property
83
+ (including `__proto__`) or any non-object while walking the given property
84
+ path. This should make it easier to avoid accidental prototype pollution
85
+ vulnerabilities.
86
+
36
87
  ## v2.2.0
37
88
 
38
89
  Compatibility changes:
package/README.md CHANGED
@@ -272,16 +272,8 @@ You should create your database as utf8mb4_bin.
272
272
  If you enabled TLS on your Redis database (available since Redis 6.0) you will
273
273
  need to change your connections parameters, here is an example:
274
274
 
275
- ```
276
- settings:
277
- {
278
- host:
279
- port: rediss://<redis_database_address>:<redis_database_port>
280
- socket:
281
- database:
282
- password:
283
- client_options
284
- }
275
+ ```javascript
276
+ const db = new ueberdb.Database('redis', {url: 'rediss://localhost'});
285
277
  ```
286
278
 
287
279
  Do not provide a `host` value.
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
- /* eslint new-cap: ["error", {"capIsNewExceptions": ["KEYS", "SMEMBERS"]}] */
3
2
 
4
3
  /**
5
4
  * 2011 Peter 'Pita' Martischka
@@ -18,111 +17,80 @@
18
17
  */
19
18
 
20
19
  const AbstractDatabase = require('../lib/AbstractDatabase');
21
- const async = require('async');
22
20
  const redis = require('redis');
23
21
 
24
22
  exports.Database = class extends AbstractDatabase {
25
23
  constructor(settings) {
26
24
  super();
27
- this.client = null;
25
+ this._client = null;
28
26
  this.settings = settings || {};
29
27
  }
30
28
 
31
- auth(callback) {
32
- if (!this.settings.password) return callback();
33
- this.client.auth(this.settings.password, callback);
34
- }
35
-
36
- select(callback) {
37
- if (!this.settings.database) return callback();
38
- this.client.select(this.settings.database, callback);
39
- }
29
+ get isAsync() { return true; }
40
30
 
41
- _deprecatedInit(callback) {
42
- if (this.settings.socket) {
43
- // Deprecated, but kept for backwards compatibility.
44
- this.client = redis.createClient(this.settings.socket,
45
- this.settings.client_options);
46
- } else {
47
- // Deprecated, but kept for backwards compatibility.
48
- this.client = redis.createClient(this.settings.port,
49
- this.settings.host, this.settings.client_options);
50
- }
51
-
52
- this.client.database = this.settings.database;
53
- async.waterfall([this.auth.bind(this), this.select.bind(this)], callback);
31
+ async init() {
32
+ this._client = redis.createClient(this.settings);
33
+ await this._client.connect();
34
+ await this._client.ping();
54
35
  }
55
36
 
56
- init(callback) {
57
- if (this.settings.socket || this.settings.client_options) return this._deprecatedInit(callback);
58
- this.client = redis.createClient(this.settings);
59
- callback();
37
+ async get(key) {
38
+ return await this._client.get(key);
60
39
  }
61
40
 
62
- get(key, callback) {
63
- this.client.get(key, callback);
64
- }
65
-
66
- findKeys(key, notKey, callback) {
67
- // As redis provides only limited support for getting a list of all
68
- // available keys we have to limit key and notKey here.
69
- // See http://redis.io/commands/keys
70
- if (notKey == null) {
71
- this.client.KEYS(key, callback);
72
- } else if (notKey === '*:*:*') {
73
- // restrict key to format "text:*"
74
- const matches = /^([^:]+):\*$/.exec(key);
75
- if (matches) {
76
- this.client.SMEMBERS(`ueberDB:keys:${matches[1]}`, callback);
77
- } else {
78
- const msg = 'redis db only supports key patterns like pad:* when notKey is set to *:*:*';
79
- callback(new Error(msg), null);
80
- }
81
- } else {
82
- callback(new Error('redis db currently only supports *:*:* as notKey'), null);
41
+ async findKeys(key, notKey) {
42
+ const [type] = /^([^:*]+):\*$/.exec(key) || [];
43
+ if (type != null && ['*:*:*', `${key}:*`].includes(notKey)) {
44
+ // Performance optimization for a common Etherpad case.
45
+ return await this._client.sMembers(`ueberDB:keys:${type}`);
46
+ }
47
+ let keys = await this._client.keys(key.replace(/[?[\]\\]/g, '\\$&'));
48
+ if (notKey != null) {
49
+ const regex = this.createFindRegex(key, notKey);
50
+ keys = keys.filter((k) => regex.test(k));
83
51
  }
52
+ return keys;
84
53
  }
85
54
 
86
- set(key, value, callback) {
55
+ async set(key, value) {
87
56
  const matches = /^([^:]+):([^:]+)$/.exec(key);
88
- if (matches) {
89
- this.client.sadd([`ueberDB:keys:${matches[1]}`, matches[0]]);
90
- }
91
- this.client.set(key, value, callback);
57
+ await Promise.all([
58
+ matches && this._client.sAdd(`ueberDB:keys:${matches[1]}`, matches[0]),
59
+ this._client.set(key, value),
60
+ ]);
92
61
  }
93
62
 
94
- remove(key, callback) {
63
+ async remove(key) {
95
64
  const matches = /^([^:]+):([^:]+)$/.exec(key);
96
- if (matches) {
97
- this.client.srem([`ueberDB:keys:${matches[1]}`, matches[0]]);
98
- }
99
- this.client.del(key, callback);
65
+ await Promise.all([
66
+ matches && this._client.sRem(`ueberDB:keys:${matches[1]}`, matches[0]),
67
+ this._client.del(key),
68
+ ]);
100
69
  }
101
70
 
102
- doBulk(bulk, callback) {
103
- const multi = this.client.multi();
71
+ async doBulk(bulk) {
72
+ const multi = this._client.multi();
104
73
 
105
74
  for (const {key, type, value} of bulk) {
106
75
  const matches = /^([^:]+):([^:]+)$/.exec(key);
107
76
  if (type === 'set') {
108
77
  if (matches) {
109
- multi.sadd([`ueberDB:keys:${matches[1]}`, matches[0]]);
78
+ multi.sAdd(`ueberDB:keys:${matches[1]}`, matches[0]);
110
79
  }
111
80
  multi.set(key, value);
112
81
  } else if (type === 'remove') {
113
82
  if (matches) {
114
- multi.srem([`ueberDB:keys:${matches[1]}`, matches[0]]);
83
+ multi.sRem(`ueberDB:keys:${matches[1]}`, matches[0]);
115
84
  }
116
85
  multi.del(key);
117
86
  }
118
87
  }
119
88
 
120
- multi.exec(callback);
89
+ await multi.exec();
121
90
  }
122
91
 
123
- close(callback) {
124
- this.client.quit(() => {
125
- callback();
126
- });
92
+ async close() {
93
+ await this._client.quit();
94
+ this._client = null;
127
95
  }
128
96
  };
@@ -508,33 +508,27 @@ exports.Database = class {
508
508
  * "bla"]
509
509
  */
510
510
  async getSub(key, sub) {
511
- let subvalue;
512
511
  await this._lock(key);
513
512
  try {
514
- // get the full value
515
- const value = await this._getLocked(key);
516
-
517
- // everything is correct, navigate to the subvalue and return it
518
- subvalue = value;
519
-
520
- for (let i = 0; i < sub.length; i++) {
521
- // test if the subvalue exist
522
- if (subvalue != null && subvalue[sub[i]] !== undefined) {
523
- subvalue = subvalue[sub[i]];
524
- } else {
525
- // the subvalue doesn't exist, break the loop and return null
526
- subvalue = null;
527
- break;
513
+ let v = await this._getLocked(key);
514
+ for (const k of sub) {
515
+ if (typeof v !== 'object' || (v != null && !Object.prototype.hasOwnProperty.call(v, k)) ||
516
+ // __proto__ is not an "own" property but we check for it explicitly for added safety,
517
+ // to improve readability, and to help static code analysis tools rule out prototype
518
+ // pollution vulnerabilities.
519
+ k === '__proto__') {
520
+ v = null;
528
521
  }
522
+ if (v == null) break;
523
+ v = v[k];
529
524
  }
530
-
531
525
  if (this.logger.isDebugEnabled()) {
532
- this.logger.debug(`GETSUB - ${key}${JSON.stringify(sub)} - ${JSON.stringify(subvalue)}`);
526
+ this.logger.debug(`GETSUB - ${key}${JSON.stringify(sub)} - ${JSON.stringify(v)}`);
533
527
  }
528
+ return clone(v);
534
529
  } finally {
535
530
  this._unlock(key);
536
531
  }
537
- return clone(subvalue);
538
532
  }
539
533
 
540
534
  /**
package/package.json CHANGED
@@ -30,7 +30,7 @@
30
30
  "mysql": "2.18.1",
31
31
  "nano": "^10.0.0",
32
32
  "pg": "^8.7.3",
33
- "redis": "^3.1.2",
33
+ "redis": "^4.1.0",
34
34
  "rethinkdb": "^2.4.2",
35
35
  "simple-git": "^3.7.1"
36
36
  },
@@ -51,7 +51,7 @@
51
51
  "url": "https://github.com/ether/ueberDB.git"
52
52
  },
53
53
  "main": "./index",
54
- "version": "3.0.1",
54
+ "version": "4.0.1",
55
55
  "bugs": {
56
56
  "url": "https://github.com/ether/ueberDB/issues"
57
57
  },
package/test/test.js CHANGED
@@ -142,7 +142,6 @@ describe(__filename, function () {
142
142
 
143
143
  it('findKeys with exclusion works', async function () {
144
144
  if (database === 'mongodb') this.skip(); // TODO: Fix mongodb.
145
- if (database === 'redis') this.skip(); // TODO: Fix redis.
146
145
  const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
147
146
  await Promise.all([
148
147
  db.set(key, true),
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const assert = require('assert').strict;
4
+ const ueberdb = require('../index');
5
+
6
+ describe(__filename, function () {
7
+ let db;
8
+
9
+ beforeEach(async function () {
10
+ db = new ueberdb.Database('memory', {}, {});
11
+ await db.init();
12
+ await db.set('k', {s: 'v'});
13
+ });
14
+
15
+ afterEach(async function () {
16
+ if (db != null) await db.close();
17
+ db = null;
18
+ });
19
+
20
+ it('getSub stops at non-objects', async function () {
21
+ assert(await db.getSub('k', ['s', 'length']) == null);
22
+ });
23
+
24
+ it('getSub ignores non-own properties', async function () {
25
+ assert(await db.getSub('k', ['toString']) == null);
26
+ });
27
+
28
+ it('getSub ignores __proto__', async function () {
29
+ assert(await db.getSub('k', ['__proto__']) == null);
30
+ });
31
+ });
@@ -4,9 +4,19 @@ const assert = require('assert').strict;
4
4
  const ueberdb = require('../index');
5
5
 
6
6
  describe(__filename, function () {
7
- it('setSub rejects __proto__', async function () {
8
- const db = new ueberdb.Database('memory', {}, {});
7
+ let db;
8
+
9
+ beforeEach(async function () {
10
+ db = new ueberdb.Database('memory', {}, {});
9
11
  await db.init();
12
+ });
13
+
14
+ afterEach(async function () {
15
+ if (db != null) await db.close();
16
+ db = null;
17
+ });
18
+
19
+ it('setSub rejects __proto__', async function () {
10
20
  await db.set('k', {});
11
21
  await assert.rejects(db.setSub('k', ['__proto__'], 'v'));
12
22
  });