fix: Support not in access policy conditions#10767
Conversation
…ering Add a test that verifies group-based conditional row filtering: - region_test cube backed by users table (id, city, count) - region_test_view with an access policy for user_group that conditionally applies a row filter based on region_group membership - region_user (user_group + region_group): sees only San Francisco rows - region_user_no_filter (user_group only): sees all rows The access policy checks security_context.auth.groups for region_group membership. If present, it filters rows by the user's region attribute. If absent, it grants allow_all (no row filter). Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
|
Claude finished @paveltiunov's task in 1m 16s —— View job
Review SummaryThis is a well-structured smoke test addition that validates group-based conditional row filtering. The implementation follows existing patterns in the test suite and the test scenario is clearly documented. A few observations below. Findings1. Unused Both // cube.js:200-201
userAttributes: {
region: 'San Francisco',
hasRegionFilter: true, // never referenced
},However, the view's access policy in 2. No REST API test coverage for the new scenario (suggestion) Other RBAC test scenarios in this file validate behavior through both the SQL API (via // Example: sign a JWT with region_user's context
const REGION_USER_TOKEN = sign({
auth: {
username: 'region_user',
userAttributes: { region: 'San Francisco' },
roles: [],
groups: ['user_group', 'region_group'],
},
}, DEFAULT_CONFIG.CUBEJS_API_SECRET, { expiresIn: '2 days' });This is non-blocking — the SQL API tests provide good coverage of the core behavior. 3. No Tesseract coverage (informational) The new tests are added under the 4. Code quality — looks good
5. Security — no issues The access policy correctly scopes to VerdictThe PR is clean and well-tested. The only actionable item is the unused |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10767 +/- ##
==========================================
- Coverage 58.01% 57.96% -0.05%
==========================================
Files 215 215
Lines 16764 16776 +12
Branches 3383 3389 +6
==========================================
Hits 9725 9725
- Misses 6547 6559 +12
Partials 492 492
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Switch region_test cube to use public.line_items table to avoid cross-contamination from the users cube's RBAC city filter - Add cube-level allowAll access policy so view policies can layer on top correctly - Use group-based (user_group) policies with conditions to check hasRegionFilter user attribute for conditional row filtering - Use MEASURE() syntax for aggregate count queries - Both users have groups only (no roles needed) — view policy uses group: 'user_group' with conditions for mutual exclusivity Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Enable security_context to be used directly at the rowLevel property
of access policies, allowing the entire filter structure to be
controlled dynamically based on the user's security context.
Changes:
- CubePropContextTranspiler: add special handling to transpile
rowLevel/row_level when its value is an expression (not an object
literal), wrapping it in an arrow function with securityContext
- CubeValidator: accept Joi.func() as alternative for rowLevel,
alongside the existing RowLevelPolicySchema object
- CompilerApi: resolve rowLevel via evaluateContextFunction when it
is a function, and process the returned raw filters through a new
evaluateRawFilter method that handles uncompiled member references
- Update test to use the cleaner syntax where security_context
controls the rowLevel structure directly via groups.includes()
This enables patterns like:
rowLevel: security_context.auth?.groups?.includes('region_group')
? { filters: [{ member: 'col', operator: 'equals', values: ... }] }
: { allowAll: true }
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Replace the JS view with a YAML view that uses two mutually exclusive conditions on the user_group access policy: - hasRegionFilter (truthy) -> filters by allowedProductIds - noRegionFilter (truthy) -> allow_all Users carry complementary boolean attributes so the YAML conditions (which only support truthy checks, not comparisons or negation) can distinguish the two cases without policy overlap. Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Replace attribute-based conditions (hasRegionFilter/noRegionFilter) with pure group-based policy scoping: - group: region_group — row filter by allowedProductIds - group: user_group — allow_all Each user belongs to exactly one group so only one policy matches, avoiding the union overlap problem without needing conditions. Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…check
Revert all changes to cubejs-schema-compiler and cubejs-server-core.
Use standard access policy conditions to check security context groups:
- Two mutually exclusive user_group policies with conditions:
- if: security_context.auth?.groups?.includes('region_group')
→ filters by allowedProductIds
- if: !security_context.auth?.groups?.includes('region_group')
→ allowAll
- region_user has both ['user_group', 'region_group'] and is
correctly filtered because the condition routes to the filter
policy, not the allowAll policy.
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Replace JS view with YAML view using two group-scoped policies: - group: region_group — filters by allowedProductIds - group: user_group — allow_all Users belong to exactly one group to avoid the union-overlap problem where allow_all would override the filter. No core changes. Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…pport
A user with both user_group and region_group must be filtered by
region_group. This requires mutually exclusive conditions checking
groups.includes('region_group') — which needs JS (not YAML, since
the YAML Python parser cannot express negation).
Single user_group policy with two condition branches:
- groups.includes('region_group') → filter by allowedProductIds
- !groups.includes('region_group') → allowAll
No core changes. region_user now has ['user_group', 'region_group']
and is correctly filtered.
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
|
Claude finished @paveltiunov's task in 1m 48s —— View job
Review SummaryClean, well-structured smoke test addition. The test scenario is sound and the implementation follows existing patterns in the RBAC test suite. The earlier review's concern about unused Findings1. Test relies on data distribution assumption (low risk) The test at const productIds = new Set(res.rows.map((r: any) => r.product_id));
expect(productIds.size).toBeGreaterThan(2);This assumes the first 50 rows of 2. PR description mentions The PR body states the cube "exposes 3. Code quality — looks good
4. Security — no issues The access policy correctly gates on 5. Squash opportunity (suggestion) The branch has 8 commits with significant iteration (YAML→JS→YAML→JS conversions, reverts). Consider squashing into a single commit on merge for a cleaner history. VerdictApprove. The PR is clean, follows established patterns, and adds meaningful test coverage for a non-trivial RBAC scenario. No blocking issues found. |
Add support for Python boolean operators in the YAML expression parser:
- not → JS ! (unary negation)
- and → JS && (logical AND)
- or → JS || (logical OR)
This enables YAML access policy conditions like:
if: "{ not (security_context.auth.groups and security_context.auth.groups.includes('region_group')) }"
Convert region_test_view from JS to YAML using the new operators
for null-safe group membership checks in conditions.
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Check List
Description of Changes Made
1. Fix:
notsilently dropped in YAML Python expression parserThe YAML expression parser (
PythonParser.ts) silently droppednot—not xproducedxinstead of!x. TheNot_testContextvisitor checkedchildren.length === 1but the terminalnotkeyword is not a rule node, so the visitor always saw 1 child and returned it directly, ignoring the negation.Fix: Check
node.getChildCount()(which includes terminal nodes) instead ofchildren.length(which only has rule-node results). When the node has 2 children (thenotterminal + the inner expression), emitt.unaryExpression('!', ...).Also added support for
and(&&) andor(||) operators which previously threwUnsupported Python multiple children nodeerrors.not x!xNot_testContexta and ba && bAnd_testContexta or ba || bOr_testContextFile changed:
packages/cubejs-schema-compiler/src/parser/PythonParser.ts2. Test: Group-based conditional region row filtering (YAML view)
A smoke test that exercises the
notfix with a YAML view using group-based conditions:A user with both
user_groupandregion_groupis correctly filtered because the conditions are mutually exclusive —includes()vsnot ... includes(). Without thenotfix, the second condition would silently evaluate to the same as the first, and both policies would match.Test files:
birdbox-fixtures/rbac/model/cubes/region_test.yamlpublic.line_itemsbirdbox-fixtures/rbac/model/views/region_test_view.yamlnotbirdbox-fixtures/rbac/cube.jsregion_user(['user_group', 'region_group']) andregion_user_no_filter(['user_group'])test/smoke-rbac.test.tsTest Results
All 79 existing RBAC tests pass. 3 new tests pass. Only 3 pre-existing Python config failures remain (unrelated).