@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
|
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
9
|
var __export = (target, all) => {
|
|
9
10
|
for (var name in all)
|
|
10
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -32,21 +33,25 @@ __export(objectsInRedisClient_exports, {
|
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(objectsInRedisClient_exports);
|
|
34
35
|
var __import_meta_url = typeof document === "undefined" ? new (require("url".replace("", ""))).URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
|
|
35
|
-
var import_node = __toESM(require("node.extend"), 1);
|
|
36
|
-
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
37
|
-
var import_db_base = require("@iobroker/db-base");
|
|
38
36
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
39
37
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
40
38
|
var import_node_crypto = __toESM(require("node:crypto"), 1);
|
|
41
39
|
var import_node_util = require("node:util");
|
|
40
|
+
var url = __toESM(require("node:url"), 1);
|
|
42
41
|
var import_deep_clone = __toESM(require("deep-clone"), 1);
|
|
43
|
-
var utils = __toESM(require("../../lib/objects/objectsUtils.js"), 1);
|
|
44
42
|
var import_semver = __toESM(require("semver"), 1);
|
|
43
|
+
var import_node = __toESM(require("node.extend"), 1);
|
|
44
|
+
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
45
|
+
var import_db_base = require("@iobroker/db-base");
|
|
46
|
+
var import_objectsUtils = require("../../lib/objects/objectsUtils.js");
|
|
47
|
+
var utils = __toESM(require("../../lib/objects/objectsUtils.js"), 1);
|
|
45
48
|
var CONSTS = __toESM(require("../../lib/objects/constants.js"), 1);
|
|
46
|
-
var url = __toESM(require("node:url"), 1);
|
|
47
49
|
const thisDir = url.fileURLToPath(new URL(".", __import_meta_url || `file://${__filename}`));
|
|
48
50
|
const ERRORS = CONSTS.ERRORS;
|
|
49
51
|
class ObjectsInRedisClient {
|
|
52
|
+
static {
|
|
53
|
+
__name(this, "ObjectsInRedisClient");
|
|
54
|
+
}
|
|
50
55
|
client;
|
|
51
56
|
fileNamespace;
|
|
52
57
|
redisNamespace;
|
|
@@ -72,6 +77,9 @@ class ObjectsInRedisClient {
|
|
|
72
77
|
noLegacyMultihost;
|
|
73
78
|
userSubscriptions;
|
|
74
79
|
systemSubscriptions;
|
|
80
|
+
/**
|
|
81
|
+
* @param settings Settings for the objects client including connection and namespaces
|
|
82
|
+
*/
|
|
75
83
|
constructor(settings) {
|
|
76
84
|
this.settings = settings || {};
|
|
77
85
|
this.redisNamespace = `${this.settings.redisNamespace || this.settings.connection?.redisNamespace || "cfg"}.`;
|
|
@@ -126,6 +134,9 @@ class ObjectsInRedisClient {
|
|
|
126
134
|
throw new Error(`This host does not support protocol version "${protoVersion}"`);
|
|
127
135
|
}
|
|
128
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Connect to the objects database and set up the change and file subscriptions
|
|
139
|
+
*/
|
|
129
140
|
connectDb() {
|
|
130
141
|
this.settings.connection = this.settings.connection || {};
|
|
131
142
|
const onChange = this.settings.change;
|
|
@@ -166,7 +177,7 @@ class ObjectsInRedisClient {
|
|
|
166
177
|
const defaultPort = Array.isArray(configuredPort) ? null : configuredPort;
|
|
167
178
|
this.settings.connection.options.sentinels = this.settings.connection.host.map((redisNode, idx) => ({
|
|
168
179
|
host: redisNode,
|
|
169
|
-
// @ts-expect-error ts does not get that if defPort is null we have an array
|
|
180
|
+
// @ts-expect-error ts does not get that if defPort is null, we have an array
|
|
170
181
|
port: defaultPort === null ? configuredPort[idx] : defaultPort
|
|
171
182
|
}));
|
|
172
183
|
this.settings.connection.options.name = this.settings.connection.sentinelName ? this.settings.connection.sentinelName : "mymaster";
|
|
@@ -178,10 +189,10 @@ class ObjectsInRedisClient {
|
|
|
178
189
|
}
|
|
179
190
|
this.settings.connection.options.db = this.settings.connection.options.db || 0;
|
|
180
191
|
this.settings.connection.options.family = this.settings.connection.options.family || 0;
|
|
181
|
-
this.settings.connection.options.password = this.settings.connection.options.auth_pass || this.settings.connection.pass ||
|
|
192
|
+
this.settings.connection.options.password = this.settings.connection.options.auth_pass || this.settings.connection.pass || void 0;
|
|
182
193
|
this.settings.connection.options.autoResubscribe = false;
|
|
183
194
|
this.settings.connection.options.connectionName = this.namespace.replace(/\s/g, "");
|
|
184
|
-
this.client = new import_ioredis.default(this.settings.connection.options);
|
|
195
|
+
this.client = new import_ioredis.default.Redis(this.settings.connection.options);
|
|
185
196
|
this.client.on("error", (error) => {
|
|
186
197
|
if (this.settings.connection.enhancedLogging) {
|
|
187
198
|
this.log.silly(`${this.namespace} Redis ERROR Objects: (${this.stop}) ${error.message} / ${error.stack}`);
|
|
@@ -258,7 +269,7 @@ class ObjectsInRedisClient {
|
|
|
258
269
|
if (!this.subSystem && typeof onChange === "function") {
|
|
259
270
|
initCounter++;
|
|
260
271
|
this.log.debug(`${this.namespace} Objects create System PubSub Client`);
|
|
261
|
-
this.subSystem = new import_ioredis.default(this.settings.connection.options);
|
|
272
|
+
this.subSystem = new import_ioredis.default.Redis(this.settings.connection.options);
|
|
262
273
|
if (typeof this.settings.primaryHostLost === "function") {
|
|
263
274
|
try {
|
|
264
275
|
await this.client.config("SET", "notify-keyspace-events", "Exe");
|
|
@@ -303,7 +314,7 @@ class ObjectsInRedisClient {
|
|
|
303
314
|
if (id === "system.config" && obj2?.common?.defaultNewAcl && !(0, import_node_util.isDeepStrictEqual)(obj2.common.defaultNewAcl, this.defaultNewAcl)) {
|
|
304
315
|
this.defaultNewAcl = (0, import_deep_clone.default)(obj2.common.defaultNewAcl);
|
|
305
316
|
if (this.settings.controller) {
|
|
306
|
-
this.setDefaultAcl(this.defaultNewAcl);
|
|
317
|
+
this.setDefaultAcl(this.defaultNewAcl).catch((e) => this.log.error(`${this.namespace} Cannot set default ACL: ${e.message}`));
|
|
307
318
|
}
|
|
308
319
|
}
|
|
309
320
|
onChange(id, obj2);
|
|
@@ -379,7 +390,7 @@ class ObjectsInRedisClient {
|
|
|
379
390
|
if (!this.sub && (typeof onChangeUser === "function" || typeof onChangeFileUser === "function")) {
|
|
380
391
|
initCounter++;
|
|
381
392
|
this.log.debug(`${this.namespace} Objects create User PubSub Client`);
|
|
382
|
-
this.sub = new import_ioredis.default(this.settings.connection.options);
|
|
393
|
+
this.sub = new import_ioredis.default.Redis(this.settings.connection.options);
|
|
383
394
|
this.sub.on("pmessage", (pattern, channel, message) => {
|
|
384
395
|
setImmediate(() => {
|
|
385
396
|
this.log.silly(`${this.namespace} Objects user redis pmessage ${pattern}/${channel}:${message}`);
|
|
@@ -529,6 +540,9 @@ class ObjectsInRedisClient {
|
|
|
529
540
|
}
|
|
530
541
|
});
|
|
531
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* Get the current status of the database
|
|
545
|
+
*/
|
|
532
546
|
getStatus() {
|
|
533
547
|
return { type: "redis", server: false };
|
|
534
548
|
}
|
|
@@ -536,7 +550,7 @@ class ObjectsInRedisClient {
|
|
|
536
550
|
* Checks if given ID is a meta-object, else throws error
|
|
537
551
|
*
|
|
538
552
|
* @param id to check
|
|
539
|
-
* @throws Error if id is invalid
|
|
553
|
+
* @throws {Error} if id is invalid
|
|
540
554
|
*/
|
|
541
555
|
async validateMetaObject(id) {
|
|
542
556
|
if (this.existingMetaObjects[id] === void 0) {
|
|
@@ -551,6 +565,11 @@ class ObjectsInRedisClient {
|
|
|
551
565
|
throw new Error(`${id} is not an object of type "meta"`);
|
|
552
566
|
}
|
|
553
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Normalize a file name by collapsing slashes and backslashes into a single forward slash
|
|
570
|
+
*
|
|
571
|
+
* @param name The file name to normalize
|
|
572
|
+
*/
|
|
554
573
|
normalizeFilename(name) {
|
|
555
574
|
return name ? name.replace(/[/\\]+/g, "/") : name;
|
|
556
575
|
}
|
|
@@ -558,7 +577,7 @@ class ObjectsInRedisClient {
|
|
|
558
577
|
/**
|
|
559
578
|
* Sets a buffer to the Redis DB
|
|
560
579
|
*
|
|
561
|
-
* @param id
|
|
580
|
+
* @param id ID of the file
|
|
562
581
|
* @param data content, if string is passed it will be converted to a Buffer
|
|
563
582
|
*/
|
|
564
583
|
async _setBinaryState(id, data) {
|
|
@@ -572,7 +591,7 @@ class ObjectsInRedisClient {
|
|
|
572
591
|
await this.client.publish(id, data.byteLength.toString(10));
|
|
573
592
|
}
|
|
574
593
|
/**
|
|
575
|
-
*
|
|
594
|
+
* Get a buffer of a given ID from redis
|
|
576
595
|
*
|
|
577
596
|
* @param id - id of the data with namespace prefix
|
|
578
597
|
*/
|
|
@@ -583,7 +602,7 @@ class ObjectsInRedisClient {
|
|
|
583
602
|
return this.client.getBuffer(id);
|
|
584
603
|
}
|
|
585
604
|
/**
|
|
586
|
-
* deletes binary state of given
|
|
605
|
+
* deletes binary state of a given ID from redis db
|
|
587
606
|
*
|
|
588
607
|
* @param id - id to delete, with namespace prefix
|
|
589
608
|
*/
|
|
@@ -595,6 +614,13 @@ class ObjectsInRedisClient {
|
|
|
595
614
|
await this.client.publish(id, "null");
|
|
596
615
|
}
|
|
597
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Build the internal redis key for a file
|
|
619
|
+
*
|
|
620
|
+
* @param id The ID of the object owning the file
|
|
621
|
+
* @param name The file name
|
|
622
|
+
* @param isMeta Whether to return the key of the meta-entry (true) or the data entry (false)
|
|
623
|
+
*/
|
|
598
624
|
getFileId(id, name, isMeta) {
|
|
599
625
|
name = this.normalizeFilename(name);
|
|
600
626
|
if (id.endsWith(".admin")) {
|
|
@@ -617,37 +643,58 @@ class ObjectsInRedisClient {
|
|
|
617
643
|
name = normalized.name;
|
|
618
644
|
return `${this.fileNamespace + id}$%$${name}${isMeta !== void 0 ? isMeta ? "$%$meta" : "$%$data" : ""}`;
|
|
619
645
|
}
|
|
620
|
-
|
|
621
|
-
|
|
646
|
+
/**
|
|
647
|
+
* Check whether the current options have the required rights on a file
|
|
648
|
+
*
|
|
649
|
+
* @param id The id of the object owning the file
|
|
650
|
+
* @param name The file name
|
|
651
|
+
* @param userContext The resolved user context used for permission checks
|
|
652
|
+
* @param flag The access flag(s) to check for
|
|
653
|
+
*/
|
|
654
|
+
async checkFileAsync(id, name, userContext2, flag) {
|
|
655
|
+
const fileId = this.getFileId(id, name ?? "", true);
|
|
622
656
|
if (!fileId) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
return import_db_base.tools.maybeCallback(callback, false, options2, fileOptions2);
|
|
657
|
+
if (!utils.checkFile({ notExists: true }, userContext2, flag, this.defaultNewAcl)) {
|
|
658
|
+
return null;
|
|
626
659
|
}
|
|
627
|
-
return
|
|
660
|
+
return { notExists: true };
|
|
628
661
|
}
|
|
629
662
|
if (!this.client) {
|
|
630
|
-
|
|
663
|
+
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
631
664
|
}
|
|
632
|
-
let
|
|
665
|
+
let fileOptionsStr;
|
|
633
666
|
try {
|
|
634
|
-
|
|
667
|
+
fileOptionsStr = await this.client.get(fileId);
|
|
635
668
|
} catch {
|
|
636
669
|
}
|
|
637
|
-
|
|
670
|
+
fileOptionsStr ||= '{"notExists": true}';
|
|
671
|
+
let fileOptions;
|
|
638
672
|
try {
|
|
639
|
-
fileOptions = JSON.parse(
|
|
673
|
+
fileOptions = JSON.parse(fileOptionsStr);
|
|
640
674
|
} catch {
|
|
641
|
-
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${
|
|
675
|
+
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${fileOptionsStr}`);
|
|
642
676
|
fileOptions = { notExists: true };
|
|
643
677
|
}
|
|
644
|
-
if (utils.checkFile(fileOptions,
|
|
645
|
-
return
|
|
678
|
+
if (!utils.checkFile(fileOptions, userContext2, flag, this.defaultNewAcl)) {
|
|
679
|
+
return null;
|
|
646
680
|
}
|
|
647
|
-
return
|
|
681
|
+
return fileOptions;
|
|
648
682
|
}
|
|
649
|
-
|
|
650
|
-
|
|
683
|
+
/**
|
|
684
|
+
* Check whether the current user is allowed to access a file
|
|
685
|
+
*
|
|
686
|
+
* @param id The id of the object owning the file
|
|
687
|
+
* @param name The file name, or null for the whole namespace
|
|
688
|
+
* @param options The current request options including the user
|
|
689
|
+
* @param flag The access flag(s) to check for
|
|
690
|
+
* @param callback Called with the effective options once the rights have been checked
|
|
691
|
+
*/
|
|
692
|
+
checkFileRights(id, name, options, flag, callback) {
|
|
693
|
+
try {
|
|
694
|
+
utils.checkFileRightsAsync(this, id, name, options, flag).then((result) => callback(null, result.fileOptions, result.userContext)).catch((err) => callback(err));
|
|
695
|
+
} catch (error) {
|
|
696
|
+
callback(error);
|
|
697
|
+
}
|
|
651
698
|
}
|
|
652
699
|
async _setDefaultAcl(ids, defaultAcl) {
|
|
653
700
|
for (const id of ids) {
|
|
@@ -662,6 +709,11 @@ class ObjectsInRedisClient {
|
|
|
662
709
|
}
|
|
663
710
|
}
|
|
664
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Set the default ACL applied to new objects and apply it to all existing objects without an ACL
|
|
714
|
+
*
|
|
715
|
+
* @param defaultNewAcl The default ACL to use, or null to use the built-in default
|
|
716
|
+
*/
|
|
665
717
|
async setDefaultAcl(defaultNewAcl) {
|
|
666
718
|
this.defaultNewAcl = defaultNewAcl || {
|
|
667
719
|
owner: CONSTS.SYSTEM_ADMIN_USER,
|
|
@@ -679,24 +731,32 @@ class ObjectsInRedisClient {
|
|
|
679
731
|
this.log.error(`${this.namespace} Could not update default acl: ${e.message}`);
|
|
680
732
|
}
|
|
681
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Determine the groups and effective ACL of the given user
|
|
736
|
+
*
|
|
737
|
+
* @param user The id of the user to look up
|
|
738
|
+
* @param callback Called with the user, its groups, and the effective ACL
|
|
739
|
+
*/
|
|
682
740
|
getUserGroup(user, callback) {
|
|
683
741
|
return utils.getUserGroup(this, user, (error, user2, userGroups, userAcl) => {
|
|
684
742
|
if (error) {
|
|
685
743
|
this.log.error(`${this.namespace} ${error.stack}`);
|
|
686
744
|
}
|
|
687
|
-
|
|
745
|
+
if (typeof callback === "function") {
|
|
746
|
+
callback(user2, userGroups, userAcl);
|
|
747
|
+
}
|
|
688
748
|
});
|
|
689
749
|
}
|
|
690
|
-
async _writeFile(id, name, data,
|
|
750
|
+
async _writeFile(id, name, data, options, callback, meta) {
|
|
691
751
|
const matchedExtension = name.match(/\.[^.]+$/);
|
|
692
|
-
const ext = matchedExtension
|
|
752
|
+
const ext = matchedExtension?.[0] || "";
|
|
693
753
|
const isTextData = typeof data === "string";
|
|
694
754
|
const { mimeType, isBinary } = utils.getMimeType(ext, isTextData);
|
|
695
755
|
const metaID = this.getFileId(id, name, true);
|
|
696
756
|
if (!this.client) {
|
|
697
757
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
698
758
|
}
|
|
699
|
-
if (
|
|
759
|
+
if (options.virtualFile) {
|
|
700
760
|
meta = {
|
|
701
761
|
notExists: true,
|
|
702
762
|
virtualFile: true
|
|
@@ -708,28 +768,24 @@ class ObjectsInRedisClient {
|
|
|
708
768
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
709
769
|
}
|
|
710
770
|
} else {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
ownerGroup: options2.group || this.defaultNewAcl && this.defaultNewAcl.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP,
|
|
718
|
-
permissions: options2.mode || this.defaultNewAcl && this.defaultNewAcl.file || 1604
|
|
719
|
-
};
|
|
720
|
-
}
|
|
771
|
+
meta ||= { createdAt: Date.now() };
|
|
772
|
+
meta.acl ||= {
|
|
773
|
+
owner: options.user || this.defaultNewAcl?.owner || CONSTS.SYSTEM_ADMIN_USER,
|
|
774
|
+
ownerGroup: options.group || this.defaultNewAcl?.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP,
|
|
775
|
+
permissions: options.mode || this.defaultNewAcl?.file || 1604
|
|
776
|
+
};
|
|
721
777
|
meta.stats = {
|
|
722
|
-
size: data
|
|
778
|
+
size: data?.length || 0
|
|
723
779
|
};
|
|
724
780
|
if (Object.prototype.hasOwnProperty.call(meta, "notExists")) {
|
|
725
781
|
delete meta.notExists;
|
|
726
782
|
}
|
|
727
|
-
meta.mimeType =
|
|
783
|
+
meta.mimeType = options.mimeType || mimeType;
|
|
728
784
|
meta.binary = isBinary;
|
|
729
|
-
meta.acl.ownerGroup
|
|
785
|
+
meta.acl.ownerGroup ||= this.defaultNewAcl?.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP;
|
|
730
786
|
meta.modifiedAt = Date.now();
|
|
731
787
|
try {
|
|
732
|
-
await this._setBinaryState(this.getFileId(id, name, false), data);
|
|
788
|
+
await this._setBinaryState(this.getFileId(id, name, false), data || "");
|
|
733
789
|
await this.client.set(metaID, JSON.stringify(meta));
|
|
734
790
|
return import_db_base.tools.maybeCallback(callback);
|
|
735
791
|
} catch (e) {
|
|
@@ -737,19 +793,22 @@ class ObjectsInRedisClient {
|
|
|
737
793
|
}
|
|
738
794
|
}
|
|
739
795
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
796
|
+
/**
|
|
797
|
+
* Write data into a file of an object
|
|
798
|
+
*
|
|
799
|
+
* @param id The id of the object owning the file
|
|
800
|
+
* @param name The file name
|
|
801
|
+
* @param data The data to write
|
|
802
|
+
* @param options The current request options including the user, or the callback
|
|
803
|
+
* @param callback Called once the file has been written
|
|
804
|
+
*/
|
|
805
|
+
async writeFile(id, name, data, options, callback) {
|
|
806
|
+
if (typeof options === "function") {
|
|
807
|
+
callback = options;
|
|
808
|
+
options = null;
|
|
750
809
|
}
|
|
751
810
|
if (!callback) {
|
|
752
|
-
return this.writeFileAsync(id, name, data,
|
|
811
|
+
return this.writeFileAsync(id, name, data, options);
|
|
753
812
|
}
|
|
754
813
|
try {
|
|
755
814
|
await this.validateMetaObject(id);
|
|
@@ -766,15 +825,23 @@ class ObjectsInRedisClient {
|
|
|
766
825
|
if (data === void 0) {
|
|
767
826
|
data = null;
|
|
768
827
|
}
|
|
769
|
-
return this.checkFileRights(id, name,
|
|
828
|
+
return this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta) => {
|
|
770
829
|
if (err) {
|
|
771
830
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
772
831
|
}
|
|
773
|
-
return this._writeFile(id, name, data,
|
|
832
|
+
return this._writeFile(id, name, data, options || {}, callback, meta);
|
|
774
833
|
});
|
|
775
834
|
}
|
|
776
|
-
|
|
777
|
-
|
|
835
|
+
/**
|
|
836
|
+
* Promise-version of writeFile
|
|
837
|
+
*
|
|
838
|
+
* @param id The ID of the object owning the file
|
|
839
|
+
* @param name The file name
|
|
840
|
+
* @param data The data to write
|
|
841
|
+
* @param options The current request options including the user
|
|
842
|
+
*/
|
|
843
|
+
writeFileAsync(id, name, data, options) {
|
|
844
|
+
return new Promise((resolve, reject) => this.writeFile(id, name, data, options, (err) => err ? reject(err) : resolve()));
|
|
778
845
|
}
|
|
779
846
|
async _readFile(id, name, meta) {
|
|
780
847
|
if (meta.notExists) {
|
|
@@ -783,24 +850,28 @@ class ObjectsInRedisClient {
|
|
|
783
850
|
if (!this.client) {
|
|
784
851
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
785
852
|
}
|
|
786
|
-
let
|
|
787
|
-
buffer = await this._getBinaryState(this.getFileId(id, name, false));
|
|
853
|
+
let file = await this._getBinaryState(this.getFileId(id, name, false));
|
|
788
854
|
const mimeType = meta?.mimeType;
|
|
789
|
-
if (meta && !meta.binary &&
|
|
790
|
-
|
|
855
|
+
if (meta && !meta.binary && file) {
|
|
856
|
+
file = file.toString();
|
|
791
857
|
}
|
|
792
|
-
return { file
|
|
858
|
+
return { file, mimeType };
|
|
793
859
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
860
|
+
/**
|
|
861
|
+
* Read a file of an object
|
|
862
|
+
*
|
|
863
|
+
* @param id The ID of the object owning the file
|
|
864
|
+
* @param name The file name
|
|
865
|
+
* @param options The current request options including the user, or the callback
|
|
866
|
+
* @param callback Called with the file content and mime type
|
|
867
|
+
*/
|
|
868
|
+
readFile(id, name, options, callback) {
|
|
869
|
+
if (typeof options === "function") {
|
|
870
|
+
callback = options;
|
|
871
|
+
options = null;
|
|
801
872
|
}
|
|
802
873
|
if (!callback) {
|
|
803
|
-
return new Promise((resolve, reject) => this.readFile(id, name,
|
|
874
|
+
return new Promise((resolve, reject) => this.readFile(id, name, options, (err, res, mimeType) => err ? reject(err) : resolve({ file: res, mimeType })));
|
|
804
875
|
}
|
|
805
876
|
if (typeof name !== "string" || !name.length || name === "/") {
|
|
806
877
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
@@ -808,8 +879,8 @@ class ObjectsInRedisClient {
|
|
|
808
879
|
if (name.startsWith("/")) {
|
|
809
880
|
name = name.substring(1);
|
|
810
881
|
}
|
|
811
|
-
|
|
812
|
-
this.checkFileRights(id, name,
|
|
882
|
+
options ||= {};
|
|
883
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, async (err, meta) => {
|
|
813
884
|
if (err) {
|
|
814
885
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
815
886
|
}
|
|
@@ -822,27 +893,21 @@ class ObjectsInRedisClient {
|
|
|
822
893
|
});
|
|
823
894
|
}
|
|
824
895
|
/**
|
|
825
|
-
* Check if given object exists
|
|
896
|
+
* Check if a given object exists
|
|
826
897
|
*
|
|
827
|
-
* @param id
|
|
898
|
+
* @param id ID of the object
|
|
828
899
|
* @param options optional user context
|
|
829
900
|
*/
|
|
830
|
-
async objectExists(id,
|
|
901
|
+
async objectExists(id, options) {
|
|
831
902
|
if (!this.client) {
|
|
832
903
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
833
904
|
}
|
|
834
905
|
if (!id || typeof id !== "string") {
|
|
835
|
-
throw new Error(`invalid
|
|
906
|
+
throw new Error(`invalid ID ${JSON.stringify(id)}`);
|
|
836
907
|
}
|
|
837
908
|
try {
|
|
838
909
|
await new Promise((resolve, reject) => {
|
|
839
|
-
utils.checkObjectRights(this, null, null,
|
|
840
|
-
if (err) {
|
|
841
|
-
reject(err);
|
|
842
|
-
} else {
|
|
843
|
-
resolve();
|
|
844
|
-
}
|
|
845
|
-
});
|
|
910
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_LIST, (err) => err ? reject(err) : resolve());
|
|
846
911
|
});
|
|
847
912
|
const exists = await this.client.exists(this.objNamespace + id);
|
|
848
913
|
return !!exists;
|
|
@@ -852,13 +917,13 @@ class ObjectsInRedisClient {
|
|
|
852
917
|
}
|
|
853
918
|
}
|
|
854
919
|
/**
|
|
855
|
-
* Check if given file exists
|
|
920
|
+
* Check if a given file exists
|
|
856
921
|
*
|
|
857
|
-
* @param id
|
|
922
|
+
* @param id ID of the namespace
|
|
858
923
|
* @param name name of the file
|
|
859
924
|
* @param options optional user context
|
|
860
925
|
*/
|
|
861
|
-
async fileExists(id, name,
|
|
926
|
+
async fileExists(id, name, options) {
|
|
862
927
|
if (typeof name !== "string") {
|
|
863
928
|
name = "";
|
|
864
929
|
}
|
|
@@ -866,15 +931,7 @@ class ObjectsInRedisClient {
|
|
|
866
931
|
name = name.substring(1);
|
|
867
932
|
}
|
|
868
933
|
try {
|
|
869
|
-
await
|
|
870
|
-
this.checkFileRights(id, name, options2, CONSTS.ACCESS_READ, (err) => {
|
|
871
|
-
if (err) {
|
|
872
|
-
reject(err);
|
|
873
|
-
} else {
|
|
874
|
-
resolve();
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
});
|
|
934
|
+
await utils.checkFileRightsAsync(this, id, name, options, CONSTS.ACCESS_READ);
|
|
878
935
|
if (!this.client) {
|
|
879
936
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
880
937
|
}
|
|
@@ -886,25 +943,30 @@ class ObjectsInRedisClient {
|
|
|
886
943
|
throw new Error(`Cannot check file existence of "${id}": ${e.message}`);
|
|
887
944
|
}
|
|
888
945
|
}
|
|
889
|
-
async _unlink(id, name,
|
|
946
|
+
async _unlink(id, name, userContext2, meta) {
|
|
890
947
|
if (!this.client) {
|
|
891
948
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
892
949
|
}
|
|
893
|
-
if (meta
|
|
894
|
-
return this._rm(id, name,
|
|
950
|
+
if (meta?.notExists) {
|
|
951
|
+
return this._rm(id, name, userContext2);
|
|
895
952
|
}
|
|
896
953
|
const metaID = this.getFileId(id, name, true);
|
|
897
954
|
const dataID = this.getFileId(id, name, false);
|
|
898
955
|
await this._delBinaryState(dataID);
|
|
899
956
|
await this.client.del(metaID);
|
|
900
957
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
958
|
+
/**
|
|
959
|
+
* Delete a file or directory of an object
|
|
960
|
+
*
|
|
961
|
+
* @param id The id of the object owning the file
|
|
962
|
+
* @param name The file or directory name to delete
|
|
963
|
+
* @param options The current request options including the user, or the callback
|
|
964
|
+
* @param callback Called with the list of removed files
|
|
965
|
+
*/
|
|
966
|
+
unlink(id, name, options, callback) {
|
|
967
|
+
if (typeof options === "function") {
|
|
968
|
+
callback = options;
|
|
969
|
+
options = null;
|
|
908
970
|
}
|
|
909
971
|
if (typeof name !== "string") {
|
|
910
972
|
name = "";
|
|
@@ -912,31 +974,53 @@ class ObjectsInRedisClient {
|
|
|
912
974
|
if (name.startsWith("/")) {
|
|
913
975
|
name = name.substring(1);
|
|
914
976
|
}
|
|
915
|
-
this.checkFileRights(id, name,
|
|
977
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_DELETE, async (err, meta, userContext2) => {
|
|
916
978
|
if (err) {
|
|
917
979
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
918
980
|
}
|
|
919
|
-
if (!
|
|
981
|
+
if (!userContext2.acl.file.delete) {
|
|
920
982
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
921
983
|
}
|
|
922
984
|
try {
|
|
923
|
-
const files = await this._unlink(id, name,
|
|
985
|
+
const files = await this._unlink(id, name, userContext2, meta);
|
|
924
986
|
return import_db_base.tools.maybeCallbackWithError(callback, null, files);
|
|
925
987
|
} catch (e) {
|
|
926
988
|
return import_db_base.tools.maybeCallbackWithError(callback, e);
|
|
927
989
|
}
|
|
928
990
|
});
|
|
929
991
|
}
|
|
930
|
-
|
|
931
|
-
|
|
992
|
+
/**
|
|
993
|
+
* Promise-version of unlink
|
|
994
|
+
*
|
|
995
|
+
* @param id The id of the object owning the file
|
|
996
|
+
* @param name The file or directory name to delete
|
|
997
|
+
* @param options The current request options including the user
|
|
998
|
+
*/
|
|
999
|
+
unlinkAsync(id, name, options) {
|
|
1000
|
+
return new Promise((resolve, reject) => this.unlink(id, name, options, (err) => err ? reject(err) : resolve()));
|
|
932
1001
|
}
|
|
933
|
-
|
|
934
|
-
|
|
1002
|
+
/**
|
|
1003
|
+
* Delete a file of an object (alias for {@link unlink})
|
|
1004
|
+
*
|
|
1005
|
+
* @param id The id of the object owning the file
|
|
1006
|
+
* @param name The file name to delete
|
|
1007
|
+
* @param options The current request options including the user
|
|
1008
|
+
* @param callback Called once the file has been deleted
|
|
1009
|
+
*/
|
|
1010
|
+
delFile(id, name, options, callback) {
|
|
1011
|
+
return this.unlink(id, name, options, callback);
|
|
935
1012
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1013
|
+
/**
|
|
1014
|
+
* Promise-version of delFile
|
|
1015
|
+
*
|
|
1016
|
+
* @param id The id of the object owning the file
|
|
1017
|
+
* @param name The file name to delete
|
|
1018
|
+
* @param options The current request options including the user
|
|
1019
|
+
*/
|
|
1020
|
+
delFileAsync(id, name, options) {
|
|
1021
|
+
return this.unlinkAsync(id, name, options);
|
|
938
1022
|
}
|
|
939
|
-
async _readDir(id, name,
|
|
1023
|
+
async _readDir(id, name, userContext2, callback) {
|
|
940
1024
|
name = this.normalizeFilename(name);
|
|
941
1025
|
if (!this.client) {
|
|
942
1026
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
@@ -952,15 +1036,15 @@ class ObjectsInRedisClient {
|
|
|
952
1036
|
if (!this.client) {
|
|
953
1037
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
954
1038
|
}
|
|
955
|
-
const
|
|
1039
|
+
const result2 = [];
|
|
956
1040
|
if (!keys3 || !keys3.length) {
|
|
957
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
1041
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result2);
|
|
958
1042
|
}
|
|
959
1043
|
let lastDir;
|
|
960
1044
|
keys3.sort().forEach((dir) => {
|
|
961
1045
|
dir = dir.substring(this.fileNamespaceL, dir.indexOf("$%$"));
|
|
962
1046
|
if (dir !== lastDir) {
|
|
963
|
-
|
|
1047
|
+
result2.push({
|
|
964
1048
|
file: dir,
|
|
965
1049
|
stats: {},
|
|
966
1050
|
isDir: true
|
|
@@ -968,7 +1052,7 @@ class ObjectsInRedisClient {
|
|
|
968
1052
|
}
|
|
969
1053
|
lastDir = dir;
|
|
970
1054
|
});
|
|
971
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
1055
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result2);
|
|
972
1056
|
}
|
|
973
1057
|
try {
|
|
974
1058
|
await this.validateMetaObject(id);
|
|
@@ -1006,12 +1090,12 @@ class ObjectsInRedisClient {
|
|
|
1006
1090
|
}
|
|
1007
1091
|
});
|
|
1008
1092
|
if (!keys2.length) {
|
|
1009
|
-
const
|
|
1093
|
+
const result2 = dirs.map((file) => ({
|
|
1010
1094
|
file,
|
|
1011
1095
|
stats: {},
|
|
1012
1096
|
isDir: true
|
|
1013
1097
|
}));
|
|
1014
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
1098
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result2);
|
|
1015
1099
|
}
|
|
1016
1100
|
let strObjs;
|
|
1017
1101
|
try {
|
|
@@ -1019,12 +1103,12 @@ class ObjectsInRedisClient {
|
|
|
1019
1103
|
} catch (e) {
|
|
1020
1104
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
1021
1105
|
}
|
|
1022
|
-
const
|
|
1023
|
-
const dontCheck =
|
|
1106
|
+
const result = [];
|
|
1107
|
+
const dontCheck = userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1024
1108
|
for (let i = 0; i < keys2.length; i++) {
|
|
1025
1109
|
const file = keys2[i].substring(start + baseName.length, keys2[i].length - end);
|
|
1026
1110
|
while (dirs.length && dirs[0] < file) {
|
|
1027
|
-
|
|
1111
|
+
result.push({
|
|
1028
1112
|
file: dirs.shift(),
|
|
1029
1113
|
stats: {},
|
|
1030
1114
|
isDir: true
|
|
@@ -1038,21 +1122,21 @@ class ObjectsInRedisClient {
|
|
|
1038
1122
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${strObj}`);
|
|
1039
1123
|
continue;
|
|
1040
1124
|
}
|
|
1041
|
-
if (dontCheck || utils.checkObject(obj,
|
|
1125
|
+
if (dontCheck || utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ)) {
|
|
1042
1126
|
if (!obj || obj.virtualFile) {
|
|
1043
1127
|
continue;
|
|
1044
1128
|
}
|
|
1045
|
-
obj.acl
|
|
1046
|
-
if (
|
|
1129
|
+
obj.acl ||= {};
|
|
1130
|
+
if (userContext2.user !== CONSTS.SYSTEM_ADMIN_USER && !userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP)) {
|
|
1047
1131
|
obj.acl.read = !!(obj.acl.permissions & CONSTS.ACCESS_EVERY_READ);
|
|
1048
1132
|
obj.acl.write = !!(obj.acl.permissions & CONSTS.ACCESS_EVERY_WRITE);
|
|
1049
1133
|
} else {
|
|
1050
1134
|
obj.acl.read = true;
|
|
1051
1135
|
obj.acl.write = true;
|
|
1052
1136
|
}
|
|
1053
|
-
|
|
1137
|
+
result.push({
|
|
1054
1138
|
file,
|
|
1055
|
-
stats: obj.stats,
|
|
1139
|
+
stats: obj.stats || {},
|
|
1056
1140
|
isDir: false,
|
|
1057
1141
|
acl: obj.acl,
|
|
1058
1142
|
modifiedAt: obj.modifiedAt,
|
|
@@ -1061,21 +1145,26 @@ class ObjectsInRedisClient {
|
|
|
1061
1145
|
}
|
|
1062
1146
|
}
|
|
1063
1147
|
while (dirs.length) {
|
|
1064
|
-
|
|
1148
|
+
result.push({
|
|
1065
1149
|
file: dirs.shift(),
|
|
1066
1150
|
stats: {},
|
|
1067
1151
|
isDir: true
|
|
1068
1152
|
});
|
|
1069
1153
|
}
|
|
1070
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
1154
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result);
|
|
1071
1155
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1156
|
+
/**
|
|
1157
|
+
* List the contents of a directory of an object
|
|
1158
|
+
*
|
|
1159
|
+
* @param id The id of the object owning the files
|
|
1160
|
+
* @param name The directory name to list
|
|
1161
|
+
* @param options The current request options including the user, or the callback
|
|
1162
|
+
* @param callback Called with the directory entries
|
|
1163
|
+
*/
|
|
1164
|
+
readDir(id, name, options, callback) {
|
|
1165
|
+
if (typeof options === "function") {
|
|
1166
|
+
callback = options;
|
|
1167
|
+
options = null;
|
|
1079
1168
|
}
|
|
1080
1169
|
if (typeof name !== "string") {
|
|
1081
1170
|
name = "";
|
|
@@ -1086,18 +1175,25 @@ class ObjectsInRedisClient {
|
|
|
1086
1175
|
if (name.endsWith("/")) {
|
|
1087
1176
|
name = name.substring(0, name.length - 1);
|
|
1088
1177
|
}
|
|
1089
|
-
this.checkFileRights(id, name,
|
|
1178
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_READ, (err, options2, userContext2) => {
|
|
1090
1179
|
if (err) {
|
|
1091
1180
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1092
1181
|
}
|
|
1093
|
-
if (!
|
|
1182
|
+
if (!userContext2.acl.file.list) {
|
|
1094
1183
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1095
1184
|
}
|
|
1096
|
-
this._readDir(id, name,
|
|
1185
|
+
return this._readDir(id, name, userContext2, callback);
|
|
1097
1186
|
});
|
|
1098
1187
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1188
|
+
/**
|
|
1189
|
+
* Promise-version of readDir
|
|
1190
|
+
*
|
|
1191
|
+
* @param id The id of the object owning the files
|
|
1192
|
+
* @param name The directory name to list
|
|
1193
|
+
* @param options The current request options including the user
|
|
1194
|
+
*/
|
|
1195
|
+
readDirAsync(id, name, options) {
|
|
1196
|
+
return new Promise((resolve, reject) => this.readDir(id, name, options, (err, res) => err ? reject(err) : resolve(res)));
|
|
1101
1197
|
}
|
|
1102
1198
|
async _renameHelper(keys2, oldBase, newBase, callback) {
|
|
1103
1199
|
if (!keys2 || !keys2.length) {
|
|
@@ -1122,7 +1218,7 @@ class ObjectsInRedisClient {
|
|
|
1122
1218
|
}
|
|
1123
1219
|
return import_db_base.tools.maybeCallback(callback);
|
|
1124
1220
|
}
|
|
1125
|
-
async _rename(id, oldName, newName,
|
|
1221
|
+
async _rename(id, oldName, newName, userContext2, callback, meta) {
|
|
1126
1222
|
const oldMetaID = this.getFileId(id, oldName, true);
|
|
1127
1223
|
const oldDataID = this.getFileId(id, oldName, false);
|
|
1128
1224
|
const newMetaID = this.getFileId(id, newName, true);
|
|
@@ -1167,10 +1263,10 @@ class ObjectsInRedisClient {
|
|
|
1167
1263
|
} catch (e) {
|
|
1168
1264
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
1169
1265
|
}
|
|
1170
|
-
let
|
|
1171
|
-
const dontCheck =
|
|
1266
|
+
let result;
|
|
1267
|
+
const dontCheck = userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1172
1268
|
if (!dontCheck) {
|
|
1173
|
-
|
|
1269
|
+
result = [];
|
|
1174
1270
|
for (let i = 0; i < keys2.length; i++) {
|
|
1175
1271
|
const strObj = strObjs[i];
|
|
1176
1272
|
let obj;
|
|
@@ -1180,14 +1276,14 @@ class ObjectsInRedisClient {
|
|
|
1180
1276
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${strObj}`);
|
|
1181
1277
|
continue;
|
|
1182
1278
|
}
|
|
1183
|
-
if (utils.checkObject(obj,
|
|
1184
|
-
|
|
1279
|
+
if (utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ)) {
|
|
1280
|
+
result.push(keys2[i]);
|
|
1185
1281
|
}
|
|
1186
1282
|
}
|
|
1187
1283
|
} else {
|
|
1188
|
-
|
|
1284
|
+
result = keys2;
|
|
1189
1285
|
}
|
|
1190
|
-
return this._renameHelper(
|
|
1286
|
+
return this._renameHelper(result, oldBase, newBase, callback);
|
|
1191
1287
|
}
|
|
1192
1288
|
try {
|
|
1193
1289
|
await this.client.rename(oldDataID, newDataID);
|
|
@@ -1197,13 +1293,19 @@ class ObjectsInRedisClient {
|
|
|
1197
1293
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
1198
1294
|
}
|
|
1199
1295
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1296
|
+
/**
|
|
1297
|
+
* Rename a file or directory of an object
|
|
1298
|
+
*
|
|
1299
|
+
* @param id The id of the object owning the file
|
|
1300
|
+
* @param oldName The current file or directory name
|
|
1301
|
+
* @param newName The new file or directory name
|
|
1302
|
+
* @param options The current request options including the user, or the callback
|
|
1303
|
+
* @param callback Called once the file has been renamed
|
|
1304
|
+
*/
|
|
1305
|
+
rename(id, oldName, newName, options, callback) {
|
|
1306
|
+
if (typeof options === "function") {
|
|
1307
|
+
callback = options;
|
|
1308
|
+
options = null;
|
|
1207
1309
|
}
|
|
1208
1310
|
if (typeof oldName !== "string" || !oldName.length || oldName === "/" || oldName === "//" || typeof newName !== "string" || !newName.length || newName === "/" || newName === "//") {
|
|
1209
1311
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
@@ -1220,20 +1322,29 @@ class ObjectsInRedisClient {
|
|
|
1220
1322
|
if (newName.endsWith("/")) {
|
|
1221
1323
|
newName = newName.substring(0, newName.length - 1);
|
|
1222
1324
|
}
|
|
1223
|
-
this.checkFileRights(id, oldName,
|
|
1325
|
+
this.checkFileRights(id, oldName, options, CONSTS.ACCESS_WRITE, (err, meta, userContext2) => {
|
|
1224
1326
|
if (err) {
|
|
1225
1327
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1226
1328
|
}
|
|
1227
|
-
if (!
|
|
1329
|
+
if (!userContext2.acl.file.write) {
|
|
1228
1330
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1229
1331
|
}
|
|
1230
|
-
this._rename(id, oldName, newName,
|
|
1332
|
+
return this._rename(id, oldName, newName, userContext2, callback, meta);
|
|
1231
1333
|
});
|
|
1232
1334
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1335
|
+
/**
|
|
1336
|
+
* Promise-version of rename
|
|
1337
|
+
*
|
|
1338
|
+
* @param id The id of the object owning the file
|
|
1339
|
+
* @param oldName The current file or directory name
|
|
1340
|
+
* @param newName The new file or directory name
|
|
1341
|
+
* @param options The current request options including the user
|
|
1342
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1343
|
+
*/
|
|
1344
|
+
renameAsync(id, oldName, newName, options) {
|
|
1345
|
+
return new Promise((resolve, reject) => this.rename(id, oldName, newName, options, (err) => err ? reject(err) : resolve()));
|
|
1235
1346
|
}
|
|
1236
|
-
async _touch(id, name,
|
|
1347
|
+
async _touch(id, name, meta, callback) {
|
|
1237
1348
|
const metaID = this.getFileId(id, name, true);
|
|
1238
1349
|
if (!this.client) {
|
|
1239
1350
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
@@ -1249,13 +1360,18 @@ class ObjectsInRedisClient {
|
|
|
1249
1360
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
1250
1361
|
}
|
|
1251
1362
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1363
|
+
/**
|
|
1364
|
+
* Update the modification time of a file
|
|
1365
|
+
*
|
|
1366
|
+
* @param id The id of the object owning the file
|
|
1367
|
+
* @param name The file name
|
|
1368
|
+
* @param options The current request options including the user, or the callback
|
|
1369
|
+
* @param callback Called once the file has been touched
|
|
1370
|
+
*/
|
|
1371
|
+
touch(id, name, options, callback) {
|
|
1372
|
+
if (typeof options === "function") {
|
|
1373
|
+
callback = options;
|
|
1374
|
+
options = null;
|
|
1259
1375
|
}
|
|
1260
1376
|
if (typeof name !== "string" || !name.length || name === "/") {
|
|
1261
1377
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
@@ -1263,15 +1379,23 @@ class ObjectsInRedisClient {
|
|
|
1263
1379
|
if (name.startsWith("/")) {
|
|
1264
1380
|
name = name.substring(1);
|
|
1265
1381
|
}
|
|
1266
|
-
this.checkFileRights(id, name,
|
|
1382
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta) => {
|
|
1267
1383
|
if (err) {
|
|
1268
1384
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1269
1385
|
}
|
|
1270
|
-
return this._touch(id, name,
|
|
1386
|
+
return this._touch(id, name, meta, callback);
|
|
1271
1387
|
});
|
|
1272
1388
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1389
|
+
/**
|
|
1390
|
+
* Promise-version of touch
|
|
1391
|
+
*
|
|
1392
|
+
* @param id The id of the object owning the file
|
|
1393
|
+
* @param name The file name
|
|
1394
|
+
* @param options The current request options including the user
|
|
1395
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1396
|
+
*/
|
|
1397
|
+
touchAsync(id, name, options) {
|
|
1398
|
+
return new Promise((resolve, reject) => this.touch(id, name, options, (err) => err ? reject(err) : resolve()));
|
|
1275
1399
|
}
|
|
1276
1400
|
async _rmHelper(keys2) {
|
|
1277
1401
|
if (!keys2.length) {
|
|
@@ -1285,7 +1409,7 @@ class ObjectsInRedisClient {
|
|
|
1285
1409
|
await this.client.del(id);
|
|
1286
1410
|
}
|
|
1287
1411
|
}
|
|
1288
|
-
async _rm(id, name,
|
|
1412
|
+
async _rm(id, name, userContext2, meta) {
|
|
1289
1413
|
if (meta && !meta.isDir) {
|
|
1290
1414
|
const metaID = this.getFileId(id, name, true);
|
|
1291
1415
|
const dataID = this.getFileId(id, name, false);
|
|
@@ -1313,31 +1437,31 @@ class ObjectsInRedisClient {
|
|
|
1313
1437
|
if (!keys2.length) {
|
|
1314
1438
|
throw new Error(ERRORS.ERROR_NOT_FOUND);
|
|
1315
1439
|
}
|
|
1316
|
-
let
|
|
1440
|
+
let strObjs;
|
|
1317
1441
|
try {
|
|
1318
|
-
|
|
1442
|
+
strObjs = await this.client.mget(keys2);
|
|
1319
1443
|
} catch {
|
|
1320
1444
|
}
|
|
1321
|
-
let
|
|
1322
|
-
const dontCheck =
|
|
1323
|
-
|
|
1445
|
+
let result;
|
|
1446
|
+
const dontCheck = userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1447
|
+
strObjs ||= [];
|
|
1324
1448
|
if (!dontCheck) {
|
|
1325
|
-
|
|
1449
|
+
result = [];
|
|
1326
1450
|
for (let i = 0; i < keys2.length; i++) {
|
|
1451
|
+
const strObj = strObjs[i];
|
|
1327
1452
|
try {
|
|
1328
|
-
const strObj = objs2[i];
|
|
1329
1453
|
const obj = strObj ? JSON.parse(strObj) : null;
|
|
1330
|
-
if (utils.checkObject(obj,
|
|
1331
|
-
|
|
1454
|
+
if (utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ)) {
|
|
1455
|
+
result.push(keys2[i]);
|
|
1332
1456
|
}
|
|
1333
1457
|
} catch {
|
|
1334
|
-
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${
|
|
1458
|
+
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${strObj}`);
|
|
1335
1459
|
}
|
|
1336
1460
|
}
|
|
1337
1461
|
} else {
|
|
1338
|
-
|
|
1462
|
+
result = keys2;
|
|
1339
1463
|
}
|
|
1340
|
-
const files =
|
|
1464
|
+
const files = result.map((key) => {
|
|
1341
1465
|
const name2 = key.substring(this.fileNamespaceL + id.length + 3, key.length - 7);
|
|
1342
1466
|
const pos = name2.lastIndexOf("/");
|
|
1343
1467
|
if (pos !== -1) {
|
|
@@ -1346,47 +1470,68 @@ class ObjectsInRedisClient {
|
|
|
1346
1470
|
return { file: id, path: "" };
|
|
1347
1471
|
});
|
|
1348
1472
|
try {
|
|
1349
|
-
await this._rmHelper(
|
|
1473
|
+
await this._rmHelper(result);
|
|
1350
1474
|
} catch (e) {
|
|
1351
1475
|
this.log.error(`${this.namespace} Could not remove files: ${e.message}`);
|
|
1352
1476
|
}
|
|
1353
1477
|
return files;
|
|
1354
1478
|
}
|
|
1355
1479
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1480
|
+
/**
|
|
1481
|
+
* Delete a file or directory of an object
|
|
1482
|
+
*
|
|
1483
|
+
* @param id The id of the object owning the file
|
|
1484
|
+
* @param name The file or directory name to delete
|
|
1485
|
+
* @param options The current request options including the user, or the callback
|
|
1486
|
+
* @param callback Called with the list of removed files
|
|
1487
|
+
*/
|
|
1488
|
+
rm(id, name, options, callback) {
|
|
1489
|
+
if (typeof options === "function") {
|
|
1490
|
+
callback = options;
|
|
1491
|
+
options = null;
|
|
1363
1492
|
}
|
|
1364
1493
|
if (typeof name !== "string") {
|
|
1365
1494
|
name = "";
|
|
1366
1495
|
}
|
|
1367
|
-
this.checkFileRights(id, null,
|
|
1496
|
+
this.checkFileRights(id, null, options, CONSTS.ACCESS_DELETE, async (err, meta, userContext2) => {
|
|
1368
1497
|
if (err) {
|
|
1369
1498
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1370
1499
|
}
|
|
1371
|
-
if (!
|
|
1500
|
+
if (!userContext2.acl.file.delete) {
|
|
1372
1501
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1373
1502
|
}
|
|
1374
1503
|
try {
|
|
1375
|
-
const files = await this._rm(id, name,
|
|
1504
|
+
const files = await this._rm(id, name, userContext2, meta?.notExists ? null : meta);
|
|
1376
1505
|
return import_db_base.tools.maybeCallbackWithError(callback, null, files);
|
|
1377
1506
|
} catch (e) {
|
|
1378
1507
|
return import_db_base.tools.maybeCallbackWithError(callback, e);
|
|
1379
1508
|
}
|
|
1380
1509
|
});
|
|
1381
1510
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1511
|
+
/**
|
|
1512
|
+
* Promise-version of rm
|
|
1513
|
+
*
|
|
1514
|
+
* @param id The id of the object owning the file
|
|
1515
|
+
* @param name The file or directory name to delete
|
|
1516
|
+
* @param options The current request options including the user
|
|
1517
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1518
|
+
*/
|
|
1519
|
+
rmAsync(id, name, options) {
|
|
1520
|
+
return new Promise((resolve, reject) => this.rm(id, name, options, (err, files) => err ? reject(err) : resolve(files)));
|
|
1384
1521
|
}
|
|
1385
1522
|
// simulate. redis has no dirs
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1523
|
+
/**
|
|
1524
|
+
* Create a directory for an object's files (simulated, as redis has no real directories)
|
|
1525
|
+
*
|
|
1526
|
+
* @param id The id of the object owning the files
|
|
1527
|
+
* @param dirName The directory name to create
|
|
1528
|
+
* @param options The current request options including the user, or the callback
|
|
1529
|
+
* @param callback Called once the directory has been created
|
|
1530
|
+
*/
|
|
1531
|
+
mkdir(id, dirName, options, callback) {
|
|
1532
|
+
if (typeof options === "function") {
|
|
1533
|
+
callback = options;
|
|
1534
|
+
options = null;
|
|
1390
1535
|
}
|
|
1391
1536
|
if (typeof dirName !== "string") {
|
|
1392
1537
|
dirName = "";
|
|
@@ -1395,22 +1540,29 @@ class ObjectsInRedisClient {
|
|
|
1395
1540
|
if (dirName.startsWith("/")) {
|
|
1396
1541
|
dirName = dirName.substring(1);
|
|
1397
1542
|
}
|
|
1398
|
-
this.checkFileRights(id, dirName,
|
|
1543
|
+
this.checkFileRights(id, dirName, options, CONSTS.ACCESS_WRITE, (err, meta, userContext2) => {
|
|
1399
1544
|
if (err) {
|
|
1400
1545
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1401
1546
|
}
|
|
1402
|
-
if (!
|
|
1547
|
+
if (!userContext2.acl.file.write) {
|
|
1403
1548
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1404
1549
|
}
|
|
1405
|
-
|
|
1550
|
+
meta = { ...options, virtualFile: true };
|
|
1406
1551
|
const realName = dirName + (dirName.endsWith("/") ? "" : "/");
|
|
1407
|
-
this.writeFile(id, `${realName}_data.json`, "",
|
|
1552
|
+
return this.writeFile(id, `${realName}_data.json`, "", meta, callback);
|
|
1408
1553
|
});
|
|
1409
1554
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1555
|
+
/**
|
|
1556
|
+
* Promise-version of mkdir
|
|
1557
|
+
*
|
|
1558
|
+
* @param id The id of the object owning the files
|
|
1559
|
+
* @param dirName The directory name to create
|
|
1560
|
+
* @param options The current request options including the user
|
|
1561
|
+
*/
|
|
1562
|
+
mkdirAsync(id, dirName, options) {
|
|
1563
|
+
return new Promise((resolve, reject) => this.mkdir(id, dirName, options, (err) => err ? reject(err) : resolve()));
|
|
1412
1564
|
}
|
|
1413
|
-
async _chownFileHelper(keys2, metas,
|
|
1565
|
+
async _chownFileHelper(keys2, metas, options, callback) {
|
|
1414
1566
|
if (!keys2.length) {
|
|
1415
1567
|
return import_db_base.tools.maybeCallback(callback);
|
|
1416
1568
|
}
|
|
@@ -1419,8 +1571,8 @@ class ObjectsInRedisClient {
|
|
|
1419
1571
|
}
|
|
1420
1572
|
for (const id of keys2) {
|
|
1421
1573
|
const meta = metas.shift();
|
|
1422
|
-
meta.acl.owner =
|
|
1423
|
-
meta.acl.ownerGroup =
|
|
1574
|
+
meta.acl.owner = options.owner;
|
|
1575
|
+
meta.acl.ownerGroup = options.ownerGroup;
|
|
1424
1576
|
try {
|
|
1425
1577
|
await this.client.set(id, JSON.stringify(meta));
|
|
1426
1578
|
} catch (e) {
|
|
@@ -1429,7 +1581,7 @@ class ObjectsInRedisClient {
|
|
|
1429
1581
|
}
|
|
1430
1582
|
return import_db_base.tools.maybeCallback(callback);
|
|
1431
1583
|
}
|
|
1432
|
-
async _chownFile(id, name,
|
|
1584
|
+
async _chownFile(id, name, options, meta, userContext2, callback) {
|
|
1433
1585
|
if (!meta) {
|
|
1434
1586
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1435
1587
|
}
|
|
@@ -1439,8 +1591,8 @@ class ObjectsInRedisClient {
|
|
|
1439
1591
|
}
|
|
1440
1592
|
if (!meta.isDir && !meta.notExists) {
|
|
1441
1593
|
const metaID = this.getFileId(id, name, true);
|
|
1442
|
-
meta.acl.owner =
|
|
1443
|
-
meta.acl.ownerGroup =
|
|
1594
|
+
meta.acl.owner = options.owner;
|
|
1595
|
+
meta.acl.ownerGroup = options.ownerGroup;
|
|
1444
1596
|
try {
|
|
1445
1597
|
await this.client.set(metaID, JSON.stringify(meta));
|
|
1446
1598
|
} catch (e) {
|
|
@@ -1486,7 +1638,7 @@ class ObjectsInRedisClient {
|
|
|
1486
1638
|
} catch (e) {
|
|
1487
1639
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
1488
1640
|
}
|
|
1489
|
-
const dontCheck =
|
|
1641
|
+
const dontCheck = userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1490
1642
|
const keysFiltered = [];
|
|
1491
1643
|
const objsFiltered = [];
|
|
1492
1644
|
const processed = [];
|
|
@@ -1501,7 +1653,7 @@ class ObjectsInRedisClient {
|
|
|
1501
1653
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${metaStr}`);
|
|
1502
1654
|
continue;
|
|
1503
1655
|
}
|
|
1504
|
-
if (dontCheck || utils.checkObject(meta2,
|
|
1656
|
+
if (dontCheck || utils.checkObject(meta2, userContext2, CONSTS.ACCESS_WRITE)) {
|
|
1505
1657
|
if (!meta2 || meta2.virtualFile) {
|
|
1506
1658
|
continue;
|
|
1507
1659
|
}
|
|
@@ -1521,76 +1673,92 @@ class ObjectsInRedisClient {
|
|
|
1521
1673
|
});
|
|
1522
1674
|
}
|
|
1523
1675
|
}
|
|
1524
|
-
this._chownFileHelper(keysFiltered, objsFiltered,
|
|
1676
|
+
return this._chownFileHelper(keysFiltered, objsFiltered, options, (err) => {
|
|
1525
1677
|
return import_db_base.tools.maybeCallbackWithError(callback, err, processed);
|
|
1526
1678
|
});
|
|
1527
1679
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1680
|
+
/**
|
|
1681
|
+
* Change the owner and owner group of a file
|
|
1682
|
+
*
|
|
1683
|
+
* @param id The id of the object owning the file
|
|
1684
|
+
* @param name The file name
|
|
1685
|
+
* @param options The current request options including the new owner and the user
|
|
1686
|
+
* @param options.owner The new owner (user id) to assign to the file
|
|
1687
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1688
|
+
* @param options.ownerGroup The new owner group to assign to the file
|
|
1689
|
+
* @param options.group The new owner group to assign (alias for ownerGroup)
|
|
1690
|
+
* @param callback Called with the processed file(s)
|
|
1691
|
+
*/
|
|
1692
|
+
chownFile(id, name, options, callback) {
|
|
1537
1693
|
if (typeof name !== "string" || !name.length || name === "/") {
|
|
1538
1694
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1539
1695
|
}
|
|
1540
1696
|
if (name.startsWith("/")) {
|
|
1541
1697
|
name = name.substring(1);
|
|
1542
1698
|
}
|
|
1543
|
-
if (
|
|
1544
|
-
|
|
1699
|
+
if (options?.group && !options.ownerGroup) {
|
|
1700
|
+
options.ownerGroup = options.group;
|
|
1545
1701
|
}
|
|
1546
|
-
if (
|
|
1547
|
-
|
|
1702
|
+
if (options?.user && !options.owner) {
|
|
1703
|
+
options.owner = options.user;
|
|
1548
1704
|
}
|
|
1549
|
-
if (!
|
|
1705
|
+
if (!options?.owner) {
|
|
1550
1706
|
this.log.error(`${this.namespace} user is not defined`);
|
|
1551
1707
|
return import_db_base.tools.maybeCallbackWithError(callback, "invalid parameter");
|
|
1552
1708
|
}
|
|
1553
|
-
if (!
|
|
1554
|
-
this.getUserGroup(
|
|
1555
|
-
if (!groups
|
|
1556
|
-
return import_db_base.tools.maybeCallbackWithError(callback, `user "${
|
|
1709
|
+
if (!options.ownerGroup) {
|
|
1710
|
+
this.getUserGroup(options.owner, (user, groups) => {
|
|
1711
|
+
if (!groups?.[0]) {
|
|
1712
|
+
return import_db_base.tools.maybeCallbackWithError(callback, `user "${options.owner}" belongs to no group`);
|
|
1557
1713
|
}
|
|
1558
|
-
|
|
1559
|
-
this.chownFile(id, name,
|
|
1714
|
+
options.ownerGroup = groups[0];
|
|
1715
|
+
this.chownFile(id, name, options, callback);
|
|
1560
1716
|
});
|
|
1561
1717
|
return;
|
|
1562
1718
|
}
|
|
1563
|
-
this.checkFileRights(id, name,
|
|
1719
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta, userContext2) => {
|
|
1564
1720
|
if (err) {
|
|
1565
1721
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1566
1722
|
}
|
|
1567
|
-
if (!
|
|
1723
|
+
if (!userContext2.acl.file.write) {
|
|
1568
1724
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1569
1725
|
}
|
|
1570
|
-
return this._chownFile(id, name,
|
|
1726
|
+
return this._chownFile(id, name, options, meta, userContext2, callback);
|
|
1571
1727
|
});
|
|
1572
1728
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1729
|
+
/**
|
|
1730
|
+
* Promise-version of chownFile
|
|
1731
|
+
*
|
|
1732
|
+
* @param id The id of the object owning the file
|
|
1733
|
+
* @param name The file name
|
|
1734
|
+
* @param options The current request options including the new owner and the user
|
|
1735
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1736
|
+
* @param options.owner The new owner (user id) to assign to the file
|
|
1737
|
+
* @param options.ownerGroup The new owner group to assign to the file
|
|
1738
|
+
*/
|
|
1739
|
+
chownFileAsync(id, name, options) {
|
|
1740
|
+
return new Promise((resolve, reject) => this.chownFile(id, name, options, (err, processed) => err ? reject(err) : resolve(processed)));
|
|
1575
1741
|
}
|
|
1576
1742
|
/**
|
|
1577
1743
|
*
|
|
1578
1744
|
* @param keys Key names to handle
|
|
1579
1745
|
* @param metas Objects for the keys to handle
|
|
1580
1746
|
* @param options options
|
|
1747
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1748
|
+
* @param options.mode The new permission mode to apply
|
|
1581
1749
|
* @param callback callback function
|
|
1582
1750
|
*/
|
|
1583
|
-
async _chmodFileHelper(keys2, metas,
|
|
1751
|
+
async _chmodFileHelper(keys2, metas, options, callback) {
|
|
1584
1752
|
if (!keys2 || !keys2.length) {
|
|
1585
1753
|
return import_db_base.tools.maybeCallback(callback);
|
|
1586
1754
|
}
|
|
1587
1755
|
if (!this.client) {
|
|
1588
1756
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
1589
1757
|
}
|
|
1590
|
-
for (
|
|
1758
|
+
for (let i = 0; i < keys2.length; i++) {
|
|
1591
1759
|
const id = keys2[i];
|
|
1592
1760
|
const meta = metas[i];
|
|
1593
|
-
meta.acl.permissions =
|
|
1761
|
+
meta.acl.permissions = options.mode;
|
|
1594
1762
|
try {
|
|
1595
1763
|
await this.client.set(id, JSON.stringify(meta));
|
|
1596
1764
|
} catch (e) {
|
|
@@ -1599,7 +1767,7 @@ class ObjectsInRedisClient {
|
|
|
1599
1767
|
}
|
|
1600
1768
|
return import_db_base.tools.maybeCallback(callback);
|
|
1601
1769
|
}
|
|
1602
|
-
async _chmodFile(id, name,
|
|
1770
|
+
async _chmodFile(id, name, options, meta, userContext2, callback) {
|
|
1603
1771
|
if (!meta) {
|
|
1604
1772
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1605
1773
|
}
|
|
@@ -1609,7 +1777,7 @@ class ObjectsInRedisClient {
|
|
|
1609
1777
|
}
|
|
1610
1778
|
if (!meta.isDir && !meta.notExists) {
|
|
1611
1779
|
const metaID = this.getFileId(id, name, true);
|
|
1612
|
-
meta.acl.permissions =
|
|
1780
|
+
meta.acl.permissions = options.mode;
|
|
1613
1781
|
try {
|
|
1614
1782
|
await this.client.set(metaID, JSON.stringify(meta));
|
|
1615
1783
|
} catch (e) {
|
|
@@ -1655,7 +1823,7 @@ class ObjectsInRedisClient {
|
|
|
1655
1823
|
} catch (e) {
|
|
1656
1824
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
1657
1825
|
}
|
|
1658
|
-
const dontCheck =
|
|
1826
|
+
const dontCheck = userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
1659
1827
|
const keysFiltered = [];
|
|
1660
1828
|
const objsFiltered = [];
|
|
1661
1829
|
const processed = [];
|
|
@@ -1670,7 +1838,7 @@ class ObjectsInRedisClient {
|
|
|
1670
1838
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${strObj}`);
|
|
1671
1839
|
continue;
|
|
1672
1840
|
}
|
|
1673
|
-
if (dontCheck || utils.checkObject(obj,
|
|
1841
|
+
if (dontCheck || utils.checkObject(obj, userContext2, CONSTS.ACCESS_WRITE)) {
|
|
1674
1842
|
if (!obj || obj.virtualFile) {
|
|
1675
1843
|
continue;
|
|
1676
1844
|
}
|
|
@@ -1690,59 +1858,86 @@ class ObjectsInRedisClient {
|
|
|
1690
1858
|
});
|
|
1691
1859
|
}
|
|
1692
1860
|
}
|
|
1693
|
-
this._chmodFileHelper(keysFiltered, objsFiltered,
|
|
1861
|
+
return this._chmodFileHelper(keysFiltered, objsFiltered, options, (err) => import_db_base.tools.maybeCallbackWithError(callback, err, processed));
|
|
1694
1862
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1863
|
+
/**
|
|
1864
|
+
* Change the file mode (permissions) of a single file
|
|
1865
|
+
*
|
|
1866
|
+
* @param id The id of the object owning the file
|
|
1867
|
+
* @param name The file name
|
|
1868
|
+
* @param options The current request options including the new mode and the user, or the callback
|
|
1869
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1870
|
+
* @param options.mode The new permission mode to apply
|
|
1871
|
+
* @param callback Called with the processed file
|
|
1872
|
+
*/
|
|
1873
|
+
chmodFile(id, name, options, callback) {
|
|
1874
|
+
if (!options || options.mode === void 0) {
|
|
1875
|
+
throw new Error("options.mode is not defined");
|
|
1699
1876
|
}
|
|
1700
|
-
options2 = options2 || {};
|
|
1701
1877
|
if (typeof name !== "string" || !name.length || name === "/") {
|
|
1702
1878
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_NOT_FOUND);
|
|
1703
1879
|
}
|
|
1704
1880
|
if (name[0].startsWith("/")) {
|
|
1705
1881
|
name = name.substring(1);
|
|
1706
1882
|
}
|
|
1707
|
-
if (typeof
|
|
1708
|
-
|
|
1883
|
+
if (typeof options !== "object") {
|
|
1884
|
+
options = { mode: options };
|
|
1709
1885
|
}
|
|
1710
|
-
if (
|
|
1886
|
+
if (options.mode === void 0) {
|
|
1711
1887
|
this.log.error(`${this.namespace} mode is not defined`);
|
|
1712
1888
|
return import_db_base.tools.maybeCallbackWithError(callback, "invalid parameter");
|
|
1713
|
-
} else if (typeof
|
|
1714
|
-
|
|
1889
|
+
} else if (typeof options.mode === "string") {
|
|
1890
|
+
options.mode = parseInt(options.mode, 16);
|
|
1715
1891
|
}
|
|
1716
|
-
this.checkFileRights(id, name,
|
|
1892
|
+
this.checkFileRights(id, name, options, CONSTS.ACCESS_WRITE, (err, meta, userContext2) => {
|
|
1717
1893
|
if (err) {
|
|
1718
1894
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1719
1895
|
}
|
|
1720
|
-
if (!
|
|
1896
|
+
if (!userContext2.acl.file.write) {
|
|
1721
1897
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
1722
1898
|
}
|
|
1723
|
-
return this._chmodFile(id, name,
|
|
1899
|
+
return this._chmodFile(id, name, options, meta, userContext2, callback);
|
|
1724
1900
|
});
|
|
1725
1901
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1902
|
+
/**
|
|
1903
|
+
* Promise-version of chmodFile
|
|
1904
|
+
*
|
|
1905
|
+
* @param id The id of the object owning the file
|
|
1906
|
+
* @param name The file name
|
|
1907
|
+
* @param options The current request options including the new mode and the user
|
|
1908
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
1909
|
+
* @param options.mode The new permission mode to apply
|
|
1910
|
+
*/
|
|
1911
|
+
chmodFileAsync(id, name, options) {
|
|
1912
|
+
return new Promise((resolve, reject) => this.chmodFile(id, name, options, (err, processed) => err ? reject(err) : resolve(processed)));
|
|
1728
1913
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1914
|
+
/**
|
|
1915
|
+
* Enable or disable the file cache
|
|
1916
|
+
*
|
|
1917
|
+
* @param enabled Whether the file cache should be enabled
|
|
1918
|
+
* @param options The current request options including the user, or the callback
|
|
1919
|
+
* @param callback Called with the resulting cache state
|
|
1920
|
+
*/
|
|
1921
|
+
enableFileCache(enabled, options, callback) {
|
|
1922
|
+
if (typeof options === "function") {
|
|
1923
|
+
callback = options;
|
|
1924
|
+
options = null;
|
|
1736
1925
|
}
|
|
1737
|
-
utils.checkObjectRights(this, null, null,
|
|
1926
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err) => {
|
|
1738
1927
|
if (err) {
|
|
1739
1928
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1740
1929
|
}
|
|
1741
1930
|
return import_db_base.tools.maybeCallbackWithError(callback, null, false);
|
|
1742
1931
|
});
|
|
1743
1932
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1933
|
+
/**
|
|
1934
|
+
* Promise-version of enableFileCache
|
|
1935
|
+
*
|
|
1936
|
+
* @param enabled Whether the file cache should be enabled
|
|
1937
|
+
* @param options The current request options including the user
|
|
1938
|
+
*/
|
|
1939
|
+
enableFileCacheAsync(enabled, options) {
|
|
1940
|
+
return new Promise((resolve, reject) => this.enableFileCache(enabled, options, (err, res) => err ? reject(err) : resolve(res)));
|
|
1746
1941
|
}
|
|
1747
1942
|
async _subscribeFile(id, pattern) {
|
|
1748
1943
|
if (!this.sub) {
|
|
@@ -1788,9 +1983,16 @@ class ObjectsInRedisClient {
|
|
|
1788
1983
|
}
|
|
1789
1984
|
}
|
|
1790
1985
|
}
|
|
1791
|
-
|
|
1986
|
+
/**
|
|
1987
|
+
* Subscribe a user to file changes of an object
|
|
1988
|
+
*
|
|
1989
|
+
* @param id The id of the object owning the files
|
|
1990
|
+
* @param pattern One or more file name patterns to subscribe to
|
|
1991
|
+
* @param options The current request options including the user
|
|
1992
|
+
*/
|
|
1993
|
+
subscribeUserFile(id, pattern, options) {
|
|
1792
1994
|
return new Promise((resolve, reject) => {
|
|
1793
|
-
utils.checkObjectRights(this, null, null,
|
|
1995
|
+
utils.checkObjectRights(this, null, null, options, "list", (err) => {
|
|
1794
1996
|
if (err) {
|
|
1795
1997
|
reject(err);
|
|
1796
1998
|
} else {
|
|
@@ -1799,9 +2001,16 @@ class ObjectsInRedisClient {
|
|
|
1799
2001
|
});
|
|
1800
2002
|
});
|
|
1801
2003
|
}
|
|
1802
|
-
|
|
2004
|
+
/**
|
|
2005
|
+
* Unsubscribe a user from file changes of an object
|
|
2006
|
+
*
|
|
2007
|
+
* @param id The id of the object owning the files
|
|
2008
|
+
* @param pattern One or more file name patterns to unsubscribe from
|
|
2009
|
+
* @param options The current request options including the user
|
|
2010
|
+
*/
|
|
2011
|
+
unsubscribeUserFile(id, pattern, options) {
|
|
1803
2012
|
return new Promise((resolve, reject) => {
|
|
1804
|
-
utils.checkObjectRights(this, null, null,
|
|
2013
|
+
utils.checkObjectRights(this, null, null, options, "list", (err) => {
|
|
1805
2014
|
if (err) {
|
|
1806
2015
|
reject(err);
|
|
1807
2016
|
} else {
|
|
@@ -1840,38 +2049,64 @@ class ObjectsInRedisClient {
|
|
|
1840
2049
|
});
|
|
1841
2050
|
}
|
|
1842
2051
|
}
|
|
1843
|
-
subscribeConfig(pattern,
|
|
1844
|
-
utils.checkObjectRights(this, null, null,
|
|
2052
|
+
subscribeConfig(pattern, options, callback) {
|
|
2053
|
+
utils.checkObjectRights(this, null, null, options, "list", (err) => {
|
|
1845
2054
|
if (err) {
|
|
1846
2055
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
1847
2056
|
}
|
|
1848
2057
|
return this._subscribe(pattern, false, callback);
|
|
1849
2058
|
});
|
|
1850
2059
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2060
|
+
/**
|
|
2061
|
+
* Subscribe to object changes matching the given pattern
|
|
2062
|
+
*
|
|
2063
|
+
* @param pattern One or more patterns to subscribe to
|
|
2064
|
+
* @param options The current request options including the user, or the callback
|
|
2065
|
+
* @param callback Called once the subscription is registered
|
|
2066
|
+
*/
|
|
2067
|
+
subscribe(pattern, options, callback) {
|
|
2068
|
+
if (typeof options === "function") {
|
|
2069
|
+
callback = options;
|
|
2070
|
+
options = null;
|
|
1855
2071
|
}
|
|
1856
|
-
return this.subscribeConfig(pattern,
|
|
2072
|
+
return this.subscribeConfig(pattern, options, callback);
|
|
1857
2073
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
2074
|
+
/**
|
|
2075
|
+
* Promise-version of subscribe
|
|
2076
|
+
*
|
|
2077
|
+
* @param pattern One or more patterns to subscribe to
|
|
2078
|
+
* @param options The current request options including the user
|
|
2079
|
+
*/
|
|
2080
|
+
subscribeAsync(pattern, options) {
|
|
2081
|
+
return new Promise((resolve, reject) => this.subscribe(pattern, options, (err) => err ? reject(err) : resolve()));
|
|
1860
2082
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2083
|
+
/**
|
|
2084
|
+
* Subscribe a user to object changes matching the given pattern
|
|
2085
|
+
*
|
|
2086
|
+
* @param pattern One or more patterns to subscribe to
|
|
2087
|
+
* @param options The current request options including the user, or the callback
|
|
2088
|
+
* @param callback Called once the subscription is registered
|
|
2089
|
+
*/
|
|
2090
|
+
subscribeUser(pattern, options, callback) {
|
|
2091
|
+
if (typeof options === "function") {
|
|
2092
|
+
callback = options;
|
|
2093
|
+
options = null;
|
|
1865
2094
|
}
|
|
1866
|
-
utils.checkObjectRights(this, null, null,
|
|
2095
|
+
utils.checkObjectRights(this, null, null, options, "list", (err) => {
|
|
1867
2096
|
if (err) {
|
|
1868
2097
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
1869
2098
|
}
|
|
1870
2099
|
return this._subscribe(pattern, true, callback);
|
|
1871
2100
|
});
|
|
1872
2101
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
2102
|
+
/**
|
|
2103
|
+
* Promise-version of subscribeUser
|
|
2104
|
+
*
|
|
2105
|
+
* @param pattern One or more patterns to subscribe to
|
|
2106
|
+
* @param options The current request options including the user
|
|
2107
|
+
*/
|
|
2108
|
+
subscribeUserAsync(pattern, options) {
|
|
2109
|
+
return new Promise((resolve, reject) => this.subscribeUser(pattern, options, (err) => err ? reject(err) : resolve()));
|
|
1875
2110
|
}
|
|
1876
2111
|
async _unsubscribe(pattern, asUser) {
|
|
1877
2112
|
const subClient = asUser ? this.sub : this.subSystem;
|
|
@@ -1896,8 +2131,8 @@ class ObjectsInRedisClient {
|
|
|
1896
2131
|
}
|
|
1897
2132
|
}
|
|
1898
2133
|
}
|
|
1899
|
-
unsubscribeConfig(pattern,
|
|
1900
|
-
utils.checkObjectRights(this, null, null,
|
|
2134
|
+
unsubscribeConfig(pattern, options, callback) {
|
|
2135
|
+
utils.checkObjectRights(this, null, null, options, "list", async (err) => {
|
|
1901
2136
|
if (err) {
|
|
1902
2137
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
1903
2138
|
}
|
|
@@ -1909,22 +2144,42 @@ class ObjectsInRedisClient {
|
|
|
1909
2144
|
}
|
|
1910
2145
|
});
|
|
1911
2146
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2147
|
+
/**
|
|
2148
|
+
* Unsubscribe from object changes matching the given pattern
|
|
2149
|
+
*
|
|
2150
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2151
|
+
* @param options The current request options including the user, or the callback
|
|
2152
|
+
* @param callback Called once the subscription is removed
|
|
2153
|
+
*/
|
|
2154
|
+
unsubscribe(pattern, options, callback) {
|
|
2155
|
+
if (typeof options === "function") {
|
|
2156
|
+
callback = options;
|
|
2157
|
+
options = null;
|
|
1916
2158
|
}
|
|
1917
|
-
return this.unsubscribeConfig(pattern,
|
|
2159
|
+
return this.unsubscribeConfig(pattern, options, callback);
|
|
1918
2160
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
2161
|
+
/**
|
|
2162
|
+
* Promise-version of unsubscribe
|
|
2163
|
+
*
|
|
2164
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2165
|
+
* @param options The current request options including the user
|
|
2166
|
+
*/
|
|
2167
|
+
unsubscribeAsync(pattern, options) {
|
|
2168
|
+
return new Promise((resolve, reject) => this.unsubscribe(pattern, options, (err) => err ? reject(err) : resolve()));
|
|
1921
2169
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2170
|
+
/**
|
|
2171
|
+
* Unsubscribe a user from object changes matching the given pattern
|
|
2172
|
+
*
|
|
2173
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2174
|
+
* @param options The current request options including the user, or the callback
|
|
2175
|
+
* @param callback Called once the subscription is removed
|
|
2176
|
+
*/
|
|
2177
|
+
unsubscribeUser(pattern, options, callback) {
|
|
2178
|
+
if (typeof options === "function") {
|
|
2179
|
+
callback = options;
|
|
2180
|
+
options = null;
|
|
1926
2181
|
}
|
|
1927
|
-
utils.checkObjectRights(this, null, null,
|
|
2182
|
+
utils.checkObjectRights(this, null, null, options, "list", async (err) => {
|
|
1928
2183
|
if (err) {
|
|
1929
2184
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1930
2185
|
}
|
|
@@ -1936,8 +2191,14 @@ class ObjectsInRedisClient {
|
|
|
1936
2191
|
}
|
|
1937
2192
|
});
|
|
1938
2193
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
2194
|
+
/**
|
|
2195
|
+
* Promise-version of unsubscribeUser
|
|
2196
|
+
*
|
|
2197
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
2198
|
+
* @param options The current request options including the user
|
|
2199
|
+
*/
|
|
2200
|
+
unsubscribeUserAsync(pattern, options) {
|
|
2201
|
+
return new Promise((resolve, reject) => this.unsubscribeUser(pattern, options, (err) => err ? reject(err) : resolve()));
|
|
1941
2202
|
}
|
|
1942
2203
|
async _objectHelper(keys2, objs2) {
|
|
1943
2204
|
if (!keys2.length) {
|
|
@@ -1967,8 +2228,8 @@ class ObjectsInRedisClient {
|
|
|
1967
2228
|
await this.client.publish(id, message);
|
|
1968
2229
|
}
|
|
1969
2230
|
}
|
|
1970
|
-
_chownObject(pattern,
|
|
1971
|
-
this.getKeys(pattern,
|
|
2231
|
+
_chownObject(pattern, options, userContext2, callback) {
|
|
2232
|
+
this.getKeys(pattern, options, async (err, keys2) => {
|
|
1972
2233
|
if (err) {
|
|
1973
2234
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
1974
2235
|
}
|
|
@@ -1995,7 +2256,7 @@ class ObjectsInRedisClient {
|
|
|
1995
2256
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${strObj}`);
|
|
1996
2257
|
continue;
|
|
1997
2258
|
}
|
|
1998
|
-
if (!obj || !utils.checkObject(obj,
|
|
2259
|
+
if (!obj || !utils.checkObject(obj, userContext2, CONSTS.ACCESS_WRITE)) {
|
|
1999
2260
|
continue;
|
|
2000
2261
|
}
|
|
2001
2262
|
if (!obj.acl) {
|
|
@@ -2009,8 +2270,8 @@ class ObjectsInRedisClient {
|
|
|
2009
2270
|
obj.acl.state = this.defaultNewAcl?.state || CONSTS.ACCESS_USER_RW | CONSTS.ACCESS_GROUP_READ | CONSTS.ACCESS_EVERY_READ;
|
|
2010
2271
|
}
|
|
2011
2272
|
}
|
|
2012
|
-
obj.acl.owner =
|
|
2013
|
-
obj.acl.ownerGroup =
|
|
2273
|
+
obj.acl.owner = options.owner || obj.acl.owner;
|
|
2274
|
+
obj.acl.ownerGroup = options.ownerGroup || obj.acl.ownerGroup;
|
|
2014
2275
|
filteredKeys.push(keys2[i]);
|
|
2015
2276
|
filteredObjs.push(obj);
|
|
2016
2277
|
}
|
|
@@ -2022,51 +2283,63 @@ class ObjectsInRedisClient {
|
|
|
2022
2283
|
return import_db_base.tools.maybeCallbackWithError(callback, null, filteredObjs);
|
|
2023
2284
|
}, true);
|
|
2024
2285
|
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2286
|
+
/**
|
|
2287
|
+
* Change the owner and owner group of all objects matching the given pattern
|
|
2288
|
+
*
|
|
2289
|
+
* @param pattern The pattern of object ids whose owner should be changed
|
|
2290
|
+
* @param options The current request options including the new owner and the user, or the callback
|
|
2291
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2292
|
+
* @param options.owner The new owner (user id) to assign to the objects
|
|
2293
|
+
* @param options.ownerGroup The new owner group to assign to the objects
|
|
2294
|
+
* @param options.group The new owner group to assign (alias for ownerGroup)
|
|
2295
|
+
* @param callback Called with the list of changed objects
|
|
2296
|
+
*/
|
|
2297
|
+
chownObject(pattern, options, callback) {
|
|
2298
|
+
if (options?.group && !options.ownerGroup) {
|
|
2299
|
+
options.ownerGroup = options.group;
|
|
2037
2300
|
}
|
|
2038
|
-
if (
|
|
2039
|
-
|
|
2301
|
+
if (options?.user && !options.owner) {
|
|
2302
|
+
options.owner = options.user;
|
|
2040
2303
|
}
|
|
2041
|
-
if (!
|
|
2304
|
+
if (!options.owner) {
|
|
2042
2305
|
this.log.error(`${this.namespace} user is not defined`);
|
|
2043
2306
|
return import_db_base.tools.maybeCallbackWithError(callback, "invalid parameter");
|
|
2044
2307
|
}
|
|
2045
|
-
if (!
|
|
2046
|
-
this.getUserGroup(
|
|
2047
|
-
if (!groups
|
|
2048
|
-
return import_db_base.tools.maybeCallbackWithError(callback, `user "${
|
|
2308
|
+
if (!options.ownerGroup) {
|
|
2309
|
+
this.getUserGroup(options.owner, (user, groups) => {
|
|
2310
|
+
if (!groups?.[0]) {
|
|
2311
|
+
return import_db_base.tools.maybeCallbackWithError(callback, `user "${options.owner}" belongs to no group`);
|
|
2049
2312
|
}
|
|
2050
|
-
|
|
2051
|
-
this.chownObject(pattern,
|
|
2313
|
+
options.ownerGroup = groups[0];
|
|
2314
|
+
return this.chownObject(pattern, options, callback);
|
|
2052
2315
|
});
|
|
2053
2316
|
return;
|
|
2054
2317
|
}
|
|
2055
|
-
utils.checkObjectRights(this, null, null,
|
|
2318
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, userContext2) => {
|
|
2056
2319
|
if (err) {
|
|
2057
2320
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2058
2321
|
}
|
|
2059
|
-
if (!
|
|
2322
|
+
if (!userContext2.acl.object || !userContext2.acl.object.write) {
|
|
2060
2323
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
2061
2324
|
}
|
|
2062
|
-
return this._chownObject(pattern,
|
|
2325
|
+
return this._chownObject(pattern, options, userContext2, callback);
|
|
2063
2326
|
});
|
|
2064
2327
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2328
|
+
/**
|
|
2329
|
+
* Promise-version of chownObject
|
|
2330
|
+
*
|
|
2331
|
+
* @param pattern The pattern of object ids whose owner should be changed
|
|
2332
|
+
* @param options The current request options including the new owner and the user
|
|
2333
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2334
|
+
* @param options.owner The new owner (user id) to assign to the objects
|
|
2335
|
+
* @param options.ownerGroup The new owner group to assign to the objects
|
|
2336
|
+
* @param options.group The new owner group to assign (alias for ownerGroup)
|
|
2337
|
+
*/
|
|
2338
|
+
chownObjectAsync(pattern, options) {
|
|
2339
|
+
return new Promise((resolve, reject) => this.chownObject(pattern, options, (err, list) => err ? reject(err) : resolve(list)));
|
|
2067
2340
|
}
|
|
2068
|
-
_chmodObject(pattern,
|
|
2069
|
-
this.getKeys(pattern,
|
|
2341
|
+
_chmodObject(pattern, options, userContext2, callback) {
|
|
2342
|
+
this.getKeys(pattern, options, async (err, keys2) => {
|
|
2070
2343
|
if (err) {
|
|
2071
2344
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2072
2345
|
}
|
|
@@ -2093,7 +2366,7 @@ class ObjectsInRedisClient {
|
|
|
2093
2366
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${strObj}`);
|
|
2094
2367
|
continue;
|
|
2095
2368
|
}
|
|
2096
|
-
if (!utils.checkObject(obj,
|
|
2369
|
+
if (!utils.checkObject(obj, userContext2, CONSTS.ACCESS_WRITE) || !obj) {
|
|
2097
2370
|
continue;
|
|
2098
2371
|
}
|
|
2099
2372
|
if (!obj.acl) {
|
|
@@ -2107,11 +2380,11 @@ class ObjectsInRedisClient {
|
|
|
2107
2380
|
obj.acl.state = this.defaultNewAcl?.state || CONSTS.ACCESS_USER_RW | CONSTS.ACCESS_GROUP_READ | CONSTS.ACCESS_EVERY_READ;
|
|
2108
2381
|
}
|
|
2109
2382
|
}
|
|
2110
|
-
if (
|
|
2111
|
-
obj.acl.object =
|
|
2383
|
+
if (options.object !== void 0) {
|
|
2384
|
+
obj.acl.object = options.object;
|
|
2112
2385
|
}
|
|
2113
|
-
if (
|
|
2114
|
-
obj.acl.state =
|
|
2386
|
+
if (options.state !== void 0 && "state" in obj.acl) {
|
|
2387
|
+
obj.acl.state = options.state;
|
|
2115
2388
|
}
|
|
2116
2389
|
filteredKeys.push(keys2[i]);
|
|
2117
2390
|
filteredObjs.push(obj);
|
|
@@ -2123,39 +2396,55 @@ class ObjectsInRedisClient {
|
|
|
2123
2396
|
}
|
|
2124
2397
|
}, true);
|
|
2125
2398
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2399
|
+
/**
|
|
2400
|
+
* Change the file mode (permissions) of all files matching the given pattern
|
|
2401
|
+
*
|
|
2402
|
+
* @param pattern The pattern of object ids whose files should be changed
|
|
2403
|
+
* @param options The current request options including the new mode and the user, or the callback
|
|
2404
|
+
* @param options.mode The new permission mode to apply
|
|
2405
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2406
|
+
* @param options.object The permission bitmask for object access
|
|
2407
|
+
* @param options.state The permission bitmask for state access
|
|
2408
|
+
* @param callback Called with the list of changed objects
|
|
2409
|
+
*/
|
|
2410
|
+
chmodObject(pattern, options, callback) {
|
|
2411
|
+
if (!options) {
|
|
2412
|
+
this.log.error(`${this.namespace} mode is not defined`);
|
|
2413
|
+
return import_db_base.tools.maybeCallbackWithError(callback, "invalid parameter");
|
|
2135
2414
|
}
|
|
2136
|
-
if (
|
|
2137
|
-
|
|
2415
|
+
if (options.mode && !options.object) {
|
|
2416
|
+
options.object = options.mode;
|
|
2138
2417
|
}
|
|
2139
|
-
if (
|
|
2418
|
+
if (options.object === void 0) {
|
|
2140
2419
|
this.log.error(`${this.namespace} mode is not defined`);
|
|
2141
2420
|
return import_db_base.tools.maybeCallbackWithError(callback, "invalid parameter");
|
|
2142
|
-
} else if (typeof
|
|
2143
|
-
|
|
2421
|
+
} else if (typeof options.mode === "string") {
|
|
2422
|
+
options.mode = parseInt(options.mode, 16);
|
|
2144
2423
|
}
|
|
2145
|
-
utils.checkObjectRights(this, null, null,
|
|
2424
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, userContext2) => {
|
|
2146
2425
|
if (err) {
|
|
2147
2426
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2148
2427
|
}
|
|
2149
|
-
if (!
|
|
2428
|
+
if (!userContext2.acl.file.write) {
|
|
2150
2429
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
2151
2430
|
}
|
|
2152
|
-
return this._chmodObject(pattern,
|
|
2431
|
+
return this._chmodObject(pattern, options, userContext2, callback);
|
|
2153
2432
|
});
|
|
2154
2433
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2434
|
+
/**
|
|
2435
|
+
* Promise-version of chmodObject
|
|
2436
|
+
*
|
|
2437
|
+
* @param pattern The pattern of object ids whose files should be changed
|
|
2438
|
+
* @param options The current request options including the new mode and the user
|
|
2439
|
+
* @param options.mode The new permission mode to apply
|
|
2440
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
2441
|
+
* @param options.object The permission bitmask for object access
|
|
2442
|
+
* @param options.state The permission bitmask for state access
|
|
2443
|
+
*/
|
|
2444
|
+
chmodObjectAsync(pattern, options) {
|
|
2445
|
+
return new Promise((resolve, reject) => this.chmodObject(pattern, options, (err, list) => err ? reject(err) : resolve(list)));
|
|
2157
2446
|
}
|
|
2158
|
-
async _getObject(id,
|
|
2447
|
+
async _getObject(id, userContext2, callback) {
|
|
2159
2448
|
if (!this.client) {
|
|
2160
2449
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
2161
2450
|
}
|
|
@@ -2179,43 +2468,48 @@ class ObjectsInRedisClient {
|
|
|
2179
2468
|
}
|
|
2180
2469
|
}
|
|
2181
2470
|
if (obj) {
|
|
2182
|
-
if (utils.checkObject(obj,
|
|
2471
|
+
if (utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ)) {
|
|
2183
2472
|
return import_db_base.tools.maybeCallbackWithError(callback, null, obj);
|
|
2184
2473
|
}
|
|
2185
2474
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
2186
2475
|
}
|
|
2187
2476
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err, obj);
|
|
2188
2477
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2478
|
+
/**
|
|
2479
|
+
* Get a single object by its id
|
|
2480
|
+
*
|
|
2481
|
+
* @param id The id of the object to read
|
|
2482
|
+
* @param options The current request options including the user, or the callback
|
|
2483
|
+
* @param callback Called with the read object
|
|
2484
|
+
*/
|
|
2485
|
+
getObject(id, options, callback) {
|
|
2486
|
+
if (typeof options === "function") {
|
|
2487
|
+
callback = options;
|
|
2488
|
+
options = null;
|
|
2193
2489
|
}
|
|
2194
2490
|
if (!callback) {
|
|
2195
|
-
return new Promise((resolve, reject) => this.getObject(id,
|
|
2491
|
+
return new Promise((resolve, reject) => this.getObject(id, options, (err, obj) => err ? reject(err) : resolve(obj)));
|
|
2196
2492
|
}
|
|
2197
2493
|
if (typeof callback === "function") {
|
|
2198
|
-
|
|
2199
|
-
options2.acl = null;
|
|
2200
|
-
}
|
|
2201
|
-
utils.checkObjectRights(this, null, null, options2, CONSTS.ACCESS_READ, (err, options3) => {
|
|
2494
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, userContext2) => {
|
|
2202
2495
|
if (err) {
|
|
2203
2496
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
2204
2497
|
}
|
|
2205
|
-
return this._getObject(id,
|
|
2498
|
+
return this._getObject(id, userContext2, callback);
|
|
2206
2499
|
});
|
|
2207
2500
|
}
|
|
2208
2501
|
}
|
|
2209
2502
|
/**
|
|
2503
|
+
* Promise-version of getObject
|
|
2210
2504
|
*
|
|
2211
|
-
* @param id
|
|
2212
|
-
* @param options
|
|
2505
|
+
* @param id The id of the object to read
|
|
2506
|
+
* @param options The current request options including the user
|
|
2213
2507
|
* @deprecated use `getObject` without callback instead
|
|
2214
2508
|
*/
|
|
2215
|
-
getObjectAsync(id,
|
|
2216
|
-
return new Promise((resolve, reject) => this.getObject(id,
|
|
2509
|
+
getObjectAsync(id, options) {
|
|
2510
|
+
return new Promise((resolve, reject) => this.getObject(id, options, (err, obj) => err ? reject(err) : resolve(obj)));
|
|
2217
2511
|
}
|
|
2218
|
-
async _getKeys(pattern,
|
|
2512
|
+
async _getKeys(pattern, userContext2, dontModify, callback) {
|
|
2219
2513
|
if (!this.client) {
|
|
2220
2514
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
2221
2515
|
}
|
|
@@ -2232,23 +2526,23 @@ class ObjectsInRedisClient {
|
|
|
2232
2526
|
if (!this.client) {
|
|
2233
2527
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
2234
2528
|
}
|
|
2235
|
-
const
|
|
2529
|
+
const result = [];
|
|
2236
2530
|
if (keys2) {
|
|
2237
2531
|
keys2.sort();
|
|
2238
|
-
const
|
|
2239
|
-
const dontCheck =
|
|
2532
|
+
const result2 = [];
|
|
2533
|
+
const dontCheck = userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP);
|
|
2240
2534
|
if (dontCheck) {
|
|
2241
2535
|
for (let i = 0; i < keys2.length; i++) {
|
|
2242
2536
|
const id = keys2[i].substring(this.objNamespaceL);
|
|
2243
2537
|
if (r.test(id)) {
|
|
2244
2538
|
if (!dontModify) {
|
|
2245
|
-
|
|
2539
|
+
result2.push(id);
|
|
2246
2540
|
} else {
|
|
2247
|
-
|
|
2541
|
+
result2.push(keys2[i]);
|
|
2248
2542
|
}
|
|
2249
2543
|
}
|
|
2250
2544
|
}
|
|
2251
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
2545
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result2);
|
|
2252
2546
|
}
|
|
2253
2547
|
let metas;
|
|
2254
2548
|
try {
|
|
@@ -2266,39 +2560,54 @@ class ObjectsInRedisClient {
|
|
|
2266
2560
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys2[i]}: ${metaStr}`);
|
|
2267
2561
|
continue;
|
|
2268
2562
|
}
|
|
2269
|
-
if (r.test(keys2[i]) && utils.checkObject(meta,
|
|
2563
|
+
if (r.test(keys2[i]) && utils.checkObject(meta, userContext2, CONSTS.ACCESS_READ)) {
|
|
2270
2564
|
if (!dontModify) {
|
|
2271
|
-
|
|
2565
|
+
result2.push(keys2[i].substring(this.objNamespaceL));
|
|
2272
2566
|
} else {
|
|
2273
|
-
|
|
2567
|
+
result2.push(keys2[i]);
|
|
2274
2568
|
}
|
|
2275
2569
|
}
|
|
2276
2570
|
}
|
|
2277
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
2571
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result2);
|
|
2278
2572
|
}
|
|
2279
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
2573
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result);
|
|
2280
2574
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2575
|
+
/**
|
|
2576
|
+
* Get all object ids matching the given pattern
|
|
2577
|
+
*
|
|
2578
|
+
* @param pattern The pattern to match object ids against
|
|
2579
|
+
* @param options The current request options including the user, or the callback
|
|
2580
|
+
* @param callback Called with the matching keys
|
|
2581
|
+
* @param dontModify If true, the returned keys are not stripped of the namespace
|
|
2582
|
+
*/
|
|
2583
|
+
getKeys(pattern, options, callback, dontModify) {
|
|
2584
|
+
if (typeof options === "function") {
|
|
2585
|
+
callback = options;
|
|
2586
|
+
options = null;
|
|
2285
2587
|
}
|
|
2286
2588
|
if (!callback) {
|
|
2287
|
-
return new Promise((resolve, reject) => this.getKeys(pattern,
|
|
2589
|
+
return new Promise((resolve, reject) => this.getKeys(pattern, options, (err, obj) => err ? reject(err) : resolve(obj), dontModify));
|
|
2288
2590
|
}
|
|
2289
2591
|
if (typeof callback === "function") {
|
|
2290
|
-
utils.checkObjectRights(this, null, null,
|
|
2592
|
+
utils.checkObjectRights(this, null, null, options, "list", (err, userContext2) => {
|
|
2291
2593
|
if (err) {
|
|
2292
2594
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2293
2595
|
}
|
|
2294
|
-
return this._getKeys(pattern,
|
|
2596
|
+
return this._getKeys(pattern, userContext2, dontModify, callback);
|
|
2295
2597
|
});
|
|
2296
2598
|
}
|
|
2297
2599
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2600
|
+
/**
|
|
2601
|
+
* Promise-version of getKeys
|
|
2602
|
+
*
|
|
2603
|
+
* @param pattern The pattern to match object ids against
|
|
2604
|
+
* @param options The current request options including the user
|
|
2605
|
+
* @param options.user The user on whose behalf the keys are read
|
|
2606
|
+
*/
|
|
2607
|
+
getKeysAsync(pattern, options) {
|
|
2608
|
+
return this.getKeys(pattern, options);
|
|
2300
2609
|
}
|
|
2301
|
-
async _getObjects(keys2,
|
|
2610
|
+
async _getObjects(keys2, userContext2, callback, dontModify) {
|
|
2302
2611
|
if (!keys2) {
|
|
2303
2612
|
return import_db_base.tools.maybeCallbackWithError(callback, "no keys");
|
|
2304
2613
|
}
|
|
@@ -2326,9 +2635,9 @@ class ObjectsInRedisClient {
|
|
|
2326
2635
|
} catch (e) {
|
|
2327
2636
|
this.log.warn(`${this.namespace} redis mget ${!objs2 ? 0 : objs2.length} ${_keys.length}, err: ${e.message}`);
|
|
2328
2637
|
}
|
|
2329
|
-
let
|
|
2638
|
+
let result = [];
|
|
2330
2639
|
if (objs2) {
|
|
2331
|
-
const dontCheck =
|
|
2640
|
+
const dontCheck = userContext2 && (userContext2.user === CONSTS.SYSTEM_ADMIN_USER || userContext2.group === CONSTS.SYSTEM_ADMIN_GROUP || userContext2.groups?.includes(CONSTS.SYSTEM_ADMIN_GROUP));
|
|
2332
2641
|
if (!dontCheck) {
|
|
2333
2642
|
for (let i = 0; i < objs2.length; i++) {
|
|
2334
2643
|
const strObj = objs2[i];
|
|
@@ -2337,17 +2646,17 @@ class ObjectsInRedisClient {
|
|
|
2337
2646
|
obj = strObj ? JSON.parse(strObj) : null;
|
|
2338
2647
|
} catch {
|
|
2339
2648
|
this.log.error(`${this.namespace} Cannot parse JSON ${_keys[i]}: ${objs2[i]}`);
|
|
2340
|
-
|
|
2649
|
+
result.push({ error: ERRORS.ERROR_PERMISSION });
|
|
2341
2650
|
continue;
|
|
2342
2651
|
}
|
|
2343
|
-
if (utils.checkObject(obj,
|
|
2344
|
-
|
|
2652
|
+
if (utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ)) {
|
|
2653
|
+
result.push(obj);
|
|
2345
2654
|
} else {
|
|
2346
|
-
|
|
2655
|
+
result.push({ error: ERRORS.ERROR_PERMISSION });
|
|
2347
2656
|
}
|
|
2348
2657
|
}
|
|
2349
2658
|
} else {
|
|
2350
|
-
|
|
2659
|
+
result = objs2.map((obj, i) => {
|
|
2351
2660
|
try {
|
|
2352
2661
|
return obj !== null ? JSON.parse(obj) : obj;
|
|
2353
2662
|
} catch {
|
|
@@ -2357,35 +2666,46 @@ class ObjectsInRedisClient {
|
|
|
2357
2666
|
});
|
|
2358
2667
|
}
|
|
2359
2668
|
}
|
|
2360
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null,
|
|
2669
|
+
return import_db_base.tools.maybeCallbackWithError(callback, null, result);
|
|
2361
2670
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2671
|
+
/**
|
|
2672
|
+
* Get multiple objects by their ids
|
|
2673
|
+
*
|
|
2674
|
+
* @param keys The ids of the objects to read
|
|
2675
|
+
* @param options The current request options including the user, or the callback
|
|
2676
|
+
* @param callback Called with the read objects
|
|
2677
|
+
* @param dontModify If true, the returned objects are not cloned/modified
|
|
2678
|
+
*/
|
|
2679
|
+
getObjects(keys2, options, callback, dontModify) {
|
|
2680
|
+
if (typeof options === "function") {
|
|
2681
|
+
callback = options;
|
|
2682
|
+
options = null;
|
|
2366
2683
|
}
|
|
2367
2684
|
if (!callback) {
|
|
2368
2685
|
return new Promise((resolve, reject) => (
|
|
2369
2686
|
// @ts-expect-error need to clarify, that objs is not undefined if no error is provided
|
|
2370
|
-
this.getObjects(keys2,
|
|
2687
|
+
this.getObjects(keys2, options, (err, objs2) => err ? reject(err) : resolve(objs2), dontModify)
|
|
2371
2688
|
));
|
|
2372
2689
|
}
|
|
2373
|
-
if (options2?.acl) {
|
|
2374
|
-
options2.acl = null;
|
|
2375
|
-
}
|
|
2376
2690
|
if (typeof callback === "function") {
|
|
2377
|
-
utils.checkObjectRights(this, null, null,
|
|
2691
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, userContext2) => {
|
|
2378
2692
|
if (err) {
|
|
2379
2693
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2380
2694
|
}
|
|
2381
|
-
return this._getObjects(keys2,
|
|
2695
|
+
return this._getObjects(keys2, userContext2, callback, dontModify);
|
|
2382
2696
|
});
|
|
2383
2697
|
}
|
|
2384
2698
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2699
|
+
/**
|
|
2700
|
+
* Promise-version of getObjects
|
|
2701
|
+
*
|
|
2702
|
+
* @param keys The ids of the objects to read
|
|
2703
|
+
* @param options The current request options including the user
|
|
2704
|
+
*/
|
|
2705
|
+
getObjectsAsync(keys2, options) {
|
|
2706
|
+
return this.getObjects(keys2, options);
|
|
2387
2707
|
}
|
|
2388
|
-
async _getObjectsByPattern(pattern,
|
|
2708
|
+
async _getObjectsByPattern(pattern, userContext2, callback) {
|
|
2389
2709
|
if (!pattern || typeof pattern !== "string") {
|
|
2390
2710
|
return import_db_base.tools.maybeCallbackWithError(callback, `invalid pattern ${JSON.stringify(pattern)}`);
|
|
2391
2711
|
}
|
|
@@ -2404,32 +2724,43 @@ class ObjectsInRedisClient {
|
|
|
2404
2724
|
if (this.settings.connection.enhancedLogging) {
|
|
2405
2725
|
this.log.silly(`${this.namespace} redis keys ${keys2.length} ${pattern}`);
|
|
2406
2726
|
}
|
|
2407
|
-
this._getObjects(keys2,
|
|
2727
|
+
this._getObjects(keys2, userContext2, callback, true).catch((e) => this.log.error(`${this.namespace} Cannot get objects: ${e.message}`));
|
|
2408
2728
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2729
|
+
/**
|
|
2730
|
+
* Get all objects whose id matches the given pattern
|
|
2731
|
+
*
|
|
2732
|
+
* @param pattern The pattern to match object ids against
|
|
2733
|
+
* @param options The current request options including the user, or the callback
|
|
2734
|
+
* @param callback Called with the matching objects
|
|
2735
|
+
*/
|
|
2736
|
+
getObjectsByPattern(pattern, options, callback) {
|
|
2737
|
+
if (typeof options === "function") {
|
|
2738
|
+
callback = options;
|
|
2739
|
+
options = null;
|
|
2413
2740
|
}
|
|
2414
2741
|
if (!callback) {
|
|
2415
|
-
return new Promise((resolve, reject) => this.getObjectsByPattern(pattern,
|
|
2416
|
-
}
|
|
2417
|
-
if (options2?.acl) {
|
|
2418
|
-
options2.acl = null;
|
|
2742
|
+
return new Promise((resolve, reject) => this.getObjectsByPattern(pattern, options, (err, obj) => err ? reject(err) : resolve(obj)));
|
|
2419
2743
|
}
|
|
2420
2744
|
if (typeof callback === "function") {
|
|
2421
|
-
utils.checkObjectRights(this, null, null,
|
|
2745
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_READ, (err, userContext2) => {
|
|
2422
2746
|
if (err) {
|
|
2423
2747
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2424
2748
|
}
|
|
2425
|
-
return this._getObjectsByPattern(pattern,
|
|
2749
|
+
return this._getObjectsByPattern(pattern, userContext2, callback);
|
|
2426
2750
|
});
|
|
2427
2751
|
}
|
|
2428
2752
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2753
|
+
/**
|
|
2754
|
+
* Promise-version of getObjectsByPattern
|
|
2755
|
+
*
|
|
2756
|
+
* @param pattern The pattern to match object ids against
|
|
2757
|
+
* @param options The current request options including the user
|
|
2758
|
+
* @param options.user The user on whose behalf the objects are read
|
|
2759
|
+
*/
|
|
2760
|
+
getObjectsByPatternAsync(pattern, options) {
|
|
2761
|
+
return new Promise((resolve, reject) => this.getObjectsByPattern(pattern, options, (err, objs2) => err ? reject(err) : resolve(objs2)));
|
|
2431
2762
|
}
|
|
2432
|
-
async _setObject(id, obj,
|
|
2763
|
+
async _setObject(id, obj, userContext2) {
|
|
2433
2764
|
if (!id || typeof id !== "string" || utils.REG_CHECK_ID.test(id)) {
|
|
2434
2765
|
throw new Error(`Invalid ID: ${id}`);
|
|
2435
2766
|
}
|
|
@@ -2438,7 +2769,7 @@ class ObjectsInRedisClient {
|
|
|
2438
2769
|
throw new Error("obj is null");
|
|
2439
2770
|
}
|
|
2440
2771
|
if (!import_db_base.tools.isObject(obj)) {
|
|
2441
|
-
this.log.warn(`${this.namespace} setObject: Argument object is no object: ${obj}`);
|
|
2772
|
+
this.log.warn(`${this.namespace} setObject: Argument object is no object: ${JSON.stringify(obj)}`);
|
|
2442
2773
|
throw new Error("obj is no object");
|
|
2443
2774
|
}
|
|
2444
2775
|
if (!this.client) {
|
|
@@ -2530,15 +2861,15 @@ class ObjectsInRedisClient {
|
|
|
2530
2861
|
if (obj.type !== "state") {
|
|
2531
2862
|
delete obj.acl.state;
|
|
2532
2863
|
}
|
|
2533
|
-
if (
|
|
2534
|
-
obj.acl.owner =
|
|
2864
|
+
if (userContext2.user && userContext2.user !== CONSTS.SYSTEM_ADMIN_USER) {
|
|
2865
|
+
obj.acl.owner = userContext2.user;
|
|
2535
2866
|
}
|
|
2536
|
-
if (
|
|
2537
|
-
obj.acl.ownerGroup =
|
|
2867
|
+
if (userContext2.group && userContext2.group !== CONSTS.SYSTEM_ADMIN_GROUP) {
|
|
2868
|
+
obj.acl.ownerGroup = userContext2.group;
|
|
2538
2869
|
}
|
|
2539
2870
|
}
|
|
2540
|
-
if (this.defaultNewAcl && obj.acl && !obj.acl.ownerGroup &&
|
|
2541
|
-
obj.acl.ownerGroup =
|
|
2871
|
+
if (this.defaultNewAcl && obj.acl && !obj.acl.ownerGroup && userContext2.group) {
|
|
2872
|
+
obj.acl.ownerGroup = userContext2.group;
|
|
2542
2873
|
}
|
|
2543
2874
|
const message = JSON.stringify(obj);
|
|
2544
2875
|
const commands = [];
|
|
@@ -2569,32 +2900,29 @@ class ObjectsInRedisClient {
|
|
|
2569
2900
|
return { id };
|
|
2570
2901
|
}
|
|
2571
2902
|
/**
|
|
2572
|
-
* set anew or update object
|
|
2903
|
+
* set anew or update an object
|
|
2573
2904
|
*
|
|
2574
2905
|
* This function writes the object into DB
|
|
2575
2906
|
*
|
|
2576
2907
|
* @param id ID of the object
|
|
2577
|
-
* @param obj
|
|
2908
|
+
* @param obj The object to write
|
|
2578
2909
|
* @param options options for access control are optional
|
|
2579
2910
|
* @param callback return function
|
|
2580
2911
|
*/
|
|
2581
|
-
setObject(id, obj,
|
|
2582
|
-
if (typeof
|
|
2583
|
-
callback =
|
|
2584
|
-
|
|
2912
|
+
setObject(id, obj, options, callback) {
|
|
2913
|
+
if (typeof options === "function") {
|
|
2914
|
+
callback = options;
|
|
2915
|
+
options = {};
|
|
2585
2916
|
}
|
|
2586
2917
|
if (!callback) {
|
|
2587
|
-
return new Promise((resolve, reject) => this.setObject(id, obj,
|
|
2588
|
-
}
|
|
2589
|
-
if (options2?.acl) {
|
|
2590
|
-
options2.acl = null;
|
|
2918
|
+
return new Promise((resolve, reject) => this.setObject(id, obj, options, (err, res) => err ? reject(err) : resolve(res)));
|
|
2591
2919
|
}
|
|
2592
|
-
utils.checkObjectRights(this, null, null,
|
|
2920
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, async (err, userContext2) => {
|
|
2593
2921
|
if (err) {
|
|
2594
2922
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
2595
2923
|
}
|
|
2596
2924
|
try {
|
|
2597
|
-
const res = await this._setObject(id, obj,
|
|
2925
|
+
const res = await this._setObject(id, obj, userContext2);
|
|
2598
2926
|
return import_db_base.tools.maybeCallbackWithError(callback, null, res);
|
|
2599
2927
|
} catch (e) {
|
|
2600
2928
|
return import_db_base.tools.maybeCallbackWithError(callback, e);
|
|
@@ -2602,19 +2930,20 @@ class ObjectsInRedisClient {
|
|
|
2602
2930
|
});
|
|
2603
2931
|
}
|
|
2604
2932
|
/**
|
|
2933
|
+
* Promise-version of setObject
|
|
2605
2934
|
*
|
|
2606
|
-
* @param id
|
|
2607
|
-
* @param obj
|
|
2608
|
-
* @param options
|
|
2935
|
+
* @param id ID of the object
|
|
2936
|
+
* @param obj The object to write
|
|
2937
|
+
* @param options options for access control are optional
|
|
2609
2938
|
* @deprecated use `setObject` without callback instead
|
|
2610
2939
|
*/
|
|
2611
|
-
setObjectAsync(id, obj,
|
|
2940
|
+
setObjectAsync(id, obj, options) {
|
|
2612
2941
|
return new Promise((resolve, reject) => (
|
|
2613
2942
|
// @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
|
|
2614
|
-
this.setObject(id, obj,
|
|
2943
|
+
this.setObject(id, obj, options, (err, res) => err ? reject(err) : resolve(res))
|
|
2615
2944
|
));
|
|
2616
2945
|
}
|
|
2617
|
-
async _delObject(id,
|
|
2946
|
+
async _delObject(id, userContext2) {
|
|
2618
2947
|
if (!id || typeof id !== "string" || utils.REG_CHECK_ID.test(id)) {
|
|
2619
2948
|
throw new Error(`Invalid ID: ${id}`);
|
|
2620
2949
|
}
|
|
@@ -2638,7 +2967,7 @@ class ObjectsInRedisClient {
|
|
|
2638
2967
|
this.log.warn(`${this.namespace} Cannot parse ${id} - ${oldObjStr}: ${e.message}`);
|
|
2639
2968
|
oldObj = null;
|
|
2640
2969
|
}
|
|
2641
|
-
if (!utils.checkObject(oldObj,
|
|
2970
|
+
if (!utils.checkObject(oldObj, userContext2, CONSTS.ACCESS_WRITE)) {
|
|
2642
2971
|
throw new Error(ERRORS.ERROR_PERMISSION);
|
|
2643
2972
|
} else {
|
|
2644
2973
|
const commands = [];
|
|
@@ -2662,34 +2991,45 @@ class ObjectsInRedisClient {
|
|
|
2662
2991
|
await this.client.publish(this.objNamespace + id, "null");
|
|
2663
2992
|
}
|
|
2664
2993
|
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2994
|
+
/**
|
|
2995
|
+
* Delete an object
|
|
2996
|
+
*
|
|
2997
|
+
* @param id The id of the object to delete
|
|
2998
|
+
* @param options The current request options including the user, or the callback
|
|
2999
|
+
* @param callback Called once the object has been deleted
|
|
3000
|
+
*/
|
|
3001
|
+
delObject(id, options, callback) {
|
|
3002
|
+
if (typeof options === "function") {
|
|
3003
|
+
callback = options;
|
|
3004
|
+
options = null;
|
|
2669
3005
|
}
|
|
2670
3006
|
if (!callback) {
|
|
2671
|
-
return new Promise((resolve, reject) => this.delObject(id,
|
|
3007
|
+
return new Promise((resolve, reject) => this.delObject(id, options, (err) => err ? reject(err) : resolve()));
|
|
2672
3008
|
}
|
|
2673
|
-
|
|
2674
|
-
options2.acl = null;
|
|
2675
|
-
}
|
|
2676
|
-
utils.checkObjectRights(this, null, null, options2, CONSTS.ACCESS_DELETE, async (err, options3) => {
|
|
3009
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_DELETE, async (err, userContext2) => {
|
|
2677
3010
|
if (err) {
|
|
2678
3011
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
2679
3012
|
}
|
|
2680
3013
|
try {
|
|
2681
|
-
await this._delObject(id,
|
|
3014
|
+
await this._delObject(id, userContext2);
|
|
2682
3015
|
return import_db_base.tools.maybeCallback(callback);
|
|
2683
3016
|
} catch (e) {
|
|
2684
3017
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
2685
3018
|
}
|
|
2686
3019
|
});
|
|
2687
3020
|
}
|
|
2688
|
-
|
|
2689
|
-
|
|
3021
|
+
/**
|
|
3022
|
+
* Promise-version of delObject
|
|
3023
|
+
*
|
|
3024
|
+
* @param id The id of the object to delete
|
|
3025
|
+
* @param options The current request options including the user
|
|
3026
|
+
* @param options.user The user on whose behalf the operation is performed
|
|
3027
|
+
*/
|
|
3028
|
+
delObjectAsync(id, options) {
|
|
3029
|
+
return this.delObject(id, options);
|
|
2690
3030
|
}
|
|
2691
3031
|
/**
|
|
2692
|
-
* Function to
|
|
3032
|
+
* Function to check if comparisons will work according to the configured Locale
|
|
2693
3033
|
*/
|
|
2694
3034
|
async isSystemLocaleSupported() {
|
|
2695
3035
|
if (!this.client) {
|
|
@@ -2704,14 +3044,12 @@ class ObjectsInRedisClient {
|
|
|
2704
3044
|
}
|
|
2705
3045
|
}
|
|
2706
3046
|
// this function is very ineffective. Because reads all objects and then process them
|
|
2707
|
-
async _applyViewFunc(func, params,
|
|
3047
|
+
async _applyViewFunc(func, params, userContext) {
|
|
2708
3048
|
if (!this.client) {
|
|
2709
3049
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
2710
3050
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
};
|
|
2714
|
-
const filterEntries = (arr, duplicateFiltering) => {
|
|
3051
|
+
let rows = [];
|
|
3052
|
+
const filterEntries = /* @__PURE__ */ __name((arr, duplicateFiltering) => {
|
|
2715
3053
|
if (duplicateFiltering) {
|
|
2716
3054
|
const included = /* @__PURE__ */ new Map();
|
|
2717
3055
|
return arr.filter((obj) => {
|
|
@@ -2725,10 +3063,10 @@ class ObjectsInRedisClient {
|
|
|
2725
3063
|
return arr.filter((obj) => {
|
|
2726
3064
|
return obj.value !== null;
|
|
2727
3065
|
});
|
|
2728
|
-
};
|
|
2729
|
-
params
|
|
2730
|
-
params.startkey
|
|
2731
|
-
params.endkey
|
|
3066
|
+
}, "filterEntries");
|
|
3067
|
+
params ||= {};
|
|
3068
|
+
params.startkey ||= "";
|
|
3069
|
+
params.endkey ||= "\u9999";
|
|
2732
3070
|
const wildcardPos = params.endkey.indexOf("\u9999");
|
|
2733
3071
|
let wildCardLastPos = true;
|
|
2734
3072
|
if (wildcardPos !== -1 && wildcardPos !== params.endkey.length - 1) {
|
|
@@ -2747,16 +3085,7 @@ class ObjectsInRedisClient {
|
|
|
2747
3085
|
}
|
|
2748
3086
|
let objs2;
|
|
2749
3087
|
try {
|
|
2750
|
-
objs2 = await this.client.evalsha([
|
|
2751
|
-
this.scripts.filter,
|
|
2752
|
-
6,
|
|
2753
|
-
this.objNamespace,
|
|
2754
|
-
params.startkey,
|
|
2755
|
-
params.endkey,
|
|
2756
|
-
matches[1],
|
|
2757
|
-
cursor,
|
|
2758
|
-
`${this.setNamespace}object.type.${matches[1]}`
|
|
2759
|
-
]);
|
|
3088
|
+
objs2 = await this.client.evalsha(this.scripts.filter, 6, this.objNamespace, params.startkey, params.endkey, matches[1], cursor, `${this.setNamespace}object.type.${matches[1]}`);
|
|
2760
3089
|
} catch (e) {
|
|
2761
3090
|
this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
|
|
2762
3091
|
throw e;
|
|
@@ -2783,7 +3112,8 @@ class ObjectsInRedisClient {
|
|
|
2783
3112
|
}
|
|
2784
3113
|
if (matches[2] && matches[2].trim() === "doc._id") {
|
|
2785
3114
|
return { id: obj._id, value: obj };
|
|
2786
|
-
}
|
|
3115
|
+
}
|
|
3116
|
+
if (matches[2] && matches[2].trim() === "doc.common.name" && obj.common) {
|
|
2787
3117
|
if (typeof obj.common.name === "object") {
|
|
2788
3118
|
if (obj.common.name.en) {
|
|
2789
3119
|
return { id: obj.common.name.en, value: obj };
|
|
@@ -2796,25 +3126,26 @@ class ObjectsInRedisClient {
|
|
|
2796
3126
|
return { id: "parseError", value: null };
|
|
2797
3127
|
});
|
|
2798
3128
|
if (currRows.length) {
|
|
2799
|
-
|
|
3129
|
+
rows = [...rows, ...currRows];
|
|
2800
3130
|
}
|
|
2801
3131
|
} while (cursor !== "0");
|
|
2802
3132
|
if (func.reduce === "_stats") {
|
|
2803
3133
|
let max = null;
|
|
2804
|
-
for (let i = 0; i <
|
|
2805
|
-
|
|
2806
|
-
|
|
3134
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3135
|
+
const _value = rows[i].value;
|
|
3136
|
+
if (max === null || _value !== null && _value > max) {
|
|
3137
|
+
max = _value;
|
|
2807
3138
|
}
|
|
2808
3139
|
}
|
|
2809
3140
|
if (max !== null) {
|
|
2810
|
-
|
|
3141
|
+
rows = [{ id: "_stats", value: { max } }];
|
|
2811
3142
|
} else {
|
|
2812
|
-
|
|
3143
|
+
rows = [];
|
|
2813
3144
|
}
|
|
2814
3145
|
}
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
3146
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3147
|
+
}
|
|
3148
|
+
if (
|
|
2818
3149
|
// filter by script
|
|
2819
3150
|
wildCardLastPos && func?.map && this.scripts.script && func.map.includes("doc.common.engineType")
|
|
2820
3151
|
) {
|
|
@@ -2826,15 +3157,7 @@ class ObjectsInRedisClient {
|
|
|
2826
3157
|
}
|
|
2827
3158
|
let res;
|
|
2828
3159
|
try {
|
|
2829
|
-
res = await this.client.evalsha(
|
|
2830
|
-
this.scripts.script,
|
|
2831
|
-
5,
|
|
2832
|
-
this.objNamespace,
|
|
2833
|
-
params.startkey,
|
|
2834
|
-
params.endkey,
|
|
2835
|
-
cursor,
|
|
2836
|
-
`${this.setNamespace}object.type.script`
|
|
2837
|
-
]);
|
|
3160
|
+
res = await this.client.evalsha(this.scripts.script, 5, this.objNamespace, params.startkey, params.endkey, cursor, `${this.setNamespace}object.type.script`);
|
|
2838
3161
|
} catch (e) {
|
|
2839
3162
|
this.log.warn(`${this.namespace} Cannot get "scripts" view: ${e.message}`);
|
|
2840
3163
|
throw e;
|
|
@@ -2858,12 +3181,12 @@ class ObjectsInRedisClient {
|
|
|
2858
3181
|
}
|
|
2859
3182
|
});
|
|
2860
3183
|
if (currRows.length) {
|
|
2861
|
-
|
|
3184
|
+
rows = [...rows, ...currRows];
|
|
2862
3185
|
}
|
|
2863
3186
|
} while (cursor !== "0");
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
3187
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3188
|
+
}
|
|
3189
|
+
if (
|
|
2867
3190
|
// filter by hm-rega programs
|
|
2868
3191
|
wildCardLastPos && func?.map && this.scripts.programs && func.map.includes("doc.native.TypeName === 'PROGRAM'")
|
|
2869
3192
|
) {
|
|
@@ -2875,15 +3198,7 @@ class ObjectsInRedisClient {
|
|
|
2875
3198
|
}
|
|
2876
3199
|
let objs2;
|
|
2877
3200
|
try {
|
|
2878
|
-
objs2 = await this.client.evalsha(
|
|
2879
|
-
this.scripts.programs,
|
|
2880
|
-
5,
|
|
2881
|
-
`${this.objNamespace}hm-rega.`,
|
|
2882
|
-
params.startkey,
|
|
2883
|
-
params.endkey,
|
|
2884
|
-
cursor,
|
|
2885
|
-
`${this.setNamespace}object.type.channel`
|
|
2886
|
-
]);
|
|
3201
|
+
objs2 = await this.client.evalsha(this.scripts.programs, 5, `${this.objNamespace}hm-rega.`, params.startkey, params.endkey, cursor, `${this.setNamespace}object.type.channel`);
|
|
2887
3202
|
} catch (e) {
|
|
2888
3203
|
this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
|
|
2889
3204
|
throw e;
|
|
@@ -2905,12 +3220,12 @@ class ObjectsInRedisClient {
|
|
|
2905
3220
|
}
|
|
2906
3221
|
});
|
|
2907
3222
|
if (currRows.length) {
|
|
2908
|
-
|
|
3223
|
+
rows = [...rows, ...currRows];
|
|
2909
3224
|
}
|
|
2910
3225
|
} while (cursor !== "0");
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
3226
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3227
|
+
}
|
|
3228
|
+
if (
|
|
2914
3229
|
// filter by hm-rega variables
|
|
2915
3230
|
wildCardLastPos && func?.map && this.scripts.variables && func.map.includes("doc.native.TypeName === 'ALARMDP'")
|
|
2916
3231
|
) {
|
|
@@ -2922,15 +3237,7 @@ class ObjectsInRedisClient {
|
|
|
2922
3237
|
}
|
|
2923
3238
|
let objs2;
|
|
2924
3239
|
try {
|
|
2925
|
-
objs2 = await this.client.evalsha(
|
|
2926
|
-
this.scripts.variables,
|
|
2927
|
-
5,
|
|
2928
|
-
`${this.objNamespace}hm-rega.`,
|
|
2929
|
-
params.startkey,
|
|
2930
|
-
params.endkey,
|
|
2931
|
-
cursor,
|
|
2932
|
-
`${this.setNamespace}object.type.state`
|
|
2933
|
-
]);
|
|
3240
|
+
objs2 = await this.client.evalsha(this.scripts.variables, 5, `${this.objNamespace}hm-rega.`, params.startkey, params.endkey, cursor, `${this.setNamespace}object.type.state`);
|
|
2934
3241
|
} catch (e) {
|
|
2935
3242
|
this.log.warn(`${this.namespace} Cannot get view ${e.message}`);
|
|
2936
3243
|
throw e;
|
|
@@ -2952,12 +3259,12 @@ class ObjectsInRedisClient {
|
|
|
2952
3259
|
}
|
|
2953
3260
|
});
|
|
2954
3261
|
if (currRows.length) {
|
|
2955
|
-
|
|
3262
|
+
rows = [...rows, ...currRows];
|
|
2956
3263
|
}
|
|
2957
3264
|
} while (cursor !== "0");
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3265
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3266
|
+
}
|
|
3267
|
+
if (
|
|
2961
3268
|
// filter by custom, redis also returns if common.custom is not present
|
|
2962
3269
|
wildCardLastPos && func?.map && this.scripts.custom && func.map.includes("doc.common.custom")
|
|
2963
3270
|
) {
|
|
@@ -2969,15 +3276,7 @@ class ObjectsInRedisClient {
|
|
|
2969
3276
|
}
|
|
2970
3277
|
let objs2;
|
|
2971
3278
|
try {
|
|
2972
|
-
objs2 = await this.client.evalsha(
|
|
2973
|
-
this.scripts.custom,
|
|
2974
|
-
5,
|
|
2975
|
-
this.objNamespace,
|
|
2976
|
-
params.startkey,
|
|
2977
|
-
params.endkey,
|
|
2978
|
-
cursor,
|
|
2979
|
-
`${this.setNamespace}object.common.custom`
|
|
2980
|
-
]);
|
|
3279
|
+
objs2 = await this.client.evalsha(this.scripts.custom, 5, this.objNamespace, params.startkey, params.endkey, cursor, `${this.setNamespace}object.common.custom`);
|
|
2981
3280
|
} catch (e) {
|
|
2982
3281
|
this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
|
|
2983
3282
|
throw e;
|
|
@@ -3000,15 +3299,14 @@ class ObjectsInRedisClient {
|
|
|
3000
3299
|
}
|
|
3001
3300
|
if (obj?.common?.custom) {
|
|
3002
3301
|
if (useFullObject) {
|
|
3003
|
-
|
|
3302
|
+
rows.push({ id: obj._id, value: obj });
|
|
3004
3303
|
} else {
|
|
3005
|
-
|
|
3304
|
+
rows.push({ id: obj._id, value: obj.common.custom });
|
|
3006
3305
|
}
|
|
3007
3306
|
}
|
|
3008
3307
|
}
|
|
3009
3308
|
} while (cursor !== "0");
|
|
3010
|
-
|
|
3011
|
-
return result;
|
|
3309
|
+
return { rows: filterEntries(rows, filterRequired) };
|
|
3012
3310
|
}
|
|
3013
3311
|
if (!wildCardLastPos) {
|
|
3014
3312
|
this.log.debug(`${this.namespace} Search can't be optimized because wildcard not at the end, fallback to keys!: ${func.map}`);
|
|
@@ -3051,9 +3349,9 @@ class ObjectsInRedisClient {
|
|
|
3051
3349
|
} catch {
|
|
3052
3350
|
objs = [];
|
|
3053
3351
|
}
|
|
3054
|
-
const _emit_ = (id, obj) => {
|
|
3055
|
-
|
|
3056
|
-
};
|
|
3352
|
+
const _emit_ = /* @__PURE__ */ __name((id, obj) => {
|
|
3353
|
+
rows.push({ id, value: obj });
|
|
3354
|
+
}, "_emit_");
|
|
3057
3355
|
const f = eval(`(${func.map.replace(/^function\(([a-z0-9A-Z_]+)\)/g, "function($1, emit)")})`);
|
|
3058
3356
|
for (let i = 0; i < keys.length; i++) {
|
|
3059
3357
|
const strObj = objs[i];
|
|
@@ -3064,7 +3362,7 @@ class ObjectsInRedisClient {
|
|
|
3064
3362
|
this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${objs[i]}`);
|
|
3065
3363
|
continue;
|
|
3066
3364
|
}
|
|
3067
|
-
if (!utils.checkObject(obj,
|
|
3365
|
+
if (!utils.checkObject(obj, userContext, CONSTS.ACCESS_READ)) {
|
|
3068
3366
|
continue;
|
|
3069
3367
|
}
|
|
3070
3368
|
if (obj) {
|
|
@@ -3077,20 +3375,21 @@ class ObjectsInRedisClient {
|
|
|
3077
3375
|
}
|
|
3078
3376
|
if (func.reduce === "_stats") {
|
|
3079
3377
|
let max = null;
|
|
3080
|
-
for (let i = 0; i <
|
|
3081
|
-
|
|
3082
|
-
|
|
3378
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3379
|
+
const _value = rows[i].value;
|
|
3380
|
+
if (max === null || _value !== null && _value > max) {
|
|
3381
|
+
max = rows[i].value;
|
|
3083
3382
|
}
|
|
3084
3383
|
}
|
|
3085
3384
|
if (max !== null) {
|
|
3086
|
-
|
|
3385
|
+
rows = [{ id: "_stats", value: { max } }];
|
|
3087
3386
|
} else {
|
|
3088
|
-
|
|
3387
|
+
rows = [];
|
|
3089
3388
|
}
|
|
3090
3389
|
}
|
|
3091
|
-
return
|
|
3390
|
+
return { rows };
|
|
3092
3391
|
}
|
|
3093
|
-
async _getObjectView(design, search, params2,
|
|
3392
|
+
async _getObjectView(design, search, params2, userContext2) {
|
|
3094
3393
|
if (!this.client) {
|
|
3095
3394
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
3096
3395
|
}
|
|
@@ -3109,7 +3408,7 @@ class ObjectsInRedisClient {
|
|
|
3109
3408
|
throw new Error(`Cannot parse JSON: "_design/${design}" / "${obj}"`);
|
|
3110
3409
|
}
|
|
3111
3410
|
if (obj.views?.[search]) {
|
|
3112
|
-
return this._applyViewFunc(obj.views[search], params2,
|
|
3411
|
+
return this._applyViewFunc(obj.views[search], params2, userContext2);
|
|
3113
3412
|
}
|
|
3114
3413
|
this.log.error(`${this.namespace} Cannot find search "${search}" in "${design}"`);
|
|
3115
3414
|
throw new Error(`Cannot find search "${search}" in "${design}"`);
|
|
@@ -3118,24 +3417,30 @@ class ObjectsInRedisClient {
|
|
|
3118
3417
|
throw new Error(`Cannot find view "${design}"`);
|
|
3119
3418
|
}
|
|
3120
3419
|
}
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3420
|
+
/**
|
|
3421
|
+
* Run a predefined object view (design document) and return the matching rows
|
|
3422
|
+
*
|
|
3423
|
+
* @param design The design document name
|
|
3424
|
+
* @param search The view name within the design document
|
|
3425
|
+
* @param params Query parameters such as startkey and endkey
|
|
3426
|
+
* @param options The current request options including the user, or the callback
|
|
3427
|
+
* @param callback Called with the matching rows
|
|
3428
|
+
*/
|
|
3429
|
+
getObjectView(design, search, params2, options, callback) {
|
|
3430
|
+
if (typeof options === "function") {
|
|
3431
|
+
callback = options;
|
|
3432
|
+
options = null;
|
|
3125
3433
|
}
|
|
3126
3434
|
if (!callback) {
|
|
3127
|
-
return new Promise((resolve, reject) => this.getObjectView(design, search, params2,
|
|
3128
|
-
}
|
|
3129
|
-
if (options2?.acl) {
|
|
3130
|
-
options2.acl = null;
|
|
3435
|
+
return new Promise((resolve, reject) => this.getObjectView(design, search, params2, options, (err, obj) => err ? reject(err) : resolve(obj)));
|
|
3131
3436
|
}
|
|
3132
3437
|
if (typeof callback === "function") {
|
|
3133
|
-
utils.checkObjectRights(this, null, null,
|
|
3438
|
+
utils.checkObjectRights(this, null, null, options, "list", async (err, userContext2) => {
|
|
3134
3439
|
if (err) {
|
|
3135
3440
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
3136
3441
|
}
|
|
3137
3442
|
try {
|
|
3138
|
-
const res = await this._getObjectView(design, search, params2,
|
|
3443
|
+
const res = await this._getObjectView(design, search, params2, userContext2);
|
|
3139
3444
|
return import_db_base.tools.maybeCallbackWithError(callback, null, res);
|
|
3140
3445
|
} catch (e) {
|
|
3141
3446
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
@@ -3143,16 +3448,25 @@ class ObjectsInRedisClient {
|
|
|
3143
3448
|
});
|
|
3144
3449
|
}
|
|
3145
3450
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3451
|
+
/**
|
|
3452
|
+
* Promise-version of getObjectView
|
|
3453
|
+
*
|
|
3454
|
+
* @param design The design document name
|
|
3455
|
+
* @param search The view name within the design document
|
|
3456
|
+
* @param params Query parameters such as startkey and endkey
|
|
3457
|
+
* @param options The current request options including the user
|
|
3458
|
+
* @param options.user The user on whose behalf the view is queried
|
|
3459
|
+
*/
|
|
3460
|
+
getObjectViewAsync(design, search, params2, options) {
|
|
3461
|
+
return this.getObjectView(design, search, params2, options);
|
|
3148
3462
|
}
|
|
3149
|
-
async _getObjectList(params2,
|
|
3463
|
+
async _getObjectList(params2, userContext2) {
|
|
3150
3464
|
if (!this.client) {
|
|
3151
3465
|
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
3152
3466
|
}
|
|
3153
|
-
params2
|
|
3154
|
-
params2.startkey
|
|
3155
|
-
params2.endkey
|
|
3467
|
+
params2 ||= {};
|
|
3468
|
+
params2.startkey ||= "";
|
|
3469
|
+
params2.endkey ||= "\u9999";
|
|
3156
3470
|
const pattern = params2.endkey.substring(0, params2.startkey.length) === params2.startkey ? `${this.objNamespace + params2.startkey}*` : `${this.objNamespace}*`;
|
|
3157
3471
|
const keys2 = await this._getKeysViaScan(pattern);
|
|
3158
3472
|
if (!this.client) {
|
|
@@ -3181,7 +3495,7 @@ class ObjectsInRedisClient {
|
|
|
3181
3495
|
objs2 = await this.client.mget(_keys);
|
|
3182
3496
|
} catch {
|
|
3183
3497
|
}
|
|
3184
|
-
const
|
|
3498
|
+
const result = {
|
|
3185
3499
|
rows: []
|
|
3186
3500
|
};
|
|
3187
3501
|
if (objs2) {
|
|
@@ -3194,32 +3508,36 @@ class ObjectsInRedisClient {
|
|
|
3194
3508
|
this.log.error(`${this.namespace} Cannot parse JSON ${_keys[i]}: ${objs2[i]}`);
|
|
3195
3509
|
continue;
|
|
3196
3510
|
}
|
|
3197
|
-
if (!obj || !utils.checkObject(obj,
|
|
3511
|
+
if (!obj || !utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ)) {
|
|
3198
3512
|
continue;
|
|
3199
3513
|
}
|
|
3200
|
-
|
|
3514
|
+
result.rows.push({ id: obj._id, value: obj, doc: obj });
|
|
3201
3515
|
}
|
|
3202
3516
|
}
|
|
3203
|
-
return
|
|
3517
|
+
return result;
|
|
3204
3518
|
}
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3519
|
+
/**
|
|
3520
|
+
* Get the list of objects matching the given parameters
|
|
3521
|
+
*
|
|
3522
|
+
* @param params Query parameters such as startkey and endkey
|
|
3523
|
+
* @param options The current request options including the user, or the callback
|
|
3524
|
+
* @param callback Called with the matching objects
|
|
3525
|
+
*/
|
|
3526
|
+
getObjectList(params2, options, callback) {
|
|
3527
|
+
if (typeof options === "function") {
|
|
3528
|
+
callback = options;
|
|
3529
|
+
options = null;
|
|
3209
3530
|
}
|
|
3210
3531
|
if (!callback) {
|
|
3211
|
-
return new Promise((resolve, reject) => this.getObjectList(params2,
|
|
3212
|
-
}
|
|
3213
|
-
if (options2?.acl) {
|
|
3214
|
-
options2.acl = null;
|
|
3532
|
+
return new Promise((resolve, reject) => this.getObjectList(params2, options, (err, obj) => err ? reject(err) : resolve(obj)));
|
|
3215
3533
|
}
|
|
3216
3534
|
if (typeof callback === "function") {
|
|
3217
|
-
utils.checkObjectRights(this, null, null,
|
|
3535
|
+
utils.checkObjectRights(this, null, null, options, "list", async (err, userContext2) => {
|
|
3218
3536
|
if (err) {
|
|
3219
3537
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
3220
3538
|
}
|
|
3221
3539
|
try {
|
|
3222
|
-
const res = await this._getObjectList(params2,
|
|
3540
|
+
const res = await this._getObjectList(params2, userContext2);
|
|
3223
3541
|
return import_db_base.tools.maybeCallbackWithError(callback, null, res);
|
|
3224
3542
|
} catch (e) {
|
|
3225
3543
|
return import_db_base.tools.maybeCallbackWithError(callback, e);
|
|
@@ -3227,156 +3545,147 @@ class ObjectsInRedisClient {
|
|
|
3227
3545
|
});
|
|
3228
3546
|
}
|
|
3229
3547
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3548
|
+
/**
|
|
3549
|
+
* Promise-version of getObjectList
|
|
3550
|
+
*
|
|
3551
|
+
* @param params Query parameters such as startkey and endkey
|
|
3552
|
+
* @param options The current request options including the user
|
|
3553
|
+
*/
|
|
3554
|
+
getObjectListAsync(params2, options) {
|
|
3555
|
+
return this.getObjectList(params2, options);
|
|
3232
3556
|
}
|
|
3233
|
-
// could be optimized, to read object only once. Now it will read 3 times
|
|
3234
|
-
async _extendObject(id, obj,
|
|
3557
|
+
// could be optimized, to read an object only once. Now it will read 3 times
|
|
3558
|
+
async _extendObject(id, obj, options, userContext2) {
|
|
3235
3559
|
if (!id || typeof id !== "string" || utils.REG_CHECK_ID.test(id)) {
|
|
3236
|
-
|
|
3560
|
+
throw new Error(`Invalid ID: ${id}`);
|
|
3237
3561
|
}
|
|
3238
3562
|
if (!this.client) {
|
|
3239
|
-
|
|
3563
|
+
throw new Error(ERRORS.ERROR_DB_CLOSED);
|
|
3240
3564
|
}
|
|
3565
|
+
const oldObjStr = await this.client.get(this.objNamespace + id);
|
|
3241
3566
|
let oldObj;
|
|
3242
3567
|
try {
|
|
3243
|
-
oldObj =
|
|
3244
|
-
} catch (e) {
|
|
3245
|
-
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
3246
|
-
}
|
|
3247
|
-
try {
|
|
3248
|
-
oldObj = oldObj ? JSON.parse(oldObj) : null;
|
|
3568
|
+
oldObj = oldObjStr ? JSON.parse(oldObjStr) : null;
|
|
3249
3569
|
} catch {
|
|
3250
|
-
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${
|
|
3251
|
-
|
|
3570
|
+
this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${oldObjStr}`);
|
|
3571
|
+
throw new Error(`Cannot parse JSON ${id}: ${oldObjStr}`);
|
|
3252
3572
|
}
|
|
3253
|
-
if (!utils.checkObject(oldObj,
|
|
3254
|
-
|
|
3573
|
+
if (!utils.checkObject(oldObj, userContext2, CONSTS.ACCESS_WRITE)) {
|
|
3574
|
+
throw new Error(ERRORS.ERROR_PERMISSION);
|
|
3255
3575
|
}
|
|
3256
3576
|
let _oldObj;
|
|
3257
3577
|
if (oldObj?.nonEdit) {
|
|
3258
3578
|
_oldObj = (0, import_deep_clone.default)(oldObj);
|
|
3259
3579
|
}
|
|
3260
3580
|
const oldObjHasCustom = !!oldObj?.common?.custom;
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
if (
|
|
3264
|
-
delete
|
|
3265
|
-
}
|
|
3266
|
-
const oldType =
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
if (this.defaultNewAcl && !
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
}
|
|
3275
|
-
if (
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
}
|
|
3581
|
+
const objClone = (0, import_deep_clone.default)(obj);
|
|
3582
|
+
let oldObjNotNull = oldObj || {};
|
|
3583
|
+
if (oldObjNotNull.common?.custom !== void 0 && oldObjNotNull.common.custom !== null && !import_db_base.tools.isObject(oldObjNotNull.common.custom)) {
|
|
3584
|
+
delete oldObjNotNull.common.custom;
|
|
3585
|
+
}
|
|
3586
|
+
const oldType = oldObjNotNull.type;
|
|
3587
|
+
oldObjNotNull = (0, import_node.default)(true, oldObjNotNull, objClone);
|
|
3588
|
+
oldObjNotNull._id = id;
|
|
3589
|
+
if (this.defaultNewAcl && !oldObjNotNull.acl) {
|
|
3590
|
+
oldObjNotNull.acl = {
|
|
3591
|
+
owner: this.defaultNewAcl.owner,
|
|
3592
|
+
ownerGroup: this.defaultNewAcl.ownerGroup,
|
|
3593
|
+
object: this.defaultNewAcl.object
|
|
3594
|
+
};
|
|
3595
|
+
if (oldObjNotNull.type === "state") {
|
|
3596
|
+
oldObjNotNull.acl.state = this.defaultNewAcl.state;
|
|
3597
|
+
}
|
|
3598
|
+
if (options.owner) {
|
|
3599
|
+
oldObjNotNull.acl.owner = options.owner;
|
|
3600
|
+
if (!options.ownerGroup) {
|
|
3601
|
+
const [, groups] = await utils.getUserGroupAsync(this, options.owner);
|
|
3602
|
+
if (!groups?.[0]) {
|
|
3603
|
+
options.ownerGroup = this.defaultNewAcl.ownerGroup || CONSTS.SYSTEM_ADMIN_GROUP;
|
|
3604
|
+
} else {
|
|
3605
|
+
options.ownerGroup = groups[0];
|
|
3606
|
+
}
|
|
3287
3607
|
}
|
|
3288
3608
|
}
|
|
3289
3609
|
}
|
|
3290
|
-
if (this.defaultNewAcl &&
|
|
3291
|
-
|
|
3610
|
+
if (this.defaultNewAcl && options.ownerGroup && oldObjNotNull.acl && !oldObjNotNull.acl.ownerGroup) {
|
|
3611
|
+
oldObjNotNull.acl.ownerGroup = options.ownerGroup;
|
|
3292
3612
|
}
|
|
3293
3613
|
if (obj.common && "alias" in obj.common && obj.common.alias.id) {
|
|
3294
3614
|
if (typeof obj.common.alias.id === "object") {
|
|
3295
3615
|
if (typeof obj.common.alias.id.write !== "string" || typeof obj.common.alias.id.read !== "string") {
|
|
3296
|
-
|
|
3616
|
+
throw new Error("Invalid alias ID");
|
|
3297
3617
|
}
|
|
3298
3618
|
if (obj.common.alias.id.write.startsWith("alias.") || obj.common.alias.id.read.startsWith("alias.")) {
|
|
3299
|
-
|
|
3619
|
+
throw new Error("Cannot make alias on alias");
|
|
3300
3620
|
}
|
|
3301
3621
|
} else {
|
|
3302
3622
|
if (typeof obj.common.alias.id !== "string") {
|
|
3303
|
-
|
|
3623
|
+
throw new Error("Invalid alias ID");
|
|
3304
3624
|
}
|
|
3305
3625
|
if (obj.common.alias.id.startsWith("alias.")) {
|
|
3306
|
-
|
|
3626
|
+
throw new Error("Cannot make alias on alias");
|
|
3307
3627
|
}
|
|
3308
3628
|
}
|
|
3309
3629
|
}
|
|
3310
|
-
if (_oldObj && !import_db_base.tools.checkNonEditable(_oldObj,
|
|
3311
|
-
|
|
3630
|
+
if (_oldObj && !import_db_base.tools.checkNonEditable(_oldObj, oldObjNotNull)) {
|
|
3631
|
+
throw new Error("Invalid password for update of vendor information");
|
|
3312
3632
|
}
|
|
3313
|
-
const message = JSON.stringify(
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
if (
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
}
|
|
3322
|
-
commands.push(["srem", `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
if (oldObj.common?.custom && !oldObjHasCustom) {
|
|
3326
|
-
commands.push(["sadd", `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3327
|
-
} else if (oldObjHasCustom && (!oldObj.common || !oldObj.common.custom)) {
|
|
3328
|
-
commands.push(["srem", `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3329
|
-
}
|
|
3330
|
-
if (!commands.length) {
|
|
3331
|
-
await this.client.set(this.objNamespace + id, message);
|
|
3332
|
-
} else {
|
|
3333
|
-
commands.push(["set", this.objNamespace + id, message]);
|
|
3334
|
-
await this.client.multi(commands).exec();
|
|
3335
|
-
}
|
|
3336
|
-
if (this.existingMetaObjects[id] === false && oldObj && oldObj.type === "meta") {
|
|
3337
|
-
this.existingMetaObjects[id] = true;
|
|
3633
|
+
const message = JSON.stringify(oldObjNotNull);
|
|
3634
|
+
const commands = [];
|
|
3635
|
+
if (this.useSets) {
|
|
3636
|
+
if (oldObjNotNull.type && !oldType) {
|
|
3637
|
+
commands.push(["sadd", `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3638
|
+
} else if (oldObjNotNull.type && oldType && oldObjNotNull.type !== oldType) {
|
|
3639
|
+
commands.push(["sadd", `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id], ["srem", `${this.setNamespace}object.type.${oldObjNotNull.type}`, this.objNamespace + id]);
|
|
3640
|
+
} else if (oldType && !oldObjNotNull.type) {
|
|
3641
|
+
commands.push(["srem", `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
|
|
3338
3642
|
}
|
|
3339
|
-
await this.client.publish(this.objNamespace + id, message);
|
|
3340
|
-
return import_db_base.tools.maybeCallbackWithError(callback, null, { id, value: oldObj }, id);
|
|
3341
|
-
} catch (e) {
|
|
3342
|
-
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
3343
3643
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
if (
|
|
3347
|
-
|
|
3348
|
-
options2 = null;
|
|
3644
|
+
if (oldObjNotNull.common?.custom && !oldObjHasCustom) {
|
|
3645
|
+
commands.push(["sadd", `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3646
|
+
} else if (oldObjHasCustom && (!oldObjNotNull.common || !oldObjNotNull.common.custom)) {
|
|
3647
|
+
commands.push(["srem", `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
|
|
3349
3648
|
}
|
|
3350
|
-
if (!
|
|
3351
|
-
|
|
3649
|
+
if (!commands.length) {
|
|
3650
|
+
await this.client.set(this.objNamespace + id, message);
|
|
3651
|
+
} else {
|
|
3652
|
+
commands.push(["set", this.objNamespace + id, message]);
|
|
3653
|
+
await this.client.multi(commands).exec();
|
|
3352
3654
|
}
|
|
3353
|
-
if (
|
|
3354
|
-
|
|
3655
|
+
if (this.existingMetaObjects[id] === false && oldObjNotNull?.type === "meta") {
|
|
3656
|
+
this.existingMetaObjects[id] = true;
|
|
3355
3657
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
3359
|
-
}
|
|
3360
|
-
return this._extendObject(id, obj, options3, callback);
|
|
3361
|
-
});
|
|
3658
|
+
await this.client.publish(this.objNamespace + id, message);
|
|
3659
|
+
return { id, value: oldObjNotNull };
|
|
3362
3660
|
}
|
|
3363
|
-
|
|
3364
|
-
|
|
3661
|
+
/**
|
|
3662
|
+
* Extend an existing object with the given partial object, creating it if it does not exist
|
|
3663
|
+
*
|
|
3664
|
+
* @param id The id of the object to extend
|
|
3665
|
+
* @param obj The partial object to merge into the existing object
|
|
3666
|
+
* @param options The current request options including the user, or the callback
|
|
3667
|
+
*/
|
|
3668
|
+
async extendObject(id, obj, options) {
|
|
3669
|
+
const userContext2 = await (0, import_objectsUtils.checkObjectRightsAsync)(this, null, null, options, CONSTS.ACCESS_WRITE);
|
|
3670
|
+
return this._extendObject(id, obj, options || {}, userContext2);
|
|
3365
3671
|
}
|
|
3366
3672
|
/**
|
|
3367
3673
|
* Returns the object id if found
|
|
3368
3674
|
*
|
|
3369
|
-
* @param idOrName
|
|
3370
|
-
* @param type
|
|
3371
|
-
* @param options
|
|
3372
|
-
* @param
|
|
3675
|
+
* @param idOrName The id or name to search for
|
|
3676
|
+
* @param type The expected common type, or null for any
|
|
3677
|
+
* @param options The current request options (may include a language)
|
|
3678
|
+
* @param options.language The language to use when resolving names
|
|
3679
|
+
* @param options.user The user on whose behalf the lookup is performed
|
|
3680
|
+
* @param userContext The resolved user context to check the rights against
|
|
3681
|
+
* @param callback Called with the found id and the original id/name
|
|
3373
3682
|
*/
|
|
3374
|
-
_findObject(idOrName, type,
|
|
3375
|
-
this._getObject(idOrName,
|
|
3376
|
-
if (obj && utils.checkObject(obj,
|
|
3683
|
+
_findObject(idOrName, type, options, userContext2, callback) {
|
|
3684
|
+
this._getObject(idOrName, userContext2, (err, obj) => {
|
|
3685
|
+
if (obj && utils.checkObject(obj, userContext2, CONSTS.ACCESS_READ) && (!type || obj.common?.type === type)) {
|
|
3377
3686
|
return import_db_base.tools.maybeCallbackWithError(callback, null, idOrName, obj.common.name);
|
|
3378
3687
|
}
|
|
3379
|
-
this._getKeys("*",
|
|
3688
|
+
return this._getKeys("*", userContext2, true, async (err2, keys2) => {
|
|
3380
3689
|
if (!this.client) {
|
|
3381
3690
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_DB_CLOSED);
|
|
3382
3691
|
}
|
|
@@ -3402,7 +3711,7 @@ class ObjectsInRedisClient {
|
|
|
3402
3711
|
if (obj2?.common && (!type || "type" in obj2.common && obj2.common.type === type)) {
|
|
3403
3712
|
let name = obj2?.common?.name;
|
|
3404
3713
|
if (name && typeof name === "object") {
|
|
3405
|
-
name = name[
|
|
3714
|
+
name = name[options.language || "en"] || name.en;
|
|
3406
3715
|
}
|
|
3407
3716
|
if (name === idOrName) {
|
|
3408
3717
|
return import_db_base.tools.maybeCallbackWithError(callback, null, obj2._id, obj2.common.name);
|
|
@@ -3410,35 +3719,45 @@ class ObjectsInRedisClient {
|
|
|
3410
3719
|
}
|
|
3411
3720
|
}
|
|
3412
3721
|
return import_db_base.tools.maybeCallbackWithError(callback, null, void 0, idOrName);
|
|
3413
|
-
}
|
|
3414
|
-
});
|
|
3722
|
+
});
|
|
3723
|
+
}).catch((e) => this.log.error(`${this.namespace} Cannot find object: ${e.message}`));
|
|
3415
3724
|
}
|
|
3416
|
-
|
|
3725
|
+
/**
|
|
3726
|
+
* Find an object by its id or name
|
|
3727
|
+
*
|
|
3728
|
+
* @param idOrName The id or name to search for
|
|
3729
|
+
* @param type The expected common type, or null for any
|
|
3730
|
+
* @param options The current request options (may include a language), or the callback
|
|
3731
|
+
* @param callback Called with the found id and the original id/name
|
|
3732
|
+
*/
|
|
3733
|
+
findObject(idOrName, type, options, callback) {
|
|
3417
3734
|
if (typeof type === "function") {
|
|
3418
3735
|
callback = type;
|
|
3419
|
-
|
|
3736
|
+
options = null;
|
|
3420
3737
|
type = null;
|
|
3421
3738
|
}
|
|
3422
|
-
if (typeof
|
|
3423
|
-
callback =
|
|
3424
|
-
|
|
3739
|
+
if (typeof options === "function") {
|
|
3740
|
+
callback = options;
|
|
3741
|
+
options = null;
|
|
3425
3742
|
}
|
|
3426
3743
|
if (!callback) {
|
|
3427
|
-
return new Promise((resolve, reject) => this.findObject(idOrName, type,
|
|
3428
|
-
}
|
|
3429
|
-
if (options2?.acl) {
|
|
3430
|
-
options2.acl = null;
|
|
3744
|
+
return new Promise((resolve, reject) => this.findObject(idOrName, type, options, (err, id, _idOrName) => err ? reject(err) : resolve(id)));
|
|
3431
3745
|
}
|
|
3432
3746
|
if (typeof callback === "function") {
|
|
3433
|
-
utils.checkObjectRights(this, null, null,
|
|
3747
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_LIST, (err, userContext2) => {
|
|
3434
3748
|
if (err) {
|
|
3435
3749
|
return import_db_base.tools.maybeCallbackWithError(callback, err);
|
|
3436
3750
|
}
|
|
3437
|
-
return this._findObject(idOrName, type,
|
|
3751
|
+
return this._findObject(idOrName, type, options, userContext2, callback);
|
|
3438
3752
|
});
|
|
3439
3753
|
}
|
|
3440
3754
|
}
|
|
3441
3755
|
// can be called only from js-controller
|
|
3756
|
+
/**
|
|
3757
|
+
* Add object property paths that should be preserved when an object is overwritten (controller only)
|
|
3758
|
+
*
|
|
3759
|
+
* @param settings One or more property paths to preserve
|
|
3760
|
+
*/
|
|
3442
3761
|
addPreserveSettings(settings) {
|
|
3443
3762
|
if (!Array.isArray(settings)) {
|
|
3444
3763
|
settings = [settings];
|
|
@@ -3475,26 +3794,40 @@ class ObjectsInRedisClient {
|
|
|
3475
3794
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, e);
|
|
3476
3795
|
}
|
|
3477
3796
|
}
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3797
|
+
/**
|
|
3798
|
+
* Delete the whole objects database (requires admin rights)
|
|
3799
|
+
*
|
|
3800
|
+
* @param options The current request options including the user, or the callback
|
|
3801
|
+
* @param callback Called once the database has been destroyed
|
|
3802
|
+
*/
|
|
3803
|
+
destroyDB(options, callback) {
|
|
3804
|
+
if (typeof options === "function") {
|
|
3805
|
+
callback = options;
|
|
3806
|
+
options = null;
|
|
3482
3807
|
}
|
|
3483
|
-
|
|
3484
|
-
utils.checkObjectRights(this, null, null,
|
|
3808
|
+
options = options || {};
|
|
3809
|
+
utils.checkObjectRights(this, null, null, options, CONSTS.ACCESS_WRITE, (err, userContext2) => {
|
|
3485
3810
|
if (err) {
|
|
3486
3811
|
return import_db_base.tools.maybeCallbackWithRedisError(callback, err);
|
|
3487
3812
|
}
|
|
3488
|
-
if (!
|
|
3813
|
+
if (!userContext2.acl.file.write || userContext2.user !== CONSTS.SYSTEM_ADMIN_USER) {
|
|
3489
3814
|
return import_db_base.tools.maybeCallbackWithError(callback, ERRORS.ERROR_PERMISSION);
|
|
3490
3815
|
}
|
|
3491
3816
|
return this._destroyDB(callback);
|
|
3492
3817
|
});
|
|
3493
3818
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3819
|
+
/**
|
|
3820
|
+
* Promise-version of destroyDB
|
|
3821
|
+
*
|
|
3822
|
+
* @param options The current request options including the user
|
|
3823
|
+
* @param options.user The user on whose behalf the database is destroyed
|
|
3824
|
+
*/
|
|
3825
|
+
destroyDBAsync(options) {
|
|
3826
|
+
return new Promise((resolve, reject) => this.destroyDB(options, (err) => err ? reject(err) : resolve()));
|
|
3496
3827
|
}
|
|
3497
|
-
|
|
3828
|
+
/**
|
|
3829
|
+
* Destructor of the class. Called when shutting down to close the redis connections.
|
|
3830
|
+
*/
|
|
3498
3831
|
async destroy() {
|
|
3499
3832
|
this.stop = true;
|
|
3500
3833
|
if (this.client) {
|
|
@@ -3522,6 +3855,9 @@ class ObjectsInRedisClient {
|
|
|
3522
3855
|
}
|
|
3523
3856
|
}
|
|
3524
3857
|
}
|
|
3858
|
+
/**
|
|
3859
|
+
* Load and register the Lua scripts used for atomic operations on the redis server
|
|
3860
|
+
*/
|
|
3525
3861
|
async loadLuaScripts() {
|
|
3526
3862
|
let luaDirName;
|
|
3527
3863
|
if (this.noLegacyMultihost && this.useSets) {
|
|
@@ -3540,15 +3876,13 @@ class ObjectsInRedisClient {
|
|
|
3540
3876
|
return { name: name.replace(/\.lua$/, ""), text: script, hash };
|
|
3541
3877
|
});
|
|
3542
3878
|
const hashes = scripts.map((e) => e.hash);
|
|
3543
|
-
hashes.unshift("EXISTS");
|
|
3544
3879
|
if (!this.client) {
|
|
3545
3880
|
throw new Error(import_db_base.tools.ERRORS.ERROR_DB_CLOSED);
|
|
3546
3881
|
}
|
|
3547
|
-
let arr;
|
|
3548
3882
|
try {
|
|
3549
|
-
arr = await this.client.script(hashes);
|
|
3883
|
+
const arr = await this.client.script("EXISTS", ...hashes);
|
|
3550
3884
|
if (arr) {
|
|
3551
|
-
scripts.forEach((
|
|
3885
|
+
scripts.forEach((_e, i) => scripts[i].loaded = !!arr[i]);
|
|
3552
3886
|
}
|
|
3553
3887
|
} catch {
|
|
3554
3888
|
}
|
|
@@ -3559,7 +3893,7 @@ class ObjectsInRedisClient {
|
|
|
3559
3893
|
if (!script.loaded) {
|
|
3560
3894
|
let hash;
|
|
3561
3895
|
try {
|
|
3562
|
-
hash = await this.client.script(
|
|
3896
|
+
hash = await this.client.script("LOAD", script.text);
|
|
3563
3897
|
script.loaded = true;
|
|
3564
3898
|
} catch (e) {
|
|
3565
3899
|
script.loaded = false;
|
|
@@ -3568,16 +3902,16 @@ class ObjectsInRedisClient {
|
|
|
3568
3902
|
throw new Error(`Cannot load "${script.name}" into objects database: ${e.message}`);
|
|
3569
3903
|
}
|
|
3570
3904
|
}
|
|
3571
|
-
script.hash = hash;
|
|
3905
|
+
script.hash = hash || "";
|
|
3572
3906
|
}
|
|
3573
3907
|
}
|
|
3574
3908
|
this.scripts = {};
|
|
3575
3909
|
scripts.forEach((e) => this.scripts[e.name] = e.hash);
|
|
3576
3910
|
}
|
|
3577
3911
|
/**
|
|
3578
|
-
* Get all keys matching a pattern using redis SCAN command
|
|
3912
|
+
* Get all keys matching a pattern using redis SCAN command; duplicates are filtered out
|
|
3579
3913
|
*
|
|
3580
|
-
* @param pattern - pattern to match, e.
|
|
3914
|
+
* @param pattern - pattern to match, e.g. io.hm-rpc.0*
|
|
3581
3915
|
* @param count - count argument used by redis SCAN, default is 250
|
|
3582
3916
|
*/
|
|
3583
3917
|
_getKeysViaScan(pattern, count = 250) {
|
|
@@ -3597,6 +3931,7 @@ class ObjectsInRedisClient {
|
|
|
3597
3931
|
stream.on("end", () => {
|
|
3598
3932
|
resolve(Array.from(new Set(uniqueKeys)));
|
|
3599
3933
|
});
|
|
3934
|
+
stream.on("error", reject);
|
|
3600
3935
|
});
|
|
3601
3936
|
}
|
|
3602
3937
|
/**
|
|
@@ -3667,17 +4002,11 @@ class ObjectsInRedisClient {
|
|
|
3667
4002
|
if (!this.noLegacyMultihost) {
|
|
3668
4003
|
return Promise.resolve(0);
|
|
3669
4004
|
}
|
|
3670
|
-
return this.client.evalsha(
|
|
3671
|
-
this.scripts.redlock_extend,
|
|
3672
|
-
3,
|
|
3673
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
3674
|
-
this.hostname,
|
|
3675
|
-
ms
|
|
3676
|
-
]);
|
|
4005
|
+
return this.client.evalsha(this.scripts.redlock_extend, 3, `${this.metaNamespace}objects.primaryHost`, this.hostname, ms);
|
|
3677
4006
|
}
|
|
3678
4007
|
/**
|
|
3679
|
-
* Sets current host as primary if no primary host active
|
|
3680
|
-
* Value
|
|
4008
|
+
* Sets the current host as primary if no primary host active
|
|
4009
|
+
* Value expires after ms milliseconds
|
|
3681
4010
|
*
|
|
3682
4011
|
* @param ms - ms until value expires
|
|
3683
4012
|
* @returns 1 if lock acquired else 0
|
|
@@ -3689,16 +4018,10 @@ class ObjectsInRedisClient {
|
|
|
3689
4018
|
if (!this.noLegacyMultihost) {
|
|
3690
4019
|
return Promise.resolve(0);
|
|
3691
4020
|
}
|
|
3692
|
-
return this.client.evalsha(
|
|
3693
|
-
this.scripts.redlock_acquire,
|
|
3694
|
-
3,
|
|
3695
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
3696
|
-
this.hostname,
|
|
3697
|
-
ms
|
|
3698
|
-
]);
|
|
4021
|
+
return this.client.evalsha(this.scripts.redlock_acquire, 3, `${this.metaNamespace}objects.primaryHost`, this.hostname, ms);
|
|
3699
4022
|
}
|
|
3700
4023
|
/**
|
|
3701
|
-
* Get name of the primary host
|
|
4024
|
+
* Get the name of the primary host
|
|
3702
4025
|
*/
|
|
3703
4026
|
getPrimaryHost() {
|
|
3704
4027
|
if (!this.client) {
|
|
@@ -3719,14 +4042,7 @@ class ObjectsInRedisClient {
|
|
|
3719
4042
|
if (!this.noLegacyMultihost) {
|
|
3720
4043
|
return Promise.resolve();
|
|
3721
4044
|
}
|
|
3722
|
-
return this.client.evalsha(
|
|
3723
|
-
this.scripts.redlock_release,
|
|
3724
|
-
4,
|
|
3725
|
-
`${this.metaNamespace}objects.primaryHost`,
|
|
3726
|
-
this.hostname,
|
|
3727
|
-
this.settings.connection.options.db,
|
|
3728
|
-
`${this.metaNamespace}objects.primaryHost`
|
|
3729
|
-
]);
|
|
4045
|
+
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`);
|
|
3730
4046
|
}
|
|
3731
4047
|
/**
|
|
3732
4048
|
* Sets the protocol version to the DB
|
|
@@ -3746,7 +4062,7 @@ class ObjectsInRedisClient {
|
|
|
3746
4062
|
}
|
|
3747
4063
|
}
|
|
3748
4064
|
/**
|
|
3749
|
-
* Subscribe to expired events to get expiration of primary host
|
|
4065
|
+
* Subscribe to expired events to get expiration of the primary host
|
|
3750
4066
|
*/
|
|
3751
4067
|
async subscribePrimaryHost() {
|
|
3752
4068
|
if (this.subSystem) {
|