Skip to content

[#13845] fix(server): Support authorized existence probing for view loads - #13846

Open
jarredhj0214 wants to merge 5 commits into
apache:mainfrom
jarredhj0214:codex/fix-view-existence-probe
Open

jarredhj0214 wants to merge 5 commits into
apache:mainfrom
jarredhj0214:codex/fix-view-existence-probe

Conversation

@jarredhj0214

@jarredhj0214 jarredhj0214 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Add authorized existence probing to view loads using the existing table-like probe expression, and close the analogous absence-probe authorization gap for table loads.

After the primary load permission is denied, a caller with probe permission uses the existing internal loadTable or loadView path. A successful internal load still returns an authorization denial. A missing object produces a terminal, standard 404 response in the interceptor without entering the REST load handler. View probes against catalogs without view support retain the original 403 behavior; other backend errors propagate.

Both paths retain the existing internal loading and metadata import behavior. No new Core probe interface is introduced.

Why are the changes needed?

During Flink qualified name resolution, a missing candidate table can fall back to a view lookup. Returning 403 for that missing candidate view prevents resolution from continuing even when the actual source table is accessible.

An absence observation must not authorize a later load: another request could create the same-named table or view between the probe and the handler, exposing metadata without load permission. Terminal 404 responses close this gap for both table and view loads.

Fix: #13845

Does this PR introduce any user-facing change?

Authorized probes of missing tables and views return 404 without invoking the normal load handler. Existing objects remain forbidden without their primary load permission, and callers without probe permission cannot check existence. Probe-only requests cannot read metadata from an object created after the absence observation.

How was this patch tested?

Added table and view race regressions using real authorization executors, the interceptor, and REST resource methods with deterministic mocked reads: the internal load observes absence while a concurrent create makes the object available for a subsequent load. Both requests return their standard not-found error and never enter the handler.

Coverage also checks successful internal loads remaining forbidden, denied probes avoiding dispatcher calls, primary permission bypassing probes, unsupported view operations, backend error propagation, and endpoint wiring.

Passed ./gradlew spotlessApply and ./gradlew :server:test spotlessCheck -PskipITs --no-parallel (671 non-Docker Server tests). Docker-based integration validation remains pending CI.

@jarredhj0214

Copy link
Copy Markdown
Contributor Author

This follows the table-like existence probing introduced in #12482.

The gap is in existence-probe handling, rather than in the normal table/view read permissions:

  • loadTable first checks its normal authorization expression. If that denies access, it evaluates PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION. A caller with probe permission can check existence through the internal table dispatcher: an absent table proceeds to the REST handler and returns 404, while an existing table remains forbidden without read permission.
  • loadView previously only checked its normal authorization expression, so the same caller could receive 403 for a missing candidate view before existence was checked.

This matters for Flink name resolution: getTable() falls back to loadView() after NoSuchTableException. A candidate such as current_db.source_db can therefore return 404 as a table but 403 as a view, aborting resolution of the actual qualified table name.

This PR applies the same probe expression and decision flow to views, using the internal view dispatcher. It preserves 403 for existing views without load permission and does not allow existence checks when probe permission is denied. Regression tests cover these cases and endpoint/executor wiring.

roryqi
roryqi previously approved these changes Oct 14, 2026
@jarredhj0214

Copy link
Copy Markdown
Contributor Author

Updated in d4ac59e. When a catalog does not support view operations, the existence probe now preserves the original authorization denial (403) instead of failing with an internal authorization error. Other backend failures still propagate; supported-view probe behavior is unchanged.

Added executor and interception regression tests covering the unsupported-view case, HTTP 403, skipped REST handler execution, and authorization-denial event delivery. The targeted server tests and spotless checks passed; Spark integration validation is pending CI.

@yuqi1129

Copy link
Copy Markdown
Contributor

Thanks for the update in d4ac59e. The new UnsupportedOperationException handling addresses the unsupported-view authorization error. I checked that delta against the version I reviewed (d0eff76cc9), and two concerns remain:

  1. [Blocker] An absence probe must not authorize the subsequent load.

    In LoadViewAuthorizationExecutor.execute, the primary load permission can be denied while an allowed probe observes that the view does not exist and returns true. The interceptor then calls the REST handler. If another request creates the same-named view between the probe and ViewOperations.loadView, the handler loads and returns that view without another load-permission check.

    A deterministic local probe using the real executor, interceptor, and REST method, with mocked reads fixing this interleaving, returned HTTP 200 including the view SQL despite the primary load permission being false. This is not a live Hive concurrency test, but it exercises the authorization-to-handler boundary.

    Please distinguish a successful absence probe from permission to load: return a terminal, standard 404 response for the probe-only absent case, without proceeding to the normal load handler. Simply throwing NoSuchViewException from the executor would also need appropriate interceptor handling so it does not become a generic authorization 500. Add a regression test for creation between the absence observation and handler execution. The table executor has the analogous pattern and should be followed up as well.

  2. [Important] The internal dispatcher’s viewExists is not a read-only existence probe.

    The actual chain is ViewNormalizeDispatcher.viewExists → the default ViewCatalog.viewExists → ViewOperationDispatcher.loadView. For an external view not yet registered in Gravitino, this reaches schema loading and importView, including store.put. Thus a caller with only probe permission can trigger metadata import and write locking even though the request ultimately returns 403. A local test using the real Core dispatcher confirmed that viewExists imports the external view into the entity store.

    Please provide a non-importing existence path that delegates to the connector’s viewExists, preserving its existence semantics, rather than routing through the dispatcher’s importing loadView. Add a test asserting that a denied load with allowed probe permission does not import the view or its parent schema.

Validation on d0eff76cc9: all 666 non-Docker server tests passed; the temporary probes reproduced the race and import behavior. Reversing the existence predicate made the new missing-view test fail as expected; after restoration, the 20 relevant server tests passed. I have inspected the new commit’s delta, but have not rerun the full suite on d4ac59ea2 or run live Flink/Hive integration tests.

@jarredhj0214

Copy link
Copy Markdown
Contributor Author

Addressed in b8bbe8e. An allowed absence probe now returns a terminal 404 without entering the load handler, closing the concurrent-create authorization gap. Added a dedicated non-importing probe path that delegates to the connector’s viewExists; ordinary viewExists behavior is unchanged.

@roryqi

roryqi commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

I disagree with 2nd option. It doesn't know our code context.
For the 1st point, you should modify the table logic, too.
you should use

   if (tableIdentifier != null) {
       GravitinoEnv.getInstance().internalTableDispatcher().loadTable(tableIdentifier);
   }
   return false;

@github-actions

Copy link
Copy Markdown

Code Coverage Report

Overall Project 71.79% +0.53% 🟢
Files changed 84.88% 🟢

Module Coverage
aliyun 19.74% 🔴
api 54.68% 🟢
authorization-common 85.96% 🟢
authorization-ranger 14.44% 🔴
aws 53.54% 🟢
aws-bundle 0.0% 🔴
azure 32.1% 🔴
azure-bundle 0.0% 🔴
catalog-common 29.49% 🔴
catalog-fileset 82.37% 🟢
catalog-glue 73.24% 🟢
catalog-hive 83.63% 🟢
catalog-jdbc-common 57.98% 🟢
catalog-jdbc-doris 87.56% 🟢
catalog-jdbc-mysql 81.91% 🟢
catalog-jdbc-postgresql 84.02% 🟢
catalog-jdbc-starrocks 79.16% 🟢
catalog-kafka 75.21% 🟢
catalog-lakehouse-generic 80.67% -7.38% 🟢
catalog-lakehouse-hudi 79.1% 🟢
catalog-lakehouse-iceberg 85.93% 🟢
catalog-lakehouse-paimon 84.36% 🟢
catalog-model 77.99% 🟢
cli 44.62% 🟢
client-java 79.34% 🟢
client-java-runtime 0.0% 🔴
common 60.49% 🟢
core 87.4% -0.24% 🟢
filesystem-hadoop3 76.89% 🟢
flink 55.44% 🟢
flink-common 32.2% 🔴
flink-runtime 0.0% 🔴
gcp 32.2% 🔴
hadoop-auth 68.0% 🟢
hadoop-common 17.84% 🔴
hive-metastore-common 62.3% 🟢
hive-metastore3-libs 58.8% 🟢
iceberg-aliyun-bundle 0.0% 🔴
iceberg-common 66.53% 🟢
iceberg-rest-server 77.01% 🟢
idp-basic 86.77% 🟢
integration-test-common 0.0% 🔴
jobs 71.35% 🟢
k8s-job-executor 99.81% 🟢
lance-common 39.9% 🔴
lance-rest-server 69.51% 🟢
lineage 59.39% 🟢
optimizer 84.27% 🟢
optimizer-api 46.58% 🟢
server 91.42% +0.2% 🟢
server-common 83.83% -0.22% 🟢
spark 61.79% 🟢
tencent 81.78% 🟢
tms 80.0% 🟢
trino-connector 63.31% 🟢
Files
Module File Coverage
catalog-lakehouse-generic LanceTableOperations.java 74.09% 🟢
core ViewNormalizeDispatcher.java 100.0% 🟢
StatisticMetaService.java 98.73% 🟢
ModelVersionMetaService.java 98.26% 🟢
TagMetaService.java 97.08% 🟢
JobManager.java 92.13% 🟢
TableOperationDispatcher.java 91.19% 🟢
ExceptionUtils.java 88.89% 🟢
RelationalEntityStore.java 88.68% 🟢
ViewOperationDispatcher.java 82.77% 🟢
TagManager.java 82.61% 🟢
TopicOperationDispatcher.java 82.21% 🟢
CatalogManager.java 76.62% 🟢
SchemaOperationDispatcher.java 71.38% 🟢
ViewDispatcher.java 0.0% 🔴
server ViewOperations.java 100.0% 🟢
GravitinoInterceptionService.java 93.41% 🟢
LoadViewAuthorizationExecutor.java 91.3% 🟢
AuthorizeExecutorFactory.java 70.0% 🟢
server-common AuthorizationRequest.java 0.0% 🔴

@jarredhj0214

Copy link
Copy Markdown
Contributor Author

Updated in 3f4fe3e. Removed the non-importing probe path. Both table and view probes now use internal loads: success retains 403; absence returns a terminal 404 without entering the REST handler.

}
GravitinoEnv.getInstance().internalViewDispatcher().loadView(viewIdentifier);
return false;
} catch (NoSuchViewException e) {

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.

We don't need this. The client needs NoSuchViewException or NoSuchTableException. So we should return NoSuchViewException or NoSuchTableException.

throw new MissingViewProbeException(viewIdentifier);
}
GravitinoEnv.getInstance().internalViewDispatcher().loadView(viewIdentifier);
return false;

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.

return false will block the subsequent load operation. This is enough.

@jarredhj0214

Copy link
Copy Markdown
Contributor Author

Updated in e0730d9 following your comments. Removed both custom missing-probe exception classes. Table and view probes now propagate the original NoSuchTableException / NoSuchViewException; the interceptor maps those standard exceptions directly to terminal 404 responses without invoking the REST load handler.

After a successful internal load, the executors return false, retaining the authorization denial and blocking the subsequent load operation. The view path now uses a single final return false, including the unsupported-operation case.

The executor tests, interception tests (including concurrent-create regressions), view REST tests, and formatting checks passed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug report] Missing view existence probing breaks Flink qualified name resolution

3 participants