@flowerforce/flowerbase 1.8.4-beta.1 → 1.8.4-beta.2

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.
@@ -77,7 +77,7 @@ export declare const getErrorDetails: (error: unknown) => {
77
77
  };
78
78
  export declare const pickHeaders: (headers: FastifyRequest["headers"]) => unknown;
79
79
  export declare const createEventStore: (maxAgeMs: number, maxEvents: number) => EventStore;
80
- export declare const classifyRequest: (url: string) => "function" | "auth" | "http_endpoint" | "trigger" | "api" | "http";
80
+ export declare const classifyRequest: (url: string) => "function" | "api" | "auth" | "http_endpoint" | "trigger" | "http";
81
81
  export declare const createEventId: () => string;
82
82
  export declare const buildRulesMeta: (meta?: MonitMeta) => {
83
83
  collection: string;
@@ -490,7 +490,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
490
490
  emitMongoEvent('findOne');
491
491
  return null;
492
492
  }
493
- const winningRole = (0, utils_2.getWinningRole)(result, user, roles);
493
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(result, user, roles);
494
494
  logDebug('findOne winningRole', {
495
495
  collection: collName,
496
496
  winningRoleName: (_a = winningRole === null || winningRole === void 0 ? void 0 : winningRole.name) !== null && _a !== void 0 ? _a : null,
@@ -546,7 +546,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
546
546
  const formattedQuery = (0, utils_3.getFormattedQuery)(filters, query, user);
547
547
  // Retrieve the document to check permissions before deleting
548
548
  const result = yield collection.findOne(buildAndQuery(formattedQuery));
549
- const winningRole = (0, utils_2.getWinningRole)(result, user, roles);
549
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(result, user, roles);
550
550
  logDebug('delete winningRole', {
551
551
  collection: collName,
552
552
  userId: getUserId(user),
@@ -600,7 +600,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
600
600
  try {
601
601
  if (!run_as_system) {
602
602
  (0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.CREATE);
603
- const winningRole = (0, utils_2.getWinningRole)(data, user, roles);
603
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(data, user, roles);
604
604
  const { status, document } = winningRole
605
605
  ? yield (0, machines_1.checkValidation)(winningRole, {
606
606
  type: 'insert',
@@ -673,7 +673,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
673
673
  }
674
674
  throw new Error('Update not permitted');
675
675
  }
676
- const winningRole = (0, utils_2.getWinningRole)(result, user, roles);
676
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(result, user, roles);
677
677
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
678
678
  const updatedPaths = getUpdatedPaths(normalizedData);
679
679
  const docToCheck = applyDocumentUpdateOperators(result, normalizedData);
@@ -756,7 +756,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
756
756
  : [applyDocumentUpdateOperators(currentDoc, normalizedData)];
757
757
  docToCheck = computedDoc;
758
758
  }
759
- const winningRole = (0, utils_2.getWinningRole)(docToCheck, user, roles);
759
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(docToCheck, user, roles);
760
760
  const { status, document } = winningRole
761
761
  ? yield (0, machines_1.checkValidation)(winningRole, {
762
762
  type: validationType,
@@ -776,7 +776,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
776
776
  emitMongoEvent('findOneAndUpdate');
777
777
  return updateResult;
778
778
  }
779
- const readRole = (0, utils_2.getWinningRole)(updateResult, user, roles);
779
+ const readRole = yield (0, utils_2.getWinningRoleAsync)(updateResult, user, roles);
780
780
  const readResult = readRole
781
781
  ? yield (0, machines_1.checkValidation)(readRole, {
782
782
  type: 'read',
@@ -844,7 +844,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
844
844
  const response = yield originalToArray();
845
845
  const filteredResponse = yield Promise.all(response.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
846
846
  var _a;
847
- const winningRole = (0, utils_2.getWinningRole)(currentDoc, user, roles);
847
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
848
848
  logDebug('find winningRole', {
849
849
  collection: collName,
850
850
  userId: getUserId(user),
@@ -976,7 +976,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
976
976
  const isValidChange = (change) => __awaiter(void 0, void 0, void 0, function* () {
977
977
  const { fullDocument, updateDescription } = change;
978
978
  const hasFullDocument = !!fullDocument;
979
- const winningRole = (0, utils_2.getWinningRole)(fullDocument, user, roles);
979
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(fullDocument, user, roles);
980
980
  const fullDocumentValidation = winningRole
981
981
  ? yield (0, machines_1.checkValidation)(winningRole, {
982
982
  type: 'read',
@@ -1088,7 +1088,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
1088
1088
  (0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.CREATE);
1089
1089
  // Validate each document against user's roles
1090
1090
  const filteredItems = yield Promise.all(documents.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
1091
- const winningRole = (0, utils_2.getWinningRole)(currentDoc, user, roles);
1091
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
1092
1092
  const { status, document } = winningRole
1093
1093
  ? yield (0, machines_1.checkValidation)(winningRole, {
1094
1094
  type: 'insert',
@@ -1133,7 +1133,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
1133
1133
  const updatedPaths = getUpdatedPaths(normalizedData);
1134
1134
  const docsToCheck = result.map((currentDoc) => applyDocumentUpdateOperators(currentDoc, normalizedData));
1135
1135
  const filteredItems = yield Promise.all(docsToCheck.map((currentDoc, index) => __awaiter(void 0, void 0, void 0, function* () {
1136
- const winningRole = (0, utils_2.getWinningRole)(currentDoc, user, roles);
1136
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
1137
1137
  const { status, document } = winningRole
1138
1138
  ? yield (0, machines_1.checkValidation)(winningRole, {
1139
1139
  type: 'write',
@@ -1187,7 +1187,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
1187
1187
  const data = yield collection.find({ $and: formattedQuery }).toArray();
1188
1188
  // Filter and validate each document based on user's roles
1189
1189
  const filteredItems = yield Promise.all(data.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
1190
- const winningRole = (0, utils_2.getWinningRole)(currentDoc, user, roles);
1190
+ const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
1191
1191
  const { status, document } = winningRole
1192
1192
  ? yield (0, machines_1.checkValidation)(winningRole, {
1193
1193
  type: 'delete',
@@ -35,11 +35,11 @@ export declare const generateContextData: ({ user, services, app, rules, current
35
35
  request: {
36
36
  remoteIPAddress: string | undefined;
37
37
  id?: string | undefined;
38
- host?: string | undefined;
39
- method?: string | undefined;
40
- url?: string | undefined;
41
38
  ips?: string[];
39
+ host?: string | undefined;
42
40
  hostname?: string | undefined;
41
+ url?: string | undefined;
42
+ method?: string | undefined;
43
43
  ip?: string | undefined;
44
44
  };
45
45
  user: unknown;
@@ -1,4 +1,5 @@
1
1
  import { PermissionExpression } from './interface';
2
2
  import { MachineContext } from './machines/interface';
3
+ export declare const evaluateExpandedExpression: (expression: unknown, params: MachineContext["params"], user?: MachineContext["user"]) => Promise<boolean>;
3
4
  export declare const evaluateExpression: (params: MachineContext["params"], expression?: PermissionExpression, user?: MachineContext["user"]) => Promise<boolean>;
4
5
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/roles/helpers.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAkBrD,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,cAAc,CAAC,QAAQ,CAAC,EAChC,aAAa,oBAAoB,EACjC,OAAO,cAAc,CAAC,MAAM,CAAC,KAC5B,OAAO,CAAC,OAAO,CAoBjB,CAAA"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/roles/helpers.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA+DrD,eAAO,MAAM,0BAA0B,GACrC,YAAY,OAAO,EACnB,QAAQ,cAAc,CAAC,QAAQ,CAAC,EAChC,OAAO,cAAc,CAAC,MAAM,CAAC,KAC5B,OAAO,CAAC,OAAO,CAwDjB,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,cAAc,CAAC,QAAQ,CAAC,EAChC,aAAa,oBAAoB,EACjC,OAAO,cAAc,CAAC,MAAM,CAAC,KAC5B,OAAO,CAAC,OAAO,CAMjB,CAAA"}
@@ -12,13 +12,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.evaluateExpression = void 0;
15
+ exports.evaluateExpression = exports.evaluateExpandedExpression = void 0;
16
16
  const services_1 = require("../../services");
17
17
  const state_1 = require("../../state");
18
18
  const context_1 = require("../context");
19
19
  const rules_1 = require("../rules");
20
20
  const utils_1 = __importDefault(require("../rules-matcher/utils"));
21
21
  const functionsConditions = ['%%true', '%%false'];
22
+ const andConditions = ['$and', '%and'];
23
+ const orConditions = ['$or', '%or'];
22
24
  const normalizeUserRole = (user) => {
23
25
  if (!user)
24
26
  return user;
@@ -34,17 +36,84 @@ const normalizeUserRole = (user) => {
34
36
  ? Object.assign(Object.assign({}, candidate), { role: customRole })
35
37
  : user;
36
38
  };
39
+ const buildEvaluationContext = (params, user) => {
40
+ var _a, _b, _c;
41
+ const normalizedUser = normalizeUserRole(user);
42
+ return Object.assign(Object.assign(Object.assign({}, ((_a = params.expansions) !== null && _a !== void 0 ? _a : {})), ((_b = params.cursor) !== null && _b !== void 0 ? _b : {})), { '%%root': params.cursor, '%%prevRoot': (_c = params.expansions) === null || _c === void 0 ? void 0 : _c['%%prevRoot'], '%%user': normalizedUser, '%%true': true, '%%false': false });
43
+ };
44
+ const getFunctionCondition = (expression) => {
45
+ if (!expression || typeof expression !== 'object' || Array.isArray(expression)) {
46
+ return null;
47
+ }
48
+ const entries = Object.entries(expression);
49
+ if (entries.length !== 1) {
50
+ return null;
51
+ }
52
+ const [key, value] = entries[0];
53
+ if (!functionsConditions.includes(key)) {
54
+ return null;
55
+ }
56
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
57
+ return null;
58
+ }
59
+ return Object.prototype.hasOwnProperty.call(value, '%function')
60
+ ? [key, value]
61
+ : null;
62
+ };
63
+ const evaluateExpandedExpression = (expression, params, user) => __awaiter(void 0, void 0, void 0, function* () {
64
+ if (typeof expression === 'boolean') {
65
+ return expression;
66
+ }
67
+ if (!expression || typeof expression !== 'object') {
68
+ return Boolean(expression);
69
+ }
70
+ const block = expression;
71
+ const functionCondition = getFunctionCondition(block);
72
+ if (functionCondition) {
73
+ return evaluateComplexExpression(functionCondition, params, user);
74
+ }
75
+ const andKey = andConditions.find((key) => Object.prototype.hasOwnProperty.call(block, key));
76
+ if (andKey) {
77
+ const conditions = Array.isArray(block[andKey]) ? block[andKey] : [];
78
+ if (!conditions.length)
79
+ return true;
80
+ for (const condition of conditions) {
81
+ if (!(yield (0, exports.evaluateExpandedExpression)(condition, params, user))) {
82
+ return false;
83
+ }
84
+ }
85
+ return true;
86
+ }
87
+ const orKey = orConditions.find((key) => Object.prototype.hasOwnProperty.call(block, key));
88
+ if (orKey) {
89
+ const conditions = Array.isArray(block[orKey]) ? block[orKey] : [];
90
+ if (!conditions.length)
91
+ return true;
92
+ for (const condition of conditions) {
93
+ if (yield (0, exports.evaluateExpandedExpression)(condition, params, user)) {
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ const keys = Object.keys(block);
100
+ if (keys.length > 1) {
101
+ for (const key of keys) {
102
+ if (!(yield (0, exports.evaluateExpandedExpression)({ [key]: block[key] }, params, user))) {
103
+ return false;
104
+ }
105
+ }
106
+ return true;
107
+ }
108
+ return utils_1.default.checkRule(block, buildEvaluationContext(params, user), {});
109
+ });
110
+ exports.evaluateExpandedExpression = evaluateExpandedExpression;
37
111
  const evaluateExpression = (params, expression, user) => __awaiter(void 0, void 0, void 0, function* () {
38
- var _a;
39
112
  if (!expression || typeof expression === 'boolean')
40
113
  return !!expression;
41
- const normalizedUser = normalizeUserRole(user);
42
- const value = Object.assign(Object.assign(Object.assign({}, params.expansions), params.cursor), { '%%root': params.cursor, '%%prevRoot': (_a = params.expansions) === null || _a === void 0 ? void 0 : _a['%%prevRoot'], '%%user': normalizedUser, '%%true': true, '%%false': false });
114
+ const value = buildEvaluationContext(params, user);
43
115
  const conditions = (0, rules_1.expandQuery)(expression, value);
44
- const complexCondition = Object.entries(conditions).find(([key]) => functionsConditions.includes(key));
45
- return complexCondition
46
- ? yield evaluateComplexExpression(complexCondition, params, normalizedUser)
47
- : utils_1.default.checkRule(conditions, value, {});
116
+ return (0, exports.evaluateExpandedExpression)(conditions, params, user);
48
117
  });
49
118
  exports.evaluateExpression = evaluateExpression;
50
119
  const evaluateComplexExpression = (condition, params, user) => __awaiter(void 0, void 0, void 0, function* () {
@@ -12,6 +12,7 @@ import { LogMachineInfoParams } from './interface';
12
12
  * @returns {Role | null} - Returns the first role that matches the `apply_when` condition, or `null` if none match.
13
13
  */
14
14
  export declare const getWinningRole: (document: OptionalId<Document> | null, user: User, roles?: Role[]) => Role | null;
15
+ export declare const getWinningRoleAsync: (document: OptionalId<Document> | null, user: User, roles?: Role[]) => Promise<Role | null>;
15
16
  /**
16
17
  * Checks if the `apply_when` condition is valid for the given user and document.
17
18
  *
@@ -22,6 +23,7 @@ export declare const getWinningRole: (document: OptionalId<Document> | null, use
22
23
  * @returns {boolean} - Returns `true` if at least one valid rule is found, otherwise `false`.
23
24
  */
24
25
  export declare const checkApplyWhen: (apply_when: Role["apply_when"], user: User, document: OptionalId<Document> | null) => boolean;
26
+ export declare const checkApplyWhenAsync: (apply_when: Role["apply_when"], user: User, document: OptionalId<Document> | null) => Promise<boolean>;
25
27
  /**
26
28
  * Logs machine step information if logging is enabled.
27
29
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/roles/machines/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAGzC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,EACrC,MAAM,IAAI,EACV,QAAO,IAAI,EAAO,KACjB,IAAI,GAAG,IAQT,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,YAAY,IAAI,CAAC,YAAY,CAAC,EAC9B,MAAM,IAAI,EACV,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,YAQtC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,sCAK5B,oBAAoB,SAEtB,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/roles/machines/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAIzC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,EACrC,MAAM,IAAI,EACV,QAAO,IAAI,EAAO,KACjB,IAAI,GAAG,IAQT,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,EACrC,MAAM,IAAI,EACV,QAAO,IAAI,EAAO,KACjB,OAAO,CAAC,IAAI,GAAG,IAAI,CAQrB,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,YAAY,IAAI,CAAC,YAAY,CAAC,EAC9B,MAAM,IAAI,EACV,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,YAQtC,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,YAAY,IAAI,CAAC,YAAY,CAAC,EAC9B,MAAM,IAAI,EACV,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,qBActC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,sCAK5B,oBAAoB,SAEtB,CAAA"}
@@ -1,7 +1,17 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.logMachineInfo = exports.checkApplyWhen = exports.getWinningRole = void 0;
12
+ exports.logMachineInfo = exports.checkApplyWhenAsync = exports.checkApplyWhen = exports.getWinningRoleAsync = exports.getWinningRole = void 0;
4
13
  const utils_1 = require("../../../services/mongodb-atlas/utils");
14
+ const helpers_1 = require("../helpers");
5
15
  /**
6
16
  * Determines the first applicable role for a given user and document.
7
17
  *
@@ -22,6 +32,17 @@ const getWinningRole = (document, user, roles = []) => {
22
32
  return null;
23
33
  };
24
34
  exports.getWinningRole = getWinningRole;
35
+ const getWinningRoleAsync = (document_1, user_1, ...args_1) => __awaiter(void 0, [document_1, user_1, ...args_1], void 0, function* (document, user, roles = []) {
36
+ if (!roles.length)
37
+ return null;
38
+ for (const role of roles) {
39
+ if (yield (0, exports.checkApplyWhenAsync)(role.apply_when, user, document)) {
40
+ return role;
41
+ }
42
+ }
43
+ return null;
44
+ });
45
+ exports.getWinningRoleAsync = getWinningRoleAsync;
25
46
  /**
26
47
  * Checks if the `apply_when` condition is valid for the given user and document.
27
48
  *
@@ -40,6 +61,17 @@ const checkApplyWhen = (apply_when, user, document) => {
40
61
  return !!validRule.length;
41
62
  };
42
63
  exports.checkApplyWhen = checkApplyWhen;
64
+ const checkApplyWhenAsync = (apply_when, user, document) => __awaiter(void 0, void 0, void 0, function* () {
65
+ return (0, helpers_1.evaluateExpression)({
66
+ type: 'read',
67
+ roles: [],
68
+ cursor: document,
69
+ expansions: {
70
+ '%%prevRoot': undefined
71
+ }
72
+ }, apply_when, user);
73
+ });
74
+ exports.checkApplyWhenAsync = checkApplyWhenAsync;
43
75
  /**
44
76
  * Logs machine step information if logging is enabled.
45
77
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.8.4-beta.1",
3
+ "version": "1.8.4-beta.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,7 +19,7 @@ import {
19
19
  import { Rules } from '../../features/rules/interface'
20
20
  import { buildRulesMeta } from '../../monitoring/utils'
21
21
  import { checkValidation } from '../../utils/roles/machines'
22
- import { getWinningRole } from '../../utils/roles/machines/utils'
22
+ import { getWinningRoleAsync } from '../../utils/roles/machines/utils'
23
23
  import { emitServiceEvent } from '../monitoring'
24
24
  import { CHANGESTREAM } from '../../constants'
25
25
  import {
@@ -561,9 +561,9 @@ const getOperators: GetOperatorsFunction = (
561
561
  const resolvedOptions =
562
562
  projection || normalizedOptions
563
563
  ? {
564
- ...(normalizedOptions ?? {}),
565
- ...(projection ? { projection } : {})
566
- }
564
+ ...(normalizedOptions ?? {}),
565
+ ...(projection ? { projection } : {})
566
+ }
567
567
  : undefined
568
568
  const resolvedQuery = query ?? {}
569
569
  if (!run_as_system) {
@@ -604,7 +604,7 @@ const getOperators: GetOperatorsFunction = (
604
604
  return null
605
605
  }
606
606
 
607
- const winningRole = getWinningRole(result, user, roles)
607
+ const winningRole = await getWinningRoleAsync(result, user, roles)
608
608
 
609
609
  logDebug('findOne winningRole', {
610
610
  collection: collName,
@@ -613,15 +613,15 @@ const getOperators: GetOperatorsFunction = (
613
613
  })
614
614
  const { status, document } = winningRole
615
615
  ? await checkValidation(
616
- winningRole,
617
- {
618
- type: 'read',
619
- roles,
620
- cursor: result,
621
- expansions: getValidationExpansions(result)
622
- },
623
- user
624
- )
616
+ winningRole,
617
+ {
618
+ type: 'read',
619
+ roles,
620
+ cursor: result,
621
+ expansions: getValidationExpansions(result)
622
+ },
623
+ user
624
+ )
625
625
  : fallbackAccess(result)
626
626
 
627
627
  // Return validated document or empty object if not permitted
@@ -669,7 +669,7 @@ const getOperators: GetOperatorsFunction = (
669
669
 
670
670
  // Retrieve the document to check permissions before deleting
671
671
  const result = await collection.findOne(buildAndQuery(formattedQuery))
672
- const winningRole = getWinningRole(result, user, roles)
672
+ const winningRole = await getWinningRoleAsync(result, user, roles)
673
673
 
674
674
  logDebug('delete winningRole', {
675
675
  collection: collName,
@@ -678,15 +678,15 @@ const getOperators: GetOperatorsFunction = (
678
678
  })
679
679
  const { status } = winningRole
680
680
  ? await checkValidation(
681
- winningRole,
682
- {
683
- type: 'delete',
684
- roles,
685
- cursor: result,
686
- expansions: getValidationExpansions(result)
687
- },
688
- user
689
- )
681
+ winningRole,
682
+ {
683
+ type: 'delete',
684
+ roles,
685
+ cursor: result,
686
+ expansions: getValidationExpansions(result)
687
+ },
688
+ user
689
+ )
690
690
  : fallbackAccess(result)
691
691
 
692
692
  if (!status) {
@@ -733,19 +733,19 @@ const getOperators: GetOperatorsFunction = (
733
733
  collection.collectionName,
734
734
  CRUD_OPERATIONS.CREATE
735
735
  )
736
- const winningRole = getWinningRole(data, user, roles)
736
+ const winningRole = await getWinningRoleAsync(data, user, roles)
737
737
 
738
738
  const { status, document } = winningRole
739
739
  ? await checkValidation(
740
- winningRole,
741
- {
742
- type: 'insert',
743
- roles,
744
- cursor: data,
745
- expansions: getValidationExpansions()
746
- },
747
- user
748
- )
740
+ winningRole,
741
+ {
742
+ type: 'insert',
743
+ roles,
744
+ cursor: data,
745
+ expansions: getValidationExpansions()
746
+ },
747
+ user
748
+ )
749
749
  : fallbackAccess(data)
750
750
 
751
751
  if (!status || !isEqual(data, document)) {
@@ -823,7 +823,7 @@ const getOperators: GetOperatorsFunction = (
823
823
  throw new Error('Update not permitted')
824
824
  }
825
825
 
826
- const winningRole = getWinningRole(result, user, roles)
826
+ const winningRole = await getWinningRoleAsync(result, user, roles)
827
827
 
828
828
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
829
829
  const updatedPaths = getUpdatedPaths(normalizedData)
@@ -831,15 +831,15 @@ const getOperators: GetOperatorsFunction = (
831
831
  // Validate update permissions
832
832
  const { status, document } = winningRole
833
833
  ? await checkValidation(
834
- winningRole,
835
- {
836
- type: 'write',
837
- roles,
838
- cursor: docToCheck,
839
- expansions: getValidationExpansions(result)
840
- },
841
- user
842
- )
834
+ winningRole,
835
+ {
836
+ type: 'write',
837
+ roles,
838
+ cursor: docToCheck,
839
+ expansions: getValidationExpansions(result)
840
+ },
841
+ user
842
+ )
843
843
  : fallbackAccess(docToCheck)
844
844
  // Ensure no unauthorized changes are made
845
845
  const areDocumentsEqual = areUpdatedFieldsAllowed(
@@ -918,31 +918,31 @@ const getOperators: GetOperatorsFunction = (
918
918
  } else {
919
919
  const [computedDoc] = Array.isArray(normalizedData)
920
920
  ? await collection
921
- .aggregate([
922
- { $match: buildAndQuery(safeQuery) },
923
- { $limit: 1 },
924
- ...normalizedData
925
- ])
926
- .toArray()
921
+ .aggregate([
922
+ { $match: buildAndQuery(safeQuery) },
923
+ { $limit: 1 },
924
+ ...normalizedData
925
+ ])
926
+ .toArray()
927
927
  : [applyDocumentUpdateOperators(currentDoc, normalizedData as Document)]
928
928
  docToCheck = computedDoc
929
929
  }
930
930
 
931
- const winningRole = getWinningRole(docToCheck, user, roles)
931
+ const winningRole = await getWinningRoleAsync(docToCheck, user, roles)
932
932
 
933
933
  const { status, document } = winningRole
934
934
  ? await checkValidation(
935
- winningRole,
936
- {
937
- type: validationType,
938
- roles,
939
- cursor: docToCheck,
940
- expansions: getValidationExpansions(
941
- validationType === 'insert' ? undefined : currentDoc
942
- )
943
- },
944
- user
945
- )
935
+ winningRole,
936
+ {
937
+ type: validationType,
938
+ roles,
939
+ cursor: docToCheck,
940
+ expansions: getValidationExpansions(
941
+ validationType === 'insert' ? undefined : currentDoc
942
+ )
943
+ },
944
+ user
945
+ )
946
946
  : fallbackAccess(docToCheck)
947
947
 
948
948
  const areDocumentsEqual = areUpdatedFieldsAllowed(
@@ -956,28 +956,28 @@ const getOperators: GetOperatorsFunction = (
956
956
 
957
957
  const updateResult = normalizedOptions
958
958
  ? await collection.findOneAndUpdate(
959
- buildAndQuery(safeQuery),
960
- normalizedData,
961
- normalizedOptions
962
- )
959
+ buildAndQuery(safeQuery),
960
+ normalizedData,
961
+ normalizedOptions
962
+ )
963
963
  : await collection.findOneAndUpdate(buildAndQuery(safeQuery), normalizedData)
964
964
  if (!updateResult) {
965
965
  emitMongoEvent('findOneAndUpdate')
966
966
  return updateResult
967
967
  }
968
968
 
969
- const readRole = getWinningRole(updateResult, user, roles)
969
+ const readRole = await getWinningRoleAsync(updateResult, user, roles)
970
970
  const readResult = readRole
971
971
  ? await checkValidation(
972
- readRole,
973
- {
974
- type: 'read',
975
- roles,
976
- cursor: updateResult,
977
- expansions: getValidationExpansions(updateResult)
978
- },
979
- user
980
- )
972
+ readRole,
973
+ {
974
+ type: 'read',
975
+ roles,
976
+ cursor: updateResult,
977
+ expansions: getValidationExpansions(updateResult)
978
+ },
979
+ user
980
+ )
981
981
  : fallbackAccess(updateResult)
982
982
 
983
983
  const sanitizedDoc = readResult.status
@@ -1026,9 +1026,9 @@ const getOperators: GetOperatorsFunction = (
1026
1026
  const resolvedOptions =
1027
1027
  projection || normalizedOptions
1028
1028
  ? {
1029
- ...(normalizedOptions ?? {}),
1030
- ...(projection ? { projection } : {})
1031
- }
1029
+ ...(normalizedOptions ?? {}),
1030
+ ...(projection ? { projection } : {})
1031
+ }
1032
1032
  : undefined
1033
1033
  if (!run_as_system) {
1034
1034
  checkDenyOperation(
@@ -1053,7 +1053,7 @@ const getOperators: GetOperatorsFunction = (
1053
1053
 
1054
1054
  const filteredResponse = await Promise.all(
1055
1055
  response.map(async (currentDoc) => {
1056
- const winningRole = getWinningRole(currentDoc, user, roles)
1056
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1057
1057
 
1058
1058
  logDebug('find winningRole', {
1059
1059
  collection: collName,
@@ -1063,15 +1063,15 @@ const getOperators: GetOperatorsFunction = (
1063
1063
  })
1064
1064
  const { status, document } = winningRole
1065
1065
  ? await checkValidation(
1066
- winningRole,
1067
- {
1068
- type: 'read',
1069
- roles,
1070
- cursor: currentDoc,
1071
- expansions: getValidationExpansions(currentDoc)
1072
- },
1073
- user
1074
- )
1066
+ winningRole,
1067
+ {
1068
+ type: 'read',
1069
+ roles,
1070
+ cursor: currentDoc,
1071
+ expansions: getValidationExpansions(currentDoc)
1072
+ },
1073
+ user
1074
+ )
1075
1075
  : fallbackAccess(currentDoc)
1076
1076
 
1077
1077
  return status ? document : undefined
@@ -1190,19 +1190,19 @@ const getOperators: GetOperatorsFunction = (
1190
1190
  const allowDeleteBypass = watchPipelineRequestsDelete(requestedPipeline)
1191
1191
  const firstStep = watchFormattedQuery.length
1192
1192
  ? {
1193
- $match: allowDeleteBypass
1194
- ? {
1195
- $or: [
1196
- {
1197
- $and: watchFormattedQuery
1198
- },
1199
- { operationType: 'delete' }
1200
- ]
1201
- }
1202
- : {
1193
+ $match: allowDeleteBypass
1194
+ ? {
1195
+ $or: [
1196
+ {
1203
1197
  $and: watchFormattedQuery
1204
- }
1205
- }
1198
+ },
1199
+ { operationType: 'delete' }
1200
+ ]
1201
+ }
1202
+ : {
1203
+ $and: watchFormattedQuery
1204
+ }
1205
+ }
1206
1206
  : undefined
1207
1207
 
1208
1208
  const formattedPipeline = [firstStep, ...requestedPipeline].filter(
@@ -1221,33 +1221,33 @@ const getOperators: GetOperatorsFunction = (
1221
1221
  const isValidChange = async (change: Document) => {
1222
1222
  const { fullDocument, updateDescription } = change
1223
1223
  const hasFullDocument = !!fullDocument
1224
- const winningRole = getWinningRole(fullDocument, user, roles)
1224
+ const winningRole = await getWinningRoleAsync(fullDocument, user, roles)
1225
1225
 
1226
1226
  const fullDocumentValidation = winningRole
1227
1227
  ? await checkValidation(
1228
- winningRole,
1229
- {
1230
- type: 'read',
1231
- roles,
1232
- cursor: fullDocument,
1233
- expansions: getValidationExpansions(fullDocument)
1234
- },
1235
- user
1236
- )
1228
+ winningRole,
1229
+ {
1230
+ type: 'read',
1231
+ roles,
1232
+ cursor: fullDocument,
1233
+ expansions: getValidationExpansions(fullDocument)
1234
+ },
1235
+ user
1236
+ )
1237
1237
  : fallbackAccess(fullDocument)
1238
1238
  const { status, document } = fullDocumentValidation
1239
1239
 
1240
1240
  const { status: updatedFieldsStatus, document: updatedFields } = winningRole
1241
1241
  ? await checkValidation(
1242
- winningRole,
1243
- {
1244
- type: 'read',
1245
- roles,
1246
- cursor: updateDescription?.updatedFields,
1247
- expansions: getValidationExpansions(fullDocument)
1248
- },
1249
- user
1250
- )
1242
+ winningRole,
1243
+ {
1244
+ type: 'read',
1245
+ roles,
1246
+ cursor: updateDescription?.updatedFields,
1247
+ expansions: getValidationExpansions(fullDocument)
1248
+ },
1249
+ user
1250
+ )
1251
1251
  : fallbackAccess(updateDescription?.updatedFields)
1252
1252
 
1253
1253
  return {
@@ -1389,19 +1389,19 @@ const getOperators: GetOperatorsFunction = (
1389
1389
  // Validate each document against user's roles
1390
1390
  const filteredItems = await Promise.all(
1391
1391
  documents.map(async (currentDoc) => {
1392
- const winningRole = getWinningRole(currentDoc, user, roles)
1392
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1393
1393
 
1394
1394
  const { status, document } = winningRole
1395
1395
  ? await checkValidation(
1396
- winningRole,
1397
- {
1398
- type: 'insert',
1399
- roles,
1400
- cursor: currentDoc,
1401
- expansions: getValidationExpansions()
1402
- },
1403
- user
1404
- )
1396
+ winningRole,
1397
+ {
1398
+ type: 'insert',
1399
+ roles,
1400
+ cursor: currentDoc,
1401
+ expansions: getValidationExpansions()
1402
+ },
1403
+ user
1404
+ )
1405
1405
  : fallbackAccess(currentDoc)
1406
1406
 
1407
1407
  return status ? document : undefined
@@ -1453,19 +1453,19 @@ const getOperators: GetOperatorsFunction = (
1453
1453
 
1454
1454
  const filteredItems = await Promise.all(
1455
1455
  docsToCheck.map(async (currentDoc, index) => {
1456
- const winningRole = getWinningRole(currentDoc, user, roles)
1456
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1457
1457
 
1458
1458
  const { status, document } = winningRole
1459
1459
  ? await checkValidation(
1460
- winningRole,
1461
- {
1462
- type: 'write',
1463
- roles,
1464
- cursor: currentDoc,
1465
- expansions: getValidationExpansions(result[index])
1466
- },
1467
- user
1468
- )
1460
+ winningRole,
1461
+ {
1462
+ type: 'write',
1463
+ roles,
1464
+ cursor: currentDoc,
1465
+ expansions: getValidationExpansions(result[index])
1466
+ },
1467
+ user
1468
+ )
1469
1469
  : fallbackAccess(currentDoc)
1470
1470
 
1471
1471
  return status ? document : undefined
@@ -1529,19 +1529,19 @@ const getOperators: GetOperatorsFunction = (
1529
1529
  // Filter and validate each document based on user's roles
1530
1530
  const filteredItems = await Promise.all(
1531
1531
  data.map(async (currentDoc) => {
1532
- const winningRole = getWinningRole(currentDoc, user, roles)
1532
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1533
1533
 
1534
1534
  const { status, document } = winningRole
1535
1535
  ? await checkValidation(
1536
- winningRole,
1537
- {
1538
- type: 'delete',
1539
- roles,
1540
- cursor: currentDoc,
1541
- expansions: getValidationExpansions(currentDoc)
1542
- },
1543
- user
1544
- )
1536
+ winningRole,
1537
+ {
1538
+ type: 'delete',
1539
+ roles,
1540
+ cursor: currentDoc,
1541
+ expansions: getValidationExpansions(currentDoc)
1542
+ },
1543
+ user
1544
+ )
1545
1545
  : fallbackAccess(currentDoc)
1546
1546
 
1547
1547
  return status ? document : undefined
@@ -1,7 +1,48 @@
1
1
  import { evaluateExpression } from '../roles/helpers'
2
2
  import { Params } from '../roles/interface'
3
+ import { GenerateContext } from '../context'
4
+ import { StateManager } from '../../state'
5
+
6
+ jest.mock('../context', () => ({
7
+ GenerateContext: jest.fn()
8
+ }))
9
+
10
+ jest.mock('../../state', () => ({
11
+ StateManager: {
12
+ select: jest.fn()
13
+ }
14
+ }))
15
+
16
+ jest.mock('../../services', () => ({
17
+ services: {}
18
+ }))
19
+
20
+ const mockedGenerateContext = jest.mocked(GenerateContext)
21
+ const mockedSelect = jest.mocked(StateManager.select)
3
22
 
4
23
  describe('evaluateExpression', () => {
24
+ beforeEach(() => {
25
+ jest.clearAllMocks()
26
+ mockedSelect.mockImplementation(((key: string) => {
27
+ switch (key) {
28
+ case 'functions':
29
+ return {
30
+ checkAccess: {
31
+ name: 'checkAccess'
32
+ }
33
+ }
34
+ case 'app':
35
+ return {
36
+ id: 'app-id'
37
+ }
38
+ case 'rules':
39
+ return {}
40
+ default:
41
+ return undefined
42
+ }
43
+ }) as never)
44
+ })
45
+
5
46
  it('supports insert-only write expressions that rely on %%prevRoot', async () => {
6
47
  const expression = {
7
48
  '%%prevRoot': {
@@ -30,4 +71,52 @@ describe('evaluateExpression', () => {
30
71
  await expect(evaluateExpression(insertParams, expression)).resolves.toBe(true)
31
72
  await expect(evaluateExpression(readParams, expression)).resolves.toBe(false)
32
73
  })
74
+
75
+ it('supports nested %function conditions inside %or/%and expressions', async () => {
76
+ mockedGenerateContext.mockResolvedValue(true)
77
+
78
+ const expression = {
79
+ '%or': [
80
+ {
81
+ '%%root.company': 'company-1'
82
+ },
83
+ {
84
+ '%and': [
85
+ {
86
+ '%%user.custom_data.type': 'customer'
87
+ },
88
+ {
89
+ '%%true': {
90
+ '%function': {
91
+ name: 'checkAccess',
92
+ arguments: ['%%root.company']
93
+ }
94
+ }
95
+ }
96
+ ]
97
+ }
98
+ ]
99
+ }
100
+
101
+ const params = {
102
+ type: 'read',
103
+ cursor: { company: 'company-2' },
104
+ expansions: {},
105
+ roles: []
106
+ } as Params
107
+
108
+ const user = {
109
+ custom_data: {
110
+ type: 'customer'
111
+ }
112
+ }
113
+
114
+ await expect(evaluateExpression(params, expression, user as never)).resolves.toBe(true)
115
+ expect(mockedGenerateContext).toHaveBeenCalledWith(
116
+ expect.objectContaining({
117
+ args: ['company-2'],
118
+ functionName: 'checkAccess'
119
+ })
120
+ )
121
+ })
33
122
  })
@@ -70,4 +70,21 @@ describe('getWinningRole', () => {
70
70
  const result = getWinningRole(null, mockUser, mockRoles)
71
71
  expect(result).toEqual(mockRoles[0])
72
72
  })
73
+
74
+ it('should return the first matching role asynchronously', async () => {
75
+ const mockCheckApplyWhenAsync = jest
76
+ .spyOn(Utils, 'checkApplyWhenAsync')
77
+ .mockResolvedValueOnce(true)
78
+
79
+ await expect(Utils.getWinningRoleAsync(mockDocument, mockUser, mockRoles)).resolves.toEqual(
80
+ mockRoles[0]
81
+ )
82
+ expect(mockCheckApplyWhenAsync).toHaveBeenCalledWith(
83
+ mockRoles[0].apply_when,
84
+ mockUser,
85
+ mockDocument
86
+ )
87
+ expect(mockCheckApplyWhenAsync).toHaveBeenCalledTimes(1)
88
+ mockCheckApplyWhenAsync.mockReset()
89
+ })
73
90
  })
@@ -7,6 +7,8 @@ import { PermissionExpression } from './interface'
7
7
  import { MachineContext } from './machines/interface'
8
8
 
9
9
  const functionsConditions = ['%%true', '%%false']
10
+ const andConditions = ['$and', '%and']
11
+ const orConditions = ['$or', '%or']
10
12
 
11
13
  const normalizeUserRole = (user?: MachineContext['user']) => {
12
14
  if (!user) return user
@@ -22,30 +24,121 @@ const normalizeUserRole = (user?: MachineContext['user']) => {
22
24
  : user
23
25
  }
24
26
 
25
- export const evaluateExpression = async (
27
+ const buildEvaluationContext = (
26
28
  params: MachineContext['params'],
27
- expression?: PermissionExpression,
28
29
  user?: MachineContext['user']
29
- ): Promise<boolean> => {
30
- if (!expression || typeof expression === 'boolean') return !!expression
30
+ ) => {
31
31
  const normalizedUser = normalizeUserRole(user)
32
32
 
33
- const value = {
34
- ...params.expansions,
35
- ...params.cursor,
33
+ return {
34
+ ...(params.expansions ?? {}),
35
+ ...(params.cursor ?? {}),
36
36
  '%%root': params.cursor,
37
37
  '%%prevRoot': params.expansions?.['%%prevRoot'],
38
38
  '%%user': normalizedUser,
39
39
  '%%true': true,
40
40
  '%%false': false
41
41
  }
42
+ }
43
+
44
+ const getFunctionCondition = (
45
+ expression: unknown
46
+ ): [string, Record<string, any>] | null => {
47
+ if (!expression || typeof expression !== 'object' || Array.isArray(expression)) {
48
+ return null
49
+ }
50
+
51
+ const entries = Object.entries(expression as Record<string, unknown>)
52
+ if (entries.length !== 1) {
53
+ return null
54
+ }
55
+
56
+ const [key, value] = entries[0]
57
+ if (!functionsConditions.includes(key)) {
58
+ return null
59
+ }
60
+
61
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
62
+ return null
63
+ }
64
+
65
+ return Object.prototype.hasOwnProperty.call(value, '%function')
66
+ ? [key, value as Record<string, any>]
67
+ : null
68
+ }
69
+
70
+ export const evaluateExpandedExpression = async (
71
+ expression: unknown,
72
+ params: MachineContext['params'],
73
+ user?: MachineContext['user']
74
+ ): Promise<boolean> => {
75
+ if (typeof expression === 'boolean') {
76
+ return expression
77
+ }
78
+
79
+ if (!expression || typeof expression !== 'object') {
80
+ return Boolean(expression)
81
+ }
82
+
83
+ const block = expression as Record<string, unknown>
84
+ const functionCondition = getFunctionCondition(block)
85
+
86
+ if (functionCondition) {
87
+ return evaluateComplexExpression(functionCondition, params, user)
88
+ }
89
+
90
+ const andKey = andConditions.find((key) => Object.prototype.hasOwnProperty.call(block, key))
91
+ if (andKey) {
92
+ const conditions = Array.isArray(block[andKey]) ? (block[andKey] as unknown[]) : []
93
+ if (!conditions.length) return true
94
+
95
+ for (const condition of conditions) {
96
+ if (!(await evaluateExpandedExpression(condition, params, user))) {
97
+ return false
98
+ }
99
+ }
100
+
101
+ return true
102
+ }
103
+
104
+ const orKey = orConditions.find((key) => Object.prototype.hasOwnProperty.call(block, key))
105
+ if (orKey) {
106
+ const conditions = Array.isArray(block[orKey]) ? (block[orKey] as unknown[]) : []
107
+ if (!conditions.length) return true
108
+
109
+ for (const condition of conditions) {
110
+ if (await evaluateExpandedExpression(condition, params, user)) {
111
+ return true
112
+ }
113
+ }
114
+
115
+ return false
116
+ }
117
+
118
+ const keys = Object.keys(block)
119
+ if (keys.length > 1) {
120
+ for (const key of keys) {
121
+ if (!(await evaluateExpandedExpression({ [key]: block[key] }, params, user))) {
122
+ return false
123
+ }
124
+ }
125
+
126
+ return true
127
+ }
128
+
129
+ return rulesMatcherUtils.checkRule(block as never, buildEvaluationContext(params, user), {})
130
+ }
131
+
132
+ export const evaluateExpression = async (
133
+ params: MachineContext['params'],
134
+ expression?: PermissionExpression,
135
+ user?: MachineContext['user']
136
+ ): Promise<boolean> => {
137
+ if (!expression || typeof expression === 'boolean') return !!expression
138
+
139
+ const value = buildEvaluationContext(params, user)
42
140
  const conditions = expandQuery(expression, value)
43
- const complexCondition = Object.entries(conditions as Record<string, any>).find(
44
- ([key]) => functionsConditions.includes(key)
45
- )
46
- return complexCondition
47
- ? await evaluateComplexExpression(complexCondition, params, normalizedUser)
48
- : rulesMatcherUtils.checkRule(conditions, value, {})
141
+ return evaluateExpandedExpression(conditions, params, user)
49
142
  }
50
143
 
51
144
  const evaluateComplexExpression = async (
@@ -74,7 +167,7 @@ const evaluateComplexExpression = async (
74
167
  const expandedArguments =
75
168
  fnArguments && fnArguments.length
76
169
  ? ((expandQuery({ args: fnArguments }, expansionContext) as { args: unknown[] })
77
- .args ?? [])
170
+ .args ?? [])
78
171
  : [params.cursor]
79
172
 
80
173
  const response = await GenerateContext({
@@ -2,6 +2,7 @@ import { Document, OptionalId } from 'mongodb'
2
2
  import { User } from '../../../auth/dtos'
3
3
  import { Filter } from '../../../features/rules/interface'
4
4
  import { getValidRule } from '../../../services/mongodb-atlas/utils'
5
+ import { evaluateExpression } from '../helpers'
5
6
  import { Role } from '../interface'
6
7
  import { LogMachineInfoParams } from './interface'
7
8
 
@@ -28,6 +29,20 @@ export const getWinningRole = (
28
29
  return null
29
30
  }
30
31
 
32
+ export const getWinningRoleAsync = async (
33
+ document: OptionalId<Document> | null,
34
+ user: User,
35
+ roles: Role[] = []
36
+ ): Promise<Role | null> => {
37
+ if (!roles.length) return null
38
+ for (const role of roles) {
39
+ if (await checkApplyWhenAsync(role.apply_when, user, document)) {
40
+ return role
41
+ }
42
+ }
43
+ return null
44
+ }
45
+
31
46
  /**
32
47
  * Checks if the `apply_when` condition is valid for the given user and document.
33
48
  *
@@ -50,6 +65,25 @@ export const checkApplyWhen = (
50
65
  return !!validRule.length
51
66
  }
52
67
 
68
+ export const checkApplyWhenAsync = async (
69
+ apply_when: Role['apply_when'],
70
+ user: User,
71
+ document: OptionalId<Document> | null
72
+ ) => {
73
+ return evaluateExpression(
74
+ {
75
+ type: 'read',
76
+ roles: [],
77
+ cursor: document,
78
+ expansions: {
79
+ '%%prevRoot': undefined
80
+ }
81
+ },
82
+ apply_when,
83
+ user
84
+ )
85
+ }
86
+
53
87
  /**
54
88
  * Logs machine step information if logging is enabled.
55
89
  *