@iobroker/db-objects-redis 7.2.3 → 8.0.0-alpha.1-20260921-e5941ca8a
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/build/cjs/index.d.ts +1 -1
- package/build/cjs/index.js +5 -3
- package/build/cjs/index.js.map +2 -2
- package/build/cjs/lib/objects/constants.js.map +2 -2
- package/build/cjs/lib/objects/interview.js +2 -0
- package/build/cjs/lib/objects/interview.js.map +1 -1
- package/build/cjs/lib/objects/lua-v3/custom.lua +23 -1
- package/build/cjs/lib/objects/lua-v3/filter.lua +23 -1
- package/build/cjs/lib/objects/lua-v3/programs.lua +23 -1
- package/build/cjs/lib/objects/lua-v3/script.lua +23 -1
- package/build/cjs/lib/objects/lua-v3/variables.lua +23 -1
- package/build/cjs/lib/objects/lua-v4/custom.lua +23 -1
- package/build/cjs/lib/objects/lua-v4/filter.lua +23 -1
- package/build/cjs/lib/objects/lua-v4/programs.lua +23 -1
- package/build/cjs/lib/objects/lua-v4/script.lua +23 -1
- package/build/cjs/lib/objects/lua-v4/variables.lua +23 -1
- package/build/cjs/lib/objects/lua-v4-no-sets/custom.lua +23 -1
- package/build/cjs/lib/objects/lua-v4-no-sets/filter.lua +23 -1
- package/build/cjs/lib/objects/lua-v4-no-sets/programs.lua +23 -1
- package/build/cjs/lib/objects/lua-v4-no-sets/script.lua +23 -1
- package/build/cjs/lib/objects/lua-v4-no-sets/variables.lua +23 -1
- package/build/cjs/lib/objects/objectsInRedisClient.d.ts +961 -125
- package/build/cjs/lib/objects/objectsInRedisClient.js +1092 -776
- package/build/cjs/lib/objects/objectsInRedisClient.js.map +3 -3
- package/build/cjs/lib/objects/objectsUtils.d.ts +139 -17
- package/build/cjs/lib/objects/objectsUtils.js +212 -192
- package/build/cjs/lib/objects/objectsUtils.js.map +3 -3
- package/build/esm/index.d.ts +1 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/lib/objects/constants.d.ts.map +1 -1
- package/build/esm/lib/objects/constants.js.map +1 -1
- package/build/esm/lib/objects/lua-v3/custom.lua +23 -1
- package/build/esm/lib/objects/lua-v3/filter.lua +23 -1
- package/build/esm/lib/objects/lua-v3/programs.lua +23 -1
- package/build/esm/lib/objects/lua-v3/script.lua +23 -1
- package/build/esm/lib/objects/lua-v3/variables.lua +23 -1
- package/build/esm/lib/objects/lua-v4/custom.lua +23 -1
- package/build/esm/lib/objects/lua-v4/filter.lua +23 -1
- package/build/esm/lib/objects/lua-v4/programs.lua +23 -1
- package/build/esm/lib/objects/lua-v4/script.lua +23 -1
- package/build/esm/lib/objects/lua-v4/variables.lua +23 -1
- package/build/esm/lib/objects/lua-v4-no-sets/custom.lua +23 -1
- package/build/esm/lib/objects/lua-v4-no-sets/filter.lua +23 -1
- package/build/esm/lib/objects/lua-v4-no-sets/programs.lua +23 -1
- package/build/esm/lib/objects/lua-v4-no-sets/script.lua +23 -1
- package/build/esm/lib/objects/lua-v4-no-sets/variables.lua +23 -1
- package/build/esm/lib/objects/objectsInRedisClient.d.ts +961 -125
- package/build/esm/lib/objects/objectsInRedisClient.d.ts.map +1 -1
- package/build/esm/lib/objects/objectsInRedisClient.js +880 -571
- package/build/esm/lib/objects/objectsInRedisClient.js.map +1 -1
- package/build/esm/lib/objects/objectsUtils.d.ts +139 -17
- package/build/esm/lib/objects/objectsUtils.d.ts.map +1 -1
- package/build/esm/lib/objects/objectsUtils.js +273 -221
- package/build/esm/lib/objects/objectsUtils.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +9 -9
|
@@ -2,25 +2,29 @@
|
|
|
2
2
|
* Object DB in REDIS - Client
|
|
3
3
|
*
|
|
4
4
|
* MIT License
|
|
5
|
-
* Written by bluefox <dogafox@gmail.com>, 2014-
|
|
5
|
+
* Written by bluefox <dogafox@gmail.com>, 2014-2026
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
|
-
// @ts-expect-error no ts module
|
|
9
|
-
import extend from 'node.extend';
|
|
10
|
-
import Redis from 'ioredis';
|
|
11
|
-
import { tools } from '@iobroker/db-base';
|
|
12
8
|
import fs from 'node:fs';
|
|
13
9
|
import path from 'node:path';
|
|
14
10
|
import crypto from 'node:crypto';
|
|
15
11
|
import { isDeepStrictEqual } from 'node:util';
|
|
12
|
+
import * as url from 'node:url';
|
|
16
13
|
import deepClone from 'deep-clone';
|
|
17
|
-
import * as utils from '../../lib/objects/objectsUtils.js';
|
|
18
14
|
import semver from 'semver';
|
|
15
|
+
// @ts-expect-error no ts module
|
|
16
|
+
import extend from 'node.extend';
|
|
17
|
+
import Redis from 'ioredis';
|
|
18
|
+
import { tools } from '@iobroker/db-base';
|
|
19
|
+
import { checkObjectRightsAsync, } from '../../lib/objects/objectsUtils.js';
|
|
20
|
+
import * as utils from '../../lib/objects/objectsUtils.js';
|
|
19
21
|
import * as CONSTS from '../../lib/objects/constants.js';
|
|
20
|
-
import * as url from 'node:url';
|
|
21
22
|
// eslint-disable-next-line unicorn/prefer-module
|
|
22
23
|
const thisDir = url.fileURLToPath(new URL('.', import.meta.url || `file://${__filename}`));
|
|
23
24
|
const ERRORS = CONSTS.ERRORS;
|
|
25
|
+
/**
|
|
26
|
+
* Client for the objects database backed by Redis (or the in-memory redis-protocol server)
|
|
27
|
+
*/
|
|
24
28
|
export class ObjectsInRedisClient {
|
|
25
29
|
client;
|
|
26
30
|
fileNamespace;
|
|
@@ -47,6 +51,9 @@ export class ObjectsInRedisClient {
|
|
|
47
51
|
noLegacyMultihost;
|
|
48
52
|
userSubscriptions;
|
|
49
53
|
systemSubscriptions;
|
|
54
|
+
/**
|
|
55
|
+
* @param settings Settings for the objects client including connection and namespaces
|
|
56
|
+
*/
|
|
50
57
|
constructor(settings) {
|
|
51
58
|
this.settings = settings || {};
|
|
52
59
|
this.redisNamespace = `${this.settings.redisNamespace || this.settings.connection?.redisNamespace || 'cfg'}.`;
|
|
@@ -107,6 +114,9 @@ export class ObjectsInRedisClient {
|
|
|
107
114
|
throw new Error(`This host does not support protocol version "${protoVersion}"`);
|
|
108
115
|
}
|
|
109
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Connect to the objects database and set up the change and file subscriptions
|
|
119
|
+
*/
|
|
110
120
|
connectDb() {
|
|
111
121
|
this.settings.connection = this.settings.connection || {};
|
|
112
122
|
const onChange = this.settings.change; // on change handler
|
|
@@ -135,7 +145,7 @@ export class ObjectsInRedisClient {
|
|
|
135
145
|
if (ready && reconnectCount >= retry_max_count) {
|
|
136
146
|
return new Error('Stop trying to reconnect');
|
|
137
147
|
}
|
|
138
|
-
// A function that receives an options object as parameter including the retry attempt,
|
|
148
|
+
// A function that receives an options object as a parameter including the retry attempt,
|
|
139
149
|
// the total_retry_time indicating how much time passed since the last time connected,
|
|
140
150
|
// the error why the connection was lost and the number of times_connected in total.
|
|
141
151
|
// If you return a number from this function, the retry will happen exactly after that
|
|
@@ -161,7 +171,7 @@ export class ObjectsInRedisClient {
|
|
|
161
171
|
const defaultPort = Array.isArray(configuredPort) ? null : configuredPort;
|
|
162
172
|
this.settings.connection.options.sentinels = this.settings.connection.host.map((redisNode, idx) => ({
|
|
163
173
|
host: redisNode,
|
|
164
|
-
// @ts-expect-error ts does not get that if defPort is null we have an array
|
|
174
|
+
// @ts-expect-error ts does not get that if defPort is null, we have an array
|
|
165
175
|
port: defaultPort === null ? configuredPort[idx] : defaultPort,
|
|
166
176
|
}));
|
|
167
177
|
this.settings.connection.options.name = this.settings.connection.sentinelName
|
|
@@ -177,11 +187,11 @@ export class ObjectsInRedisClient {
|
|
|
177
187
|
this.settings.connection.options.db = this.settings.connection.options.db || 0;
|
|
178
188
|
this.settings.connection.options.family = this.settings.connection.options.family || 0;
|
|
179
189
|
this.settings.connection.options.password =
|
|
180
|
-
this.settings.connection.options.auth_pass || this.settings.connection.pass ||
|
|
181
|
-
this.settings.connection.options.autoResubscribe = false; // We do our own
|
|
190
|
+
this.settings.connection.options.auth_pass || this.settings.connection.pass || undefined;
|
|
191
|
+
this.settings.connection.options.autoResubscribe = false; // We do our own re-subscribing because other sometimes not work
|
|
182
192
|
// REDIS does not allow whitespaces, we have some because of pid
|
|
183
193
|
this.settings.connection.options.connectionName = this.namespace.replace(/\s/g, '');
|
|
184
|
-
this.client = new Redis(this.settings.connection.options);
|
|
194
|
+
this.client = new Redis.Redis(this.settings.connection.options);
|
|
185
195
|
this.client.on('error', error => {
|
|
186
196
|
if (this.settings.connection.enhancedLogging) {
|
|
187
197
|
this.log.silly(`${this.namespace} Redis ERROR Objects: (${this.stop}) ${error.message} / ${error.stack}`);
|
|
@@ -236,7 +246,7 @@ export class ObjectsInRedisClient {
|
|
|
236
246
|
this.log.silly(`${this.namespace} Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`);
|
|
237
247
|
}
|
|
238
248
|
if (reconnectCounter > 2) {
|
|
239
|
-
// fallback logic for
|
|
249
|
+
// fallback logic for Node.js <10
|
|
240
250
|
this.log.error(`${this.namespace} The DB port ${this.settings.connection.options.port} is occupied by something that is not a Redis protocol server. Please check other software running on this port or, if you use iobroker, make sure to update to js-controller 2.0 or higher!`);
|
|
241
251
|
return;
|
|
242
252
|
}
|
|
@@ -263,7 +273,7 @@ export class ObjectsInRedisClient {
|
|
|
263
273
|
if (!this.subSystem && typeof onChange === 'function') {
|
|
264
274
|
initCounter++;
|
|
265
275
|
this.log.debug(`${this.namespace} Objects create System PubSub Client`);
|
|
266
|
-
this.subSystem = new Redis(this.settings.connection.options);
|
|
276
|
+
this.subSystem = new Redis.Redis(this.settings.connection.options);
|
|
267
277
|
if (typeof this.settings.primaryHostLost === 'function') {
|
|
268
278
|
try {
|
|
269
279
|
// enable Expiry/Evicted events in server - same as states (could be same db)
|
|
@@ -300,7 +310,7 @@ export class ObjectsInRedisClient {
|
|
|
300
310
|
if (newUseSets !== this.useSets) {
|
|
301
311
|
this.log.info(`${this.namespace} Sets ${newUseSets ? 'activated' : 'deactivated'}: restarting ...`);
|
|
302
312
|
this.useSets = newUseSets;
|
|
303
|
-
// luas are no longer up to date,
|
|
313
|
+
// luas are no longer up to date, let's restart
|
|
304
314
|
if (typeof this.settings.disconnected === 'function') {
|
|
305
315
|
this.settings.disconnected();
|
|
306
316
|
}
|
|
@@ -318,7 +328,7 @@ export class ObjectsInRedisClient {
|
|
|
318
328
|
!isDeepStrictEqual(obj.common.defaultNewAcl, this.defaultNewAcl)) {
|
|
319
329
|
this.defaultNewAcl = deepClone(obj.common.defaultNewAcl);
|
|
320
330
|
if (this.settings.controller) {
|
|
321
|
-
this.setDefaultAcl(this.defaultNewAcl);
|
|
331
|
+
this.setDefaultAcl(this.defaultNewAcl).catch(e => this.log.error(`${this.namespace} Cannot set default ACL: ${e.message}`));
|
|
322
332
|
}
|
|
323
333
|
}
|
|
324
334
|
onChange(id, obj);
|
|
@@ -357,7 +367,7 @@ export class ObjectsInRedisClient {
|
|
|
357
367
|
if (this.settings.connection.enhancedLogging) {
|
|
358
368
|
this.subSystem.on('connect', () => this.log.silly(`${this.namespace} PubSub System client Objects-Redis Event connect (stop=${this.stop})`));
|
|
359
369
|
this.subSystem.on('close', () => this.log.silly(`${this.namespace} PubSub System client Objects-Redis Event close (stop=${this.stop})`));
|
|
360
|
-
this.subSystem.on('reconnecting', reconnectCounter => this.log.silly(`${this.namespace} PubSub System client Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`));
|
|
370
|
+
this.subSystem.on('reconnecting', (reconnectCounter) => this.log.silly(`${this.namespace} PubSub System client Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`));
|
|
361
371
|
}
|
|
362
372
|
this.subSystem.on('ready', async () => {
|
|
363
373
|
if (--initCounter < 1) {
|
|
@@ -372,7 +382,7 @@ export class ObjectsInRedisClient {
|
|
|
372
382
|
}
|
|
373
383
|
ready = true;
|
|
374
384
|
}
|
|
375
|
-
// subscribe
|
|
385
|
+
// subscribe to system.config anytime because also adapters need stuff like defaultNewAcl (especially admin)
|
|
376
386
|
try {
|
|
377
387
|
if (this.subSystem) {
|
|
378
388
|
await this.subSystem.psubscribe(`${this.objNamespace}system.config`);
|
|
@@ -405,7 +415,7 @@ export class ObjectsInRedisClient {
|
|
|
405
415
|
if (!this.sub && (typeof onChangeUser === 'function' || typeof onChangeFileUser === 'function')) {
|
|
406
416
|
initCounter++;
|
|
407
417
|
this.log.debug(`${this.namespace} Objects create User PubSub Client`);
|
|
408
|
-
this.sub = new Redis(this.settings.connection.options);
|
|
418
|
+
this.sub = new Redis.Redis(this.settings.connection.options);
|
|
409
419
|
this.sub.on('pmessage', (pattern, channel, message) => {
|
|
410
420
|
setImmediate(() => {
|
|
411
421
|
this.log.silly(`${this.namespace} Objects user redis pmessage ${pattern}/${channel}:${message}`);
|
|
@@ -465,11 +475,11 @@ export class ObjectsInRedisClient {
|
|
|
465
475
|
if (this.settings.connection.enhancedLogging) {
|
|
466
476
|
this.sub.on('connect', () => this.log.silly(`${this.namespace} PubSub user client Objects-Redis Event connect (stop=${this.stop})`));
|
|
467
477
|
this.sub.on('close', () => this.log.silly(`${this.namespace} PubSub user client Objects-Redis Event close (stop=${this.stop})`));
|
|
468
|
-
this.sub.on('reconnecting', reconnectCounter => this.log.silly(`${this.namespace} PubSub user client Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`));
|
|
478
|
+
this.sub.on('reconnecting', (reconnectCounter) => this.log.silly(`${this.namespace} PubSub user client Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`));
|
|
469
479
|
}
|
|
470
480
|
this.sub.on('ready', async () => {
|
|
471
481
|
if (!this.sub) {
|
|
472
|
-
// client gone while ready emitted, can maybe not happen but ts is happy
|
|
482
|
+
// client gone while ready emitted, can maybe not happen, but ts is happy
|
|
473
483
|
return;
|
|
474
484
|
}
|
|
475
485
|
if (--initCounter < 1) {
|
|
@@ -502,7 +512,7 @@ export class ObjectsInRedisClient {
|
|
|
502
512
|
this.useSets = !!parseInt((await this.client.get(`${this.metaNamespace}objects.features.useSets`)) || '0');
|
|
503
513
|
}
|
|
504
514
|
catch (e) {
|
|
505
|
-
// if unsupported we have a legacy host
|
|
515
|
+
// if unsupported, we have a legacy host
|
|
506
516
|
if (!e.message.includes('UNSUPPORTED')) {
|
|
507
517
|
this.log.error(`${this.namespace} Cannot determine Set feature status: ${e.message}`);
|
|
508
518
|
return;
|
|
@@ -516,13 +526,13 @@ export class ObjectsInRedisClient {
|
|
|
516
526
|
this.log.error(`${this.namespace} ${e.message}`);
|
|
517
527
|
throw new Error('Objects DB is not allowed to start in the current Multihost environment');
|
|
518
528
|
}
|
|
519
|
-
// for controller v4 we have to check if we can use the new
|
|
529
|
+
// for controller v4 we have to check if we can use the new Lua scripts and set logic
|
|
520
530
|
// TODO: remove this backward shim if controller v4.0 is old enough
|
|
521
531
|
let keys = await this._getKeysViaScan(`${this.objNamespace}system.host.*`);
|
|
522
532
|
// filter out obvious non-host objects
|
|
523
533
|
const hostRegex = new RegExp(`^${this.objNamespace.replace(/\./g, '\\.')}system\\.host\\.[^.]+$`);
|
|
524
534
|
keys = keys.filter(id => hostRegex.test(id));
|
|
525
|
-
/** if false we have a host smaller 4 (no proto version for this existing) */
|
|
535
|
+
/** if false, we have a host smaller 4 (no proto version for this existing) */
|
|
526
536
|
this.noLegacyMultihost = true;
|
|
527
537
|
try {
|
|
528
538
|
if (keys.length) {
|
|
@@ -590,6 +600,9 @@ export class ObjectsInRedisClient {
|
|
|
590
600
|
}
|
|
591
601
|
});
|
|
592
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* Get the current status of the database
|
|
605
|
+
*/
|
|
593
606
|
getStatus() {
|
|
594
607
|
return { type: 'redis', server: false };
|
|
595
608
|
}
|
|
@@ -597,7 +610,7 @@ export class ObjectsInRedisClient {
|
|
|
597
610
|
* Checks if given ID is a meta-object, else throws error
|
|
598
611
|
*
|
|
599
612
|
* @param id to check
|
|
600
|
-
* @throws Error if id is invalid
|
|
613
|
+
* @throws {Error} if id is invalid
|
|
601
614
|
*/
|
|
602
615
|
async validateMetaObject(id) {
|
|
603
616
|
if (this.existingMetaObjects[id] === undefined) {
|
|
@@ -615,6 +628,11 @@ export class ObjectsInRedisClient {
|
|
|
615
628
|
throw new Error(`${id} is not an object of type "meta"`);
|
|
616
629
|
}
|
|
617
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Normalize a file name by collapsing slashes and backslashes into a single forward slash
|
|
633
|
+
*
|
|
634
|
+
* @param name The file name to normalize
|
|
635
|
+
*/
|
|
618
636
|
normalizeFilename(name) {
|
|
619
637
|
return name ? name.replace(/[/\\]+/g, '/') : name;
|
|
620
638
|
}
|
|
@@ -622,7 +640,7 @@ export class ObjectsInRedisClient {
|
|
|
622
640
|
/**
|
|
623
641
|
* Sets a buffer to the Redis DB
|
|
624
642
|
*
|
|
625
|
-
* @param id
|
|
643
|
+
* @param id ID of the file
|
|
626
644
|
* @param data content, if string is passed it will be converted to a Buffer
|
|
627
645
|
*/
|
|
628
646
|
async _setBinaryState(id, data) {
|
|
@@ -636,7 +654,7 @@ export class ObjectsInRedisClient {
|
|
|
636
654
|
await this.client.publish(id, data.byteLength.toString(10));
|
|
637
655
|
}
|
|
638
656
|
/**
|
|
639
|
-
*
|
|
657
|
+
* Get a buffer of a given ID from redis
|
|
640
658
|
*
|
|
641
659
|
* @param id - id of the data with namespace prefix
|
|
642
660
|
*/
|
|
@@ -647,7 +665,7 @@ export class ObjectsInRedisClient {
|
|
|
647
665
|
return this.client.getBuffer(id);
|
|
648
666
|
}
|
|
649
667
|
/**
|
|
650
|
-
* deletes binary state of given
|
|
668
|
+
* deletes binary state of a given ID from redis db
|
|
651
669
|
*
|
|
652
670
|
* @param id - id to delete, with namespace prefix
|
|
653
671
|
*/
|
|
@@ -660,6 +678,13 @@ export class ObjectsInRedisClient {
|
|
|
660
678
|
await this.client.publish(id, 'null'); // inform about deletion
|
|
661
679
|
}
|
|
662
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Build the internal redis key for a file
|
|
683
|
+
*
|
|
684
|
+
* @param id The ID of the object owning the file
|
|
685
|
+
* @param name The file name
|
|
686
|
+
* @param isMeta Whether to return the key of the meta-entry (true) or the data entry (false)
|
|
687
|
+
*/
|
|
663
688
|
getFileId(id, name, isMeta) {
|
|
664
689
|
name = this.normalizeFilename(name);
|
|
665
690
|
// e.g. ekey.admin and admin/ekey.png
|
|
@@ -686,42 +711,66 @@ export class ObjectsInRedisClient {
|
|
|
686
711
|
name = normalized.name;
|
|
687
712
|
return `${this.fileNamespace + id}$%$${name}${isMeta !== undefined ? (isMeta ? '$%$meta' : '$%$data') : ''}`;
|
|
688
713
|
}
|
|
689
|
-
|
|
714
|
+
/**
|
|
715
|
+
* Check whether the current options have the required rights on a file
|
|
716
|
+
*
|
|
717
|
+
* @param id The id of the object owning the file
|
|
718
|
+
* @param name The file name
|
|
719
|
+
* @param userContext The resolved user context used for permission checks
|
|
720
|
+
* @param flag The access flag(s) to check for
|
|
721
|
+
*/
|
|
722
|
+
async checkFileAsync(id, name, userContext, flag) {
|
|
690
723
|
// read file settings from redis
|
|
691
|
-
const fileId = this.getFileId(id, name, true);
|
|
724
|
+
const fileId = this.getFileId(id, name ?? '', true);
|
|
692
725
|
if (!fileId) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
return tools.maybeCallback(callback, false, options, fileOptions); // NO error
|
|
726
|
+
if (!utils.checkFile({ notExists: true }, userContext, flag, this.defaultNewAcl)) {
|
|
727
|
+
return null;
|
|
696
728
|
}
|
|
697
|
-
return
|
|
729
|
+
return { notExists: true };
|
|
698
730
|
}
|
|
699
731
|
if (!this.client) {
|
|
700
|
-
|
|
701
|
-
return tools.maybeCallbackWithRedisError(callback, ERRORS.ERROR_DB_CLOSED, options);
|
|
732
|
+
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
702
733
|
}
|
|
703
|
-
let
|
|
734
|
+
let fileOptionsStr;
|
|
704
735
|
try {
|
|
705
|
-
|
|
736
|
+
fileOptionsStr = await this.client.get(fileId);
|
|
706
737
|
}
|
|
707
738
|
catch {
|
|
708
739
|
// ignore
|
|
709
740
|
}
|
|
710
|
-
|
|
741
|
+
fileOptionsStr ||= '{"notExists": true}';
|
|
742
|
+
let fileOptions;
|
|
711
743
|
try {
|
|
712
|
-
fileOptions = JSON.parse(
|
|
744
|
+
fileOptions = JSON.parse(fileOptionsStr);
|
|
713
745
|
}
|
|
714
746
|
catch {
|
|
715
|
-
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${
|
|
747
|
+
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${fileOptionsStr}`);
|
|
716
748
|
fileOptions = { notExists: true };
|
|
717
749
|
}
|
|
718
|
-
if (utils.checkFile(fileOptions,
|
|
719
|
-
return
|
|
750
|
+
if (!utils.checkFile(fileOptions, userContext, flag, this.defaultNewAcl)) {
|
|
751
|
+
return null;
|
|
720
752
|
}
|
|
721
|
-
return
|
|
753
|
+
return fileOptions;
|
|
722
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Check whether the current user is allowed to access a file
|
|
757
|
+
*
|
|
758
|
+
* @param id The id of the object owning the file
|
|
759
|
+
* @param name The file name, or null for the whole namespace
|
|
760
|
+
* @param options The current request options including the user
|
|
761
|
+
* @param flag The access flag(s) to check for
|
|
762
|
+
* @param callback Called with the effective options once the rights have been checked
|
|
763
|
+
*/
|
|
723
764
|
checkFileRights(id, name, options, flag, callback) {
|
|
724
|
-
|
|
765
|
+
try {
|
|
766
|
+
utils
|
|
767
|
+
.checkFileRightsAsync(this, id, name, options, flag)
|
|
768
|
+
.then(result => callback(null, result.fileOptions, result.userContext))
|
|
769
|
+
.catch(err => callback(err));
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
callback(error);
|
|
773
|
+
}
|
|
725
774
|
}
|
|
726
775
|
async _setDefaultAcl(ids, defaultAcl) {
|
|
727
776
|
for (const id of ids) {
|
|
@@ -737,6 +786,11 @@ export class ObjectsInRedisClient {
|
|
|
737
786
|
}
|
|
738
787
|
}
|
|
739
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* Set the default ACL applied to new objects and apply it to all existing objects without an ACL
|
|
791
|
+
*
|
|
792
|
+
* @param defaultNewAcl The default ACL to use, or null to use the built-in default
|
|
793
|
+
*/
|
|
740
794
|
async setDefaultAcl(defaultNewAcl) {
|
|
741
795
|
this.defaultNewAcl = defaultNewAcl || {
|
|
742
796
|
owner: CONSTS.SYSTEM_ADMIN_USER,
|
|
@@ -756,17 +810,25 @@ export class ObjectsInRedisClient {
|
|
|
756
810
|
this.log.error(`${this.namespace} Could not update default acl: ${e.message}`);
|
|
757
811
|
}
|
|
758
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* Determine the groups and effective ACL of the given user
|
|
815
|
+
*
|
|
816
|
+
* @param user The id of the user to look up
|
|
817
|
+
* @param callback Called with the user, its groups, and the effective ACL
|
|
818
|
+
*/
|
|
759
819
|
getUserGroup(user, callback) {
|
|
760
820
|
return utils.getUserGroup(this, user, (error, user, userGroups, userAcl) => {
|
|
761
821
|
if (error) {
|
|
762
822
|
this.log.error(`${this.namespace} ${error.stack}`);
|
|
763
823
|
}
|
|
764
|
-
|
|
824
|
+
if (typeof callback === 'function') {
|
|
825
|
+
callback(user, userGroups, userAcl);
|
|
826
|
+
}
|
|
765
827
|
});
|
|
766
828
|
}
|
|
767
829
|
async _writeFile(id, name, data, options, callback, meta) {
|
|
768
830
|
const matchedExtension = name.match(/\.[^.]+$/);
|
|
769
|
-
const ext = matchedExtension
|
|
831
|
+
const ext = matchedExtension?.[0] || '';
|
|
770
832
|
const isTextData = typeof data === 'string';
|
|
771
833
|
const { mimeType, isBinary } = utils.getMimeType(ext, isTextData);
|
|
772
834
|
const metaID = this.getFileId(id, name, true);
|
|
@@ -788,33 +850,24 @@ export class ObjectsInRedisClient {
|
|
|
788
850
|
}
|
|
789
851
|
}
|
|
790
852
|
else {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
ownerGroup: options.group ||
|
|
798
|
-
(this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
|
|
799
|
-
CONSTS.SYSTEM_ADMIN_GROUP,
|
|
800
|
-
permissions: options.mode || (this.defaultNewAcl && this.defaultNewAcl.file) || 0x644,
|
|
801
|
-
};
|
|
802
|
-
}
|
|
853
|
+
meta ||= { createdAt: Date.now() };
|
|
854
|
+
meta.acl ||= {
|
|
855
|
+
owner: options.user || this.defaultNewAcl?.owner || CONSTS.SYSTEM_ADMIN_USER,
|
|
856
|
+
ownerGroup: options.group || this.defaultNewAcl?.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP,
|
|
857
|
+
permissions: options.mode || this.defaultNewAcl?.file || 0x644,
|
|
858
|
+
};
|
|
803
859
|
meta.stats = {
|
|
804
|
-
size: data
|
|
860
|
+
size: data?.length || 0,
|
|
805
861
|
};
|
|
806
862
|
if (Object.prototype.hasOwnProperty.call(meta, 'notExists')) {
|
|
807
863
|
delete meta.notExists;
|
|
808
864
|
}
|
|
809
865
|
meta.mimeType = options.mimeType || mimeType;
|
|
810
866
|
meta.binary = isBinary;
|
|
811
|
-
meta.acl.ownerGroup
|
|
812
|
-
meta.acl.ownerGroup ||
|
|
813
|
-
(this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
|
|
814
|
-
CONSTS.SYSTEM_ADMIN_GROUP;
|
|
867
|
+
meta.acl.ownerGroup ||= this.defaultNewAcl?.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP;
|
|
815
868
|
meta.modifiedAt = Date.now();
|
|
816
869
|
try {
|
|
817
|
-
await this._setBinaryState(this.getFileId(id, name, false), data);
|
|
870
|
+
await this._setBinaryState(this.getFileId(id, name, false), data || '');
|
|
818
871
|
await this.client.set(metaID, JSON.stringify(meta));
|
|
819
872
|
return tools.maybeCallback(callback);
|
|
820
873
|
}
|
|
@@ -823,17 +876,20 @@ export class ObjectsInRedisClient {
|
|
|
823
876
|
}
|
|
824
877
|
}
|
|
825
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Write data into a file of an object
|
|
881
|
+
*
|
|
882
|
+
* @param id The id of the object owning the file
|
|
883
|
+
* @param name The file name
|
|
884
|
+
* @param data The data to write
|
|
885
|
+
* @param options The current request options including the user, or the callback
|
|
886
|
+
* @param callback Called once the file has been written
|
|
887
|
+
*/
|
|
826
888
|
async writeFile(id, name, data, options, callback) {
|
|
827
889
|
if (typeof options === 'function') {
|
|
828
890
|
callback = options;
|
|
829
891
|
options = null;
|
|
830
892
|
}
|
|
831
|
-
if (typeof options === 'string') {
|
|
832
|
-
options = { mimeType: options };
|
|
833
|
-
}
|
|
834
|
-
if (options?.acl) {
|
|
835
|
-
options.acl = null;
|
|
836
|
-
}
|
|
837
893
|
if (!callback) {
|
|
838
894
|
return this.writeFileAsync(id, name, data, options);
|
|
839
895
|
}
|
|
@@ -854,13 +910,21 @@ export class ObjectsInRedisClient {
|
|
|
854
910
|
data = null;
|
|
855
911
|
}
|
|
856
912
|
// If file yet exists => check the permissions
|
|
857
|
-
return this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err,
|
|
913
|
+
return this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta) => {
|
|
858
914
|
if (err) {
|
|
859
915
|
return tools.maybeCallbackWithError(callback, err);
|
|
860
916
|
}
|
|
861
|
-
return this._writeFile(id, name, data, options, callback, meta);
|
|
917
|
+
return this._writeFile(id, name, data, options || {}, callback, meta);
|
|
862
918
|
});
|
|
863
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Promise-version of writeFile
|
|
922
|
+
*
|
|
923
|
+
* @param id The ID of the object owning the file
|
|
924
|
+
* @param name The file name
|
|
925
|
+
* @param data The data to write
|
|
926
|
+
* @param options The current request options including the user
|
|
927
|
+
*/
|
|
864
928
|
writeFileAsync(id, name, data, options) {
|
|
865
929
|
return new Promise((resolve, reject) => this.writeFile(id, name, data, options, err => (err ? reject(err) : resolve())));
|
|
866
930
|
}
|
|
@@ -871,22 +935,26 @@ export class ObjectsInRedisClient {
|
|
|
871
935
|
if (!this.client) {
|
|
872
936
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
873
937
|
}
|
|
874
|
-
let
|
|
875
|
-
buffer = await this._getBinaryState(this.getFileId(id, name, false));
|
|
938
|
+
let file = await this._getBinaryState(this.getFileId(id, name, false));
|
|
876
939
|
const mimeType = meta?.mimeType;
|
|
877
|
-
if (meta && !meta.binary &&
|
|
878
|
-
|
|
940
|
+
if (meta && !meta.binary && file) {
|
|
941
|
+
file = file.toString();
|
|
879
942
|
}
|
|
880
|
-
return { file
|
|
943
|
+
return { file, mimeType };
|
|
881
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Read a file of an object
|
|
947
|
+
*
|
|
948
|
+
* @param id The ID of the object owning the file
|
|
949
|
+
* @param name The file name
|
|
950
|
+
* @param options The current request options including the user, or the callback
|
|
951
|
+
* @param callback Called with the file content and mime type
|
|
952
|
+
*/
|
|
882
953
|
readFile(id, name, options, callback) {
|
|
883
954
|
if (typeof options === 'function') {
|
|
884
955
|
callback = options;
|
|
885
956
|
options = null;
|
|
886
957
|
}
|
|
887
|
-
if (options?.acl) {
|
|
888
|
-
options.acl = null;
|
|
889
|
-
}
|
|
890
958
|
if (!callback) {
|
|
891
959
|
return new Promise((resolve, reject) => this.readFile(id, name, options, (err, res, mimeType) => err ? reject(err) : resolve({ file: res, mimeType: mimeType })));
|
|
892
960
|
}
|
|
@@ -896,8 +964,8 @@ export class ObjectsInRedisClient {
|
|
|
896
964
|
if (name.startsWith('/')) {
|
|
897
965
|
name = name.substring(1);
|
|
898
966
|
}
|
|
899
|
-
options
|
|
900
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, async (err,
|
|
967
|
+
options ||= {};
|
|
968
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, async (err, meta) => {
|
|
901
969
|
if (err) {
|
|
902
970
|
return tools.maybeCallbackWithError(callback, err);
|
|
903
971
|
}
|
|
@@ -911,9 +979,9 @@ export class ObjectsInRedisClient {
|
|
|
911
979
|
});
|
|
912
980
|
}
|
|
913
981
|
/**
|
|
914
|
-
* Check if given object exists
|
|
982
|
+
* Check if a given object exists
|
|
915
983
|
*
|
|
916
|
-
* @param id
|
|
984
|
+
* @param id ID of the object
|
|
917
985
|
* @param options optional user context
|
|
918
986
|
*/
|
|
919
987
|
async objectExists(id, options) {
|
|
@@ -921,18 +989,11 @@ export class ObjectsInRedisClient {
|
|
|
921
989
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
922
990
|
}
|
|
923
991
|
if (!id || typeof id !== 'string') {
|
|
924
|
-
throw new Error(`invalid
|
|
992
|
+
throw new Error(`invalid ID ${JSON.stringify(id)}`);
|
|
925
993
|
}
|
|
926
994
|
try {
|
|
927
995
|
await new Promise((resolve, reject) => {
|
|
928
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_LIST, err =>
|
|
929
|
-
if (err) {
|
|
930
|
-
reject(err);
|
|
931
|
-
}
|
|
932
|
-
else {
|
|
933
|
-
resolve();
|
|
934
|
-
}
|
|
935
|
-
});
|
|
996
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_LIST, err => err ? reject(err) : resolve());
|
|
936
997
|
});
|
|
937
998
|
const exists = await this.client.exists(this.objNamespace + id);
|
|
938
999
|
return !!exists;
|
|
@@ -943,9 +1004,9 @@ export class ObjectsInRedisClient {
|
|
|
943
1004
|
}
|
|
944
1005
|
}
|
|
945
1006
|
/**
|
|
946
|
-
* Check if given file exists
|
|
1007
|
+
* Check if a given file exists
|
|
947
1008
|
*
|
|
948
|
-
* @param id
|
|
1009
|
+
* @param id ID of the namespace
|
|
949
1010
|
* @param name name of the file
|
|
950
1011
|
* @param options optional user context
|
|
951
1012
|
*/
|
|
@@ -957,16 +1018,7 @@ export class ObjectsInRedisClient {
|
|
|
957
1018
|
name = name.substring(1);
|
|
958
1019
|
}
|
|
959
1020
|
try {
|
|
960
|
-
await
|
|
961
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, err => {
|
|
962
|
-
if (err) {
|
|
963
|
-
reject(err);
|
|
964
|
-
}
|
|
965
|
-
else {
|
|
966
|
-
resolve();
|
|
967
|
-
}
|
|
968
|
-
});
|
|
969
|
-
});
|
|
1021
|
+
await utils.checkFileRightsAsync(this, id, name, options, CONSTS.ACCESS_READ);
|
|
970
1022
|
if (!this.client) {
|
|
971
1023
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
972
1024
|
}
|
|
@@ -979,41 +1031,46 @@ export class ObjectsInRedisClient {
|
|
|
979
1031
|
throw new Error(`Cannot check file existence of "${id}": ${e.message}`);
|
|
980
1032
|
}
|
|
981
1033
|
}
|
|
982
|
-
async _unlink(id, name,
|
|
1034
|
+
async _unlink(id, name, userContext, meta) {
|
|
983
1035
|
if (!this.client) {
|
|
984
1036
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
985
1037
|
}
|
|
986
|
-
if (meta
|
|
987
|
-
return this._rm(id, name,
|
|
1038
|
+
if (meta?.notExists) {
|
|
1039
|
+
return this._rm(id, name, userContext);
|
|
988
1040
|
}
|
|
989
1041
|
const metaID = this.getFileId(id, name, true);
|
|
990
1042
|
const dataID = this.getFileId(id, name, false);
|
|
991
1043
|
await this._delBinaryState(dataID);
|
|
992
1044
|
await this.client.del(metaID);
|
|
993
1045
|
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Delete a file or directory of an object
|
|
1048
|
+
*
|
|
1049
|
+
* @param id The id of the object owning the file
|
|
1050
|
+
* @param name The file or directory name to delete
|
|
1051
|
+
* @param options The current request options including the user, or the callback
|
|
1052
|
+
* @param callback Called with the list of removed files
|
|
1053
|
+
*/
|
|
994
1054
|
unlink(id, name, options, callback) {
|
|
995
1055
|
if (typeof options === 'function') {
|
|
996
1056
|
callback = options;
|
|
997
1057
|
options = null;
|
|
998
1058
|
}
|
|
999
|
-
if (options?.acl) {
|
|
1000
|
-
options.acl = null;
|
|
1001
|
-
}
|
|
1002
1059
|
if (typeof name !== 'string') {
|
|
1003
1060
|
name = '';
|
|
1004
1061
|
}
|
|
1005
1062
|
if (name.startsWith('/')) {
|
|
1006
1063
|
name = name.substring(1);
|
|
1007
1064
|
}
|
|
1008
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_DELETE, async (err,
|
|
1065
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_DELETE, async (err, meta, userContext) => {
|
|
1009
1066
|
if (err) {
|
|
1010
1067
|
return tools.maybeCallbackWithError(callback, err);
|
|
1011
1068
|
}
|
|
1012
|
-
if (!
|
|
1069
|
+
if (!userContext.acl.file.delete) {
|
|
1013
1070
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1014
1071
|
}
|
|
1015
1072
|
try {
|
|
1016
|
-
const files = await this._unlink(id, name,
|
|
1073
|
+
const files = await this._unlink(id, name, userContext, meta);
|
|
1017
1074
|
return tools.maybeCallbackWithError(callback, null, files);
|
|
1018
1075
|
}
|
|
1019
1076
|
catch (e) {
|
|
@@ -1021,16 +1078,38 @@ export class ObjectsInRedisClient {
|
|
|
1021
1078
|
}
|
|
1022
1079
|
});
|
|
1023
1080
|
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Promise-version of unlink
|
|
1083
|
+
*
|
|
1084
|
+
* @param id The id of the object owning the file
|
|
1085
|
+
* @param name The file or directory name to delete
|
|
1086
|
+
* @param options The current request options including the user
|
|
1087
|
+
*/
|
|
1024
1088
|
unlinkAsync(id, name, options) {
|
|
1025
1089
|
return new Promise((resolve, reject) => this.unlink(id, name, options, err => (err ? reject(err) : resolve())));
|
|
1026
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Delete a file of an object (alias for {@link unlink})
|
|
1093
|
+
*
|
|
1094
|
+
* @param id The id of the object owning the file
|
|
1095
|
+
* @param name The file name to delete
|
|
1096
|
+
* @param options The current request options including the user
|
|
1097
|
+
* @param callback Called once the file has been deleted
|
|
1098
|
+
*/
|
|
1027
1099
|
delFile(id, name, options, callback) {
|
|
1028
1100
|
return this.unlink(id, name, options, callback);
|
|
1029
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Promise-version of delFile
|
|
1104
|
+
*
|
|
1105
|
+
* @param id The id of the object owning the file
|
|
1106
|
+
* @param name The file name to delete
|
|
1107
|
+
* @param options The current request options including the user
|
|
1108
|
+
*/
|
|
1030
1109
|
delFileAsync(id, name, options) {
|
|
1031
1110
|
return this.unlinkAsync(id, name, options);
|
|
1032
1111
|
}
|
|
1033
|
-
async _readDir(id, name,
|
|
1112
|
+
async _readDir(id, name, userContext, callback) {
|
|
1034
1113
|
name = this.normalizeFilename(name);
|
|
1035
1114
|
if (!this.client) {
|
|
1036
1115
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
@@ -1120,9 +1199,9 @@ export class ObjectsInRedisClient {
|
|
|
1120
1199
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
1121
1200
|
}
|
|
1122
1201
|
const result = [];
|
|
1123
|
-
const dontCheck =
|
|
1124
|
-
|
|
1125
|
-
|
|
1202
|
+
const dontCheck = userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
1203
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
1204
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1126
1205
|
for (let i = 0; i < keys.length; i++) {
|
|
1127
1206
|
const file = keys[i].substring(start + baseName.length, keys[i].length - end);
|
|
1128
1207
|
while (dirs.length && dirs[0] < file) {
|
|
@@ -1141,13 +1220,14 @@ export class ObjectsInRedisClient {
|
|
|
1141
1220
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${strObj}`);
|
|
1142
1221
|
continue;
|
|
1143
1222
|
}
|
|
1144
|
-
if (dontCheck || utils.checkObject(obj,
|
|
1223
|
+
if (dontCheck || utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
1145
1224
|
if (!obj || obj.virtualFile) {
|
|
1146
1225
|
// virtual file, ignore
|
|
1147
1226
|
continue;
|
|
1148
1227
|
}
|
|
1149
|
-
obj.acl
|
|
1150
|
-
if (
|
|
1228
|
+
obj.acl ||= {};
|
|
1229
|
+
if (userContext.user !== CONSTS.SYSTEM_ADMIN_USER &&
|
|
1230
|
+
!userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP)) {
|
|
1151
1231
|
obj.acl.read = !!(obj.acl.permissions & CONSTS.ACCESS_EVERY_READ);
|
|
1152
1232
|
obj.acl.write = !!(obj.acl.permissions & CONSTS.ACCESS_EVERY_WRITE);
|
|
1153
1233
|
}
|
|
@@ -1156,8 +1236,8 @@ export class ObjectsInRedisClient {
|
|
|
1156
1236
|
obj.acl.write = true;
|
|
1157
1237
|
}
|
|
1158
1238
|
result.push({
|
|
1159
|
-
file
|
|
1160
|
-
stats: obj.stats,
|
|
1239
|
+
file,
|
|
1240
|
+
stats: obj.stats || {},
|
|
1161
1241
|
isDir: false,
|
|
1162
1242
|
acl: obj.acl,
|
|
1163
1243
|
modifiedAt: obj.modifiedAt,
|
|
@@ -1174,14 +1254,19 @@ export class ObjectsInRedisClient {
|
|
|
1174
1254
|
}
|
|
1175
1255
|
return tools.maybeCallbackWithError(callback, null, result);
|
|
1176
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* List the contents of a directory of an object
|
|
1259
|
+
*
|
|
1260
|
+
* @param id The id of the object owning the files
|
|
1261
|
+
* @param name The directory name to list
|
|
1262
|
+
* @param options The current request options including the user, or the callback
|
|
1263
|
+
* @param callback Called with the directory entries
|
|
1264
|
+
*/
|
|
1177
1265
|
readDir(id, name, options, callback) {
|
|
1178
1266
|
if (typeof options === 'function') {
|
|
1179
1267
|
callback = options;
|
|
1180
1268
|
options = null;
|
|
1181
1269
|
}
|
|
1182
|
-
if (options?.acl) {
|
|
1183
|
-
options.acl = null;
|
|
1184
|
-
}
|
|
1185
1270
|
if (typeof name !== 'string') {
|
|
1186
1271
|
name = '';
|
|
1187
1272
|
}
|
|
@@ -1192,16 +1277,23 @@ export class ObjectsInRedisClient {
|
|
|
1192
1277
|
if (name.endsWith('/')) {
|
|
1193
1278
|
name = name.substring(0, name.length - 1);
|
|
1194
1279
|
}
|
|
1195
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, (err, options) => {
|
|
1280
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, (err, options, userContext) => {
|
|
1196
1281
|
if (err) {
|
|
1197
1282
|
return tools.maybeCallbackWithError(callback, err);
|
|
1198
1283
|
}
|
|
1199
|
-
if (!
|
|
1284
|
+
if (!userContext.acl.file.list) {
|
|
1200
1285
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1201
1286
|
}
|
|
1202
|
-
this._readDir(id, name,
|
|
1287
|
+
return this._readDir(id, name, userContext, callback);
|
|
1203
1288
|
});
|
|
1204
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Promise-version of readDir
|
|
1292
|
+
*
|
|
1293
|
+
* @param id The id of the object owning the files
|
|
1294
|
+
* @param name The directory name to list
|
|
1295
|
+
* @param options The current request options including the user
|
|
1296
|
+
*/
|
|
1205
1297
|
readDirAsync(id, name, options) {
|
|
1206
1298
|
return new Promise((resolve, reject) => this.readDir(id, name, options, (err, res) => (err ? reject(err) : resolve(res))));
|
|
1207
1299
|
}
|
|
@@ -1231,7 +1323,7 @@ export class ObjectsInRedisClient {
|
|
|
1231
1323
|
}
|
|
1232
1324
|
return tools.maybeCallback(callback);
|
|
1233
1325
|
}
|
|
1234
|
-
async _rename(id, oldName, newName,
|
|
1326
|
+
async _rename(id, oldName, newName, userContext, callback, meta) {
|
|
1235
1327
|
const oldMetaID = this.getFileId(id, oldName, true);
|
|
1236
1328
|
const oldDataID = this.getFileId(id, oldName, false);
|
|
1237
1329
|
const newMetaID = this.getFileId(id, newName, true);
|
|
@@ -1284,9 +1376,9 @@ export class ObjectsInRedisClient {
|
|
|
1284
1376
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
1285
1377
|
}
|
|
1286
1378
|
let result;
|
|
1287
|
-
const dontCheck =
|
|
1288
|
-
|
|
1289
|
-
|
|
1379
|
+
const dontCheck = userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
1380
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
1381
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1290
1382
|
if (!dontCheck) {
|
|
1291
1383
|
result = [];
|
|
1292
1384
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -1299,7 +1391,7 @@ export class ObjectsInRedisClient {
|
|
|
1299
1391
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${strObj}`);
|
|
1300
1392
|
continue;
|
|
1301
1393
|
}
|
|
1302
|
-
if (utils.checkObject(obj,
|
|
1394
|
+
if (utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
1303
1395
|
result.push(keys[i]);
|
|
1304
1396
|
}
|
|
1305
1397
|
}
|
|
@@ -1318,14 +1410,20 @@ export class ObjectsInRedisClient {
|
|
|
1318
1410
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
1319
1411
|
}
|
|
1320
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Rename a file or directory of an object
|
|
1415
|
+
*
|
|
1416
|
+
* @param id The id of the object owning the file
|
|
1417
|
+
* @param oldName The current file or directory name
|
|
1418
|
+
* @param newName The new file or directory name
|
|
1419
|
+
* @param options The current request options including the user, or the callback
|
|
1420
|
+
* @param callback Called once the file has been renamed
|
|
1421
|
+
*/
|
|
1321
1422
|
rename(id, oldName, newName, options, callback) {
|
|
1322
1423
|
if (typeof options === 'function') {
|
|
1323
1424
|
callback = options;
|
|
1324
1425
|
options = null;
|
|
1325
1426
|
}
|
|
1326
|
-
if (options?.acl) {
|
|
1327
|
-
options.acl = null;
|
|
1328
|
-
}
|
|
1329
1427
|
if (typeof oldName !== 'string' ||
|
|
1330
1428
|
!oldName.length ||
|
|
1331
1429
|
oldName === '/' ||
|
|
@@ -1348,20 +1446,29 @@ export class ObjectsInRedisClient {
|
|
|
1348
1446
|
if (newName.endsWith('/')) {
|
|
1349
1447
|
newName = newName.substring(0, newName.length - 1);
|
|
1350
1448
|
}
|
|
1351
|
-
this.checkFileRights(id, oldName, options, CONSTS.ACCESS_WRITE, (err,
|
|
1449
|
+
this.checkFileRights(id, oldName, options, CONSTS.ACCESS_WRITE, (err, meta, userContext) => {
|
|
1352
1450
|
if (err) {
|
|
1353
1451
|
return tools.maybeCallbackWithError(callback, err);
|
|
1354
1452
|
}
|
|
1355
|
-
if (!
|
|
1453
|
+
if (!userContext.acl.file.write) {
|
|
1356
1454
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1357
1455
|
}
|
|
1358
|
-
this._rename(id, oldName, newName,
|
|
1456
|
+
return this._rename(id, oldName, newName, userContext, callback, meta);
|
|
1359
1457
|
});
|
|
1360
1458
|
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Promise-version of rename
|
|
1461
|
+
*
|
|
1462
|
+
* @param id The id of the object owning the file
|
|
1463
|
+
* @param oldName The current file or directory name
|
|
1464
|
+
* @param newName The new file or directory name
|
|
1465
|
+
* @param options The current request options including the user
|
|
1466
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1467
|
+
*/
|
|
1361
1468
|
renameAsync(id, oldName, newName, options) {
|
|
1362
1469
|
return new Promise((resolve, reject) => this.rename(id, oldName, newName, options, err => (err ? reject(err) : resolve())));
|
|
1363
1470
|
}
|
|
1364
|
-
async _touch(id, name,
|
|
1471
|
+
async _touch(id, name, meta, callback) {
|
|
1365
1472
|
const metaID = this.getFileId(id, name, true);
|
|
1366
1473
|
if (!this.client) {
|
|
1367
1474
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
@@ -1378,27 +1485,40 @@ export class ObjectsInRedisClient {
|
|
|
1378
1485
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
1379
1486
|
}
|
|
1380
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Update the modification time of a file
|
|
1490
|
+
*
|
|
1491
|
+
* @param id The id of the object owning the file
|
|
1492
|
+
* @param name The file name
|
|
1493
|
+
* @param options The current request options including the user, or the callback
|
|
1494
|
+
* @param callback Called once the file has been touched
|
|
1495
|
+
*/
|
|
1381
1496
|
touch(id, name, options, callback) {
|
|
1382
1497
|
if (typeof options === 'function') {
|
|
1383
1498
|
callback = options;
|
|
1384
1499
|
options = null;
|
|
1385
1500
|
}
|
|
1386
|
-
if (options?.acl) {
|
|
1387
|
-
options.acl = null;
|
|
1388
|
-
}
|
|
1389
1501
|
if (typeof name !== 'string' || !name.length || name === '/') {
|
|
1390
1502
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1391
1503
|
}
|
|
1392
1504
|
if (name.startsWith('/')) {
|
|
1393
1505
|
name = name.substring(1);
|
|
1394
1506
|
}
|
|
1395
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err,
|
|
1507
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta) => {
|
|
1396
1508
|
if (err) {
|
|
1397
1509
|
return tools.maybeCallbackWithError(callback, err);
|
|
1398
1510
|
}
|
|
1399
|
-
return this._touch(id, name,
|
|
1511
|
+
return this._touch(id, name, meta, callback);
|
|
1400
1512
|
});
|
|
1401
1513
|
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Promise-version of touch
|
|
1516
|
+
*
|
|
1517
|
+
* @param id The id of the object owning the file
|
|
1518
|
+
* @param name The file name
|
|
1519
|
+
* @param options The current request options including the user
|
|
1520
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1521
|
+
*/
|
|
1402
1522
|
touchAsync(id, name, options) {
|
|
1403
1523
|
return new Promise((resolve, reject) => this.touch(id, name, options, err => (err ? reject(err) : resolve())));
|
|
1404
1524
|
}
|
|
@@ -1414,7 +1534,7 @@ export class ObjectsInRedisClient {
|
|
|
1414
1534
|
await this.client.del(id);
|
|
1415
1535
|
}
|
|
1416
1536
|
}
|
|
1417
|
-
async _rm(id, name,
|
|
1537
|
+
async _rm(id, name, userContext, meta) {
|
|
1418
1538
|
if (meta && !meta.isDir) {
|
|
1419
1539
|
// it is a file
|
|
1420
1540
|
const metaID = this.getFileId(id, name, true);
|
|
@@ -1447,30 +1567,30 @@ export class ObjectsInRedisClient {
|
|
|
1447
1567
|
throw new Error(ERRORS.ERROR_NOT_FOUND);
|
|
1448
1568
|
}
|
|
1449
1569
|
// Check permissions
|
|
1450
|
-
let
|
|
1570
|
+
let strObjs;
|
|
1451
1571
|
try {
|
|
1452
|
-
|
|
1572
|
+
strObjs = await this.client.mget(keys);
|
|
1453
1573
|
}
|
|
1454
1574
|
catch {
|
|
1455
1575
|
// ignore
|
|
1456
1576
|
}
|
|
1457
1577
|
let result;
|
|
1458
|
-
const dontCheck =
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1578
|
+
const dontCheck = userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
1579
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
1580
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1581
|
+
strObjs ||= [];
|
|
1462
1582
|
if (!dontCheck) {
|
|
1463
1583
|
result = [];
|
|
1464
1584
|
for (let i = 0; i < keys.length; i++) {
|
|
1585
|
+
const strObj = strObjs[i];
|
|
1465
1586
|
try {
|
|
1466
|
-
const strObj = objs[i];
|
|
1467
1587
|
const obj = strObj ? JSON.parse(strObj) : null;
|
|
1468
|
-
if (utils.checkObject(obj,
|
|
1588
|
+
if (utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
1469
1589
|
result.push(keys[i]);
|
|
1470
1590
|
}
|
|
1471
1591
|
}
|
|
1472
1592
|
catch {
|
|
1473
|
-
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${
|
|
1593
|
+
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${strObj}`);
|
|
1474
1594
|
}
|
|
1475
1595
|
}
|
|
1476
1596
|
}
|
|
@@ -1494,26 +1614,31 @@ export class ObjectsInRedisClient {
|
|
|
1494
1614
|
return files;
|
|
1495
1615
|
}
|
|
1496
1616
|
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Delete a file or directory of an object
|
|
1619
|
+
*
|
|
1620
|
+
* @param id The id of the object owning the file
|
|
1621
|
+
* @param name The file or directory name to delete
|
|
1622
|
+
* @param options The current request options including the user, or the callback
|
|
1623
|
+
* @param callback Called with the list of removed files
|
|
1624
|
+
*/
|
|
1497
1625
|
rm(id, name, options, callback) {
|
|
1498
1626
|
if (typeof options === 'function') {
|
|
1499
1627
|
callback = options;
|
|
1500
1628
|
options = null;
|
|
1501
1629
|
}
|
|
1502
|
-
if (options?.acl) {
|
|
1503
|
-
options.acl = null;
|
|
1504
|
-
}
|
|
1505
1630
|
if (typeof name !== 'string') {
|
|
1506
1631
|
name = '';
|
|
1507
1632
|
}
|
|
1508
|
-
this.checkFileRights(id, null, options, CONSTS.ACCESS_DELETE, async (err,
|
|
1633
|
+
this.checkFileRights(id, null, options, CONSTS.ACCESS_DELETE, async (err, meta, userContext) => {
|
|
1509
1634
|
if (err) {
|
|
1510
1635
|
return tools.maybeCallbackWithError(callback, err);
|
|
1511
1636
|
}
|
|
1512
|
-
if (!
|
|
1637
|
+
if (!userContext.acl.file.delete) {
|
|
1513
1638
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1514
1639
|
}
|
|
1515
1640
|
try {
|
|
1516
|
-
const files = await this._rm(id, name,
|
|
1641
|
+
const files = await this._rm(id, name, userContext, meta?.notExists ? null : meta);
|
|
1517
1642
|
return tools.maybeCallbackWithError(callback, null, files);
|
|
1518
1643
|
}
|
|
1519
1644
|
catch (e) {
|
|
@@ -1521,10 +1646,26 @@ export class ObjectsInRedisClient {
|
|
|
1521
1646
|
}
|
|
1522
1647
|
});
|
|
1523
1648
|
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Promise-version of rm
|
|
1651
|
+
*
|
|
1652
|
+
* @param id The id of the object owning the file
|
|
1653
|
+
* @param name The file or directory name to delete
|
|
1654
|
+
* @param options The current request options including the user
|
|
1655
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1656
|
+
*/
|
|
1524
1657
|
rmAsync(id, name, options) {
|
|
1525
1658
|
return new Promise((resolve, reject) => this.rm(id, name, options, (err, files) => (err ? reject(err) : resolve(files))));
|
|
1526
1659
|
}
|
|
1527
1660
|
// simulate. redis has no dirs
|
|
1661
|
+
/**
|
|
1662
|
+
* Create a directory for an object's files (simulated, as redis has no real directories)
|
|
1663
|
+
*
|
|
1664
|
+
* @param id The id of the object owning the files
|
|
1665
|
+
* @param dirName The directory name to create
|
|
1666
|
+
* @param options The current request options including the user, or the callback
|
|
1667
|
+
* @param callback Called once the directory has been created
|
|
1668
|
+
*/
|
|
1528
1669
|
mkdir(id, dirName, options, callback) {
|
|
1529
1670
|
if (typeof options === 'function') {
|
|
1530
1671
|
callback = options;
|
|
@@ -1537,19 +1678,26 @@ export class ObjectsInRedisClient {
|
|
|
1537
1678
|
if (dirName.startsWith('/')) {
|
|
1538
1679
|
dirName = dirName.substring(1);
|
|
1539
1680
|
}
|
|
1540
|
-
this.checkFileRights(id, dirName, options, CONSTS.ACCESS_WRITE, (err,
|
|
1681
|
+
this.checkFileRights(id, dirName, options, CONSTS.ACCESS_WRITE, (err, meta, userContext) => {
|
|
1541
1682
|
if (err) {
|
|
1542
1683
|
return tools.maybeCallbackWithError(callback, err);
|
|
1543
1684
|
}
|
|
1544
|
-
if (!
|
|
1685
|
+
if (!userContext.acl.file.write) {
|
|
1545
1686
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1546
1687
|
}
|
|
1547
|
-
// we create a dummy file (for file this file exists to store
|
|
1548
|
-
|
|
1688
|
+
// we create a dummy file (for file this file exists to store metadata) - do not override passed options
|
|
1689
|
+
meta = { ...options, virtualFile: true };
|
|
1549
1690
|
const realName = dirName + (dirName.endsWith('/') ? '' : '/');
|
|
1550
|
-
this.writeFile(id, `${realName}_data.json`, '',
|
|
1691
|
+
return this.writeFile(id, `${realName}_data.json`, '', meta, callback);
|
|
1551
1692
|
});
|
|
1552
1693
|
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Promise-version of mkdir
|
|
1696
|
+
*
|
|
1697
|
+
* @param id The id of the object owning the files
|
|
1698
|
+
* @param dirName The directory name to create
|
|
1699
|
+
* @param options The current request options including the user
|
|
1700
|
+
*/
|
|
1553
1701
|
mkdirAsync(id, dirName, options) {
|
|
1554
1702
|
return new Promise((resolve, reject) => this.mkdir(id, dirName, options, err => (err ? reject(err) : resolve())));
|
|
1555
1703
|
}
|
|
@@ -1573,7 +1721,7 @@ export class ObjectsInRedisClient {
|
|
|
1573
1721
|
}
|
|
1574
1722
|
return tools.maybeCallback(callback);
|
|
1575
1723
|
}
|
|
1576
|
-
async _chownFile(id, name, options,
|
|
1724
|
+
async _chownFile(id, name, options, meta, userContext, callback) {
|
|
1577
1725
|
if (!meta) {
|
|
1578
1726
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1579
1727
|
}
|
|
@@ -1582,7 +1730,7 @@ export class ObjectsInRedisClient {
|
|
|
1582
1730
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
1583
1731
|
}
|
|
1584
1732
|
if (!meta.isDir && !meta.notExists) {
|
|
1585
|
-
// it is file
|
|
1733
|
+
// it is a file
|
|
1586
1734
|
const metaID = this.getFileId(id, name, true);
|
|
1587
1735
|
meta.acl.owner = options.owner;
|
|
1588
1736
|
meta.acl.ownerGroup = options.ownerGroup;
|
|
@@ -1597,7 +1745,7 @@ export class ObjectsInRedisClient {
|
|
|
1597
1745
|
const res = [
|
|
1598
1746
|
{
|
|
1599
1747
|
path: nameArr.join('/'),
|
|
1600
|
-
file
|
|
1748
|
+
file,
|
|
1601
1749
|
stats: meta.stats,
|
|
1602
1750
|
isDir: false,
|
|
1603
1751
|
acl: meta.acl || {},
|
|
@@ -1637,9 +1785,9 @@ export class ObjectsInRedisClient {
|
|
|
1637
1785
|
catch (e) {
|
|
1638
1786
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
1639
1787
|
}
|
|
1640
|
-
const dontCheck =
|
|
1641
|
-
|
|
1642
|
-
|
|
1788
|
+
const dontCheck = userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
1789
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
1790
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1643
1791
|
const keysFiltered = [];
|
|
1644
1792
|
const objsFiltered = [];
|
|
1645
1793
|
const processed = [];
|
|
@@ -1655,7 +1803,7 @@ export class ObjectsInRedisClient {
|
|
|
1655
1803
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${metaStr}`);
|
|
1656
1804
|
continue;
|
|
1657
1805
|
}
|
|
1658
|
-
if (dontCheck || utils.checkObject(meta,
|
|
1806
|
+
if (dontCheck || utils.checkObject(meta, userContext, CONSTS.ACCESS_WRITE)) {
|
|
1659
1807
|
if (!meta || meta.virtualFile) {
|
|
1660
1808
|
continue;
|
|
1661
1809
|
} // virtual file, ignore
|
|
@@ -1675,39 +1823,43 @@ export class ObjectsInRedisClient {
|
|
|
1675
1823
|
});
|
|
1676
1824
|
}
|
|
1677
1825
|
}
|
|
1678
|
-
this._chownFileHelper(keysFiltered, objsFiltered, options, err => {
|
|
1826
|
+
return this._chownFileHelper(keysFiltered, objsFiltered, options, err => {
|
|
1679
1827
|
return tools.maybeCallbackWithError(callback, err, processed);
|
|
1680
1828
|
});
|
|
1681
1829
|
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Change the owner and owner group of a file
|
|
1832
|
+
*
|
|
1833
|
+
* @param id The id of the object owning the file
|
|
1834
|
+
* @param name The file name
|
|
1835
|
+
* @param options The current request options including the new owner and the user
|
|
1836
|
+
* @param options.owner The new owner (user id) to assign to the file
|
|
1837
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1838
|
+
* @param options.ownerGroup The new owner group to assign to the file
|
|
1839
|
+
* @param options.group The new owner group to assign (alias for ownerGroup)
|
|
1840
|
+
* @param callback Called with the processed file(s)
|
|
1841
|
+
*/
|
|
1682
1842
|
chownFile(id, name, options, callback) {
|
|
1683
|
-
if (typeof options === 'function') {
|
|
1684
|
-
callback = options;
|
|
1685
|
-
options = {};
|
|
1686
|
-
}
|
|
1687
|
-
options = options || {};
|
|
1688
|
-
if (typeof options !== 'object') {
|
|
1689
|
-
options = { owner: options };
|
|
1690
|
-
}
|
|
1691
1843
|
if (typeof name !== 'string' || !name.length || name === '/') {
|
|
1692
1844
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1693
1845
|
}
|
|
1694
1846
|
if (name.startsWith('/')) {
|
|
1695
1847
|
name = name.substring(1);
|
|
1696
1848
|
}
|
|
1697
|
-
if (
|
|
1849
|
+
if (options?.group && !options.ownerGroup) {
|
|
1698
1850
|
options.ownerGroup = options.group;
|
|
1699
1851
|
}
|
|
1700
|
-
if (
|
|
1852
|
+
if (options?.user && !options.owner) {
|
|
1701
1853
|
options.owner = options.user;
|
|
1702
1854
|
}
|
|
1703
|
-
if (!options
|
|
1855
|
+
if (!options?.owner) {
|
|
1704
1856
|
this.log.error(`${this.namespace} user is not defined`);
|
|
1705
1857
|
return tools.maybeCallbackWithError(callback, 'invalid parameter');
|
|
1706
1858
|
}
|
|
1707
1859
|
if (!options.ownerGroup) {
|
|
1708
1860
|
// get user group
|
|
1709
1861
|
this.getUserGroup(options.owner, (user, groups) => {
|
|
1710
|
-
if (!groups
|
|
1862
|
+
if (!groups?.[0]) {
|
|
1711
1863
|
return tools.maybeCallbackWithError(callback, `user "${options.owner}" belongs to no group`);
|
|
1712
1864
|
}
|
|
1713
1865
|
options.ownerGroup = groups[0];
|
|
@@ -1715,16 +1867,26 @@ export class ObjectsInRedisClient {
|
|
|
1715
1867
|
});
|
|
1716
1868
|
return;
|
|
1717
1869
|
}
|
|
1718
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err,
|
|
1870
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta, userContext) => {
|
|
1719
1871
|
if (err) {
|
|
1720
1872
|
return tools.maybeCallbackWithError(callback, err);
|
|
1721
1873
|
}
|
|
1722
|
-
if (!
|
|
1874
|
+
if (!userContext.acl.file.write) {
|
|
1723
1875
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1724
1876
|
}
|
|
1725
|
-
return this._chownFile(id, name, options,
|
|
1877
|
+
return this._chownFile(id, name, options, meta, userContext, callback);
|
|
1726
1878
|
});
|
|
1727
1879
|
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Promise-version of chownFile
|
|
1882
|
+
*
|
|
1883
|
+
* @param id The id of the object owning the file
|
|
1884
|
+
* @param name The file name
|
|
1885
|
+
* @param options The current request options including the new owner and the user
|
|
1886
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1887
|
+
* @param options.owner The new owner (user id) to assign to the file
|
|
1888
|
+
* @param options.ownerGroup The new owner group to assign to the file
|
|
1889
|
+
*/
|
|
1728
1890
|
chownFileAsync(id, name, options) {
|
|
1729
1891
|
return new Promise((resolve, reject) => this.chownFile(id, name, options, (err, processed) => (err ? reject(err) : resolve(processed))));
|
|
1730
1892
|
}
|
|
@@ -1733,6 +1895,8 @@ export class ObjectsInRedisClient {
|
|
|
1733
1895
|
* @param keys Key names to handle
|
|
1734
1896
|
* @param metas Objects for the keys to handle
|
|
1735
1897
|
* @param options options
|
|
1898
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1899
|
+
* @param options.mode The new permission mode to apply
|
|
1736
1900
|
* @param callback callback function
|
|
1737
1901
|
*/
|
|
1738
1902
|
async _chmodFileHelper(keys, metas, options, callback) {
|
|
@@ -1742,7 +1906,7 @@ export class ObjectsInRedisClient {
|
|
|
1742
1906
|
if (!this.client) {
|
|
1743
1907
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
1744
1908
|
}
|
|
1745
|
-
for (
|
|
1909
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1746
1910
|
const id = keys[i];
|
|
1747
1911
|
const meta = metas[i];
|
|
1748
1912
|
meta.acl.permissions = options.mode;
|
|
@@ -1755,7 +1919,7 @@ export class ObjectsInRedisClient {
|
|
|
1755
1919
|
}
|
|
1756
1920
|
return tools.maybeCallback(callback);
|
|
1757
1921
|
}
|
|
1758
|
-
async _chmodFile(id, name, options,
|
|
1922
|
+
async _chmodFile(id, name, options, meta, userContext, callback) {
|
|
1759
1923
|
if (!meta) {
|
|
1760
1924
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1761
1925
|
}
|
|
@@ -1764,7 +1928,7 @@ export class ObjectsInRedisClient {
|
|
|
1764
1928
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
1765
1929
|
}
|
|
1766
1930
|
if (!meta.isDir && !meta.notExists) {
|
|
1767
|
-
// it is file
|
|
1931
|
+
// it is a file
|
|
1768
1932
|
const metaID = this.getFileId(id, name, true);
|
|
1769
1933
|
meta.acl.permissions = options.mode;
|
|
1770
1934
|
try {
|
|
@@ -1778,7 +1942,7 @@ export class ObjectsInRedisClient {
|
|
|
1778
1942
|
const res = [
|
|
1779
1943
|
{
|
|
1780
1944
|
path: nameArr.join('/'),
|
|
1781
|
-
file
|
|
1945
|
+
file,
|
|
1782
1946
|
stats: meta.stats,
|
|
1783
1947
|
isDir: false,
|
|
1784
1948
|
acl: meta.acl || {},
|
|
@@ -1818,9 +1982,9 @@ export class ObjectsInRedisClient {
|
|
|
1818
1982
|
catch (e) {
|
|
1819
1983
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
1820
1984
|
}
|
|
1821
|
-
const dontCheck =
|
|
1822
|
-
|
|
1823
|
-
|
|
1985
|
+
const dontCheck = userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
1986
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
1987
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1824
1988
|
const keysFiltered = [];
|
|
1825
1989
|
const objsFiltered = [];
|
|
1826
1990
|
const processed = [];
|
|
@@ -1836,7 +2000,7 @@ export class ObjectsInRedisClient {
|
|
|
1836
2000
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${strObj}`);
|
|
1837
2001
|
continue;
|
|
1838
2002
|
}
|
|
1839
|
-
if (dontCheck || utils.checkObject(obj,
|
|
2003
|
+
if (dontCheck || utils.checkObject(obj, userContext, CONSTS.ACCESS_WRITE)) {
|
|
1840
2004
|
if (!obj || obj.virtualFile) {
|
|
1841
2005
|
continue;
|
|
1842
2006
|
} // virtual file, ignore
|
|
@@ -1856,14 +2020,22 @@ export class ObjectsInRedisClient {
|
|
|
1856
2020
|
});
|
|
1857
2021
|
}
|
|
1858
2022
|
}
|
|
1859
|
-
this._chmodFileHelper(keysFiltered, objsFiltered, options, err => tools.maybeCallbackWithError(callback, err, processed));
|
|
2023
|
+
return this._chmodFileHelper(keysFiltered, objsFiltered, options, err => tools.maybeCallbackWithError(callback, err, processed));
|
|
1860
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Change the file mode (permissions) of a single file
|
|
2027
|
+
*
|
|
2028
|
+
* @param id The id of the object owning the file
|
|
2029
|
+
* @param name The file name
|
|
2030
|
+
* @param options The current request options including the new mode and the user, or the callback
|
|
2031
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2032
|
+
* @param options.mode The new permission mode to apply
|
|
2033
|
+
* @param callback Called with the processed file
|
|
2034
|
+
*/
|
|
1861
2035
|
chmodFile(id, name, options, callback) {
|
|
1862
|
-
if (
|
|
1863
|
-
|
|
1864
|
-
options = null;
|
|
2036
|
+
if (!options || options.mode === undefined) {
|
|
2037
|
+
throw new Error('options.mode is not defined');
|
|
1865
2038
|
}
|
|
1866
|
-
options = options || {};
|
|
1867
2039
|
if (typeof name !== 'string' || !name.length || name === '/') {
|
|
1868
2040
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1869
2041
|
}
|
|
@@ -1880,28 +2052,41 @@ export class ObjectsInRedisClient {
|
|
|
1880
2052
|
else if (typeof options.mode === 'string') {
|
|
1881
2053
|
options.mode = parseInt(options.mode, 16);
|
|
1882
2054
|
}
|
|
1883
|
-
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err,
|
|
2055
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta, userContext) => {
|
|
1884
2056
|
if (err) {
|
|
1885
2057
|
return tools.maybeCallbackWithError(callback, err);
|
|
1886
2058
|
}
|
|
1887
|
-
if (!
|
|
2059
|
+
if (!userContext.acl.file.write) {
|
|
1888
2060
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1889
2061
|
}
|
|
1890
|
-
return this._chmodFile(id, name, options,
|
|
2062
|
+
return this._chmodFile(id, name, options, meta, userContext, callback);
|
|
1891
2063
|
});
|
|
1892
2064
|
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Promise-version of chmodFile
|
|
2067
|
+
*
|
|
2068
|
+
* @param id The id of the object owning the file
|
|
2069
|
+
* @param name The file name
|
|
2070
|
+
* @param options The current request options including the new mode and the user
|
|
2071
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2072
|
+
* @param options.mode The new permission mode to apply
|
|
2073
|
+
*/
|
|
1893
2074
|
chmodFileAsync(id, name, options) {
|
|
1894
2075
|
return new Promise((resolve, reject) => this.chmodFile(id, name, options, (err, processed) => (err ? reject(err) : resolve(processed))));
|
|
1895
2076
|
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Enable or disable the file cache
|
|
2079
|
+
*
|
|
2080
|
+
* @param enabled Whether the file cache should be enabled
|
|
2081
|
+
* @param options The current request options including the user, or the callback
|
|
2082
|
+
* @param callback Called with the resulting cache state
|
|
2083
|
+
*/
|
|
1896
2084
|
enableFileCache(enabled, options, callback) {
|
|
1897
2085
|
if (typeof options === 'function') {
|
|
1898
2086
|
callback = options;
|
|
1899
2087
|
options = null;
|
|
1900
2088
|
}
|
|
1901
|
-
|
|
1902
|
-
options.acl = null;
|
|
1903
|
-
}
|
|
1904
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, _options) => {
|
|
2089
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, err => {
|
|
1905
2090
|
if (err) {
|
|
1906
2091
|
return tools.maybeCallbackWithError(callback, err);
|
|
1907
2092
|
}
|
|
@@ -1909,6 +2094,12 @@ export class ObjectsInRedisClient {
|
|
|
1909
2094
|
return tools.maybeCallbackWithError(callback, null, false);
|
|
1910
2095
|
});
|
|
1911
2096
|
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Promise-version of enableFileCache
|
|
2099
|
+
*
|
|
2100
|
+
* @param enabled Whether the file cache should be enabled
|
|
2101
|
+
* @param options The current request options including the user
|
|
2102
|
+
*/
|
|
1912
2103
|
enableFileCacheAsync(enabled, options) {
|
|
1913
2104
|
return new Promise((resolve, reject) => this.enableFileCache(enabled, options, (err, res) => (err ? reject(err) : resolve(res))));
|
|
1914
2105
|
}
|
|
@@ -1958,6 +2149,13 @@ export class ObjectsInRedisClient {
|
|
|
1958
2149
|
}
|
|
1959
2150
|
}
|
|
1960
2151
|
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Subscribe a user to file changes of an object
|
|
2154
|
+
*
|
|
2155
|
+
* @param id The id of the object owning the files
|
|
2156
|
+
* @param pattern One or more file name patterns to subscribe to
|
|
2157
|
+
* @param options The current request options including the user
|
|
2158
|
+
*/
|
|
1961
2159
|
subscribeUserFile(id, pattern, options) {
|
|
1962
2160
|
return new Promise((resolve, reject) => {
|
|
1963
2161
|
utils.checkObjectRights(this, null, null, options, 'list', err => {
|
|
@@ -1972,6 +2170,13 @@ export class ObjectsInRedisClient {
|
|
|
1972
2170
|
});
|
|
1973
2171
|
});
|
|
1974
2172
|
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Unsubscribe a user from file changes of an object
|
|
2175
|
+
*
|
|
2176
|
+
* @param id The id of the object owning the files
|
|
2177
|
+
* @param pattern One or more file name patterns to unsubscribe from
|
|
2178
|
+
* @param options The current request options including the user
|
|
2179
|
+
*/
|
|
1975
2180
|
unsubscribeUserFile(id, pattern, options) {
|
|
1976
2181
|
return new Promise((resolve, reject) => {
|
|
1977
2182
|
utils.checkObjectRights(this, null, null, options, 'list', err => {
|
|
@@ -2025,6 +2230,13 @@ export class ObjectsInRedisClient {
|
|
|
2025
2230
|
return this._subscribe(pattern, false, callback);
|
|
2026
2231
|
});
|
|
2027
2232
|
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Subscribe to object changes matching the given pattern
|
|
2235
|
+
*
|
|
2236
|
+
* @param pattern One or more patterns to subscribe to
|
|
2237
|
+
* @param options The current request options including the user, or the callback
|
|
2238
|
+
* @param callback Called once the subscription is registered
|
|
2239
|
+
*/
|
|
2028
2240
|
subscribe(pattern, options, callback) {
|
|
2029
2241
|
if (typeof options === 'function') {
|
|
2030
2242
|
callback = options;
|
|
@@ -2032,9 +2244,22 @@ export class ObjectsInRedisClient {
|
|
|
2032
2244
|
}
|
|
2033
2245
|
return this.subscribeConfig(pattern, options, callback);
|
|
2034
2246
|
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Promise-version of subscribe
|
|
2249
|
+
*
|
|
2250
|
+
* @param pattern One or more patterns to subscribe to
|
|
2251
|
+
* @param options The current request options including the user
|
|
2252
|
+
*/
|
|
2035
2253
|
subscribeAsync(pattern, options) {
|
|
2036
2254
|
return new Promise((resolve, reject) => this.subscribe(pattern, options, err => (err ? reject(err) : resolve())));
|
|
2037
2255
|
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Subscribe a user to object changes matching the given pattern
|
|
2258
|
+
*
|
|
2259
|
+
* @param pattern One or more patterns to subscribe to
|
|
2260
|
+
* @param options The current request options including the user, or the callback
|
|
2261
|
+
* @param callback Called once the subscription is registered
|
|
2262
|
+
*/
|
|
2038
2263
|
subscribeUser(pattern, options, callback) {
|
|
2039
2264
|
if (typeof options === 'function') {
|
|
2040
2265
|
callback = options;
|
|
@@ -2047,6 +2272,12 @@ export class ObjectsInRedisClient {
|
|
|
2047
2272
|
return this._subscribe(pattern, true, callback);
|
|
2048
2273
|
});
|
|
2049
2274
|
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Promise-version of subscribeUser
|
|
2277
|
+
*
|
|
2278
|
+
* @param pattern One or more patterns to subscribe to
|
|
2279
|
+
* @param options The current request options including the user
|
|
2280
|
+
*/
|
|
2050
2281
|
subscribeUserAsync(pattern, options) {
|
|
2051
2282
|
return new Promise((resolve, reject) => this.subscribeUser(pattern, options, err => (err ? reject(err) : resolve())));
|
|
2052
2283
|
}
|
|
@@ -2088,6 +2319,13 @@ export class ObjectsInRedisClient {
|
|
|
2088
2319
|
}
|
|
2089
2320
|
});
|
|
2090
2321
|
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Unsubscribe from object changes matching the given pattern
|
|
2324
|
+
*
|
|
2325
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2326
|
+
* @param options The current request options including the user, or the callback
|
|
2327
|
+
* @param callback Called once the subscription is removed
|
|
2328
|
+
*/
|
|
2091
2329
|
unsubscribe(pattern, options, callback) {
|
|
2092
2330
|
if (typeof options === 'function') {
|
|
2093
2331
|
callback = options;
|
|
@@ -2095,9 +2333,22 @@ export class ObjectsInRedisClient {
|
|
|
2095
2333
|
}
|
|
2096
2334
|
return this.unsubscribeConfig(pattern, options, callback);
|
|
2097
2335
|
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Promise-version of unsubscribe
|
|
2338
|
+
*
|
|
2339
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2340
|
+
* @param options The current request options including the user
|
|
2341
|
+
*/
|
|
2098
2342
|
unsubscribeAsync(pattern, options) {
|
|
2099
2343
|
return new Promise((resolve, reject) => this.unsubscribe(pattern, options, err => (err ? reject(err) : resolve())));
|
|
2100
2344
|
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Unsubscribe a user from object changes matching the given pattern
|
|
2347
|
+
*
|
|
2348
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2349
|
+
* @param options The current request options including the user, or the callback
|
|
2350
|
+
* @param callback Called once the subscription is removed
|
|
2351
|
+
*/
|
|
2101
2352
|
unsubscribeUser(pattern, options, callback) {
|
|
2102
2353
|
if (typeof options === 'function') {
|
|
2103
2354
|
callback = options;
|
|
@@ -2116,6 +2367,12 @@ export class ObjectsInRedisClient {
|
|
|
2116
2367
|
}
|
|
2117
2368
|
});
|
|
2118
2369
|
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Promise-version of unsubscribeUser
|
|
2372
|
+
*
|
|
2373
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2374
|
+
* @param options The current request options including the user
|
|
2375
|
+
*/
|
|
2119
2376
|
unsubscribeUserAsync(pattern, options) {
|
|
2120
2377
|
return new Promise((resolve, reject) => this.unsubscribeUser(pattern, options, err => (err ? reject(err) : resolve())));
|
|
2121
2378
|
}
|
|
@@ -2153,7 +2410,7 @@ export class ObjectsInRedisClient {
|
|
|
2153
2410
|
await this.client.publish(id, message);
|
|
2154
2411
|
}
|
|
2155
2412
|
}
|
|
2156
|
-
_chownObject(pattern, options, callback) {
|
|
2413
|
+
_chownObject(pattern, options, userContext, callback) {
|
|
2157
2414
|
this.getKeys(pattern, options, async (err, keys) => {
|
|
2158
2415
|
if (err) {
|
|
2159
2416
|
return tools.maybeCallbackWithError(callback, err);
|
|
@@ -2183,7 +2440,7 @@ export class ObjectsInRedisClient {
|
|
|
2183
2440
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${strObj}`);
|
|
2184
2441
|
continue;
|
|
2185
2442
|
}
|
|
2186
|
-
if (!obj || !utils.checkObject(obj,
|
|
2443
|
+
if (!obj || !utils.checkObject(obj, userContext, CONSTS.ACCESS_WRITE)) {
|
|
2187
2444
|
continue;
|
|
2188
2445
|
}
|
|
2189
2446
|
if (!obj.acl) {
|
|
@@ -2214,20 +2471,22 @@ export class ObjectsInRedisClient {
|
|
|
2214
2471
|
return tools.maybeCallbackWithError(callback, null, filteredObjs);
|
|
2215
2472
|
}, true);
|
|
2216
2473
|
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Change the owner and owner group of all objects matching the given pattern
|
|
2476
|
+
*
|
|
2477
|
+
* @param pattern The pattern of object ids whose owner should be changed
|
|
2478
|
+
* @param options The current request options including the new owner and the user, or the callback
|
|
2479
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2480
|
+
* @param options.owner The new owner (user id) to assign to the objects
|
|
2481
|
+
* @param options.ownerGroup The new owner group to assign to the objects
|
|
2482
|
+
* @param options.group The new owner group to assign (alias for ownerGroup)
|
|
2483
|
+
* @param callback Called with the list of changed objects
|
|
2484
|
+
*/
|
|
2217
2485
|
chownObject(pattern, options, callback) {
|
|
2218
|
-
if (
|
|
2219
|
-
callback = options;
|
|
2220
|
-
options = {};
|
|
2221
|
-
}
|
|
2222
|
-
options = options || {};
|
|
2223
|
-
options.acl = null;
|
|
2224
|
-
if (typeof options !== 'object') {
|
|
2225
|
-
options = { owner: options };
|
|
2226
|
-
}
|
|
2227
|
-
if (!options.ownerGroup && options.group) {
|
|
2486
|
+
if (options?.group && !options.ownerGroup) {
|
|
2228
2487
|
options.ownerGroup = options.group;
|
|
2229
2488
|
}
|
|
2230
|
-
if (
|
|
2489
|
+
if (options?.user && !options.owner) {
|
|
2231
2490
|
options.owner = options.user;
|
|
2232
2491
|
}
|
|
2233
2492
|
if (!options.owner) {
|
|
@@ -2237,28 +2496,38 @@ export class ObjectsInRedisClient {
|
|
|
2237
2496
|
if (!options.ownerGroup) {
|
|
2238
2497
|
// get user group
|
|
2239
2498
|
this.getUserGroup(options.owner, (user, groups /* , permissions*/) => {
|
|
2240
|
-
if (!groups
|
|
2499
|
+
if (!groups?.[0]) {
|
|
2241
2500
|
return tools.maybeCallbackWithError(callback, `user "${options.owner}" belongs to no group`);
|
|
2242
2501
|
}
|
|
2243
2502
|
options.ownerGroup = groups[0];
|
|
2244
|
-
this.chownObject(pattern, options, callback);
|
|
2503
|
+
return this.chownObject(pattern, options, callback);
|
|
2245
2504
|
});
|
|
2246
2505
|
return;
|
|
2247
2506
|
}
|
|
2248
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err,
|
|
2507
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, userContext) => {
|
|
2249
2508
|
if (err) {
|
|
2250
2509
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
2251
2510
|
}
|
|
2252
|
-
if (!
|
|
2511
|
+
if (!userContext.acl.object || !userContext.acl.object.write) {
|
|
2253
2512
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
2254
2513
|
}
|
|
2255
|
-
return this._chownObject(pattern, options, callback);
|
|
2514
|
+
return this._chownObject(pattern, options, userContext, callback);
|
|
2256
2515
|
});
|
|
2257
2516
|
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Promise-version of chownObject
|
|
2519
|
+
*
|
|
2520
|
+
* @param pattern The pattern of object ids whose owner should be changed
|
|
2521
|
+
* @param options The current request options including the new owner and the user
|
|
2522
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2523
|
+
* @param options.owner The new owner (user id) to assign to the objects
|
|
2524
|
+
* @param options.ownerGroup The new owner group to assign to the objects
|
|
2525
|
+
* @param options.group The new owner group to assign (alias for ownerGroup)
|
|
2526
|
+
*/
|
|
2258
2527
|
chownObjectAsync(pattern, options) {
|
|
2259
2528
|
return new Promise((resolve, reject) => this.chownObject(pattern, options, (err, list) => (err ? reject(err) : resolve(list))));
|
|
2260
2529
|
}
|
|
2261
|
-
_chmodObject(pattern, options, callback) {
|
|
2530
|
+
_chmodObject(pattern, options, userContext, callback) {
|
|
2262
2531
|
this.getKeys(pattern, options, async (err, keys) => {
|
|
2263
2532
|
if (err) {
|
|
2264
2533
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
@@ -2288,7 +2557,7 @@ export class ObjectsInRedisClient {
|
|
|
2288
2557
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${strObj}`);
|
|
2289
2558
|
continue;
|
|
2290
2559
|
}
|
|
2291
|
-
if (!utils.checkObject(obj,
|
|
2560
|
+
if (!utils.checkObject(obj, userContext, CONSTS.ACCESS_WRITE) || !obj) {
|
|
2292
2561
|
continue;
|
|
2293
2562
|
}
|
|
2294
2563
|
if (!obj.acl) {
|
|
@@ -2321,15 +2590,21 @@ export class ObjectsInRedisClient {
|
|
|
2321
2590
|
}
|
|
2322
2591
|
}, true);
|
|
2323
2592
|
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Change the file mode (permissions) of all files matching the given pattern
|
|
2595
|
+
*
|
|
2596
|
+
* @param pattern The pattern of object ids whose files should be changed
|
|
2597
|
+
* @param options The current request options including the new mode and the user, or the callback
|
|
2598
|
+
* @param options.mode The new permission mode to apply
|
|
2599
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2600
|
+
* @param options.object The permission bitmask for object access
|
|
2601
|
+
* @param options.state The permission bitmask for state access
|
|
2602
|
+
* @param callback Called with the list of changed objects
|
|
2603
|
+
*/
|
|
2324
2604
|
chmodObject(pattern, options, callback) {
|
|
2325
|
-
if (
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
}
|
|
2329
|
-
options = options || {};
|
|
2330
|
-
options.acl = null;
|
|
2331
|
-
if (typeof options !== 'object') {
|
|
2332
|
-
options = { object: options };
|
|
2605
|
+
if (!options) {
|
|
2606
|
+
this.log.error(`${this.namespace} mode is not defined`);
|
|
2607
|
+
return tools.maybeCallbackWithError(callback, 'invalid parameter');
|
|
2333
2608
|
}
|
|
2334
2609
|
if (options.mode && !options.object) {
|
|
2335
2610
|
options.object = options.mode;
|
|
@@ -2341,20 +2616,30 @@ export class ObjectsInRedisClient {
|
|
|
2341
2616
|
else if (typeof options.mode === 'string') {
|
|
2342
2617
|
options.mode = parseInt(options.mode, 16);
|
|
2343
2618
|
}
|
|
2344
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err,
|
|
2619
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, userContext) => {
|
|
2345
2620
|
if (err) {
|
|
2346
2621
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
2347
2622
|
}
|
|
2348
|
-
if (!
|
|
2623
|
+
if (!userContext.acl.file.write) {
|
|
2349
2624
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
2350
2625
|
}
|
|
2351
|
-
return this._chmodObject(pattern, options, callback);
|
|
2626
|
+
return this._chmodObject(pattern, options, userContext, callback);
|
|
2352
2627
|
});
|
|
2353
2628
|
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Promise-version of chmodObject
|
|
2631
|
+
*
|
|
2632
|
+
* @param pattern The pattern of object ids whose files should be changed
|
|
2633
|
+
* @param options The current request options including the new mode and the user
|
|
2634
|
+
* @param options.mode The new permission mode to apply
|
|
2635
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2636
|
+
* @param options.object The permission bitmask for object access
|
|
2637
|
+
* @param options.state The permission bitmask for state access
|
|
2638
|
+
*/
|
|
2354
2639
|
chmodObjectAsync(pattern, options) {
|
|
2355
2640
|
return new Promise((resolve, reject) => this.chmodObject(pattern, options, (err, list) => (err ? reject(err) : resolve(list))));
|
|
2356
2641
|
}
|
|
2357
|
-
async _getObject(id,
|
|
2642
|
+
async _getObject(id, userContext, callback) {
|
|
2358
2643
|
if (!this.client) {
|
|
2359
2644
|
return tools.maybeCallbackWithRedisError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
2360
2645
|
}
|
|
@@ -2381,13 +2666,20 @@ export class ObjectsInRedisClient {
|
|
|
2381
2666
|
}
|
|
2382
2667
|
if (obj) {
|
|
2383
2668
|
// Check permissions
|
|
2384
|
-
if (utils.checkObject(obj,
|
|
2669
|
+
if (utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
2385
2670
|
return tools.maybeCallbackWithError(callback, null, obj);
|
|
2386
2671
|
}
|
|
2387
2672
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
2388
2673
|
}
|
|
2389
2674
|
return tools.maybeCallbackWithRedisError(callback, err, obj);
|
|
2390
2675
|
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Get a single object by its id
|
|
2678
|
+
*
|
|
2679
|
+
* @param id The id of the object to read
|
|
2680
|
+
* @param options The current request options including the user, or the callback
|
|
2681
|
+
* @param callback Called with the read object
|
|
2682
|
+
*/
|
|
2391
2683
|
getObject(id, options, callback) {
|
|
2392
2684
|
if (typeof options === 'function') {
|
|
2393
2685
|
callback = options;
|
|
@@ -2397,27 +2689,25 @@ export class ObjectsInRedisClient {
|
|
|
2397
2689
|
return new Promise((resolve, reject) => this.getObject(id, options, (err, obj) => (err ? reject(err) : resolve(obj))));
|
|
2398
2690
|
}
|
|
2399
2691
|
if (typeof callback === 'function') {
|
|
2400
|
-
|
|
2401
|
-
options.acl = null;
|
|
2402
|
-
}
|
|
2403
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, options) => {
|
|
2692
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, userContext) => {
|
|
2404
2693
|
if (err) {
|
|
2405
2694
|
return tools.maybeCallbackWithError(callback, err);
|
|
2406
2695
|
}
|
|
2407
|
-
return this._getObject(id,
|
|
2696
|
+
return this._getObject(id, userContext, callback);
|
|
2408
2697
|
});
|
|
2409
2698
|
}
|
|
2410
2699
|
}
|
|
2411
2700
|
/**
|
|
2701
|
+
* Promise-version of getObject
|
|
2412
2702
|
*
|
|
2413
|
-
* @param id
|
|
2414
|
-
* @param options
|
|
2703
|
+
* @param id The id of the object to read
|
|
2704
|
+
* @param options The current request options including the user
|
|
2415
2705
|
* @deprecated use `getObject` without callback instead
|
|
2416
2706
|
*/
|
|
2417
2707
|
getObjectAsync(id, options) {
|
|
2418
2708
|
return new Promise((resolve, reject) => this.getObject(id, options, (err, obj) => (err ? reject(err) : resolve(obj))));
|
|
2419
2709
|
}
|
|
2420
|
-
async _getKeys(pattern,
|
|
2710
|
+
async _getKeys(pattern, userContext, dontModify, callback) {
|
|
2421
2711
|
if (!this.client) {
|
|
2422
2712
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
2423
2713
|
}
|
|
@@ -2439,9 +2729,9 @@ export class ObjectsInRedisClient {
|
|
|
2439
2729
|
if (keys) {
|
|
2440
2730
|
keys.sort();
|
|
2441
2731
|
const result = [];
|
|
2442
|
-
const dontCheck =
|
|
2443
|
-
|
|
2444
|
-
|
|
2732
|
+
const dontCheck = userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
2733
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
2734
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
2445
2735
|
if (dontCheck) {
|
|
2446
2736
|
for (let i = 0; i < keys.length; i++) {
|
|
2447
2737
|
const id = keys[i].substring(this.objNamespaceL);
|
|
@@ -2475,7 +2765,7 @@ export class ObjectsInRedisClient {
|
|
|
2475
2765
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${metaStr}`);
|
|
2476
2766
|
continue;
|
|
2477
2767
|
}
|
|
2478
|
-
if (r.test(keys[i]) && utils.checkObject(meta,
|
|
2768
|
+
if (r.test(keys[i]) && utils.checkObject(meta, userContext, CONSTS.ACCESS_READ)) {
|
|
2479
2769
|
if (!dontModify) {
|
|
2480
2770
|
result.push(keys[i].substring(this.objNamespaceL));
|
|
2481
2771
|
}
|
|
@@ -2488,6 +2778,14 @@ export class ObjectsInRedisClient {
|
|
|
2488
2778
|
}
|
|
2489
2779
|
return tools.maybeCallbackWithError(callback, null, result);
|
|
2490
2780
|
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Get all object ids matching the given pattern
|
|
2783
|
+
*
|
|
2784
|
+
* @param pattern The pattern to match object ids against
|
|
2785
|
+
* @param options The current request options including the user, or the callback
|
|
2786
|
+
* @param callback Called with the matching keys
|
|
2787
|
+
* @param dontModify If true, the returned keys are not stripped of the namespace
|
|
2788
|
+
*/
|
|
2491
2789
|
getKeys(pattern, options, callback, dontModify) {
|
|
2492
2790
|
if (typeof options === 'function') {
|
|
2493
2791
|
callback = options;
|
|
@@ -2497,18 +2795,25 @@ export class ObjectsInRedisClient {
|
|
|
2497
2795
|
return new Promise((resolve, reject) => this.getKeys(pattern, options, (err, obj) => (err ? reject(err) : resolve(obj)), dontModify));
|
|
2498
2796
|
}
|
|
2499
2797
|
if (typeof callback === 'function') {
|
|
2500
|
-
utils.checkObjectRights(this, null, null, options, 'list', (err,
|
|
2798
|
+
utils.checkObjectRights(this, null, null, options, 'list', (err, userContext) => {
|
|
2501
2799
|
if (err) {
|
|
2502
2800
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
2503
2801
|
}
|
|
2504
|
-
return this._getKeys(pattern,
|
|
2802
|
+
return this._getKeys(pattern, userContext, dontModify, callback);
|
|
2505
2803
|
});
|
|
2506
2804
|
}
|
|
2507
2805
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2806
|
+
/**
|
|
2807
|
+
* Promise-version of getKeys
|
|
2808
|
+
*
|
|
2809
|
+
* @param pattern The pattern to match object ids against
|
|
2810
|
+
* @param options The current request options including the user
|
|
2811
|
+
* @param options.user The user on whose behalf the keys are read
|
|
2812
|
+
*/
|
|
2813
|
+
getKeysAsync(pattern, options) {
|
|
2814
|
+
return this.getKeys(pattern, options);
|
|
2510
2815
|
}
|
|
2511
|
-
async _getObjects(keys,
|
|
2816
|
+
async _getObjects(keys, userContext, callback, dontModify) {
|
|
2512
2817
|
if (!keys) {
|
|
2513
2818
|
return tools.maybeCallbackWithError(callback, 'no keys');
|
|
2514
2819
|
}
|
|
@@ -2540,10 +2845,10 @@ export class ObjectsInRedisClient {
|
|
|
2540
2845
|
}
|
|
2541
2846
|
let result = [];
|
|
2542
2847
|
if (objs) {
|
|
2543
|
-
const dontCheck =
|
|
2544
|
-
(
|
|
2545
|
-
|
|
2546
|
-
|
|
2848
|
+
const dontCheck = userContext &&
|
|
2849
|
+
(userContext.user === CONSTS.SYSTEM_ADMIN_USER ||
|
|
2850
|
+
userContext.group === CONSTS.SYSTEM_ADMIN_GROUP ||
|
|
2851
|
+
userContext.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP));
|
|
2547
2852
|
if (!dontCheck) {
|
|
2548
2853
|
for (let i = 0; i < objs.length; i++) {
|
|
2549
2854
|
const strObj = objs[i];
|
|
@@ -2557,7 +2862,7 @@ export class ObjectsInRedisClient {
|
|
|
2557
2862
|
result.push({ error: ERRORS.ERROR_PERMISSION });
|
|
2558
2863
|
continue;
|
|
2559
2864
|
}
|
|
2560
|
-
if (utils.checkObject(obj,
|
|
2865
|
+
if (utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
2561
2866
|
result.push(obj);
|
|
2562
2867
|
}
|
|
2563
2868
|
else {
|
|
@@ -2579,6 +2884,14 @@ export class ObjectsInRedisClient {
|
|
|
2579
2884
|
}
|
|
2580
2885
|
return tools.maybeCallbackWithError(callback, null, result);
|
|
2581
2886
|
}
|
|
2887
|
+
/**
|
|
2888
|
+
* Get multiple objects by their ids
|
|
2889
|
+
*
|
|
2890
|
+
* @param keys The ids of the objects to read
|
|
2891
|
+
* @param options The current request options including the user, or the callback
|
|
2892
|
+
* @param callback Called with the read objects
|
|
2893
|
+
* @param dontModify If true, the returned objects are not cloned/modified
|
|
2894
|
+
*/
|
|
2582
2895
|
getObjects(keys, options, callback, dontModify) {
|
|
2583
2896
|
if (typeof options === 'function') {
|
|
2584
2897
|
callback = options;
|
|
@@ -2589,22 +2902,25 @@ export class ObjectsInRedisClient {
|
|
|
2589
2902
|
// @ts-expect-error need to clarify, that objs is not undefined if no error is provided
|
|
2590
2903
|
this.getObjects(keys, options, (err, objs) => (err ? reject(err) : resolve(objs)), dontModify));
|
|
2591
2904
|
}
|
|
2592
|
-
if (options?.acl) {
|
|
2593
|
-
options.acl = null;
|
|
2594
|
-
}
|
|
2595
2905
|
if (typeof callback === 'function') {
|
|
2596
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err,
|
|
2906
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, userContext) => {
|
|
2597
2907
|
if (err) {
|
|
2598
2908
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
2599
2909
|
}
|
|
2600
|
-
return this._getObjects(keys,
|
|
2910
|
+
return this._getObjects(keys, userContext, callback, dontModify);
|
|
2601
2911
|
});
|
|
2602
2912
|
}
|
|
2603
2913
|
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Promise-version of getObjects
|
|
2916
|
+
*
|
|
2917
|
+
* @param keys The ids of the objects to read
|
|
2918
|
+
* @param options The current request options including the user
|
|
2919
|
+
*/
|
|
2604
2920
|
getObjectsAsync(keys, options) {
|
|
2605
2921
|
return this.getObjects(keys, options);
|
|
2606
2922
|
}
|
|
2607
|
-
async _getObjectsByPattern(pattern,
|
|
2923
|
+
async _getObjectsByPattern(pattern, userContext, callback) {
|
|
2608
2924
|
if (!pattern || typeof pattern !== 'string') {
|
|
2609
2925
|
return tools.maybeCallbackWithError(callback, `invalid pattern ${JSON.stringify(pattern)}`);
|
|
2610
2926
|
}
|
|
@@ -2624,8 +2940,15 @@ export class ObjectsInRedisClient {
|
|
|
2624
2940
|
if (this.settings.connection.enhancedLogging) {
|
|
2625
2941
|
this.log.silly(`${this.namespace} redis keys ${keys.length} ${pattern}`);
|
|
2626
2942
|
}
|
|
2627
|
-
this._getObjects(keys,
|
|
2943
|
+
this._getObjects(keys, userContext, callback, true).catch(e => this.log.error(`${this.namespace} Cannot get objects: ${e.message}`));
|
|
2628
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Get all objects whose id matches the given pattern
|
|
2947
|
+
*
|
|
2948
|
+
* @param pattern The pattern to match object ids against
|
|
2949
|
+
* @param options The current request options including the user, or the callback
|
|
2950
|
+
* @param callback Called with the matching objects
|
|
2951
|
+
*/
|
|
2629
2952
|
getObjectsByPattern(pattern, options, callback) {
|
|
2630
2953
|
if (typeof options === 'function') {
|
|
2631
2954
|
callback = options;
|
|
@@ -2634,22 +2957,26 @@ export class ObjectsInRedisClient {
|
|
|
2634
2957
|
if (!callback) {
|
|
2635
2958
|
return new Promise((resolve, reject) => this.getObjectsByPattern(pattern, options, (err, obj) => (err ? reject(err) : resolve(obj))));
|
|
2636
2959
|
}
|
|
2637
|
-
if (options?.acl) {
|
|
2638
|
-
options.acl = null;
|
|
2639
|
-
}
|
|
2640
2960
|
if (typeof callback === 'function') {
|
|
2641
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err,
|
|
2961
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, userContext) => {
|
|
2642
2962
|
if (err) {
|
|
2643
2963
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
2644
2964
|
}
|
|
2645
|
-
return this._getObjectsByPattern(pattern,
|
|
2965
|
+
return this._getObjectsByPattern(pattern, userContext, callback);
|
|
2646
2966
|
});
|
|
2647
2967
|
}
|
|
2648
2968
|
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Promise-version of getObjectsByPattern
|
|
2971
|
+
*
|
|
2972
|
+
* @param pattern The pattern to match object ids against
|
|
2973
|
+
* @param options The current request options including the user
|
|
2974
|
+
* @param options.user The user on whose behalf the objects are read
|
|
2975
|
+
*/
|
|
2649
2976
|
getObjectsByPatternAsync(pattern, options) {
|
|
2650
2977
|
return new Promise((resolve, reject) => this.getObjectsByPattern(pattern, options, (err, objs) => (err ? reject(err) : resolve(objs))));
|
|
2651
2978
|
}
|
|
2652
|
-
async _setObject(id, obj,
|
|
2979
|
+
async _setObject(id, obj, userContext) {
|
|
2653
2980
|
if (!id || typeof id !== 'string' || utils.REG_CHECK_ID.test(id)) {
|
|
2654
2981
|
throw new Error(`Invalid ID: ${id}`);
|
|
2655
2982
|
}
|
|
@@ -2658,7 +2985,7 @@ export class ObjectsInRedisClient {
|
|
|
2658
2985
|
throw new Error('obj is null');
|
|
2659
2986
|
}
|
|
2660
2987
|
if (!tools.isObject(obj)) {
|
|
2661
|
-
this.log.warn(`${this.namespace} setObject: Argument object is no object: ${obj}`);
|
|
2988
|
+
this.log.warn(`${this.namespace} setObject: Argument object is no object: ${JSON.stringify(obj)}`);
|
|
2662
2989
|
throw new Error('obj is no object');
|
|
2663
2990
|
}
|
|
2664
2991
|
if (!this.client) {
|
|
@@ -2776,16 +3103,16 @@ export class ObjectsInRedisClient {
|
|
|
2776
3103
|
delete obj.acl.state;
|
|
2777
3104
|
}
|
|
2778
3105
|
// take the current user as owner if given, but if admin we keep default
|
|
2779
|
-
if (
|
|
2780
|
-
obj.acl.owner =
|
|
3106
|
+
if (userContext.user && userContext.user !== CONSTS.SYSTEM_ADMIN_USER) {
|
|
3107
|
+
obj.acl.owner = userContext.user;
|
|
2781
3108
|
}
|
|
2782
3109
|
// take the current group as owner if given, but if admin we keep default
|
|
2783
|
-
if (
|
|
2784
|
-
obj.acl.ownerGroup =
|
|
3110
|
+
if (userContext.group && userContext.group !== CONSTS.SYSTEM_ADMIN_GROUP) {
|
|
3111
|
+
obj.acl.ownerGroup = userContext.group;
|
|
2785
3112
|
}
|
|
2786
3113
|
}
|
|
2787
|
-
if (this.defaultNewAcl && obj.acl && !obj.acl.ownerGroup &&
|
|
2788
|
-
obj.acl.ownerGroup =
|
|
3114
|
+
if (this.defaultNewAcl && obj.acl && !obj.acl.ownerGroup && userContext.group) {
|
|
3115
|
+
obj.acl.ownerGroup = userContext.group;
|
|
2789
3116
|
}
|
|
2790
3117
|
const message = JSON.stringify(obj);
|
|
2791
3118
|
const commands = [];
|
|
@@ -2828,12 +3155,12 @@ export class ObjectsInRedisClient {
|
|
|
2828
3155
|
return { id };
|
|
2829
3156
|
}
|
|
2830
3157
|
/**
|
|
2831
|
-
* set anew or update object
|
|
3158
|
+
* set anew or update an object
|
|
2832
3159
|
*
|
|
2833
3160
|
* This function writes the object into DB
|
|
2834
3161
|
*
|
|
2835
3162
|
* @param id ID of the object
|
|
2836
|
-
* @param obj
|
|
3163
|
+
* @param obj The object to write
|
|
2837
3164
|
* @param options options for access control are optional
|
|
2838
3165
|
* @param callback return function
|
|
2839
3166
|
*/
|
|
@@ -2845,16 +3172,13 @@ export class ObjectsInRedisClient {
|
|
|
2845
3172
|
if (!callback) {
|
|
2846
3173
|
return new Promise((resolve, reject) => this.setObject(id, obj, options, (err, res) => (err ? reject(err) : resolve(res))));
|
|
2847
3174
|
}
|
|
2848
|
-
|
|
2849
|
-
options.acl = null;
|
|
2850
|
-
}
|
|
2851
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, async (err) => {
|
|
3175
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, async (err, userContext) => {
|
|
2852
3176
|
// do not use options from checkObjectRights because this will mess up configured default acl
|
|
2853
3177
|
if (err) {
|
|
2854
3178
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
2855
3179
|
}
|
|
2856
3180
|
try {
|
|
2857
|
-
const res = await this._setObject(id, obj,
|
|
3181
|
+
const res = await this._setObject(id, obj, userContext);
|
|
2858
3182
|
return tools.maybeCallbackWithError(callback, null, res);
|
|
2859
3183
|
}
|
|
2860
3184
|
catch (e) {
|
|
@@ -2863,10 +3187,11 @@ export class ObjectsInRedisClient {
|
|
|
2863
3187
|
});
|
|
2864
3188
|
}
|
|
2865
3189
|
/**
|
|
3190
|
+
* Promise-version of setObject
|
|
2866
3191
|
*
|
|
2867
|
-
* @param id
|
|
2868
|
-
* @param obj
|
|
2869
|
-
* @param options
|
|
3192
|
+
* @param id ID of the object
|
|
3193
|
+
* @param obj The object to write
|
|
3194
|
+
* @param options options for access control are optional
|
|
2870
3195
|
* @deprecated use `setObject` without callback instead
|
|
2871
3196
|
*/
|
|
2872
3197
|
setObjectAsync(id, obj, options) {
|
|
@@ -2874,7 +3199,7 @@ export class ObjectsInRedisClient {
|
|
|
2874
3199
|
// @ts-expect-error TODO we are returning type Object for ease of use to devs, but formally these are AnyObjects, e.g. not guaranteed to have common
|
|
2875
3200
|
this.setObject(id, obj, options, (err, res) => (err ? reject(err) : resolve(res))));
|
|
2876
3201
|
}
|
|
2877
|
-
async _delObject(id,
|
|
3202
|
+
async _delObject(id, userContext) {
|
|
2878
3203
|
if (!id || typeof id !== 'string' || utils.REG_CHECK_ID.test(id)) {
|
|
2879
3204
|
throw new Error(`Invalid ID: ${id}`);
|
|
2880
3205
|
}
|
|
@@ -2903,7 +3228,7 @@ export class ObjectsInRedisClient {
|
|
|
2903
3228
|
this.log.warn(`${this.namespace} Cannot parse ${id} - ${oldObjStr}: ${e.message}`);
|
|
2904
3229
|
oldObj = null;
|
|
2905
3230
|
}
|
|
2906
|
-
if (!utils.checkObject(oldObj,
|
|
3231
|
+
if (!utils.checkObject(oldObj, userContext, CONSTS.ACCESS_WRITE)) {
|
|
2907
3232
|
throw new Error(ERRORS.ERROR_PERMISSION);
|
|
2908
3233
|
}
|
|
2909
3234
|
else {
|
|
@@ -2935,23 +3260,27 @@ export class ObjectsInRedisClient {
|
|
|
2935
3260
|
await this.client.publish(this.objNamespace + id, 'null');
|
|
2936
3261
|
}
|
|
2937
3262
|
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Delete an object
|
|
3265
|
+
*
|
|
3266
|
+
* @param id The id of the object to delete
|
|
3267
|
+
* @param options The current request options including the user, or the callback
|
|
3268
|
+
* @param callback Called once the object has been deleted
|
|
3269
|
+
*/
|
|
2938
3270
|
delObject(id, options, callback) {
|
|
2939
3271
|
if (typeof options === 'function') {
|
|
2940
3272
|
callback = options;
|
|
2941
3273
|
options = null;
|
|
2942
3274
|
}
|
|
2943
3275
|
if (!callback) {
|
|
2944
|
-
return new Promise((resolve, reject) => this.delObject(id, options, err =>
|
|
3276
|
+
return new Promise((resolve, reject) => this.delObject(id, options, err => err ? reject(err) : resolve()));
|
|
2945
3277
|
}
|
|
2946
|
-
|
|
2947
|
-
options.acl = null;
|
|
2948
|
-
}
|
|
2949
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_DELETE, async (err, options) => {
|
|
3278
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_DELETE, async (err, userContext) => {
|
|
2950
3279
|
if (err) {
|
|
2951
3280
|
return tools.maybeCallbackWithError(callback, err);
|
|
2952
3281
|
}
|
|
2953
3282
|
try {
|
|
2954
|
-
await this._delObject(id,
|
|
3283
|
+
await this._delObject(id, userContext);
|
|
2955
3284
|
return tools.maybeCallback(callback);
|
|
2956
3285
|
}
|
|
2957
3286
|
catch (e) {
|
|
@@ -2959,11 +3288,18 @@ export class ObjectsInRedisClient {
|
|
|
2959
3288
|
}
|
|
2960
3289
|
});
|
|
2961
3290
|
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Promise-version of delObject
|
|
3293
|
+
*
|
|
3294
|
+
* @param id The id of the object to delete
|
|
3295
|
+
* @param options The current request options including the user
|
|
3296
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
3297
|
+
*/
|
|
2962
3298
|
delObjectAsync(id, options) {
|
|
2963
3299
|
return this.delObject(id, options);
|
|
2964
3300
|
}
|
|
2965
3301
|
/**
|
|
2966
|
-
* Function to
|
|
3302
|
+
* Function to check if comparisons will work according to the configured Locale
|
|
2967
3303
|
*/
|
|
2968
3304
|
async isSystemLocaleSupported() {
|
|
2969
3305
|
if (!this.client) {
|
|
@@ -2979,15 +3315,13 @@ export class ObjectsInRedisClient {
|
|
|
2979
3315
|
}
|
|
2980
3316
|
}
|
|
2981
3317
|
// this function is very ineffective. Because reads all objects and then process them
|
|
2982
|
-
async _applyViewFunc(func, params,
|
|
3318
|
+
async _applyViewFunc(func, params, userContext) {
|
|
2983
3319
|
if (!this.client) {
|
|
2984
3320
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
2985
3321
|
}
|
|
2986
|
-
|
|
2987
|
-
rows: [],
|
|
2988
|
-
};
|
|
3322
|
+
let rows = [];
|
|
2989
3323
|
/**
|
|
2990
|
-
* filters objs which are already present (and parse Errors) in array by property 'id'
|
|
3324
|
+
* filters objs which are already present (and parse Errors) in an array by property 'id'
|
|
2991
3325
|
*
|
|
2992
3326
|
* @param arr - Array of objects which should be filtered
|
|
2993
3327
|
* @param duplicateFiltering - if duplicates need to be filtered
|
|
@@ -3008,9 +3342,9 @@ export class ObjectsInRedisClient {
|
|
|
3008
3342
|
return obj.value !== null;
|
|
3009
3343
|
});
|
|
3010
3344
|
};
|
|
3011
|
-
params
|
|
3012
|
-
params.startkey
|
|
3013
|
-
params.endkey
|
|
3345
|
+
params ||= {};
|
|
3346
|
+
params.startkey ||= '';
|
|
3347
|
+
params.endkey ||= '\u9999';
|
|
3014
3348
|
const wildcardPos = params.endkey.indexOf('\u9999');
|
|
3015
3349
|
let wildCardLastPos = true;
|
|
3016
3350
|
if (wildcardPos !== -1 && wildcardPos !== params.endkey.length - 1) {
|
|
@@ -3031,16 +3365,7 @@ export class ObjectsInRedisClient {
|
|
|
3031
3365
|
}
|
|
3032
3366
|
let objs;
|
|
3033
3367
|
try {
|
|
3034
|
-
objs = await this.client.evalsha([
|
|
3035
|
-
this.scripts.filter,
|
|
3036
|
-
6,
|
|
3037
|
-
this.objNamespace,
|
|
3038
|
-
params.startkey,
|
|
3039
|
-
params.endkey,
|
|
3040
|
-
matches[1],
|
|
3041
|
-
cursor,
|
|
3042
|
-
`${this.setNamespace}object.type.${matches[1]}`,
|
|
3043
|
-
]);
|
|
3368
|
+
objs = (await this.client.evalsha(this.scripts.filter, 6, this.objNamespace, params.startkey, params.endkey, matches[1], cursor, `${this.setNamespace}object.type.${matches[1]}`));
|
|
3044
3369
|
}
|
|
3045
3370
|
catch (e) {
|
|
3046
3371
|
this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
|
|
@@ -3072,7 +3397,7 @@ export class ObjectsInRedisClient {
|
|
|
3072
3397
|
if (matches[2] && matches[2].trim() === 'doc._id') {
|
|
3073
3398
|
return { id: obj._id, value: obj };
|
|
3074
3399
|
}
|
|
3075
|
-
|
|
3400
|
+
if (matches[2] && matches[2].trim() === 'doc.common.name' && obj.common) {
|
|
3076
3401
|
if (typeof obj.common.name === 'object') {
|
|
3077
3402
|
if (obj.common.name.en) {
|
|
3078
3403
|
return { id: obj.common.name.en, value: obj };
|
|
@@ -3085,29 +3410,30 @@ export class ObjectsInRedisClient {
|
|
|
3085
3410
|
return { id: 'parseError', value: null };
|
|
3086
3411
|
});
|
|
3087
3412
|
if (currRows.length) {
|
|
3088
|
-
|
|
3413
|
+
rows = [...rows, ...currRows];
|
|
3089
3414
|
} // endIf
|
|
3090
3415
|
} while (cursor !== '0');
|
|
3091
3416
|
// Now we have all objects -> calculate max if desired
|
|
3092
3417
|
if (func.reduce === '_stats') {
|
|
3093
3418
|
let max = null;
|
|
3094
|
-
for (let i = 0; i <
|
|
3095
|
-
|
|
3096
|
-
|
|
3419
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3420
|
+
const _value = rows[i].value;
|
|
3421
|
+
if (max === null || (_value !== null && _value > max)) {
|
|
3422
|
+
max = _value;
|
|
3097
3423
|
}
|
|
3098
3424
|
}
|
|
3099
3425
|
if (max !== null) {
|
|
3100
|
-
|
|
3426
|
+
// @ts-expect-error missing typing
|
|
3427
|
+
rows = [{ id: '_stats', value: { max } }];
|
|
3101
3428
|
}
|
|
3102
3429
|
else {
|
|
3103
|
-
|
|
3430
|
+
rows = [];
|
|
3104
3431
|
}
|
|
3105
3432
|
}
|
|
3106
3433
|
// apply filter if needed
|
|
3107
|
-
|
|
3108
|
-
return result;
|
|
3434
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3109
3435
|
}
|
|
3110
|
-
|
|
3436
|
+
if (
|
|
3111
3437
|
// filter by script
|
|
3112
3438
|
wildCardLastPos &&
|
|
3113
3439
|
func?.map &&
|
|
@@ -3121,15 +3447,7 @@ export class ObjectsInRedisClient {
|
|
|
3121
3447
|
}
|
|
3122
3448
|
let res;
|
|
3123
3449
|
try {
|
|
3124
|
-
res = await this.client.evalsha(
|
|
3125
|
-
this.scripts.script,
|
|
3126
|
-
5,
|
|
3127
|
-
this.objNamespace,
|
|
3128
|
-
params.startkey,
|
|
3129
|
-
params.endkey,
|
|
3130
|
-
cursor,
|
|
3131
|
-
`${this.setNamespace}object.type.script`,
|
|
3132
|
-
]);
|
|
3450
|
+
res = (await this.client.evalsha(this.scripts.script, 5, this.objNamespace, params.startkey, params.endkey, cursor, `${this.setNamespace}object.type.script`));
|
|
3133
3451
|
}
|
|
3134
3452
|
catch (e) {
|
|
3135
3453
|
this.log.warn(`${this.namespace} Cannot get "scripts" view: ${e.message}`);
|
|
@@ -3157,14 +3475,13 @@ export class ObjectsInRedisClient {
|
|
|
3157
3475
|
}
|
|
3158
3476
|
});
|
|
3159
3477
|
if (currRows.length) {
|
|
3160
|
-
|
|
3478
|
+
rows = [...rows, ...currRows];
|
|
3161
3479
|
} // endIf
|
|
3162
3480
|
} while (cursor !== '0');
|
|
3163
3481
|
// apply filter if needed
|
|
3164
|
-
|
|
3165
|
-
return result;
|
|
3482
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3166
3483
|
}
|
|
3167
|
-
|
|
3484
|
+
if (
|
|
3168
3485
|
// filter by hm-rega programs
|
|
3169
3486
|
wildCardLastPos &&
|
|
3170
3487
|
func?.map &&
|
|
@@ -3178,15 +3495,7 @@ export class ObjectsInRedisClient {
|
|
|
3178
3495
|
}
|
|
3179
3496
|
let objs;
|
|
3180
3497
|
try {
|
|
3181
|
-
objs = await this.client.evalsha(
|
|
3182
|
-
this.scripts.programs,
|
|
3183
|
-
5,
|
|
3184
|
-
`${this.objNamespace}hm-rega.`,
|
|
3185
|
-
params.startkey,
|
|
3186
|
-
params.endkey,
|
|
3187
|
-
cursor,
|
|
3188
|
-
`${this.setNamespace}object.type.channel`,
|
|
3189
|
-
]);
|
|
3498
|
+
objs = (await this.client.evalsha(this.scripts.programs, 5, `${this.objNamespace}hm-rega.`, params.startkey, params.endkey, cursor, `${this.setNamespace}object.type.channel`));
|
|
3190
3499
|
}
|
|
3191
3500
|
catch (e) {
|
|
3192
3501
|
this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
|
|
@@ -3212,14 +3521,13 @@ export class ObjectsInRedisClient {
|
|
|
3212
3521
|
}
|
|
3213
3522
|
});
|
|
3214
3523
|
if (currRows.length) {
|
|
3215
|
-
|
|
3524
|
+
rows = [...rows, ...currRows];
|
|
3216
3525
|
} // endIf
|
|
3217
3526
|
} while (cursor !== '0');
|
|
3218
3527
|
// apply filter if needed
|
|
3219
|
-
|
|
3220
|
-
return result;
|
|
3528
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3221
3529
|
}
|
|
3222
|
-
|
|
3530
|
+
if (
|
|
3223
3531
|
// filter by hm-rega variables
|
|
3224
3532
|
wildCardLastPos &&
|
|
3225
3533
|
func?.map &&
|
|
@@ -3233,15 +3541,7 @@ export class ObjectsInRedisClient {
|
|
|
3233
3541
|
}
|
|
3234
3542
|
let objs;
|
|
3235
3543
|
try {
|
|
3236
|
-
objs = await this.client.evalsha(
|
|
3237
|
-
this.scripts.variables,
|
|
3238
|
-
5,
|
|
3239
|
-
`${this.objNamespace}hm-rega.`,
|
|
3240
|
-
params.startkey,
|
|
3241
|
-
params.endkey,
|
|
3242
|
-
cursor,
|
|
3243
|
-
`${this.setNamespace}object.type.state`,
|
|
3244
|
-
]);
|
|
3544
|
+
objs = (await this.client.evalsha(this.scripts.variables, 5, `${this.objNamespace}hm-rega.`, params.startkey, params.endkey, cursor, `${this.setNamespace}object.type.state`));
|
|
3245
3545
|
}
|
|
3246
3546
|
catch (e) {
|
|
3247
3547
|
this.log.warn(`${this.namespace} Cannot get view ${e.message}`);
|
|
@@ -3267,14 +3567,13 @@ export class ObjectsInRedisClient {
|
|
|
3267
3567
|
}
|
|
3268
3568
|
});
|
|
3269
3569
|
if (currRows.length) {
|
|
3270
|
-
|
|
3570
|
+
rows = [...rows, ...currRows];
|
|
3271
3571
|
} // endIf
|
|
3272
3572
|
} while (cursor !== '0');
|
|
3273
3573
|
// apply filter if needed
|
|
3274
|
-
|
|
3275
|
-
return result;
|
|
3574
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3276
3575
|
}
|
|
3277
|
-
|
|
3576
|
+
if (
|
|
3278
3577
|
// filter by custom, redis also returns if common.custom is not present
|
|
3279
3578
|
wildCardLastPos &&
|
|
3280
3579
|
func?.map &&
|
|
@@ -3288,15 +3587,7 @@ export class ObjectsInRedisClient {
|
|
|
3288
3587
|
}
|
|
3289
3588
|
let objs;
|
|
3290
3589
|
try {
|
|
3291
|
-
objs = await this.client.evalsha(
|
|
3292
|
-
this.scripts.custom,
|
|
3293
|
-
5,
|
|
3294
|
-
this.objNamespace,
|
|
3295
|
-
params.startkey,
|
|
3296
|
-
params.endkey,
|
|
3297
|
-
cursor,
|
|
3298
|
-
`${this.setNamespace}object.common.custom`,
|
|
3299
|
-
]);
|
|
3590
|
+
objs = (await this.client.evalsha(this.scripts.custom, 5, this.objNamespace, params.startkey, params.endkey, cursor, `${this.setNamespace}object.common.custom`));
|
|
3300
3591
|
}
|
|
3301
3592
|
catch (e) {
|
|
3302
3593
|
this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
|
|
@@ -3323,17 +3614,17 @@ export class ObjectsInRedisClient {
|
|
|
3323
3614
|
}
|
|
3324
3615
|
if (obj?.common?.custom) {
|
|
3325
3616
|
if (useFullObject) {
|
|
3326
|
-
|
|
3617
|
+
rows.push({ id: obj._id, value: obj });
|
|
3327
3618
|
}
|
|
3328
3619
|
else {
|
|
3329
|
-
|
|
3620
|
+
// @ts-expect-error missing typing
|
|
3621
|
+
rows.push({ id: obj._id, value: obj.common.custom });
|
|
3330
3622
|
}
|
|
3331
3623
|
}
|
|
3332
3624
|
}
|
|
3333
3625
|
} while (cursor !== '0');
|
|
3334
3626
|
// apply filter if needed
|
|
3335
|
-
|
|
3336
|
-
return result;
|
|
3627
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3337
3628
|
}
|
|
3338
3629
|
if (!wildCardLastPos) {
|
|
3339
3630
|
this.log.debug(`${this.namespace} Search can't be optimized because wildcard not at the end, fallback to keys!: ${func.map}`);
|
|
@@ -3381,7 +3672,7 @@ export class ObjectsInRedisClient {
|
|
|
3381
3672
|
objs = [];
|
|
3382
3673
|
}
|
|
3383
3674
|
const _emit_ = (id, obj) => {
|
|
3384
|
-
|
|
3675
|
+
rows.push({ id: id, value: obj });
|
|
3385
3676
|
};
|
|
3386
3677
|
const f = eval(`(${func.map.replace(/^function\(([a-z0-9A-Z_]+)\)/g, 'function($1, emit)')})`);
|
|
3387
3678
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -3394,7 +3685,7 @@ export class ObjectsInRedisClient {
|
|
|
3394
3685
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${objs[i]}`);
|
|
3395
3686
|
continue;
|
|
3396
3687
|
}
|
|
3397
|
-
if (!utils.checkObject(obj,
|
|
3688
|
+
if (!utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
3398
3689
|
continue;
|
|
3399
3690
|
}
|
|
3400
3691
|
if (obj) {
|
|
@@ -3409,21 +3700,23 @@ export class ObjectsInRedisClient {
|
|
|
3409
3700
|
// Calculate max
|
|
3410
3701
|
if (func.reduce === '_stats') {
|
|
3411
3702
|
let max = null;
|
|
3412
|
-
for (let i = 0; i <
|
|
3413
|
-
|
|
3414
|
-
|
|
3703
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3704
|
+
const _value = rows[i].value;
|
|
3705
|
+
if (max === null || (_value !== null && _value > max)) {
|
|
3706
|
+
max = rows[i].value;
|
|
3415
3707
|
}
|
|
3416
3708
|
}
|
|
3417
3709
|
if (max !== null) {
|
|
3418
|
-
|
|
3710
|
+
// @ts-expect-error the _stats reduce result is not a regular object value
|
|
3711
|
+
rows = [{ id: '_stats', value: { max } }];
|
|
3419
3712
|
}
|
|
3420
3713
|
else {
|
|
3421
|
-
|
|
3714
|
+
rows = [];
|
|
3422
3715
|
}
|
|
3423
3716
|
}
|
|
3424
|
-
return
|
|
3717
|
+
return { rows: rows };
|
|
3425
3718
|
}
|
|
3426
|
-
async _getObjectView(design, search, params,
|
|
3719
|
+
async _getObjectView(design, search, params, userContext) {
|
|
3427
3720
|
if (!this.client) {
|
|
3428
3721
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
3429
3722
|
}
|
|
@@ -3444,7 +3737,8 @@ export class ObjectsInRedisClient {
|
|
|
3444
3737
|
throw new Error(`Cannot parse JSON: "_design/${design}" / "${obj}"`);
|
|
3445
3738
|
}
|
|
3446
3739
|
if (obj.views?.[search]) {
|
|
3447
|
-
|
|
3740
|
+
// @ts-expect-error missing typing
|
|
3741
|
+
return this._applyViewFunc(obj.views[search], params, userContext);
|
|
3448
3742
|
}
|
|
3449
3743
|
this.log.error(`${this.namespace} Cannot find search "${search}" in "${design}"`);
|
|
3450
3744
|
throw new Error(`Cannot find search "${search}" in "${design}"`);
|
|
@@ -3454,6 +3748,15 @@ export class ObjectsInRedisClient {
|
|
|
3454
3748
|
throw new Error(`Cannot find view "${design}"`);
|
|
3455
3749
|
}
|
|
3456
3750
|
}
|
|
3751
|
+
/**
|
|
3752
|
+
* Run a predefined object view (design document) and return the matching rows
|
|
3753
|
+
*
|
|
3754
|
+
* @param design The design document name
|
|
3755
|
+
* @param search The view name within the design document
|
|
3756
|
+
* @param params Query parameters such as startkey and endkey
|
|
3757
|
+
* @param options The current request options including the user, or the callback
|
|
3758
|
+
* @param callback Called with the matching rows
|
|
3759
|
+
*/
|
|
3457
3760
|
getObjectView(design, search, params, options, callback) {
|
|
3458
3761
|
if (typeof options === 'function') {
|
|
3459
3762
|
callback = options;
|
|
@@ -3462,16 +3765,13 @@ export class ObjectsInRedisClient {
|
|
|
3462
3765
|
if (!callback) {
|
|
3463
3766
|
return new Promise((resolve, reject) => this.getObjectView(design, search, params, options, (err, obj) => (err ? reject(err) : resolve(obj))));
|
|
3464
3767
|
}
|
|
3465
|
-
if (options?.acl) {
|
|
3466
|
-
options.acl = null;
|
|
3467
|
-
}
|
|
3468
3768
|
if (typeof callback === 'function') {
|
|
3469
|
-
utils.checkObjectRights(this, null, null, options, 'list', async (err,
|
|
3769
|
+
utils.checkObjectRights(this, null, null, options, 'list', async (err, userContext) => {
|
|
3470
3770
|
if (err) {
|
|
3471
3771
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
3472
3772
|
}
|
|
3473
3773
|
try {
|
|
3474
|
-
const res = await this._getObjectView(design, search, params,
|
|
3774
|
+
const res = await this._getObjectView(design, search, params, userContext);
|
|
3475
3775
|
return tools.maybeCallbackWithError(callback, null, res);
|
|
3476
3776
|
}
|
|
3477
3777
|
catch (e) {
|
|
@@ -3480,16 +3780,25 @@ export class ObjectsInRedisClient {
|
|
|
3480
3780
|
});
|
|
3481
3781
|
}
|
|
3482
3782
|
}
|
|
3783
|
+
/**
|
|
3784
|
+
* Promise-version of getObjectView
|
|
3785
|
+
*
|
|
3786
|
+
* @param design The design document name
|
|
3787
|
+
* @param search The view name within the design document
|
|
3788
|
+
* @param params Query parameters such as startkey and endkey
|
|
3789
|
+
* @param options The current request options including the user
|
|
3790
|
+
* @param options.user The user on whose behalf the view is queried
|
|
3791
|
+
*/
|
|
3483
3792
|
getObjectViewAsync(design, search, params, options) {
|
|
3484
3793
|
return this.getObjectView(design, search, params, options);
|
|
3485
3794
|
}
|
|
3486
|
-
async _getObjectList(params,
|
|
3795
|
+
async _getObjectList(params, userContext) {
|
|
3487
3796
|
if (!this.client) {
|
|
3488
3797
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
3489
3798
|
}
|
|
3490
|
-
params
|
|
3491
|
-
params.startkey
|
|
3492
|
-
params.endkey
|
|
3799
|
+
params ||= {};
|
|
3800
|
+
params.startkey ||= '';
|
|
3801
|
+
params.endkey ||= '\u9999';
|
|
3493
3802
|
const pattern = params.endkey.substring(0, params.startkey.length) === params.startkey
|
|
3494
3803
|
? `${this.objNamespace + params.startkey}*`
|
|
3495
3804
|
: `${this.objNamespace}*`;
|
|
@@ -3538,7 +3847,7 @@ export class ObjectsInRedisClient {
|
|
|
3538
3847
|
this.log.error(`${this.namespace} Cannot parse JSON ${_keys[i]}: ${objs[i]}`);
|
|
3539
3848
|
continue;
|
|
3540
3849
|
}
|
|
3541
|
-
if (!obj || !utils.checkObject(obj,
|
|
3850
|
+
if (!obj || !utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
3542
3851
|
continue;
|
|
3543
3852
|
}
|
|
3544
3853
|
// @ts-expect-error TODO we are returning type Object for ease of use to devs, but formally these are AnyObjects, e.g. not guaranteed to have common
|
|
@@ -3547,6 +3856,13 @@ export class ObjectsInRedisClient {
|
|
|
3547
3856
|
}
|
|
3548
3857
|
return result;
|
|
3549
3858
|
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Get the list of objects matching the given parameters
|
|
3861
|
+
*
|
|
3862
|
+
* @param params Query parameters such as startkey and endkey
|
|
3863
|
+
* @param options The current request options including the user, or the callback
|
|
3864
|
+
* @param callback Called with the matching objects
|
|
3865
|
+
*/
|
|
3550
3866
|
getObjectList(params, options, callback) {
|
|
3551
3867
|
if (typeof options === 'function') {
|
|
3552
3868
|
callback = options;
|
|
@@ -3555,16 +3871,13 @@ export class ObjectsInRedisClient {
|
|
|
3555
3871
|
if (!callback) {
|
|
3556
3872
|
return new Promise((resolve, reject) => this.getObjectList(params, options, (err, obj) => (err ? reject(err) : resolve(obj))));
|
|
3557
3873
|
}
|
|
3558
|
-
if (options?.acl) {
|
|
3559
|
-
options.acl = null;
|
|
3560
|
-
}
|
|
3561
3874
|
if (typeof callback === 'function') {
|
|
3562
|
-
utils.checkObjectRights(this, null, null, options, 'list', async (err,
|
|
3875
|
+
utils.checkObjectRights(this, null, null, options, 'list', async (err, userContext) => {
|
|
3563
3876
|
if (err) {
|
|
3564
3877
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
3565
3878
|
}
|
|
3566
3879
|
try {
|
|
3567
|
-
const res = await this._getObjectList(params,
|
|
3880
|
+
const res = await this._getObjectList(params, userContext);
|
|
3568
3881
|
return tools.maybeCallbackWithError(callback, null, res);
|
|
3569
3882
|
}
|
|
3570
3883
|
catch (e) {
|
|
@@ -3573,35 +3886,34 @@ export class ObjectsInRedisClient {
|
|
|
3573
3886
|
});
|
|
3574
3887
|
}
|
|
3575
3888
|
}
|
|
3889
|
+
/**
|
|
3890
|
+
* Promise-version of getObjectList
|
|
3891
|
+
*
|
|
3892
|
+
* @param params Query parameters such as startkey and endkey
|
|
3893
|
+
* @param options The current request options including the user
|
|
3894
|
+
*/
|
|
3576
3895
|
getObjectListAsync(params, options) {
|
|
3577
3896
|
return this.getObjectList(params, options);
|
|
3578
3897
|
}
|
|
3579
|
-
// could be optimized, to read object only once. Now it will read 3 times
|
|
3580
|
-
async _extendObject(id, obj, options,
|
|
3898
|
+
// could be optimized, to read an object only once. Now it will read 3 times
|
|
3899
|
+
async _extendObject(id, obj, options, userContext) {
|
|
3581
3900
|
if (!id || typeof id !== 'string' || utils.REG_CHECK_ID.test(id)) {
|
|
3582
|
-
|
|
3583
|
-
return tools.maybeCallbackWithError(callback, `Invalid ID: ${id}`);
|
|
3901
|
+
throw new Error(`Invalid ID: ${id}`);
|
|
3584
3902
|
}
|
|
3585
3903
|
if (!this.client) {
|
|
3586
|
-
|
|
3904
|
+
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
3587
3905
|
}
|
|
3906
|
+
const oldObjStr = await this.client.get(this.objNamespace + id);
|
|
3588
3907
|
let oldObj;
|
|
3589
3908
|
try {
|
|
3590
|
-
oldObj =
|
|
3591
|
-
}
|
|
3592
|
-
catch (e) {
|
|
3593
|
-
return tools.maybeCallbackWithRedisError(callback, e);
|
|
3594
|
-
}
|
|
3595
|
-
try {
|
|
3596
|
-
oldObj = oldObj ? JSON.parse(oldObj) : null;
|
|
3909
|
+
oldObj = oldObjStr ? JSON.parse(oldObjStr) : null;
|
|
3597
3910
|
}
|
|
3598
3911
|
catch {
|
|
3599
|
-
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${
|
|
3600
|
-
|
|
3601
|
-
return tools.maybeCallbackWithError(callback, `Cannot parse JSON ${id}: ${oldObj}`);
|
|
3912
|
+
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${oldObjStr}`);
|
|
3913
|
+
throw new Error(`Cannot parse JSON ${id}: ${oldObjStr}`);
|
|
3602
3914
|
}
|
|
3603
|
-
if (!utils.checkObject(oldObj,
|
|
3604
|
-
|
|
3915
|
+
if (!utils.checkObject(oldObj, userContext, CONSTS.ACCESS_WRITE)) {
|
|
3916
|
+
throw new Error(ERRORS.ERROR_PERMISSION);
|
|
3605
3917
|
}
|
|
3606
3918
|
let _oldObj;
|
|
3607
3919
|
if (oldObj?.nonEdit) {
|
|
@@ -3610,150 +3922,140 @@ export class ObjectsInRedisClient {
|
|
|
3610
3922
|
}
|
|
3611
3923
|
// we need to know if custom has been added/deleted
|
|
3612
3924
|
const oldObjHasCustom = !!oldObj?.common?.custom;
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3925
|
+
// Clone the update here to prevent "sandboxed" objects from the JavaScript adapter
|
|
3926
|
+
const objClone = deepClone(obj);
|
|
3927
|
+
// Start from the existing object so the update is merged into it instead of replacing it
|
|
3928
|
+
let oldObjNotNull = oldObj || {};
|
|
3929
|
+
if (oldObjNotNull.common?.custom !== undefined &&
|
|
3930
|
+
oldObjNotNull.common.custom !== null &&
|
|
3931
|
+
!tools.isObject(oldObjNotNull.common.custom)) {
|
|
3619
3932
|
// custom has to be an object, else clean up
|
|
3620
|
-
delete
|
|
3933
|
+
delete oldObjNotNull.common.custom;
|
|
3621
3934
|
}
|
|
3622
3935
|
// we need to check if type has changed
|
|
3623
|
-
const oldType =
|
|
3624
|
-
|
|
3625
|
-
|
|
3936
|
+
const oldType = oldObjNotNull.type;
|
|
3937
|
+
oldObjNotNull = extend(true, oldObjNotNull, objClone);
|
|
3938
|
+
oldObjNotNull._id = id;
|
|
3626
3939
|
// add user default rights
|
|
3627
|
-
if (this.defaultNewAcl && !
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3940
|
+
if (this.defaultNewAcl && !oldObjNotNull.acl) {
|
|
3941
|
+
oldObjNotNull.acl = {
|
|
3942
|
+
owner: this.defaultNewAcl.owner,
|
|
3943
|
+
ownerGroup: this.defaultNewAcl.ownerGroup,
|
|
3944
|
+
object: this.defaultNewAcl.object,
|
|
3945
|
+
};
|
|
3946
|
+
if (oldObjNotNull.type === 'state') {
|
|
3947
|
+
oldObjNotNull.acl.state = this.defaultNewAcl.state;
|
|
3632
3948
|
}
|
|
3633
3949
|
if (options.owner) {
|
|
3634
|
-
|
|
3950
|
+
oldObjNotNull.acl.owner = options.owner;
|
|
3635
3951
|
if (!options.ownerGroup) {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
}
|
|
3644
|
-
this._extendObject(id, obj, options, callback);
|
|
3645
|
-
});
|
|
3952
|
+
const [, groups] = await utils.getUserGroupAsync(this, options.owner);
|
|
3953
|
+
if (!groups?.[0]) {
|
|
3954
|
+
options.ownerGroup = this.defaultNewAcl.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP;
|
|
3955
|
+
}
|
|
3956
|
+
else {
|
|
3957
|
+
options.ownerGroup = groups[0];
|
|
3958
|
+
}
|
|
3646
3959
|
}
|
|
3647
3960
|
}
|
|
3648
3961
|
}
|
|
3649
|
-
if (this.defaultNewAcl && options.ownerGroup &&
|
|
3650
|
-
|
|
3962
|
+
if (this.defaultNewAcl && options.ownerGroup && oldObjNotNull.acl && !oldObjNotNull.acl.ownerGroup) {
|
|
3963
|
+
oldObjNotNull.acl.ownerGroup = options.ownerGroup;
|
|
3651
3964
|
}
|
|
3652
3965
|
if (obj.common && 'alias' in obj.common && obj.common.alias.id) {
|
|
3653
3966
|
if (typeof obj.common.alias.id === 'object') {
|
|
3654
3967
|
if (typeof obj.common.alias.id.write !== 'string' || typeof obj.common.alias.id.read !== 'string') {
|
|
3655
|
-
|
|
3968
|
+
throw new Error('Invalid alias ID');
|
|
3656
3969
|
}
|
|
3657
3970
|
if (obj.common.alias.id.write.startsWith('alias.') || obj.common.alias.id.read.startsWith('alias.')) {
|
|
3658
|
-
|
|
3971
|
+
throw new Error('Cannot make alias on alias');
|
|
3659
3972
|
}
|
|
3660
3973
|
}
|
|
3661
3974
|
else {
|
|
3662
3975
|
if (typeof obj.common.alias.id !== 'string') {
|
|
3663
|
-
|
|
3976
|
+
throw new Error('Invalid alias ID');
|
|
3664
3977
|
}
|
|
3665
3978
|
if (obj.common.alias.id.startsWith('alias.')) {
|
|
3666
|
-
|
|
3979
|
+
throw new Error('Cannot make alias on alias');
|
|
3667
3980
|
}
|
|
3668
3981
|
}
|
|
3669
3982
|
}
|
|
3670
|
-
if (_oldObj && !tools.checkNonEditable(_oldObj,
|
|
3671
|
-
|
|
3983
|
+
if (_oldObj && !tools.checkNonEditable(_oldObj, oldObjNotNull)) {
|
|
3984
|
+
throw new Error('Invalid password for update of vendor information');
|
|
3672
3985
|
}
|
|
3673
|
-
const message = JSON.stringify(
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3681
|
-
}
|
|
3682
|
-
else if (oldObj.type && oldType && oldObj.type !== oldType) {
|
|
3683
|
-
// the old obj had a type which differs from the new type -> rem old, add new
|
|
3684
|
-
commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id], ['srem', `${this.setNamespace}object.type.${oldObj.type}`, this.objNamespace + id]);
|
|
3685
|
-
}
|
|
3686
|
-
else if (oldType && !oldObj.type) {
|
|
3687
|
-
// the oldObj had a type, the new one has no -> rem
|
|
3688
|
-
commands.push(['srem', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3691
|
-
if (oldObj.common?.custom && !oldObjHasCustom) {
|
|
3692
|
-
// we now have custom, old object had no custom
|
|
3693
|
-
commands.push(['sadd', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3694
|
-
}
|
|
3695
|
-
else if (oldObjHasCustom && (!oldObj.common || !oldObj.common.custom)) {
|
|
3696
|
-
// we no longer have custom
|
|
3697
|
-
commands.push(['srem', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3698
|
-
}
|
|
3699
|
-
if (!commands.length) {
|
|
3700
|
-
await this.client.set(this.objNamespace + id, message);
|
|
3986
|
+
const message = JSON.stringify(oldObjNotNull);
|
|
3987
|
+
const commands = [];
|
|
3988
|
+
if (this.useSets) {
|
|
3989
|
+
// what is called oldObjNotNull is actually the obj we set, because it has been extended
|
|
3990
|
+
if (oldObjNotNull.type && !oldType) {
|
|
3991
|
+
// new object or oldObjNotNull had no type -> add to set + set object
|
|
3992
|
+
commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3701
3993
|
}
|
|
3702
|
-
else {
|
|
3703
|
-
//
|
|
3704
|
-
commands.push(['
|
|
3705
|
-
await this.client.multi(commands).exec();
|
|
3994
|
+
else if (oldObjNotNull.type && oldType && oldObjNotNull.type !== oldType) {
|
|
3995
|
+
// the old obj had a type which differs from the new type -> rem old, add new
|
|
3996
|
+
commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id], ['srem', `${this.setNamespace}object.type.${oldObjNotNull.type}`, this.objNamespace + id]);
|
|
3706
3997
|
}
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
this.
|
|
3998
|
+
else if (oldType && !oldObjNotNull.type) {
|
|
3999
|
+
// the oldObjNotNull had a type, the new one has no -> rem
|
|
4000
|
+
commands.push(['srem', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3710
4001
|
}
|
|
3711
|
-
await this.client.publish(this.objNamespace + id, message);
|
|
3712
|
-
return tools.maybeCallbackWithError(callback, null, { id: id, value: oldObj }, id);
|
|
3713
4002
|
}
|
|
3714
|
-
|
|
3715
|
-
|
|
4003
|
+
if (oldObjNotNull.common?.custom && !oldObjHasCustom) {
|
|
4004
|
+
// we now have custom, old object had no custom
|
|
4005
|
+
commands.push(['sadd', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3716
4006
|
}
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
callback = options;
|
|
3721
|
-
options = null;
|
|
4007
|
+
else if (oldObjHasCustom && (!oldObjNotNull.common || !oldObjNotNull.common.custom)) {
|
|
4008
|
+
// we no longer have custom
|
|
4009
|
+
commands.push(['srem', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3722
4010
|
}
|
|
3723
|
-
if (!
|
|
3724
|
-
|
|
4011
|
+
if (!commands.length) {
|
|
4012
|
+
await this.client.set(this.objNamespace + id, message);
|
|
3725
4013
|
}
|
|
3726
|
-
|
|
3727
|
-
|
|
4014
|
+
else {
|
|
4015
|
+
// set all commands atomic
|
|
4016
|
+
commands.push(['set', this.objNamespace + id, message]);
|
|
4017
|
+
await this.client.multi(commands).exec();
|
|
3728
4018
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
});
|
|
4019
|
+
// extended -> if it's now type meta and currently marked as not -> cache
|
|
4020
|
+
if (this.existingMetaObjects[id] === false && oldObjNotNull?.type === 'meta') {
|
|
4021
|
+
this.existingMetaObjects[id] = true;
|
|
4022
|
+
}
|
|
4023
|
+
await this.client.publish(this.objNamespace + id, message);
|
|
4024
|
+
return { id, value: oldObjNotNull };
|
|
3736
4025
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
4026
|
+
/**
|
|
4027
|
+
* Extend an existing object with the given partial object, creating it if it does not exist
|
|
4028
|
+
*
|
|
4029
|
+
* @param id The id of the object to extend
|
|
4030
|
+
* @param obj The partial object to merge into the existing object
|
|
4031
|
+
* @param options The current request options including the user, or the callback
|
|
4032
|
+
*/
|
|
4033
|
+
async extendObject(id, obj, options) {
|
|
4034
|
+
const userContext = await checkObjectRightsAsync(this, null, null, options, CONSTS.ACCESS_WRITE);
|
|
4035
|
+
return this._extendObject(id, obj, options || {}, userContext);
|
|
3739
4036
|
}
|
|
3740
4037
|
/**
|
|
3741
4038
|
* Returns the object id if found
|
|
3742
4039
|
*
|
|
3743
|
-
* @param idOrName
|
|
3744
|
-
* @param type
|
|
3745
|
-
* @param options
|
|
3746
|
-
* @param
|
|
4040
|
+
* @param idOrName The id or name to search for
|
|
4041
|
+
* @param type The expected common type, or null for any
|
|
4042
|
+
* @param options The current request options (may include a language)
|
|
4043
|
+
* @param options.language The language to use when resolving names
|
|
4044
|
+
* @param options.user The user on whose behalf the lookup is performed
|
|
4045
|
+
* @param userContext The resolved user context to check the rights against
|
|
4046
|
+
* @param callback Called with the found id and the original id/name
|
|
3747
4047
|
*/
|
|
3748
|
-
_findObject(idOrName, type, options, callback) {
|
|
4048
|
+
_findObject(idOrName, type, options, userContext, callback) {
|
|
3749
4049
|
// Try to read by ID
|
|
3750
|
-
this._getObject(idOrName,
|
|
4050
|
+
this._getObject(idOrName, userContext, (err, obj) => {
|
|
3751
4051
|
// Assume it is ID
|
|
3752
|
-
if (obj &&
|
|
4052
|
+
if (obj &&
|
|
4053
|
+
utils.checkObject(obj, userContext, CONSTS.ACCESS_READ) &&
|
|
4054
|
+
(!type || obj.common?.type === type)) {
|
|
3753
4055
|
return tools.maybeCallbackWithError(callback, null, idOrName, obj.common.name);
|
|
3754
4056
|
}
|
|
3755
4057
|
// Get all objects that this user may read
|
|
3756
|
-
this._getKeys('*',
|
|
4058
|
+
return this._getKeys('*', userContext, true, async (err, keys) => {
|
|
3757
4059
|
if (!this.client) {
|
|
3758
4060
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
3759
4061
|
}
|
|
@@ -3790,9 +4092,17 @@ export class ObjectsInRedisClient {
|
|
|
3790
4092
|
}
|
|
3791
4093
|
}
|
|
3792
4094
|
return tools.maybeCallbackWithError(callback, null, undefined, idOrName);
|
|
3793
|
-
}
|
|
3794
|
-
});
|
|
4095
|
+
});
|
|
4096
|
+
}).catch(e => this.log.error(`${this.namespace} Cannot find object: ${e.message}`));
|
|
3795
4097
|
}
|
|
4098
|
+
/**
|
|
4099
|
+
* Find an object by its id or name
|
|
4100
|
+
*
|
|
4101
|
+
* @param idOrName The id or name to search for
|
|
4102
|
+
* @param type The expected common type, or null for any
|
|
4103
|
+
* @param options The current request options (may include a language), or the callback
|
|
4104
|
+
* @param callback Called with the found id and the original id/name
|
|
4105
|
+
*/
|
|
3796
4106
|
findObject(idOrName, type, options, callback) {
|
|
3797
4107
|
if (typeof type === 'function') {
|
|
3798
4108
|
callback = type;
|
|
@@ -3806,19 +4116,21 @@ export class ObjectsInRedisClient {
|
|
|
3806
4116
|
if (!callback) {
|
|
3807
4117
|
return new Promise((resolve, reject) => this.findObject(idOrName, type, options, (err, id, _idOrName) => (err ? reject(err) : resolve(id))));
|
|
3808
4118
|
}
|
|
3809
|
-
if (options?.acl) {
|
|
3810
|
-
options.acl = null;
|
|
3811
|
-
}
|
|
3812
4119
|
if (typeof callback === 'function') {
|
|
3813
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_LIST, (err,
|
|
4120
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_LIST, (err, userContext) => {
|
|
3814
4121
|
if (err) {
|
|
3815
4122
|
return tools.maybeCallbackWithError(callback, err);
|
|
3816
4123
|
}
|
|
3817
|
-
return this._findObject(idOrName, type, options, callback);
|
|
4124
|
+
return this._findObject(idOrName, type, options, userContext, callback);
|
|
3818
4125
|
});
|
|
3819
4126
|
}
|
|
3820
4127
|
}
|
|
3821
4128
|
// can be called only from js-controller
|
|
4129
|
+
/**
|
|
4130
|
+
* Add object property paths that should be preserved when an object is overwritten (controller only)
|
|
4131
|
+
*
|
|
4132
|
+
* @param settings One or more property paths to preserve
|
|
4133
|
+
*/
|
|
3822
4134
|
addPreserveSettings(settings) {
|
|
3823
4135
|
if (!Array.isArray(settings)) {
|
|
3824
4136
|
settings = [settings];
|
|
@@ -3860,26 +4172,40 @@ export class ObjectsInRedisClient {
|
|
|
3860
4172
|
return tools.maybeCallbackWithRedisError(callback, e);
|
|
3861
4173
|
}
|
|
3862
4174
|
}
|
|
4175
|
+
/**
|
|
4176
|
+
* Delete the whole objects database (requires admin rights)
|
|
4177
|
+
*
|
|
4178
|
+
* @param options The current request options including the user, or the callback
|
|
4179
|
+
* @param callback Called once the database has been destroyed
|
|
4180
|
+
*/
|
|
3863
4181
|
destroyDB(options, callback) {
|
|
3864
4182
|
if (typeof options === 'function') {
|
|
3865
4183
|
callback = options;
|
|
3866
4184
|
options = null;
|
|
3867
4185
|
}
|
|
3868
4186
|
options = options || {};
|
|
3869
|
-
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err,
|
|
4187
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, userContext) => {
|
|
3870
4188
|
if (err) {
|
|
3871
4189
|
return tools.maybeCallbackWithRedisError(callback, err);
|
|
3872
4190
|
}
|
|
3873
|
-
if (!
|
|
4191
|
+
if (!userContext.acl.file.write || userContext.user !== CONSTS.SYSTEM_ADMIN_USER) {
|
|
3874
4192
|
return tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
3875
4193
|
}
|
|
3876
4194
|
return this._destroyDB(callback);
|
|
3877
4195
|
});
|
|
3878
4196
|
}
|
|
4197
|
+
/**
|
|
4198
|
+
* Promise-version of destroyDB
|
|
4199
|
+
*
|
|
4200
|
+
* @param options The current request options including the user
|
|
4201
|
+
* @param options.user The user on whose behalf the database is destroyed
|
|
4202
|
+
*/
|
|
3879
4203
|
destroyDBAsync(options) {
|
|
3880
4204
|
return new Promise((resolve, reject) => this.destroyDB(options, err => (err ? reject(err) : resolve())));
|
|
3881
4205
|
}
|
|
3882
|
-
|
|
4206
|
+
/**
|
|
4207
|
+
* Destructor of the class. Called when shutting down to close the redis connections.
|
|
4208
|
+
*/
|
|
3883
4209
|
async destroy() {
|
|
3884
4210
|
this.stop = true;
|
|
3885
4211
|
if (this.client) {
|
|
@@ -3913,6 +4239,9 @@ export class ObjectsInRedisClient {
|
|
|
3913
4239
|
}
|
|
3914
4240
|
}
|
|
3915
4241
|
}
|
|
4242
|
+
/**
|
|
4243
|
+
* Load and register the Lua scripts used for atomic operations on the redis server
|
|
4244
|
+
*/
|
|
3916
4245
|
async loadLuaScripts() {
|
|
3917
4246
|
let luaDirName;
|
|
3918
4247
|
if (this.noLegacyMultihost && this.useSets) {
|
|
@@ -3933,15 +4262,13 @@ export class ObjectsInRedisClient {
|
|
|
3933
4262
|
return { name: name.replace(/\.lua$/, ''), text: script, hash };
|
|
3934
4263
|
});
|
|
3935
4264
|
const hashes = scripts.map(e => e.hash);
|
|
3936
|
-
hashes.unshift('EXISTS');
|
|
3937
4265
|
if (!this.client) {
|
|
3938
4266
|
throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
|
|
3939
4267
|
}
|
|
3940
|
-
let arr;
|
|
3941
4268
|
try {
|
|
3942
|
-
arr = await this.client.script(hashes);
|
|
4269
|
+
const arr = (await this.client.script('EXISTS', ...hashes));
|
|
3943
4270
|
if (arr) {
|
|
3944
|
-
scripts.forEach((
|
|
4271
|
+
scripts.forEach((_e, i) => (scripts[i].loaded = !!arr[i]));
|
|
3945
4272
|
}
|
|
3946
4273
|
}
|
|
3947
4274
|
catch {
|
|
@@ -3954,7 +4281,7 @@ export class ObjectsInRedisClient {
|
|
|
3954
4281
|
if (!script.loaded) {
|
|
3955
4282
|
let hash;
|
|
3956
4283
|
try {
|
|
3957
|
-
hash = await this.client.script(
|
|
4284
|
+
hash = (await this.client.script('LOAD', script.text));
|
|
3958
4285
|
script.loaded = true;
|
|
3959
4286
|
}
|
|
3960
4287
|
catch (e) {
|
|
@@ -3966,16 +4293,16 @@ export class ObjectsInRedisClient {
|
|
|
3966
4293
|
throw new Error(`Cannot load "${script.name}" into objects database: ${e.message}`);
|
|
3967
4294
|
}
|
|
3968
4295
|
}
|
|
3969
|
-
script.hash = hash;
|
|
4296
|
+
script.hash = hash || '';
|
|
3970
4297
|
}
|
|
3971
4298
|
}
|
|
3972
4299
|
this.scripts = {};
|
|
3973
4300
|
scripts.forEach(e => (this.scripts[e.name] = e.hash));
|
|
3974
4301
|
}
|
|
3975
4302
|
/**
|
|
3976
|
-
* Get all keys matching a pattern using redis SCAN command
|
|
4303
|
+
* Get all keys matching a pattern using redis SCAN command; duplicates are filtered out
|
|
3977
4304
|
*
|
|
3978
|
-
* @param pattern - pattern to match, e.
|
|
4305
|
+
* @param pattern - pattern to match, e.g. io.hm-rpc.0*
|
|
3979
4306
|
* @param count - count argument used by redis SCAN, default is 250
|
|
3980
4307
|
*/
|
|
3981
4308
|
_getKeysViaScan(pattern, count = 250) {
|
|
@@ -3997,6 +4324,7 @@ export class ObjectsInRedisClient {
|
|
|
3997
4324
|
// return without duplicates
|
|
3998
4325
|
resolve(Array.from(new Set(uniqueKeys)));
|
|
3999
4326
|
});
|
|
4327
|
+
stream.on('error', reject);
|
|
4000
4328
|
});
|
|
4001
4329
|
}
|
|
4002
4330
|
/**
|
|
@@ -4023,7 +4351,7 @@ export class ObjectsInRedisClient {
|
|
|
4023
4351
|
if (!this.client) {
|
|
4024
4352
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
4025
4353
|
}
|
|
4026
|
-
// to be safe we remove all sets before migration
|
|
4354
|
+
// to be safe, we remove all sets before migration
|
|
4027
4355
|
const keys = await this._getKeysViaScan(`${this.setNamespace}object.type.*`);
|
|
4028
4356
|
for (const key of keys) {
|
|
4029
4357
|
await this.client.del(key);
|
|
@@ -4074,17 +4402,11 @@ export class ObjectsInRedisClient {
|
|
|
4074
4402
|
return Promise.resolve(0);
|
|
4075
4403
|
}
|
|
4076
4404
|
// try to extend lock
|
|
4077
|
-
return this.client.evalsha(
|
|
4078
|
-
this.scripts.redlock_extend,
|
|
4079
|
-
3,
|
|
4080
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
4081
|
-
this.hostname,
|
|
4082
|
-
ms,
|
|
4083
|
-
]);
|
|
4405
|
+
return this.client.evalsha(this.scripts.redlock_extend, 3, `${this.metaNamespace}objects.primaryHost`, this.hostname, ms);
|
|
4084
4406
|
}
|
|
4085
4407
|
/**
|
|
4086
|
-
* Sets current host as primary if no primary host active
|
|
4087
|
-
* Value
|
|
4408
|
+
* Sets the current host as primary if no primary host active
|
|
4409
|
+
* Value expires after ms milliseconds
|
|
4088
4410
|
*
|
|
4089
4411
|
* @param ms - ms until value expires
|
|
4090
4412
|
* @returns 1 if lock acquired else 0
|
|
@@ -4098,16 +4420,10 @@ export class ObjectsInRedisClient {
|
|
|
4098
4420
|
return Promise.resolve(0);
|
|
4099
4421
|
}
|
|
4100
4422
|
// try to acquire lock
|
|
4101
|
-
return this.client.evalsha(
|
|
4102
|
-
this.scripts.redlock_acquire,
|
|
4103
|
-
3,
|
|
4104
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
4105
|
-
this.hostname,
|
|
4106
|
-
ms,
|
|
4107
|
-
]);
|
|
4423
|
+
return this.client.evalsha(this.scripts.redlock_acquire, 3, `${this.metaNamespace}objects.primaryHost`, this.hostname, ms);
|
|
4108
4424
|
}
|
|
4109
4425
|
/**
|
|
4110
|
-
* Get name of the primary host
|
|
4426
|
+
* Get the name of the primary host
|
|
4111
4427
|
*/
|
|
4112
4428
|
getPrimaryHost() {
|
|
4113
4429
|
if (!this.client) {
|
|
@@ -4130,14 +4446,7 @@ export class ObjectsInRedisClient {
|
|
|
4130
4446
|
return Promise.resolve();
|
|
4131
4447
|
}
|
|
4132
4448
|
// try to release lock
|
|
4133
|
-
return this.client.evalsha(
|
|
4134
|
-
this.scripts.redlock_release,
|
|
4135
|
-
4,
|
|
4136
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
4137
|
-
this.hostname,
|
|
4138
|
-
this.settings.connection.options.db,
|
|
4139
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
4140
|
-
]);
|
|
4449
|
+
return this.client.evalsha(this.scripts.redlock_release, 4, `${this.metaNamespace}objects.primaryHost`, this.hostname, this.settings.connection.options.db || 0, `${this.metaNamespace}objects.primaryHost`);
|
|
4141
4450
|
}
|
|
4142
4451
|
/**
|
|
4143
4452
|
* Sets the protocol version to the DB
|
|
@@ -4159,7 +4468,7 @@ export class ObjectsInRedisClient {
|
|
|
4159
4468
|
}
|
|
4160
4469
|
}
|
|
4161
4470
|
/**
|
|
4162
|
-
* Subscribe to expired events to get expiration of primary host
|
|
4471
|
+
* Subscribe to expired events to get expiration of the primary host
|
|
4163
4472
|
*/
|
|
4164
4473
|
async subscribePrimaryHost() {
|
|
4165
4474
|
if (this.subSystem) {
|