Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,58 @@
import { ensureClientPipelineStages, getHiddenFieldsFromRulesConfig, prependUnsetStage, applyAccessControlToPipeline, mergeProjections } from '../utils'
import {
ensureClientPipelineStages,
getHiddenFieldsFromRulesConfig,
prependUnsetStage,
applyAccessControlToPipeline,
mergeProjections,
getValidRule
} from '../utils'
import { Role } from '../../../utils/roles/interface'

describe('MongoDB Atlas aggregate helpers', () => {
describe('getValidRule', () => {
it('matches legacy scalar apply_when syntax against array-valued custom data', () => {
const validRules = getValidRule({
filters: [
{
apply_when: {
'%%user.custom_data.roles': 'Admin'
},
query: {}
}
] as any,
user: {
custom_data: {
roles: ['Admin', 'SCM']
}
} as any,
record: null
})

expect(validRules).toHaveLength(1)
})

it('does not match legacy scalar apply_when syntax when the array does not contain the value', () => {
const validRules = getValidRule({
filters: [
{
apply_when: {
'%%user.custom_data.roles': 'Admin'
},
query: {}
}
] as any,
user: {
custom_data: {
roles: ['Editor']
}
} as any,
record: null
})

expect(validRules).toHaveLength(0)
})
})

describe('ensureClientPipelineStages', () => {
it('allows safe stages', () => {
expect(() =>
Expand Down
23 changes: 23 additions & 0 deletions packages/flowerbase/src/utils/__tests__/evaluateExpression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,27 @@ describe('evaluateExpression', () => {
})
)
})

it('supports scalar equality against array-valued custom data', async () => {
const expression = {
'%%user.custom_data.roles': 'Admin'
}

const params = {
type: 'read',
cursor: { _id: 'doc-1' },
expansions: {
'%%prevRoot': undefined
},
roles: []
} as Params

const user = {
custom_data: {
roles: ['Admin', 'SCM']
}
}

await expect(evaluateExpression(params, expression, user as never)).resolves.toBe(true)
})
})
4 changes: 2 additions & 2 deletions packages/flowerbase/src/utils/__tests__/rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe('rule function', () => {
expect(result.name).toBe('user.authId___%oidToString')
})

it('does not treat scalar equality as array membership in compact rules', () => {
it('treats scalar equality as array membership in compact rules', () => {
const data = {
doc: {
owners: ['user-1', 'user-2']
Expand All @@ -97,7 +97,7 @@ describe('rule function', () => {
prefix: 'doc'
})

expect(result.valid).toBe(false)
expect(result.valid).toBe(true)
expect(result.name).toBe('doc.owners___$eq')
})

Expand Down
13 changes: 11 additions & 2 deletions packages/flowerbase/src/utils/__tests__/rulesMatcherUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ describe('rulesMatcherUtils', () => {
// isFunction
expect(isFunction(2)).toBe(false)
expect(isFunction('ciao')).toBe(false)
expect(isFunction(() => {})).toBe(true)
expect(isFunction(function test() {})).toBe(true)
expect(isFunction(() => { })).toBe(true)
expect(isFunction(function test() { })).toBe(true)
// isString
expect(isString(2)).toBe(false)
expect(isString('2')).toBe(true)
Expand Down Expand Up @@ -94,4 +94,13 @@ describe('rulesMatcherUtils', () => {
)
).toBe(false)
})

it('matches scalar equality against array-valued fields', () => {
const data = {
'%%user': { custom_data: { roles: ['Admin', 'SCM'] } }
}

expect(checkRule({ '%%user.custom_data.roles': 'Admin' } as any, data, {})).toBe(true)
expect(checkRule({ '%%user.custom_data.roles': 'Manager' } as any, data, {})).toBe(false)
})
})
10 changes: 9 additions & 1 deletion packages/flowerbase/src/utils/rules-matcher/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ const includesWithSemanticEquality = (value: unknown, candidate: unknown): boole
)
)

const hasScalarArrayMembershipMatch = (left: unknown, right: unknown): boolean => {
if (Array.isArray(left) === Array.isArray(right)) {
return false
}

return includesWithSemanticEquality(left, right)
}

const resolveRefPath = (data: unknown, refPath: string, prefix?: string): unknown => {
const exactMatch = _get(data, refPath, undefined)

Expand Down Expand Up @@ -306,7 +314,7 @@ export const operators: Operators = {
$exists: (a, b) => !rulesMatcherUtils.isEmpty(a) === b,
'%exists': (a, b) => !rulesMatcherUtils.isEmpty(a) === b,

$eq: (a, b) => areSemanticallyEqual(a, b),
$eq: (a, b) => areSemanticallyEqual(a, b) || hasScalarArrayMembershipMatch(a, b),

$ne: (a, b) => !areSemanticallyEqual(a, b),

Expand Down
6 changes: 3 additions & 3 deletions tests/e2e/mongodb-atlas.rules.e2e.rules-and-triggers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type TestUser = User & {
custom_data?: {
type?: string
key?: string
accountRole?: string
accountRole?: string | string[]
company?: string
workspaces: string[]
adminIn?: string[]
Expand Down Expand Up @@ -134,7 +134,7 @@ const ownerUser: TestUser = {
custom_data: {
role: 'owner',
type: 'internal',
accountRole: 'collaborator',
accountRole: ['collaborator', 'reviewer'],
company: oidDocIds.ownerCompany.toHexString(),
key: 'publisher-owner',
workspaces: ['workspace-1'],
Expand Down Expand Up @@ -1657,7 +1657,7 @@ describe('MongoDB Atlas rule enforcement (e2e)', () => {
expect(projects[0]).toHaveProperty('summary')
})

it('returns only allowed fields when read is undefined and field-level read is configured', async () => {
it('returns only allowed fields when read is undefined and apply_when matches array-valued custom data', async () => {
const docs = (await getTeamDocsCollection(ownerUser).find({}).toArray()) as TeamDoc[]
expect(docs).toHaveLength(1)
expect(docs[0]).toEqual({
Expand Down
Loading