Skip to content

fix(frontend): scope table and view grant option checks - #24755

Merged
mergify[bot] merged 24 commits into
matrixorigin:mainfrom
ck89119:issue-24713-view-main
Jun 4, 2026
Merged

fix(frontend): scope table and view grant option checks#24755
mergify[bot] merged 24 commits into
matrixorigin:mainfrom
ck89119:issue-24713-view-main

Conversation

@ck89119

@ck89119 ck89119 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

  • API-change
  • BUG
  • Improvement
  • Documentation
  • Feature
  • Test and CI
  • Code Refactoring

Which issue(s) this PR fixes:

issue #24713

What this PR does / why we need it:

This PR scopes grant-option checks for table/view privileges by object type and object id. It prevents table/view grants and database-scoped grants from leaking across unrelated objects while still allowing global *.* WITH GRANT OPTION to cover narrower grant targets.

It also preserves compatibility for view grants that may be stored under the legacy table object type, and adds focused frontend coverage for global wildcard coverage, cross-database isolation, and view-level grant-option checks.

Validation:

  • go test ./pkg/frontend -run Test_determineGrantPrivilege -count=1
  • git diff --check
  • make && make static-check

@ck89119
ck89119 requested a review from XuPeng-SH as a code owner June 1, 2026 08:57
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

ck89119 and others added 8 commits June 2, 2026 17:41
When the target table/view of a scoped grant does not resolve, the WGO
authorization check degraded to an obj_type-only lookup, so any unrelated
same-type grant option satisfied GRANT ... ON db.missing_obj and the failure
surfaced later as "no such table/view" instead of an authorization error.

Fall back instead to only the object-id-independent scopes: the global *.*
scope always, plus the database-wide db.* scope when the grant targets a
table and the database itself still resolves. This keeps holders of global
or db-wide grant option authorized while preventing grants on unrelated
specific objects from satisfying the check. The success path reuses the same
helper, leaving its SQL sequence unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@aunjgr aunjgr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thorough privilege scoping fix with excellent test coverage (1586 lines). A few concerns:

Correctness concerns

1. VALUES privilege ownership promotion asymmetric between scoped/unscoped paths (authenticate.go). In the new scoped code, PrivilegeTypeValues sits in the same switch arm as Select/Insert/Update..., calling ownership-fallback SQL. In the old unscoped path, getSqlForCheckRoleHasPrivilegeWGODependsOnPrivType(PrivilegeTypeValues) returns only getSqlForCheckRoleHasPrivilegeWGO — no ownership fallback. Net effect: GRANT VALUES ON db.t checks TableOwnership (new behavior), but GRANT VALUES ON *.* does not (falls through to old unscoped path). If this promotion-of-VALUES is intended, it should apply uniformly.

2. determineUserCanGrantPrivilegesToOthers returns true when gp.Privileges is empty (authenticate.go:8166-8199). The loop over gp.Privileges is a no-op for an empty slice, so ret stays true. An empty-privileges GRANT statement would silently pass. While the parser should reject this upstream, worth a defensive len(gp.Privileges) == 0 check for consistency with the existing gp.Level == nil guard.

Hardening

3. build_constraint_util.go duplicates orderedDmlAliases helper. The new manual aliasByIdx array construction does the same thing as the existing orderedDmlAliases (line 255). Use the existing helper.

4. PrivilegeTypeExecute excluded from scoped checks — intentional but subtle. Execute.Scope() returns PrivilegeScopeTable, but getRoleSetThatPrivilegeGrantedToWGOScoped early-outs for non-TABLE/VIEW object types, so Execute always falls through to the unscoped path. This is correct today because Execute maps to OBJECT_TYPE_FUNCTION/PROCEDURE, but a comment documenting this dependency on the early-out would help future maintainers.

Scope concern (pre-existing, not a regression)

5. *.* account-level ownership doesn't authorize *.* table-level grants. The scoped path for *.* only queries obj_type = "table" rows. AccountOwnership (obj_type = "account") is never checked. The unscoped fallback similarly checks TableAll/TableOwnership, not AccountAll/AccountOwnership. This matches pre-existing behavior — worth documenting as a design constraint.

Verified

  • Test coverage (1586 lines) is thorough: positive/negative for *.* covers d.*, d.* covers d.t, cross-database isolation, view legacy paths, nil/empty role-set merging, error propagation.

@aunjgr aunjgr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgrading to approve. The findings from my prior comment are non-blocking:

  • VALUES privilege ownership asymmetry between scoped/unscoped paths is a design inconsistency, not a correctness bug. If it matters, it'll surface in a dedicated VALUES-grant test.
  • Empty-privileges defensive check — parser rejects empty privileges upstream, so unreachable in practice.
  • Duplicated orderedDmlAliases pattern, Execute exclusion subtlety, and *.* account-ownership scope are hardening/design notes.

Test coverage is excellent (1586 lines). LGTM.

The fallback UPDATE builder appended each update expression while ranging
over the updateKeys map, so the projected column layout depended on Go's
randomized map iteration. Iterate table columns in definition order instead,
which keeps the appended update-expression positions stable across plan
builds.

Also harden TestUpdateFallbackGeneratedColumnChainUsesFreshExpr so it no
longer asserts on a fixed slot: it now checks emp's appended region as a
whole — both generated columns must be freshly recomputed to empno and none
may reference the stale mgr column — making it order-independent and immune
to map-iteration flakiness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@XuPeng-SH XuPeng-SH left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this from three angles: correctness, runtime/impact, and test coverage.

There is one blocking correctness issue in the new VIEW compatibility path:

  • getRoleSetThatPrivilegeGrantedToWGOScoped() now checks both objectTypeView and legacy objectTypeTable records for VIEW targets. That is fine for exact-object legacy rows, but it is not safe for broader scopes like db.* and *.*: at those scopes, a real TABLE grant-option row and a legacy VIEW grant-option row are indistinguishable in mo_role_privs (obj_type = table, same obj_id, same privilege level). So a genuine TABLE WITH GRANT OPTION on db.* or *.* can still authorize re-granting on VIEW targets.

That means the table/view grant-option leak is still present for database/global scopes, even though this PR is trying to scope table and view checks separately. The added test grant privilege [ObjectType: View] database scoped view grant succeeds on legacy table record only demonstrates exactly this behavior.

I think the legacy fallback needs to be narrowed so it does not re-open cross-type authorization at broad scopes (for example, only allowing legacy exact-object view rows, or introducing a migration/marker that can distinguish legacy VIEW rows from actual TABLE rows before consulting them).

Separately, the frontend side would still benefit from an end-to-end BVT that covers object-scoped WITH GRANT OPTION isolation (db1.t1 must not authorize re-grant on db1.t2 / db2.t2), but the main reason for request-changes is the correctness issue above.

@XuPeng-SH

Copy link
Copy Markdown
Contributor

Follow-up after the longer logic review: there is another blocking correctness issue.

  • The new scoped grant-option path is only applied for PrivilegeScopeTable + table/view AST targets. Database-scoped privileges (CREATE TABLE, DROP TABLE, SHOW TABLES, CREATE VIEW, etc. granted on a specific database) still fall back to the old unscoped role-set lookup, which checks only privilege_id and ignores the target database obj_id.
  • So a role that has ... WITH GRANT OPTION on db1 can still end up authorizing the same grant on db2.

This should be scoped by database object as well, while still allowing the intended broader coverage from * / *.* where applicable.

@fengttt fengttt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of change need a bvt test.

@XuPeng-SH XuPeng-SH left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed from three angles:

  • correctness: grant-option resolution is now scoped by object type/object identity and the final logic keeps exact legacy-view compatibility without letting table wildcard grants authorize view grants
  • performance: the extra catalog lookups are limited to GRANT authorization paths and are acceptable for this control-flow
  • test coverage: unit tests plus the tenant privilege BVT cover the key regressions and wildcard/legacy edge cases

Looks good to merge.

@mergify

mergify Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-04 08:38 UTC · Rule: main
  • Checks started · in-place
  • Checks failed
  • 🚫 Left the queue2026-06-04 09:31 UTC · at 1226dc98f00c8dfcb6f1e451d81626aa70e6bd99

This pull request spent 53 minutes 15 seconds in the queue, with no time running CI.

Waiting for
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
All conditions
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)

Reason

The merge conditions cannot be satisfied due to failing checks

Hint

You may have to fix your CI before adding the pull request to the queue again.
If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

@mergify

mergify Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-04 10:42 UTC · Rule: main
  • Checks skipped · PR is already up-to-date
  • Merged2026-06-04 10:44 UTC · at 1226dc98f00c8dfcb6f1e451d81626aa70e6bd99 · squash

This pull request spent 2 minutes 2 seconds in the queue, including 16 seconds running CI.

Required conditions to merge
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Utils CI / Coverage
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/bug Something isn't working size/XXL Denotes a PR that changes 2000+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants