You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2025/12/17 22:29:30 UTC
(superset) 02/03: RLS and CLS working
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch new-dar
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 2e7df4614c8ccc7dbcd3c57888f3ea8f5769c585
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 17 17:08:58 2025 -0500
RLS and CLS working
---
superset/data_access_rules/api.py | 4 ++--
superset/data_access_rules/utils.py | 47 +++++++++++++++++++++++++++++++++----
superset/sql/parse.py | 3 +++
3 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/superset/data_access_rules/api.py b/superset/data_access_rules/api.py
index c7c9e27f5b..fcd54474fb 100644
--- a/superset/data_access_rules/api.py
+++ b/superset/data_access_rules/api.py
@@ -31,7 +31,6 @@ from marshmallow import ValidationError
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.data_access_rules.models import DataAccessRule
from superset.data_access_rules.schemas import (
- DataAccessRuleListSchema,
DataAccessRulePostSchema,
DataAccessRulePutSchema,
DataAccessRuleShowSchema,
@@ -109,7 +108,8 @@ class DataAccessRulesRestApi(BaseSupersetModelRestApi):
add_model_schema = DataAccessRulePostSchema()
edit_model_schema = DataAccessRulePutSchema()
- list_model_schema = DataAccessRuleListSchema()
+ # Don't use custom list_model_schema - let Flask-AppBuilder handle
+ # nested relationships via list_columns dot notation
show_model_schema = DataAccessRuleShowSchema()
openapi_spec_methods = {
diff --git a/superset/data_access_rules/utils.py b/superset/data_access_rules/utils.py
index 31689c8b4f..90582b7291 100644
--- a/superset/data_access_rules/utils.py
+++ b/superset/data_access_rules/utils.py
@@ -551,14 +551,51 @@ def apply_data_access_rules(
if table_cls:
cls_rules[qualified_table] = table_cls
- # Apply RLS if we have predicates
+ # Apply CLS first (before RLS) so that hidden columns are removed
+ # before RLS wraps the query in a subquery
+ if cls_rules:
+ # Build schema dict for sqlglot's qualify() to expand SELECT *
+ # sqlglot expects nested format: {catalog: {schema: {table: {col: type}}}}
+ # or {schema: {table: {col: type}}} without catalog
+ table_schemas: dict[str, Any] = {}
+ for table in cls_rules.keys():
+ try:
+ columns = database.get_columns(table)
+ col_types = {
+ col["column_name"]: str(col.get("type", "VARCHAR"))
+ for col in columns
+ }
+
+ # Build nested structure for sqlglot
+ if table.catalog:
+ if table.catalog not in table_schemas:
+ table_schemas[table.catalog] = {}
+ if table.schema:
+ if table.schema not in table_schemas[table.catalog]:
+ table_schemas[table.catalog][table.schema] = {}
+ table_schemas[table.catalog][table.schema][table.table] = col_types
+ else:
+ table_schemas[table.catalog][table.table] = col_types
+ elif table.schema:
+ if table.schema not in table_schemas:
+ table_schemas[table.schema] = {}
+ table_schemas[table.schema][table.table] = col_types
+ else:
+ table_schemas[table.table] = col_types
+ except Exception as ex:
+ logger.warning(
+ "Could not fetch schema for table %s: %s",
+ table,
+ ex,
+ )
+
+ parsed_statement.apply_cls(cls_rules, schema=table_schemas if table_schemas else None)
+
+ # Apply RLS after CLS - RLS wraps the query in a subquery with SELECT *
+ # which will pick up the already-transformed columns from CLS
if rls_predicates:
parsed_statement.apply_rls(catalog, schema, rls_predicates, method)
- # Apply CLS if we have rules
- if cls_rules:
- parsed_statement.apply_cls(cls_rules)
-
def get_allowed_tables(
database_name: str,
diff --git a/superset/sql/parse.py b/superset/sql/parse.py
index 3d4f55010b..c674342820 100644
--- a/superset/sql/parse.py
+++ b/superset/sql/parse.py
@@ -1536,11 +1536,14 @@ class SQLStatement(BaseSQLStatement[exp.Expression]):
# Without schema: qualifies single-table queries, partial for JOINs.
from sqlglot.optimizer.qualify import qualify
+ # Only expand stars if schema is provided (from DAR with feature flag enabled)
+ # to avoid potential errors in other contexts
self._parsed = qualify(
self._parsed,
schema=schema,
dialect=self._dialect,
validate_qualify_columns=False,
+ expand_stars=bool(schema),
)
transformer = CLSTransformer(rules, self._dialect)