From 73d2cd8178c46e7530d1d8455c2b8c8757f45106 Mon Sep 17 00:00:00 2001 From: JingsongLi Date: Mon, 24 Aug 2026 17:56:25 +0800 Subject: [PATCH 01/12] [api][spark] Add REST permission management --- docs/README.md | 2 +- docs/docs/concepts/rest/index.md | 3 +- docs/docs/concepts/rest/management-api.md | 118 ++++ docs/scripts/validate-rest-openapi.js | 504 +++++++++++++----- docs/sidebars.js | 3 +- docs/static/rest-management-open-api.yaml | 361 +++++++++++++ .../apache/paimon/management/ColumnMask.java | 68 +++ .../paimon/management/ColumnSelection.java | 69 +++ .../management/ListPermissionsRequest.java | 92 ++++ .../apache/paimon/management/Permission.java | 213 ++++++++ .../management/PermissionManagement.java | 31 ++ .../paimon/management/ResourceType.java | 33 ++ .../apache/paimon/management/RowFilter.java | 68 +++ .../java/org/apache/paimon/rest/RESTApi.java | 48 ++ .../paimon/rest/RESTCatalogOptions.java | 7 + .../paimon/rest/RESTPermissionManagement.java | 57 ++ .../org/apache/paimon/rest/ResourcePaths.java | 14 + .../rest/requests/GrantPermissionRequest.java | 187 +++++++ .../requests/RevokePermissionRequest.java | 162 ++++++ .../responses/ListPermissionsResponse.java | 74 +++ .../PermissionManagementJsonTest.java | 109 ++++ .../rest/RESTPermissionManagementTest.java | 232 ++++++++ .../RequestJacksonCompatibilityTest.java | 2 + .../org/apache/paimon/rest/RESTCatalog.java | 6 + .../apache/paimon/rest/RESTCatalogServer.java | 120 +++++ .../PaimonSqlExtensions.g4 | 8 +- .../apache/paimon/spark/SparkProcedures.java | 6 + .../procedure/BasePermissionProcedure.java | 316 +++++++++++ .../procedure/GrantPermissionProcedure.java | 110 ++++ .../procedure/ListPermissionsProcedure.java | 158 ++++++ .../procedure/RevokePermissionProcedure.java | 98 ++++ .../PaimonSparkTestWithRestCatalogBase.scala | 7 +- .../procedure/PermissionProcedureTest.scala | 140 +++++ 33 files changed, 3287 insertions(+), 139 deletions(-) create mode 100644 docs/docs/concepts/rest/management-api.md create mode 100644 docs/static/rest-management-open-api.yaml create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/ColumnMask.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/ColumnSelection.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/ListPermissionsRequest.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/Permission.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/PermissionManagement.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/ResourceType.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/management/RowFilter.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/rest/RESTPermissionManagement.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/rest/requests/GrantPermissionRequest.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/rest/requests/RevokePermissionRequest.java create mode 100644 paimon-api/src/main/java/org/apache/paimon/rest/responses/ListPermissionsResponse.java create mode 100644 paimon-api/src/test/java/org/apache/paimon/management/PermissionManagementJsonTest.java create mode 100644 paimon-api/src/test/java/org/apache/paimon/rest/RESTPermissionManagementTest.java create mode 100644 paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/BasePermissionProcedure.java create mode 100644 paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/GrantPermissionProcedure.java create mode 100644 paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/ListPermissionsProcedure.java create mode 100644 paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/RevokePermissionProcedure.java create mode 100644 paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/procedure/PermissionProcedureTest.scala diff --git a/docs/README.md b/docs/README.md index 67d65ae18e9f..5e7ba5586c36 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ The site will be available at http://localhost:3000/docs/master/. ## Build ```bash -# Validate the REST Catalog OpenAPI contract +# Validate the REST OpenAPI contracts yarn test:rest-openapi # Production build diff --git a/docs/docs/concepts/rest/index.md b/docs/docs/concepts/rest/index.md index bd8ada4b6712..8ed168cd4434 100644 --- a/docs/docs/concepts/rest/index.md +++ b/docs/docs/concepts/rest/index.md @@ -63,4 +63,5 @@ RESTCatalog supports multiple access authentication methods, including the follo ## REST Open API -See [REST API](./rest-api). +See [REST Catalog API](./rest-api) for catalog operations and +[REST Management API](./management-api) for permission management. diff --git a/docs/docs/concepts/rest/management-api.md b/docs/docs/concepts/rest/management-api.md new file mode 100644 index 000000000000..5e5355b57288 --- /dev/null +++ b/docs/docs/concepts/rest/management-api.md @@ -0,0 +1,118 @@ +--- +title: "REST Management API" +hide_table_of_contents: true +--- + + + +The OpenAPI 3.1 document below defines the language-neutral control-plane contract for listing, +granting, and revoking permissions on Paimon catalog resources. + +The permission payload uses a flat resource-oriented model. User and role lifecycle, +authentication, policy persistence, and authorization decisions remain responsibilities of the +REST Catalog server implementation. + +## Client configuration + +Permission management is provided directly by `RESTCatalog`; it is not part of the generic +`Catalog` interface. Configure the remote catalog identifier used in the management endpoint with +`management.catalog`. The value identifies the `{catalog}` path parameter and is independent of an +engine's local catalog alias. + +For Spark, this can be supplied by the REST server's config response or configured directly: + +```properties +spark.sql.catalog.paimon.management.catalog=my_remote_catalog +``` + +Non-REST catalog implementations fail these operations with an explicit unsupported error. + +## Spark SQL + +Spark exposes the management contract through procedures. Custom `GRANT` and `REVOKE` grammar is +not required, so the SQL layer remains a thin adapter over the same Java and REST contracts. + +Grant access to selected columns: + +```sql +CALL paimon.sys.grant_permission( + resource_type => 'COLUMN', + access => 'SELECT', + principal => 'role:analyst', + database => 'sales', + table => 'orders', + column_names => array('id', 'amount'), + expire_time => '2027-01-01T00:00:00Z' +); +``` + +`resource_type`, `access`, and `principal` are required. `CATALOG_ALL` and `DATABASE_ALL` represent +permissions inherited by descendant resources. `COLUMN`, `ROW_FILTER`, and `COLUMN_MASKING` are +independent table-scoped permissions. Exactly one of `column_names` and `excluded_column_names` may +be set for `COLUMN`. + +Grant a row filter separately. The server compiles the expression into its predicate representation: + +```sql +CALL paimon.sys.grant_permission( + resource_type => 'ROW_FILTER', + access => 'ROW_FILTER', + principal => 'role:analyst', + database => 'sales', + table => 'orders', + row_filter => 'region = ''cn''' +); +``` + +List grants using any combination of exact-match filters: + +```sql +CALL paimon.sys.list_permissions( + resource_type => 'TABLE', + principal => 'role:analyst', + max_results => 100, + page_token => '' +); +``` + +Listing `CATALOG` also includes `CATALOG_ALL`, listing `DATABASE` includes `DATABASE_ALL`, and +listing `TABLE` includes `COLUMN`, `ROW_FILTER`, and `COLUMN_MASKING` rows. The result mirrors the +flat permission payload with separate JSON columns for columns, row filters, and column masking. +The next-page token is repeated on each row and is null when no further page exists. + +Revoke by stable permission identity. Constraints and expiration are deliberately omitted: + +```sql +CALL paimon.sys.revoke_permission( + resource_type => 'COLUMN', + access => 'SELECT', + principal => 'role:analyst', + database => 'sales', + table => 'orders' +); +``` + +Grant uses upsert behavior. Revoke identifies a permission by resource type, resource, access, and +principal; row-filter, column-masking, column-selection, and expiration payloads are not part of +that identity. Revoking a permission which does not exist may return HTTP 404. + +## OpenAPI contract + + +