@flowerforce/flowerbase 1.8.4-beta.1 → 1.8.4-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/features/functions/interface.d.ts +1 -0
- package/dist/features/functions/interface.d.ts.map +1 -1
- package/dist/features/functions/utils.js +1 -1
- package/dist/monitoring/utils.d.ts +1 -1
- package/dist/services/mongodb-atlas/index.js +11 -11
- package/dist/utils/context/helpers.d.ts +3 -3
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +91 -14
- package/dist/utils/roles/helpers.d.ts +1 -0
- package/dist/utils/roles/helpers.d.ts.map +1 -1
- package/dist/utils/roles/helpers.js +77 -8
- package/dist/utils/roles/machines/utils.d.ts +2 -0
- package/dist/utils/roles/machines/utils.d.ts.map +1 -1
- package/dist/utils/roles/machines/utils.js +33 -1
- package/package.json +1 -1
- package/src/features/functions/interface.ts +4 -1
- package/src/features/functions/utils.ts +3 -3
- package/src/services/mongodb-atlas/index.ts +150 -150
- package/src/utils/__tests__/contextExecuteCompatibility.test.ts +40 -0
- package/src/utils/__tests__/evaluateExpression.test.ts +89 -0
- package/src/utils/__tests__/getWinningRole.test.ts +17 -0
- package/src/utils/context/index.ts +142 -13
- package/src/utils/roles/helpers.ts +107 -14
- package/src/utils/roles/machines/utils.ts +34 -0
|
@@ -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 {
|
|
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
|
-
|
|
565
|
-
|
|
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 =
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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 =
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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 =
|
|
736
|
+
const winningRole = await getWinningRoleAsync(data, user, roles)
|
|
737
737
|
|
|
738
738
|
const { status, document } = winningRole
|
|
739
739
|
? await checkValidation(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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 =
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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 =
|
|
931
|
+
const winningRole = await getWinningRoleAsync(docToCheck, user, roles)
|
|
932
932
|
|
|
933
933
|
const { status, document } = winningRole
|
|
934
934
|
? await checkValidation(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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 =
|
|
969
|
+
const readRole = await getWinningRoleAsync(updateResult, user, roles)
|
|
970
970
|
const readResult = readRole
|
|
971
971
|
? await checkValidation(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
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 =
|
|
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
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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 =
|
|
1224
|
+
const winningRole = await getWinningRoleAsync(fullDocument, user, roles)
|
|
1225
1225
|
|
|
1226
1226
|
const fullDocumentValidation = winningRole
|
|
1227
1227
|
? await checkValidation(
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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 =
|
|
1392
|
+
const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
|
|
1393
1393
|
|
|
1394
1394
|
const { status, document } = winningRole
|
|
1395
1395
|
? await checkValidation(
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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 =
|
|
1456
|
+
const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
|
|
1457
1457
|
|
|
1458
1458
|
const { status, document } = winningRole
|
|
1459
1459
|
? await checkValidation(
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
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 =
|
|
1532
|
+
const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
|
|
1533
1533
|
|
|
1534
1534
|
const { status, document } = winningRole
|
|
1535
1535
|
? await checkValidation(
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
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,5 +1,8 @@
|
|
|
1
1
|
import { GenerateContextSync } from '../context'
|
|
2
2
|
import { Functions } from '../../features/functions/interface'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
3
6
|
|
|
4
7
|
const mockServices = {
|
|
5
8
|
api: jest.fn().mockReturnValue({}),
|
|
@@ -117,4 +120,41 @@ describe('context.functions.execute compatibility', () => {
|
|
|
117
120
|
|
|
118
121
|
expect(result).toBe(true)
|
|
119
122
|
})
|
|
123
|
+
|
|
124
|
+
it('loads same-directory helper modules for sandboxed functions', () => {
|
|
125
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flowerbase-context-'))
|
|
126
|
+
const helperPath = path.join(tempDir, 'getFreightRate.ts')
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(
|
|
129
|
+
helperPath,
|
|
130
|
+
'export function getFreightRate([address, amount, freightRateValues]) { return { address, total: amount * freightRateValues.multiplier } }'
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const functionsList = {
|
|
134
|
+
caller: {
|
|
135
|
+
sourcePath: path.join(tempDir, 'caller.ts'),
|
|
136
|
+
code: `
|
|
137
|
+
import { getFreightRate } from './getFreightRate'
|
|
138
|
+
|
|
139
|
+
module.exports = function() {
|
|
140
|
+
return getFreightRate(['rome', 4, { multiplier: 2.5 }])
|
|
141
|
+
}
|
|
142
|
+
`
|
|
143
|
+
}
|
|
144
|
+
} as Functions
|
|
145
|
+
|
|
146
|
+
const result = GenerateContextSync({
|
|
147
|
+
args: [],
|
|
148
|
+
app: {} as any,
|
|
149
|
+
rules: {} as any,
|
|
150
|
+
user: {} as any,
|
|
151
|
+
currentFunction: functionsList.caller,
|
|
152
|
+
functionsList,
|
|
153
|
+
services: mockServices,
|
|
154
|
+
functionName: 'caller'
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual({ address: 'rome', total: 10 })
|
|
158
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
159
|
+
})
|
|
120
160
|
})
|
|
@@ -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
|
})
|