diff --git a/OpenFlow/src/Config.ts b/OpenFlow/src/Config.ts index 32d22b2a..80e39542 100644 --- a/OpenFlow/src/Config.ts +++ b/OpenFlow/src/Config.ts @@ -53,6 +53,7 @@ export class dbConfig extends Base { public log_verbose: boolean; public log_silly: boolean; public log_to_exchange: boolean; + public heapdump_onstop: boolean; public api_bypass_perm_check: boolean; public workitem_queue_monitoring_interval: number; @@ -68,6 +69,7 @@ export class dbConfig extends Base { public ensure_indexes: boolean; public text_index_name_fields: string[]; + public metadata_collections: string[]; public auto_create_users: boolean; public auto_create_user_from_jwt: boolean; @@ -153,6 +155,8 @@ export class dbConfig extends Base { Config.log_verbose = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.log_verbose) ? conf.log_verbose : Config.getEnv("log_verbose", "false")); Config.log_silly = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.log_silly) ? conf.log_silly : Config.getEnv("log_silly", "false")); Config.log_to_exchange = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.log_to_exchange) ? conf.log_to_exchange : Config.getEnv("log_to_exchange", "false")); + Config.heapdump_onstop = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.heapdump_onstop) ? conf.heapdump_onstop : Config.getEnv("heapdump_onstop", "false")); + Config.client_heartbeat_timeout = parseInt(!NoderedUtil.IsNullEmpty(conf.client_heartbeat_timeout) ? conf.client_heartbeat_timeout.toString() : Config.getEnv("client_heartbeat_timeout", "60")); Config.client_signin_timeout = parseInt(!NoderedUtil.IsNullEmpty(conf.client_signin_timeout) ? conf.client_signin_timeout.toString() : Config.getEnv("client_signin_timeout", "120")); Config.client_disconnect_signin_error = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.client_disconnect_signin_error) ? conf.client_disconnect_signin_error : Config.getEnv("client_disconnect_signin_error", "false")); @@ -171,6 +175,7 @@ export class dbConfig extends Base { Config.ensure_indexes = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.ensure_indexes) ? conf.ensure_indexes : Config.getEnv("ensure_indexes", "true")); Config.text_index_name_fields = Config.parseArray(!NoderedUtil.IsNullEmpty(conf.text_index_name_fields) ? conf.text_index_name_fields.toString() : Config.getEnv("text_index_name_fields", "name,_names")) + Config.metadata_collections = Config.parseArray(!NoderedUtil.IsNullEmpty(conf.metadata_collections) ? conf.metadata_collections.toString() : Config.getEnv("metadata_collections", "")) Config.auto_create_users = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.auto_create_users) ? conf.auto_create_users : Config.getEnv("auto_create_users", "false")) Config.auto_create_user_from_jwt = Config.parseBoolean(!NoderedUtil.IsNullEmpty(conf.auto_create_user_from_jwt) ? conf.auto_create_user_from_jwt : Config.getEnv("auto_create_user_from_jwt", "")) @@ -244,6 +249,8 @@ export class Config { Config.log_verbose = Config.parseBoolean(Config.getEnv("log_verbose", "false")); Config.log_silly = Config.parseBoolean(Config.getEnv("log_silly", "false")); Config.log_to_exchange = Config.parseBoolean(Config.getEnv("log_to_exchange", "false")); + + Config.heapdump_onstop = Config.parseBoolean(Config.getEnv("heapdump_onstop", "false")); Config.amqp_allow_replyto_empty_queuename = Config.parseBoolean(Config.getEnv("amqp_allow_replyto_empty_queuename", "false")); @@ -278,6 +285,7 @@ export class Config { Config.supports_watch = Config.parseBoolean(Config.getEnv("supports_watch", "false")); Config.ensure_indexes = Config.parseBoolean(Config.getEnv("ensure_indexes", "true")); Config.text_index_name_fields = Config.parseArray(Config.getEnv("text_index_name_fields", "name,_names")); + Config.metadata_collections = Config.parseArray(Config.getEnv("metadata_collections", "")); Config.auto_create_users = Config.parseBoolean(Config.getEnv("auto_create_users", "false")); Config.auto_create_user_from_jwt = Config.parseBoolean(Config.getEnv("auto_create_user_from_jwt", "false")); @@ -473,7 +481,9 @@ export class Config { public static log_debug: boolean = Config.parseBoolean(Config.getEnv("log_debug", "false")); public static log_verbose: boolean = Config.parseBoolean(Config.getEnv("log_verbose", "false")); public static log_silly: boolean = Config.parseBoolean(Config.getEnv("log_silly", "false")); - public static log_to_exchange: boolean = Config.parseBoolean(Config.getEnv("log_to_exchange", "false")); + public static log_to_exchange: boolean = Config.parseBoolean(Config.getEnv("log_to_exchange", "false")); + + public static heapdump_onstop: boolean = Config.parseBoolean(Config.getEnv("heapdump_onstop", "false")); public static amqp_allow_replyto_empty_queuename: boolean = Config.parseBoolean(Config.getEnv("amqp_allow_replyto_empty_queuename", "false")); @@ -509,6 +519,7 @@ export class Config { public static supports_watch: boolean = Config.parseBoolean(Config.getEnv("supports_watch", "false")); public static ensure_indexes: boolean = Config.parseBoolean(Config.getEnv("ensure_indexes", "true")); public static text_index_name_fields: string[] = Config.parseArray(Config.getEnv("text_index_name_fields", "name,_names")); + public static metadata_collections: string[] = Config.parseArray(Config.getEnv("metadata_collections", "")); public static auto_create_users: boolean = Config.parseBoolean(Config.getEnv("auto_create_users", "false")); public static auto_create_user_from_jwt: boolean = Config.parseBoolean(Config.getEnv("auto_create_user_from_jwt", "false")); diff --git a/OpenFlow/src/Crypt.ts b/OpenFlow/src/Crypt.ts index 7360f971..497e633f 100644 --- a/OpenFlow/src/Crypt.ts +++ b/OpenFlow/src/Crypt.ts @@ -149,7 +149,8 @@ export class Crypt { if (!NoderedUtil.IsNullEmpty(token)) { const o: any = jsonwebtoken.verify(token, Crypt.encryption_key, { ignoreExpiration: true }); if (!NoderedUtil.IsNullUndefinded(o) && !NoderedUtil.IsNullUndefinded(o.data) && !NoderedUtil.IsNullEmpty(o.data._id)) { - if (cli != null) { + // fake login, so we can track who was trying to login with an expired token + if (cli != null && cli.user == null) { cli.user = TokenUser.assign(o.data); cli.username = cli.user?.username; } diff --git a/OpenFlow/src/DatabaseConnection.ts b/OpenFlow/src/DatabaseConnection.ts index 63d1a86c..05ca7258 100644 --- a/OpenFlow/src/DatabaseConnection.ts +++ b/OpenFlow/src/DatabaseConnection.ts @@ -285,7 +285,9 @@ export class DatabaseConnection extends events.EventEmitter { if (payload != null) { Logger.instanse.debug("Send workitem payload '" + payload.name + "' to client " + (client.username + "/" + client.clientagent + "/" + client.id).trim(), null, { workflowid: wiq.workflowid, wi: payload._id, name: payload.name }); try { - await client.Queue(JSON.stringify(sendthis), queueid, {} as any, null) + client.Queue(JSON.stringify(sendthis), queueid, {} as any, null).catch(e=> { + Logger.instanse.error(e, null); + }); } catch (error) { } } else { @@ -496,7 +498,7 @@ export class DatabaseConnection extends events.EventEmitter { } msg.data = JSON.stringify(q); client._socketObject.send(msg.tojson(), (err) => { - if (err) Logger.instanse.warn(err, subspan, { collection: collectionname }); + if (err) Logger.instanse.warn(err as any, subspan, { collection: collectionname }); }); } catch (error) { @@ -1001,20 +1003,20 @@ export class DatabaseConnection extends events.EventEmitter { if (collectionname === "files") { collectionname = "fs.files"; } if (DatabaseConnection.usemetadata(collectionname)) { let impersonationquery; - if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, "metadata._acl", [Rights.read], span); + if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, [Rights.read], collectionname, span); if (!NoderedUtil.IsNullEmpty(queryas) && !NoderedUtil.IsNullUndefinded(impersonationquery)) { - _query = { $and: [query, this.getbasequery(user, "metadata._acl", [Rights.read]), impersonationquery] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname), impersonationquery] }; } else { - _query = { $and: [query, this.getbasequery(user, "metadata._acl", [Rights.read])] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname)] }; } projection = null; } else { let impersonationquery: any; - if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, "_acl", [Rights.read], span) + if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, [Rights.read], collectionname, span) if (!NoderedUtil.IsNullEmpty(queryas) && !NoderedUtil.IsNullUndefinded(impersonationquery)) { - _query = { $and: [query, this.getbasequery(user, "_acl", [Rights.read]), impersonationquery] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname), impersonationquery] }; } else { - _query = { $and: [query, this.getbasequery(user, "_acl", [Rights.read])] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname)] }; } } if (!top) { top = 500; } @@ -1107,19 +1109,19 @@ export class DatabaseConnection extends events.EventEmitter { if (collectionname === "files") { collectionname = "fs.files"; } if (DatabaseConnection.usemetadata(collectionname)) { let impersonationquery; - if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, "metadata._acl", [Rights.read], span); + if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, [Rights.read], collectionname, span); if (!NoderedUtil.IsNullEmpty(queryas) && !NoderedUtil.IsNullUndefinded(impersonationquery)) { - _query = { $and: [query, this.getbasequery(user, "metadata._acl", [Rights.read]), impersonationquery] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname), impersonationquery] }; } else { - _query = { $and: [query, this.getbasequery(user, "metadata._acl", [Rights.read])] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname)] }; } } else { let impersonationquery: any; - if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, "_acl", [Rights.read], span) + if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, [Rights.read], collectionname, span) if (!NoderedUtil.IsNullEmpty(queryas) && !NoderedUtil.IsNullUndefinded(impersonationquery)) { - _query = { $and: [query, this.getbasequery(user, "_acl", [Rights.read]), impersonationquery] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname), impersonationquery] }; } else { - _query = { $and: [query, this.getbasequery(user, "_acl", [Rights.read])] }; + _query = { $and: [query, this.getbasequery(user, [Rights.read], collectionname)] }; } } span?.setAttribute("collection", collectionname); @@ -1316,9 +1318,9 @@ export class DatabaseConnection extends events.EventEmitter { span?.addEvent("getbasequery"); let base: object; if (DatabaseConnection.usemetadata(collectionname)) { - base = this.getbasequery(user, "metadata._acl", [Rights.read]); + base = this.getbasequery(user, [Rights.read], collectionname); } else { - base = this.getbasequery(user, "_acl", [Rights.read]); + base = this.getbasequery(user, [Rights.read], collectionname); } if (Array.isArray(aggregates)) { aggregates.unshift({ $match: base }); @@ -1386,7 +1388,7 @@ export class DatabaseConnection extends events.EventEmitter { const user: TokenUser = await Crypt.verityToken(jwt); // TODO: Should we filter on rights other than read ? should a person with reade be allowed to know when it was updated ? // a person with read, would beablt to know anyway, so guess read should be enough for now ... - const base = this.getbasequery(user, "fullDocument._acl", [Rights.read]); + const base = this.getbasequery(user, [Rights.read], "fullDocument._acl"); if (Array.isArray(aggregates)) { aggregates.unshift({ $match: base }); } else { @@ -1512,18 +1514,14 @@ export class DatabaseConnection extends events.EventEmitter { if (user._id != WellknownIds.root && !await this.CheckEntityRestriction(user, collectionname, item, span)) { throw Error("Create " + item._type + " access denied"); } - - // const hasUser: Ace = item._acl.find(e => e._id === user._id); - // if ((hasUser === null || hasUser === undefined)) { - // Base.addRight(item, user._id, user.name, [Rights.full_control]); - // item = this.ensureResource(item, collectionname); - // } if (!DatabaseConnection.hasAuthorization(user, item, Rights.full_control)) { Base.addRight(item, user._id, user.name, [Rights.full_control]); item = this.ensureResource(item, collectionname); } } else if (DatabaseConnection.istimeseries(collectionname) && !DatabaseConnection.usemetadata(collectionname)) { - item._created = new Date(new Date().toISOString()); + if(NoderedUtil.IsNullEmpty(item[DatabaseConnection.timefield(collectionname)])) { + item[DatabaseConnection.timefield(collectionname)] = new Date(new Date().toISOString()); + } if (collectionname == "audit") { item._createdby = user.name; item._createdbyid = user._id; @@ -1535,76 +1533,53 @@ export class DatabaseConnection extends events.EventEmitter { // @ts-ignore throw Error("Create " + item._type + " access denied"); } - // item._createdby = user.name; - // item._createdbyid = user._id; - // const hasUser: Ace = item._acl.find(e => e._id === user._id); - // if ((hasUser === null || hasUser === undefined)) { - // // @ts-ignore - // Base.addRight(item, user._id, user.name, [Rights.full_control]); - // // @ts-ignore - // item = this.ensureResource(item, collectionname); - // } if (!DatabaseConnection.hasAuthorization(user, item, Rights.full_control)) { Base.addRight(item, user._id, user.name, [Rights.full_control]); item = this.ensureResource(item, collectionname); } } else { - item._created = new Date(new Date().toISOString()); - // @ts-ignore - if (NoderedUtil.IsNullUndefinded(item.metadata)) item.metadata = {}; + if(NoderedUtil.IsNullEmpty(item[DatabaseConnection.timefield(collectionname)])) { + item[DatabaseConnection.timefield(collectionname)] = new Date(new Date().toISOString()); + } + let metadata = DatabaseConnection.metadataname(collectionname); + if (NoderedUtil.IsNullUndefinded(item[metadata])) item[metadata] = {}; span?.addEvent("ensureResource"); - // @ts-ignore - item.metadata = this.ensureResource(item.metadata, collectionname); + item[metadata] = this.ensureResource(item[metadata], collectionname); if (item.hasOwnProperty("name")) { // @ts-ignore - item.metadata.name = item.name; - delete item.name; + item[metadata].name = item.name; + // delete item.name; } if (item.hasOwnProperty("_type")) { // @ts-ignore - item.metadata._type = item._type; - delete item._type; + item[metadata]._type = item._type; + // delete item._type; } if (item.hasOwnProperty("_acl")) { // @ts-ignore - item.metadata._acl = item._acl; + item[metadata]._acl = item._acl; delete item._acl; } if (collectionname == "audit") { // @ts-ignore - item.metadata.userid = item.userid; + item[metadata].userid = item.userid; // @ts-ignore - item.metadata.username = item.username; + item[metadata].username = item.username; // @ts-ignore delete item.userid; // @ts-ignore delete item.username; } - // @ts-ignore - if (user._id != WellknownIds.root && !await this.CheckEntityRestriction(user, collectionname, item.metadata, span)) { + if (user._id != WellknownIds.root && !await this.CheckEntityRestriction(user, collectionname, item[metadata], span)) { // @ts-ignore - throw Error("Create " + item.metadata._type + " access denied"); + throw Error("Create " + item[metadata]._type + " access denied"); } - // @ts-ignore - item.metadata._version = 0; - // @ts-ignore - item.metadata._createdby = user.name; - // @ts-ignore - item.metadata._createdbyid = user._id; - // @ts-ignore - // const hasUser: Ace = item.metadata._acl.find(e => e._id === user._id); - // if ((hasUser === null || hasUser === undefined)) { - // // @ts-ignore - // Base.addRight(item.metadata, user._id, user.name, [Rights.full_control]); - // // @ts-ignore - // item.metadata = this.ensureResource(item.metadata, collectionname); - // } - // @ts-ignore - if (!DatabaseConnection.hasAuthorization(user, item.metadata, Rights.create)) { - // @ts-ignore - Base.addRight(item.metadata, user._id, user.name, [Rights.full_control]); - // @ts-ignore - item.metadata = this.ensureResource(item.metadata, collectionname); + item[metadata]._version = 0; + item[metadata]._createdby = user.name; + item[metadata]._createdbyid = user._id; + if (!DatabaseConnection.hasAuthorization(user, item[metadata], Rights.create)) { + Base.addRight(item[metadata], user._id, user.name, [Rights.full_control]); + item[metadata] = this.ensureResource(item[metadata], collectionname); } } @@ -1755,8 +1730,8 @@ export class DatabaseConnection extends events.EventEmitter { item = await this.CleanACL(item, user, collectionname, span); } else { span?.addEvent("CleanACL"); - // @ts-ignore - item.metadata = await this.CleanACL(item.metadata, user, collectionname, span); + let metadata = DatabaseConnection.metadataname(collectionname); + item[metadata] = await this.CleanACL(item[metadata], user, collectionname, span); } if (collectionname === "users" && item._type === "user" && !NoderedUtil.IsNullEmpty(item._id)) { Base.addRight(item, item._id, item.name, [Rights.full_control]); @@ -1810,25 +1785,7 @@ export class DatabaseConnection extends events.EventEmitter { { _id: WellknownIds.users }, { "$push": { members: new Rolemember(item.name, item._id) } } ); - // fsc.updateOne(_query, { $set: { metadata: (q.item as any).metadata } }); - // span?.addEvent("FindRoleByName users"); - // const users: Role = await Logger.DBHelper.FindRoleByName("users", null, span); - // users.AddMember(item); - // span?.addEvent("Save Users"); - // await Logger.DBHelper.Save(users, Crypt.rootToken(), span); - let user2: User = User.assign(item as any); - // if (!NoderedUtil.IsNullEmpty(user2.customerid)) { - // // TODO: Check user has permission to this customer - // const custusers: Role = Role.assign(await this.getbyid(customer.users, "users", jwt, true, span)); - // if (!NoderedUtil.IsNullUndefinded(custusers)) { - // custusers.AddMember(item); - // await Logger.DBHelper.Save(custusers, Crypt.rootToken(), span); - // } else { - // Logger.instanse.debug("DatabaseConnection", "InsertOne", "[" + user.username + "][" + collectionname + "] Failed finding customer users " + customer.users + " role while updating item " + item._id); - // } - // } - if (Config.validate_emails && user2.emailvalidated || !Config.validate_emails) { let domain: string = user2.username; if (!NoderedUtil.IsNullEmpty(user2.email)) domain = user2.email; @@ -1939,33 +1896,55 @@ export class DatabaseConnection extends events.EventEmitter { let hadWorkitemQueue = false; let wiqids = []; for (let i = 0; i < items.length; i++) { - let item = this.ensureResource(items[i], collectionname); + let item = items[i]; + // let item = this.ensureResource(items[i], collectionname); DatabaseConnection.traversejsonencode(item); if (!await this.CheckEntityRestriction(user, collectionname, item, span)) { continue; } - let name = item.name; if (NoderedUtil.IsNullEmpty(name)) name = item._name; if (NoderedUtil.IsNullEmpty(name)) name = "Unknown"; - item._createdby = user.name; - item._createdbyid = user._id; - item._created = new Date(new Date().toISOString()); - item._modifiedby = user.name; - item._modifiedbyid = user._id; - item._modified = item._created; - if (!DatabaseConnection.hasAuthorization(user, item, Rights.full_control)) { - Base.addRight(item, user._id, user.name, [Rights.full_control]); + if (!DatabaseConnection.usemetadata(collectionname) && !DatabaseConnection.istimeseries(collectionname)) { + item._version = 0; + item._createdby = user.name; + item._createdbyid = user._id; + item._created = new Date(new Date().toISOString()); + item._modifiedby = user.name; + item._modifiedbyid = user._id; + item._modified = item._created; + if (!DatabaseConnection.hasAuthorization(user, item, Rights.full_control)) { + Base.addRight(item, user._id, user.name, [Rights.full_control]); + } item = this.ensureResource(item, collectionname); + // Logger.instanse.silly("Adding " + item._type + " " + name + " to database", span, { collection: collectionname, user: user.username }); + // if (!DatabaseConnection.hasAuthorization(user, item, Rights.create)) { throw new Error("Access denied, no authorization to InsertOne " + item._type + " " + name + " to database"); } + } else if (DatabaseConnection.istimeseries(collectionname) && !DatabaseConnection.usemetadata(collectionname)) { + + if(NoderedUtil.IsNullEmpty(item[DatabaseConnection.timefield(collectionname)])) { + item[DatabaseConnection.timefield(collectionname)] = new Date(new Date().toISOString()); + } + if (!DatabaseConnection.hasAuthorization(user, item, Rights.full_control)) { + Base.addRight(item, user._id, user.name, [Rights.full_control]); + item = this.ensureResource(item, collectionname); + } + } else { + if(NoderedUtil.IsNullEmpty(item[DatabaseConnection.timefield(collectionname)])) { + item[DatabaseConnection.timefield(collectionname)] = new Date(new Date().toISOString()); + } + let metadata = DatabaseConnection.metadataname(collectionname); + if (NoderedUtil.IsNullUndefinded(item[metadata])) item[metadata] = {}; + span?.addEvent("ensureResource"); + item[metadata] = this.ensureResource(item[metadata], collectionname); + item[metadata]._version = 0; + item[metadata]._createdby = user.name; + item[metadata]._createdbyid = user._id; + if (!DatabaseConnection.hasAuthorization(user, item[metadata], Rights.create)) { + Base.addRight(item[metadata], user._id, user.name, [Rights.full_control]); + item[metadata] = this.ensureResource(item[metadata], collectionname); + } } - // const hasUser: Ace = item._acl.find(e => e._id === user._id); - // if ((hasUser === null || hasUser === undefined)) { - // Base.addRight(item, user._id, user.name, [Rights.full_control]); - // item = this.ensureResource(item, collectionname); - // } - Logger.instanse.silly("Adding " + item._type + " " + name + " to database", span, { collection: collectionname, user: user.username }); - if (!DatabaseConnection.hasAuthorization(user, item, Rights.create)) { throw new Error("Access denied, no authorization to InsertOne " + item._type + " " + name + " to database"); } item = this.encryptentity(item) as T; var user2: User = item as any; @@ -2008,7 +1987,7 @@ export class DatabaseConnection extends events.EventEmitter { } await Logger.DBHelper.CheckCache(collectionname, item, false, false, span); } - item._version = 0; + if (item._id != null) { const basehist = await this.query({ query: { id: item._id }, projection: { _version: 1 }, top: 1, orderby: { _version: -1 }, collectionname: collectionname + "_hist", jwt: Crypt.rootToken() }, span); if (basehist.length > 0) { @@ -2038,10 +2017,20 @@ export class DatabaseConnection extends events.EventEmitter { item._id = new ObjectId().toHexString(); } span?.addEvent("CleanACL"); - item = await this.CleanACL(item, user, collectionname, span); - if (item._type === "role" && collectionname === "users") { - item = await this.Cleanmembers(item as any, null, span); + if (!DatabaseConnection.usemetadata(collectionname) && !DatabaseConnection.istimeseries(collectionname)) { + item = await this.CleanACL(item, user, collectionname, span); + if (item._type === "role" && collectionname === "users") { + item = await this.Cleanmembers(item as any, null, span); + } + } else if (DatabaseConnection.istimeseries(collectionname) && !DatabaseConnection.usemetadata(collectionname)) { + } else { + let metadata = DatabaseConnection.metadataname(collectionname); + item[metadata] = await this.CleanACL(item[metadata], user, collectionname, span); + if (item._type === "role" && collectionname === "users") { + item[metadata] = await this.Cleanmembers(item[metadata] as any, null, span); + } } + if (collectionname === "users" && item._type === "user") { const u: TokenUser = (item as any); @@ -2305,11 +2294,6 @@ export class DatabaseConnection extends events.EventEmitter { if (user._id != WellknownIds.root && original._type != q.item._type && !await this.CheckEntityRestriction(user, q.collectionname, q.item, span)) { throw Error("Create " + q.item._type + " access denied"); } - // if (!DatabaseConnection.usemetadata(q.collectionname)) { - // q.item = await this.CleanACL(q.item, user, q.collectionname, span); - // } else { - // (q.item as any).metadata = await this.CleanACL((q.item as any).metadata, user, q.collectionname, span); - // } // force cleaning members, to clean up mess with auto added members if (q.item._type === "role" && q.collectionname === "users") { q.item = await this.Cleanmembers(q.item as any, original, span); @@ -2333,52 +2317,54 @@ export class DatabaseConnection extends events.EventEmitter { DatabaseConnection.traversejsonencode(q.item); q.item = this.encryptentity(q.item); } else { - if (!DatabaseConnection.hasAuthorization(user, (q.item as any).metadata, Rights.update)) { + let metadata = DatabaseConnection.metadataname(q.collectionname); + + if (!DatabaseConnection.hasAuthorization(user, q.item[metadata], Rights.update)) { throw new Error("Access denied, no authorization to UpdateOne file " + (q.item as any).filename + " to database"); } - if (!DatabaseConnection.hasAuthorization(user, (original as any).metadata, Rights.update)) { + if (!DatabaseConnection.hasAuthorization(user, original[metadata], Rights.update)) { throw new Error("Access denied, no authorization to UpdateOne file " + (original as any).filename + " to database"); } - (q.item as any).metadata = Base.assign((q.item as any).metadata); - (q.item as any).metadata._modifiedby = user.name; - (q.item as any).metadata._modifiedbyid = user._id; - (q.item as any).metadata._modified = new Date(new Date().toISOString()); + q.item[metadata] = Base.assign(q.item[metadata]); + q.item[metadata]._modifiedby = user.name; + q.item[metadata]._modifiedbyid = user._id; + q.item[metadata]._modified = new Date(new Date().toISOString()); // now add all _ fields to the new object - const keys: string[] = Object.keys((original as any).metadata); + const keys: string[] = Object.keys(original[metadata]); for (let i: number = 0; i < keys.length; i++) { let key: string = keys[i]; if (key === "_created") { - (q.item as any).metadata[key] = new Date((original as any).metadata[key]); + q.item[metadata][key] = new Date(original[metadata][key]); } else if (key === "_type") { - (q.item as any).metadata[key] = (original as any).metadata[key]; + q.item[metadata][key] = original[metadata][key]; } else if (key === "_createdby" || key === "_createdbyid") { - (q.item as any).metadata[key] = (original as any).metadata[key]; + q.item[metadata][key] = original[metadata][key]; } else if (key === "_modifiedby" || key === "_modifiedbyid" || key === "_modified") { // allready updated } else if (key.indexOf("_") === 0) { - if (!(q.item as any).metadata.hasOwnProperty(key)) { - (q.item as any).metadata[key] = (original as any).metadata[key]; // add missing key - } else if ((q.item as any).metadata[key] === null) { - delete (q.item as any).metadata[key]; // remove key + if (!q.item[metadata].hasOwnProperty(key)) { + q.item[metadata][key] = original[metadata][key]; // add missing key + } else if (q.item[metadata][key] === null) { + delete q.item[metadata][key]; // remove key } else { // key allready exists, might been updated since last save } } } - if ((q.item as any).metadata._acl === null || (q.item as any).metadata._acl === undefined || !Array.isArray((q.item as any).metadata._acl)) { - (q.item as any).metadata._acl = (original as any).metadata._acl; - (q.item as any).metadata._version = (original as any).metadata._version; - if (!DatabaseConnection.hasAuthorization(user, (q.item as any).metadata, Rights.update)) { + if (q.item[metadata]._acl === null || q.item[metadata]._acl === undefined || !Array.isArray(q.item[metadata]._acl)) { + q.item[metadata]._acl = original[metadata]._acl; + q.item[metadata]._version = original[metadata]._version; + if (!DatabaseConnection.hasAuthorization(user, q.item[metadata], Rights.update)) { throw new Error("Access denied, no authorization to UpdateOne with current ACL"); } } - (q.item as any).metadata = this.ensureResource((q.item as any).metadata, q.collectionname); + q.item[metadata] = this.ensureResource(q.item[metadata], q.collectionname); DatabaseConnection.traversejsonencode(q.item); - (q.item as any).metadata = this.encryptentity((q.item as any).metadata); - const hasUser: Ace = (q.item as any).metadata._acl.find(e => e._id === user._id); - if ((hasUser === null || hasUser === undefined) && (q.item as any).metadata._acl.length === 0) { - Base.addRight((q.item as any).metadata, user._id, user.name, [Rights.full_control]); + q.item[metadata] = this.encryptentity(q.item[metadata]); + const hasUser: Ace = q.item[metadata]._acl.find(e => e._id === user._id); + if ((hasUser === null || hasUser === undefined) && q.item[metadata]._acl.length === 0) { + Base.addRight(q.item[metadata], user._id, user.name, [Rights.full_control]); q.item = this.ensureResource(q.item, q.collectionname); } } @@ -2457,10 +2443,10 @@ export class DatabaseConnection extends events.EventEmitter { } let _query: Object = {}; if (DatabaseConnection.usemetadata(q.collectionname)) { - _query = { $and: [q.query, this.getbasequery(user, "metadata._acl", [Rights.update])] }; + _query = { $and: [q.query, this.getbasequery(user, [Rights.update], q.collectionname)] }; } else { // todo: enforcer permissions when fetching _hist ? - _query = { $and: [q.query, this.getbasequery(user, "_acl", [Rights.update])] }; + _query = { $and: [q.query, this.getbasequery(user, [Rights.update], q.collectionname)] }; } if (Config.api_bypass_perm_check) { _query = q.query; } @@ -2478,7 +2464,8 @@ export class DatabaseConnection extends events.EventEmitter { if (!DatabaseConnection.usemetadata(q.collectionname)) { q.item = await this.CleanACL(q.item, user, q.collectionname, span); } else { - (q.item as any).metadata = await this.CleanACL((q.item as any).metadata, user, q.collectionname, span); + let metadata = DatabaseConnection.metadataname(q.collectionname); + q.item[metadata] = await this.CleanACL(q.item[metadata], user, q.collectionname, span); } } if (q.item._type === "role" && q.collectionname === "users") { @@ -2515,7 +2502,8 @@ export class DatabaseConnection extends events.EventEmitter { } else { const fsc = Config.db.db.collection(q.collectionname); const ot_end = Logger.otel.startTimer(); - q.opresult = await fsc.updateOne(_query, { $set: { metadata: (q.item as any).metadata } }); + let metadata = DatabaseConnection.metadataname(q.collectionname); + q.opresult = await fsc.updateOne(_query, { $set: { metadata: q.item[metadata] } }); Logger.otel.endTimer(ot_end, DatabaseConnection.mongodb_update, DatabaseConnection.otel_label(q.collectionname, user, "update")); if ((q.opresult && q.opresult.matchedCount == 0) && (q.w != 0)) { throw new Error("ReplaceOne failed, matched 0 documents with query {_id: '" + q.item._id + "'}"); @@ -2548,7 +2536,8 @@ export class DatabaseConnection extends events.EventEmitter { if (!DatabaseConnection.usemetadata(q.collectionname)) { q.item = this.decryptentity(q.item); } else { - (q.item as any).metadata = this.decryptentity((q.item as any).metadata); + let metadata = DatabaseConnection.metadataname(q.collectionname); + q.item[metadata] = this.decryptentity(q.item[metadata]); } if (original != null) { await Logger.DBHelper.CheckCache(q.collectionname, original, false, false, span); @@ -2690,10 +2679,10 @@ export class DatabaseConnection extends events.EventEmitter { } if (q.collectionname === "files") { q.collectionname = "fs.files"; } if (DatabaseConnection.usemetadata(q.collectionname)) { - _query = { $and: [q.query, this.getbasequery(user, "metadata._acl", [Rights.update])] }; + _query = { $and: [q.query, this.getbasequery(user, [Rights.update], q.collectionname)] }; } else { // todo: enforcer permissions when fetching _hist ? - _query = { $and: [q.query, this.getbasequery(user, "_acl", [Rights.update])] }; + _query = { $and: [q.query, this.getbasequery(user, [Rights.update], q.collectionname)] }; } if ((q.item["$set"]) === undefined) { (q.item["$set"]) = {} }; @@ -2991,9 +2980,9 @@ export class DatabaseConnection extends events.EventEmitter { const user: TokenUser = await Crypt.verityToken(jwt); let _query: any = {}; if (typeof id === 'string' || id instanceof String) { - _query = { $and: [{ _id: id }, this.getbasequery(user, "_acl", [Rights.delete])] }; + _query = { $and: [{ _id: id }, this.getbasequery(user, [Rights.delete], collectionname)] }; } else { - _query = { $and: [{ id }, this.getbasequery(user, "_acl", [Rights.delete])] }; + _query = { $and: [{ id }, this.getbasequery(user, [Rights.delete], collectionname)] }; } if (collectionname == "audit") { if (!user.HasRoleId(WellknownIds.admins)) { @@ -3003,7 +2992,7 @@ export class DatabaseConnection extends events.EventEmitter { if (collectionname === "files") { collectionname = "fs.files"; } if (DatabaseConnection.usemetadata(collectionname)) { - _query = { $and: [{ _id: safeObjectID(id) }, this.getbasequery(user, "metadata._acl", [Rights.delete])] }; + _query = { $and: [{ _id: safeObjectID(id) }, this.getbasequery(user, [Rights.delete], collectionname)] }; const ot_end = Logger.otel.startTimer(); const arr = await this.db.collection(collectionname).find(_query).toArray(); let ms = Logger.otel.endTimer(ot_end, DatabaseConnection.mongodb_query, DatabaseConnection.otel_label(collectionname, user, "query")); @@ -3168,19 +3157,19 @@ export class DatabaseConnection extends events.EventEmitter { if (collectionname === "files") { collectionname = "fs.files"; } if (DatabaseConnection.usemetadata(collectionname)) { let impersonationquery; - if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, "metadata._acl", [Rights.delete], span); + if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, [Rights.delete], collectionname, span); if (!NoderedUtil.IsNullEmpty(queryas) && !NoderedUtil.IsNullUndefinded(impersonationquery)) { baseq = impersonationquery; } else { - baseq = this.getbasequery(user, "metadata._acl", [Rights.delete]); + baseq = this.getbasequery(user, [Rights.delete], collectionname); } } else { let impersonationquery: any; - if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, "_acl", [Rights.delete], span) + if (!NoderedUtil.IsNullEmpty(queryas)) impersonationquery = await this.getbasequeryuserid(user, queryas, [Rights.delete], collectionname, span) if (!NoderedUtil.IsNullEmpty(queryas) && !NoderedUtil.IsNullUndefinded(impersonationquery)) { baseq = impersonationquery; } else { - baseq = this.getbasequery(user, "_acl", [Rights.delete]); + baseq = this.getbasequery(user, [Rights.delete], collectionname); } } let _query: any = {}; @@ -3437,7 +3426,11 @@ export class DatabaseConnection extends events.EventEmitter { * @param {number[]} bits Permission wanted on objects * @returns Object MongoDB query */ - public getbasequery(user: TokenUser | User, field: string, bits: number[]): Object { + public getbasequery(user: TokenUser | User, bits: number[], collectionname: string): Object { + let field = "_acl"; + if(DatabaseConnection.usemetadata(collectionname)) { + field = DatabaseConnection.metadataname(collectionname) + "._acl"; + } if (Config.api_bypass_perm_check) { return { _id: { $ne: "bum" } }; } @@ -3479,12 +3472,12 @@ export class DatabaseConnection extends events.EventEmitter { finalor.push(q); return { $or: finalor.concat() }; } - private async getbasequeryuserid(calluser: TokenUser, userid: string, field: string, bits: number[], parent: Span): Promise { + private async getbasequeryuserid(calluser: TokenUser, userid: string, bits: number[], collectionname: string, parent: Span): Promise { let user: User = await this.getbyid(userid, "users", Crypt.rootToken(), true, parent); if (NoderedUtil.IsNullUndefinded(user)) return null; if (user._type == "user" || user._type == "role") { user = await Logger.DBHelper.DecorateWithRoles(user as any, parent); - return this.getbasequery(user, field, bits); + return this.getbasequery(user, bits, collectionname); } else if (user._type == "customer") { user = await Logger.DBHelper.DecorateWithRoles(user as any, parent); user.roles.push(new Rolemember(user.name + " users", (user as any).users)) @@ -3494,7 +3487,7 @@ export class DatabaseConnection extends events.EventEmitter { if (!NoderedUtil.IsNullEmpty((user as any as Customer).userid)) { user.roles.push(new Rolemember((user as any as Customer).userid, (user as any as Customer).userid)) } - return this.getbasequery(user, field, bits); + return this.getbasequery(user, bits, collectionname); } } /** @@ -3978,18 +3971,57 @@ export class DatabaseConnection extends events.EventEmitter { span?.addEvent("Get collections"); let collections = await DatabaseConnection.toArray(this.db.listCollections()); collections = collections.filter(x => x.name.indexOf("system.") === -1); + collections = collections.filter(x => x.type == "timeseries"); DatabaseConnection.timeseries_collections = []; + DatabaseConnection.timeseries_collections_metadata = {}; for (let i = 0; i < collections.length; i++) { var collection = collections[i]; - if (collection.type == "timeseries") { - DatabaseConnection.timeseries_collections = DatabaseConnection.timeseries_collections.filter(x => x != collection.name); - DatabaseConnection.timeseries_collections.push(collection.name); + DatabaseConnection.timeseries_collections = DatabaseConnection.timeseries_collections.filter(x => x != collection.name); + DatabaseConnection.timeseries_collections.push(collection.name); + if(collection.options && collection.options.timeseries) { + DatabaseConnection.timeseries_collections_metadata[collection.name] = collection.options.timeseries.metaField; + DatabaseConnection.timeseries_collections_time[collection.name] = collection.options.timeseries.timeField; } } } + static istimeseries(collectionname: string) { + if (DatabaseConnection.timeseries_collections.indexOf(collectionname) > -1) { + return true; + } + return false; + } + static usemetadata(collectionname: string) { + if (collectionname == "files" || collectionname == "fs.chunks" || collectionname == "fs.files") { + return true; + } + if(collectionname == "fullDocument._acl") return true; + if(Config.metadata_collections.indexOf(collectionname) > -1) { + const metadataname = DatabaseConnection.timeseries_collections_metadata[collectionname]; + if(!NoderedUtil.IsNullEmpty(metadataname)) return true; + } + return false; + } + static metadataname(collectionname: string) { + if (collectionname == "files" || collectionname == "fs.chunks" || collectionname == "fs.files") { + return "metadata"; + } + if(collectionname == "fullDocument._acl") return "fullDocument._acl"; + const metadataname = DatabaseConnection.timeseries_collections_metadata[collectionname]; + return metadataname; + } + static timefield(collectionname: string) { + if (collectionname == "files" || collectionname == "fs.chunks" || collectionname == "fs.files") { + return "_created"; + } + if(collectionname == "fullDocument._acl") return "fullDocument._created"; + const timefield = DatabaseConnection.timeseries_collections_time[collectionname]; + return timefield; + } public static collections_with_text_index: string[] = []; public static timeseries_collections: string[] = []; + public static timeseries_collections_metadata: any = {}; + public static timeseries_collections_time: any = {}; async ensureindexes(parent: Span) { const span: Span = Logger.otel.startSubSpan("db.ensureindexes", parent); try { @@ -4252,24 +4284,6 @@ export class DatabaseConnection extends events.EventEmitter { Logger.otel.endSpan(span); } } - static istimeseries(collectionname: string) { - if (DatabaseConnection.timeseries_collections.indexOf(collectionname) > -1) { - return true; - } - return false; - } - static usemetadata(collectionname: string) { - if (collectionname == "files" || collectionname == "fs.chunks" || collectionname == "fs.files") { - return true; - } - // if (this.istimeseries("audit")) { - // return true; - // } - // if (DatabaseConnection.timeseries_collections.indexOf(collectionname) > -1) { - // return true; - // } - return false; - } static otel_label(collectionname: string, user: TokenUser | User, action: "query" | "count" | "aggregate" | "insert" | "insertmany" | "update" | "updatemany" | "replace" | "delete" | "deletemany") { if (Config.otel_trace_mongodb_per_users) { return { collection: collectionname, username: user.username }; diff --git a/OpenFlow/src/Messages/Message.ts b/OpenFlow/src/Messages/Message.ts index 5028f7d4..101b99de 100644 --- a/OpenFlow/src/Messages/Message.ts +++ b/OpenFlow/src/Messages/Message.ts @@ -1647,6 +1647,7 @@ export class Message { let tuser: TokenUser = null; let user: User = null; if (!NoderedUtil.IsNullEmpty(msg.jwt)) { + if(msg.validate_only) { this.command = "validatereply"; } span?.addEvent("using jwt, verify token"); type = "jwtsignin"; try { @@ -1774,8 +1775,10 @@ export class Message { tuser.username = msg.username; } } - if (cli) cli.clientagent = msg.clientagent as any; - if (cli) cli.clientversion = msg.clientversion; + if(msg.validate_only !== true) { + if (cli) cli.clientagent = msg.clientagent as any; + if (cli) cli.clientversion = msg.clientversion; + } if (user === null || user === undefined || tuser === null || tuser === undefined) { if (msg !== null && msg !== undefined) msg.error = "Unknown username or password"; await Audit.LoginFailed(tuser.username, type, "websocket", cli?.remoteip, cli?.clientagent, cli?.clientversion, span); @@ -1994,7 +1997,7 @@ export class Message { if (_id !== null && _id !== undefined && _id !== "" && _id != myid) { const user: TokenUser = await Crypt.verityToken(jwt); var qs: any[] = [{ _id: _id }]; - qs.push(Config.db.getbasequery(user, "_acl", [Rights.update])) + qs.push(Config.db.getbasequery(user, [Rights.update], "users")) const res = await Config.db.query({ query: { "$and": qs }, top: 1, collectionname: "users", jwt }, span); if (res.length == 0) { throw new Error("Unknown userid " + _id + " or permission denied"); diff --git a/OpenFlow/src/WebServer.ts b/OpenFlow/src/WebServer.ts index 751b498e..548d0a5e 100644 --- a/OpenFlow/src/WebServer.ts +++ b/OpenFlow/src/WebServer.ts @@ -115,6 +115,9 @@ export class WebServer { this.app.get("/livenessprobe", WebServer.get_livenessprobe.bind(this)); + // this.app.get("/heapdump", WebServer.get_heapdump.bind(this)) + // this.app.get("/crashme", WebServer.get_crashme.bind(this)) + this.app.get("/ipblock", async (req: any, res: any, next: any): Promise => { if (await WebServer.isBlocked(req)) { var remoteip = LoginProvider.remoteip(req); @@ -268,6 +271,43 @@ export class WebServer { }); Logger.instanse.info("Listening on " + Config.baseurl(), null); } + static async get_crashme(req: any, res: any, next: any): Promise { + const remoteip = LoginProvider.remoteip(req); + if(remoteip != "127.0.0.1" && remoteip != "::ffff:127.0.0.1") { + // Add security check at some point to only allow from localhost !!! + res.statusCode = 500; + return res.end(JSON.stringify({ "error": "Go away !!!", "remoteip": remoteip,"hostname": _hostname, dt: new Date() })); + } + let array = []; + while (true) { + array.push(new Array(10000000).join('x')); + await new Promise(resolve => { setTimeout(resolve, 1000) }); + } + // let buffer = []; + // const MB = (bytes) => Math.round(bytes/1024/1024) + 'MB' + // const memoryUsage = () => { + // const mem = process.memoryUsage(); + // return MB(mem.rss) + '\t' + MB(mem.heapTotal) + '\t' + MB(mem.external); + // } + // setInterval(()=>{ + // buffer.push(Buffer.alloc(1024 * 1024* 1024)); // Eat 1GB of RAM every second + // console.log(buffer.length + '\t' + memoryUsage()); + // }, 1000); + res.end(JSON.stringify({ "success": "true", "message": "Ok here we go, crash incomming!!!!", "remoteip": remoteip, "hostname": _hostname, dt: new Date() })); + res.end(); + } + + static async get_heapdump(req: any, res: any, next: any): Promise { + const remoteip = LoginProvider.remoteip(req); + if(remoteip != "127.0.0.1" && remoteip != "::ffff:127.0.0.1") { + // Add security check at some point to only allow from localhost !!! + res.statusCode = 500; + return res.end(JSON.stringify({ "error": "Go away !!!", "remoteip": remoteip,"hostname": _hostname, dt: new Date() })); + } + await Logger.otel.createheapdump(null); + res.end(JSON.stringify({ "success": "true", "remoteip": remoteip, "hostname": _hostname, dt: new Date() })); + res.end(); + } static get_livenessprobe(req: any, res: any, next: any): void { let span = Logger.otel.startSpanExpress("get_livenessprobe", req) try { diff --git a/OpenFlow/src/index.ts b/OpenFlow/src/index.ts index 7ec49ccb..58e06dc3 100644 --- a/OpenFlow/src/index.ts +++ b/OpenFlow/src/index.ts @@ -313,6 +313,7 @@ async function initDatabase(parent: Span): Promise { } }, randomNum2 * 1000); } + await Config.db.ParseTimeseries(span); return true; } catch (error) { Logger.instanse.error(error, span); @@ -365,9 +366,12 @@ var signals = { 'SIGTERM': 15 }; var housekeeping = null; -function handle(signal, value) { +async function handle(signal, value) { Logger.instanse.info(`process received a ${signal} signal with value ${value}`, null); try { + if(Config.heapdump_onstop) { + await Logger.otel.createheapdump(null); + } Config.db.shutdown(); Logger.otel.shutdown(); Logger.License.shutdown() diff --git a/OpenFlowNodeRED/package.json b/OpenFlowNodeRED/package.json index 137fc1a4..508e87d9 100644 --- a/OpenFlowNodeRED/package.json +++ b/OpenFlowNodeRED/package.json @@ -1,6 +1,6 @@ { "name": "@openiap/nodered", - "version": "1.4.35", + "version": "1.4.36", "description": "Simple wrapper around NodeRed, RabbitMQ and MongoDB to support a more scaleable NodeRed implementation.\r Also the \"backend\" for [OpenRPA](https://github.com/skadefro/OpenRPA)", "main": "index.js", "scripts": { @@ -57,4 +57,4 @@ "@types/compression": "^1.7.2", "@types/node": "^18.11.9" } -} +} \ No newline at end of file diff --git a/OpenFlowNodeRED/src/node-red-contrib-openflow-storage.ts b/OpenFlowNodeRED/src/node-red-contrib-openflow-storage.ts index 6aecca1b..70833656 100644 --- a/OpenFlowNodeRED/src/node-red-contrib-openflow-storage.ts +++ b/OpenFlowNodeRED/src/node-red-contrib-openflow-storage.ts @@ -961,9 +961,15 @@ export class noderedcontribopenflowstorage { } } } - Logger.instanse.info("storage", "onupdate", "noderedcontribopenflowstorage::onupdate DiffObjects " + new Date().toLocaleTimeString()); - if (this.DiffObjects(newsettings, oldsettings)) { - update = true; + if(oldsettings != null && newsettings != null) { + var _old = JSON.parse(JSON.stringify(oldsettings)); + var _new = JSON.parse(JSON.stringify(newsettings)); + delete _old.users; + delete _new.users; + Logger.instanse.info("storage", "onupdate", "noderedcontribopenflowstorage::onupdate DiffObjects " + new Date().toLocaleTimeString()); + if (this.DiffObjects(_new, _old)) { + update = true; + } } this._settings = newsettings; } catch (error) { @@ -996,7 +1002,7 @@ export class noderedcontribopenflowstorage { this.RED.log.warn("noderedcontribopenflowstorage::onupdate: Restart is needed, auto_restart_when_needed is false"); } else if (!exitprocess) { Logger.instanse.info("storage", "onupdate", "Restart not needed"); - this.RED.log.warn("noderedcontribopenflowstorage::onupdate: Restart not needed"); + // this.RED.log.warn("noderedcontribopenflowstorage::onupdate: Restart not needed"); } } diff --git a/OpenFlowNodeRED/src/nodered/nodes/rpa_nodes.ts b/OpenFlowNodeRED/src/nodered/nodes/rpa_nodes.ts index 38aaa2f9..1d04d327 100644 --- a/OpenFlowNodeRED/src/nodered/nodes/rpa_nodes.ts +++ b/OpenFlowNodeRED/src/nodered/nodes/rpa_nodes.ts @@ -128,7 +128,7 @@ export class rpa_detector_node { } async onclose(removed: boolean, done: any) { if (!NoderedUtil.IsNullEmpty(this.localqueue) && removed) { - NoderedUtil.CloseQueue({ queuename: this.localqueue }); + await NoderedUtil.CloseQueue({ queuename: this.localqueue }); this.localqueue = ""; } WebSocketClient.instance.events.removeListener("onsignedin", this._onsignedin); @@ -362,7 +362,9 @@ export class rpa_workflow_node { } async onclose(removed: boolean, done: any) { // if ((!NoderedUtil.IsNullEmpty(this.localqueue) && removed) || this.originallocalqueue != this.uid) { - await NoderedUtil.CloseQueue({ queuename: this.localqueue }); + if (!NoderedUtil.IsNullEmpty(this.localqueue)) { + await NoderedUtil.CloseQueue({ queuename: this.localqueue }); + } this.localqueue = ""; // } WebSocketClient.instance.events.removeListener("onsignedin", this._onsignedin); @@ -557,7 +559,7 @@ export class rpa_killworkflows_node { async onclose(removed: boolean, done: any) { // if ((!NoderedUtil.IsNullEmpty(this.localqueue) && removed) || this.originallocalqueue != this.uid) { if (!NoderedUtil.IsNullEmpty(this.localqueue)) { - NoderedUtil.CloseQueue({ queuename: this.localqueue }); + await NoderedUtil.CloseQueue({ queuename: this.localqueue }); this.localqueue = ""; } WebSocketClient.instance.events.removeListener("onsignedin", this._onsignedin); diff --git a/OpenFlowNodeRED/src/nodered/nodes/workflow_nodes.ts b/OpenFlowNodeRED/src/nodered/nodes/workflow_nodes.ts index b43a5cfa..4b4a134e 100644 --- a/OpenFlowNodeRED/src/nodered/nodes/workflow_nodes.ts +++ b/OpenFlowNodeRED/src/nodered/nodes/workflow_nodes.ts @@ -307,11 +307,11 @@ export class workflow_in_node { } } if (!NoderedUtil.IsNullEmpty(this.localqueue)) { - NoderedUtil.CloseQueue({ queuename: this.localqueue }); + await NoderedUtil.CloseQueue({ queuename: this.localqueue }); this.localqueue = ""; } if (!NoderedUtil.IsNullEmpty(this.localexchangequeue)) { - NoderedUtil.CloseQueue({ queuename: this.localexchangequeue }); + await NoderedUtil.CloseQueue({ queuename: this.localexchangequeue }); this.localexchangequeue = ""; } } catch (error) { @@ -709,7 +709,7 @@ export class assign_workflow_node { } async onclose(removed: boolean, done: any) { if (!NoderedUtil.IsNullEmpty(this.localqueue) && removed) { - NoderedUtil.CloseQueue({ queuename: this.localqueue }); + await NoderedUtil.CloseQueue({ queuename: this.localqueue }); this.localqueue = ""; } WebSocketClient.instance.events.removeListener("onsignedin", this._onsignedin); diff --git a/VERSION b/VERSION index de17646b..62f0c2ca 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.35 \ No newline at end of file +1.4.36 \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 983e687c..dc6b1aba 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -234,6 +234,12 @@ gulp.task("compose", shell.task([ // docker run -it --rm --privileged tonistiigi/binfmt --install all // docker buildx use default // docker buildx build --platform linux/amd64 -t openiap/openflow:edge . + // second work around + // wget https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64 + // chmod a+x buildx-v0.4.1.linux-amd64 + // mkdir -p ~/.docker/cli-plugins + // mv buildx-v0.4.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx + `docker buildx build -t openiap/openflow:edge -t openiap/openflow:` + version + ` --platform linux/amd64 --push .`, `docker buildx build -t openiap/nodered:edge -t openiap/nodered:` + version + ` --platform linux/amd64 --push --file ./OpenFlowNodeRED/Dockerfile .`, // `echo "docker buildx build -t openiap/openflow:edge -t openiap/openflow:` + version + ` --platform linux/amd64 --push ."`, diff --git a/package.json b/package.json index 073f3344..82e2c676 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openiap/openflow", - "version": "1.4.35", + "version": "1.4.36", "description": "Simple wrapper around NodeRed, RabbitMQ and MongoDB to support a more scaleable NodeRed implementation.\r Also the \"backend\" for [OpenRPA](https://github.com/skadefro/OpenRPA)", "main": "index.js", "scripts": { @@ -122,4 +122,4 @@ "watchify": "^4.0.0", "wtfnode": "^0.9.1" } -} +} \ No newline at end of file