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 902509b1f06302f1adb7e559fe2f1f22f3842275 Author: Beto Dealmeida <[email protected]> AuthorDate: Wed Dec 17 17:29:18 2025 -0500 Explore working --- superset/data_access_rules/utils.py | 57 +++++++++++++++++++++++++++++++++++++ superset/security/manager.py | 23 +++++++++++++++ superset/utils/filters.py | 47 +++++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/superset/data_access_rules/utils.py b/superset/data_access_rules/utils.py index 90582b7291..c56d9268f2 100644 --- a/superset/data_access_rules/utils.py +++ b/superset/data_access_rules/utils.py @@ -732,6 +732,63 @@ def get_allowed_databases() -> set[str]: return database_names +@dataclass +class AllowedTable: + """A table allowed by DAR with database context.""" + + database: str + table: str + schema: str | None = None + catalog: str | None = None + + +def get_all_allowed_tables() -> list[AllowedTable]: + """ + Get all tables that the current user has access to via Data Access Rules + across all databases. + + This function is used for dataset filtering where we need to know all + specific tables the user can access. + + Returns: + List of AllowedTable objects representing allowed tables. + """ + if not is_feature_enabled("DATA_ACCESS_RULES"): + return [] + + rules = get_user_rules() + if not rules: + return [] + + allowed_tables: list[AllowedTable] = [] + + for rule in rules: + rule_dict = rule.rule_dict + + # Collect tables from allowed entries + for entry in rule_dict.get("allowed", []): + database = entry.get("database") + if not database: + continue + + table_name = entry.get("table") + if not table_name: + # Skip database-level or schema-level access for now + # as we can't enumerate all tables without querying the DB + continue + + allowed_tables.append( + AllowedTable( + database=database, + table=table_name, + schema=entry.get("schema"), + catalog=entry.get("catalog"), + ) + ) + + return allowed_tables + + def get_all_group_keys( database_name: str | None = None, table: Table | None = None, diff --git a/superset/security/manager.py b/superset/security/manager.py index f377832194..5c48064db6 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -2489,10 +2489,33 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods assert datasource + # Check DAR access for datasource + dar_allowed = False + if is_feature_enabled("DATA_ACCESS_RULES") and hasattr( + datasource, "database" + ): + from superset.data_access_rules.utils import ( + AccessCheckResult, + check_table_access, + ) + from superset.sql.parse import Table + + dar_table = Table( + table=datasource.table_name, + schema=datasource.schema, + catalog=getattr(datasource, "catalog", None), + ) + dar_access_info = check_table_access( + database_name=datasource.database.database_name, + table=dar_table, + ) + dar_allowed = dar_access_info.access == AccessCheckResult.ALLOWED + if not ( self.can_access_schema(datasource) or self.can_access("datasource_access", datasource.perm or "") or self.is_owner(datasource) + or dar_allowed or ( # Grant access to the datasource only if dashboard RBAC is enabled # or the user is an embedded guest user with access to the dashboard diff --git a/superset/utils/filters.py b/superset/utils/filters.py index 8c4a079949..fb90dc7c7d 100644 --- a/superset/utils/filters.py +++ b/superset/utils/filters.py @@ -17,10 +17,51 @@ from typing import Any from flask_appbuilder import Model -from sqlalchemy import or_ +from sqlalchemy import and_, or_ from sqlalchemy.sql.elements import BooleanClauseList +def get_dar_dataset_filters(base_model: type[Model]) -> list[Any]: + """Get SQLAlchemy filters for DAR-allowed tables.""" + # pylint: disable=import-outside-toplevel + import logging + + from superset import is_feature_enabled + from superset.connectors.sqla.models import Database + + if not is_feature_enabled("DATA_ACCESS_RULES"): + return [] + + try: + from superset.data_access_rules.utils import get_all_allowed_tables + + allowed_tables = get_all_allowed_tables() + if not allowed_tables: + return [] + + # Build OR filters for each allowed table + table_filters = [] + for table in allowed_tables: + table_filter = and_( + Database.database_name == table.database, + base_model.table_name == table.table, + ) + # Add schema filter if specified + if table.schema: + table_filter = and_( + table_filter, + base_model.schema == table.schema, + ) + table_filters.append(table_filter) + + return table_filters + except Exception as ex: + logging.getLogger(__name__).warning( + "Error getting DAR dataset filters: %s", ex + ) + return [] + + def get_dataset_access_filters( base_model: type[Model], *args: Any, @@ -34,10 +75,14 @@ def get_dataset_access_filters( schema_perms = security_manager.user_view_menu_names("schema_access") catalog_perms = security_manager.user_view_menu_names("catalog_access") + # Get DAR-based table filters + dar_filters = get_dar_dataset_filters(base_model) + return or_( Database.id.in_(database_ids), base_model.perm.in_(perms), base_model.catalog_perm.in_(catalog_perms), base_model.schema_perm.in_(schema_perms), + *dar_filters, *args, )
