Skip to content

Commit aa675ec

Browse files
committed
Improve clean acl speed with caching
1 parent 5a792da commit aa675ec

7 files changed

Lines changed: 89 additions & 30 deletions

File tree

OpenFlow/src/Auth.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,62 @@ export class Auth {
2424
}
2525

2626
public static authorizationCache: HashTable<CachedUser> = {};
27-
public static getUser(key: string): User {
28-
var res: CachedUser = this.authorizationCache[key];
27+
private static cacheTimer: NodeJS.Timeout;
28+
public static getUser(key: string, type: string): User {
29+
if (NoderedUtil.IsNullUndefinded(this.cacheTimer)) this.cacheTimer = setInterval(this.cleanCache, 60000)
30+
var res: CachedUser = this.authorizationCache[key + type];
2931
if (res === null || res === undefined) return null;
3032
var begin: number = res.firstsignin.getTime();
3133
var end: number = new Date().getTime();
3234
var seconds = Math.round((end - begin) / 1000);
33-
if (seconds < Config.api_credential_cache_seconds) {
35+
let cache_seconds: number = Config.api_credential_cache_seconds;
36+
if (type == "grafana") cache_seconds = Config.grafana_credential_cache_seconds;
37+
if (type == "dashboard") cache_seconds = Config.dashboard_credential_cache_seconds;
38+
if (type == "cleanacl") cache_seconds = Config.cleanacl_credential_cache_seconds;
39+
if (seconds < cache_seconds) {
3440
// Logger.instanse.info("Return user " + res.user.username + " from cache");
3541
return res.user;
3642
}
37-
this.RemoveUser(key);
43+
this.RemoveUser(key, type);
3844
return null;
3945
}
40-
public static async RemoveUser(key: string): Promise<void> {
46+
private static async cleanCache() {
47+
try {
48+
if (this.authorizationCache == null) return;
49+
const keys: string[] = Object.keys(this.authorizationCache);
50+
for (let i = keys.length - 1; i >= 0; i--) {
51+
let key: string = keys[i];
52+
var res: CachedUser = this.authorizationCache[key];
53+
if (res === null || res === undefined) continue;
54+
var begin: number = res.firstsignin.getTime();
55+
var end: number = new Date().getTime();
56+
var seconds = Math.round((end - begin) / 1000);
57+
let cache_seconds: number = Config.api_credential_cache_seconds;
58+
if (res.type == "grafana") cache_seconds = Config.grafana_credential_cache_seconds;
59+
if (res.type == "dashboard") cache_seconds = Config.dashboard_credential_cache_seconds;
60+
if (res.type == "cleanacl") cache_seconds = Config.cleanacl_credential_cache_seconds;
61+
if (seconds >= cache_seconds) {
62+
this.RemoveUser(key, res.type);
63+
}
64+
}
65+
} catch (error) {
66+
Logger.instanse.error(error)
67+
}
68+
}
69+
public static async RemoveUser(key: string, type: string): Promise<void> {
4170
await semaphore.down();
42-
if (!NoderedUtil.IsNullUndefinded(this.authorizationCache[key])) {
71+
if (!NoderedUtil.IsNullUndefinded(this.authorizationCache[key + type])) {
4372
Logger.instanse.info("Delete user with key " + key + " from cache");
44-
delete this.authorizationCache[key];
73+
delete this.authorizationCache[key + type];
4574
}
4675
semaphore.up();
4776
}
48-
public static async AddUser(user: User, key: string): Promise<void> {
77+
public static async AddUser(user: User, key: string, type: string): Promise<void> {
4978
await semaphore.down();
50-
if (NoderedUtil.IsNullUndefinded(this.authorizationCache[key])) {
51-
Logger.instanse.info("Adding user " + user.username + " to cache with key " + key);
52-
var cuser: CachedUser = new CachedUser(user, user._id);
53-
this.authorizationCache[key] = cuser;
79+
if (NoderedUtil.IsNullUndefinded(this.authorizationCache[key + type])) {
80+
Logger.instanse.info("Adding user " + user.name + " to cache with key " + key);
81+
var cuser: CachedUser = new CachedUser(user, user._id, type);
82+
this.authorizationCache[key + type] = cuser;
5483
}
5584
semaphore.up();
5685
}
@@ -59,7 +88,8 @@ export class CachedUser {
5988
public firstsignin: Date;
6089
constructor(
6190
public user: User,
62-
public _id: string
91+
public _id: string,
92+
public type: string
6393
) {
6494
this.firstsignin = new Date();
6595
}

OpenFlow/src/Config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ export class Config {
168168
public static tls_passphrase: string = Config.getEnv("tls_passphrase", "");
169169

170170
public static api_credential_cache_seconds: number = parseInt(Config.getEnv("api_credential_cache_seconds", "60000"));
171+
public static dashboard_credential_cache_seconds: number = parseInt(Config.getEnv("dashboard_credential_cache_seconds", "60000"));
172+
public static grafana_credential_cache_seconds: number = parseInt(Config.getEnv("grafana_credential_cache_seconds", "60000"));
173+
public static cleanacl_credential_cache_seconds: number = parseInt(Config.getEnv("grafana_credential_cache_seconds", "60000"));
171174
public static oauth_token_cache_seconds: number = parseInt(Config.getEnv("oauth_token_cache_seconds", "60000"));
172175
public static oauth_access_token_lifetime: number = parseInt(Config.getEnv("oauth_access_token_lifetime", "604800"));
173176
public static oauth_refresh_token_lifetime: number = parseInt(Config.getEnv("oauth_refresh_token_lifetime", "604800"));

OpenFlow/src/DatabaseConnection.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { OAuthProvider } from "./OAuthProvider";
1010
import { ValueRecorder, Counter } from "@opentelemetry/api-metrics"
1111
import { Span } from "@opentelemetry/api";
1212
import { Logger } from "./Logger";
13+
import { Auth } from "./Auth";
1314
// tslint:disable-next-line: typedef
1415
const safeObjectID = (s: string | number | ObjectID) => ObjectID.isValid(s) ? new ObjectID(s) : null;
1516
const isoDatePattern = new RegExp(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/);
@@ -162,6 +163,21 @@ export class DatabaseConnection {
162163
Logger.otel.endSpan(span);
163164
}
164165
}
166+
WellknownIdsArray: string[] = [
167+
WellknownIds.root,
168+
WellknownIds.admins,
169+
WellknownIds.users,
170+
WellknownIds.robots,
171+
WellknownIds.nodered_users,
172+
WellknownIds.nodered_admins,
173+
WellknownIds.nodered_api_users,
174+
WellknownIds.filestore_users,
175+
WellknownIds.filestore_admins,
176+
WellknownIds.robot_users,
177+
WellknownIds.robot_admins,
178+
WellknownIds.personal_nodered_users,
179+
WellknownIds.robot_agent_users
180+
]
165181

166182
async CleanACL<T extends Base>(item: T, user: TokenUser, parent: Span): Promise<T> {
167183
const span: Span = Logger.otel.startSubSpan("db.CleanACL", parent);
@@ -173,16 +189,26 @@ export class DatabaseConnection {
173189
const b = new Binary(Buffer.from(ace.rights, "base64"), 0);
174190
(ace.rights as any) = b;
175191
}
176-
const ot_end = Logger.otel.startTimer();
177-
const mongodbspan: Span = Logger.otel.startSubSpan("mongodb.find", span);
178-
mongodbspan.setAttribute("collection", "users");
179-
const arr = await this.db.collection("users").find({ _id: ace._id }).project({ name: 1 }).limit(1).toArray();
180-
mongodbspan.setAttribute("results", arr.length);
181-
Logger.otel.endSpan(mongodbspan);
182-
Logger.otel.endTimer(ot_end, DatabaseConnection.mongodb_query, { collection: "users" });
183-
if (arr.length == 0) {
184-
item._acl.splice(i, 1);
185-
} else { ace.name = arr[0].name; }
192+
if (this.WellknownIdsArray.indexOf(ace._id) == -1) {
193+
let user = await Auth.getUser(ace._id, "cleanacl");
194+
if (user == null || user != null) {
195+
const ot_end = Logger.otel.startTimer();
196+
const mongodbspan: Span = Logger.otel.startSubSpan("mongodb.find", span);
197+
mongodbspan.setAttribute("collection", "users");
198+
mongodbspan.setAttribute("query", JSON.stringify({ _id: ace._id }));
199+
const arr = await this.db.collection("users").find({ _id: ace._id }).project({ name: 1 }).limit(1).toArray();
200+
mongodbspan.setAttribute("results", arr.length);
201+
Logger.otel.endSpan(mongodbspan);
202+
Logger.otel.endTimer(ot_end, DatabaseConnection.mongodb_query, { collection: "users" });
203+
if (arr.length > 0) {
204+
user = arr[0];
205+
await Auth.AddUser(user, ace._id, "cleanacl");
206+
}
207+
}
208+
if (user == null) {
209+
item._acl.splice(i, 1);
210+
} else { ace.name = user.name; }
211+
}
186212
}
187213
}
188214
if (Config.force_add_admins) {

OpenFlow/src/LoginProvider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class LoginProvider {
201201
if (!NoderedUtil.IsNullEmpty(authorization) && authorization.indexOf(" ") > 1 &&
202202
(authorization.toLocaleLowerCase().startsWith("bearer") || authorization.toLocaleLowerCase().startsWith("jwt"))) {
203203
const token = authorization.split(" ")[1];
204-
let user: User = Auth.getUser(token);
204+
let user: User = Auth.getUser(token, "dashboard");
205205
let tuser: TokenUser;
206206
if (user == null) {
207207
try {
@@ -220,7 +220,7 @@ export class LoginProvider {
220220
if (user != null) {
221221
const allowed = user.roles.filter(x => x.name == "dashboardusers" || x.name == "admins");
222222
if (allowed.length > 0) {
223-
await Auth.AddUser(user, token);
223+
await Auth.AddUser(user, token, "dashboard");
224224
// Logger.instanse.info("dashboardauth: Authorized " + user.username + " for " + req.url);
225225
return res.send({
226226
status: "success",
@@ -242,13 +242,13 @@ export class LoginProvider {
242242
const [login, password] = Buffer.from(b64auth, "base64").toString().split(':')
243243
if (login && password) {
244244
span.setAttribute("username", login);
245-
let user: User = Auth.getUser(login + ":" + password);
245+
let user: User = Auth.getUser(login + ":" + password, "dashboard");
246246
if (user == null) user = await Auth.ValidateByPassword(login, password, span);
247247
if (user != null) {
248248
const allowed = user.roles.filter(x => x.name == "dashboardusers" || x.name == "admins");
249249
if (allowed.length > 0) {
250250
// Logger.instanse.info("dashboardauth: Authorized " + user.username + " for " + req.url);
251-
Auth.AddUser(user, login + ":" + password);
251+
Auth.AddUser(user, login + ":" + password, "dashboard");
252252
return res.send({
253253
status: "success",
254254
display_status: "Success",

OpenFlowNodeRED/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openiap/nodered",
3-
"version": "1.2.65",
3+
"version": "1.2.66",
44
"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)",
55
"main": "index.js",
66
"scripts": {

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.2.65
1+
1.2.66

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openiap/openflow",
3-
"version": "1.2.65",
3+
"version": "1.2.66",
44
"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)",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)