Skip to content

Commit

Permalink
feat(Digest): Add RLS at digest generation for Charts and Dashboards (a…
Browse files Browse the repository at this point in the history
…pache#30336)

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
  • Loading branch information
geido and villebro committed Sep 24, 2024
1 parent cc1bb69 commit de3af85
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 10 deletions.
4 changes: 2 additions & 2 deletions superset/dashboards/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,14 +1046,14 @@ def trigger_celery() -> WerkzeugResponse:
cache_dashboard_screenshot.delay(
username=get_current_user(),
guest_token=g.user.guest_token
if isinstance(g.user, GuestUser)
if get_current_user() and isinstance(g.user, GuestUser)
else None,
dashboard_id=dashboard.id,
dashboard_url=dashboard_url,
cache_key=cache_key,
force=True,
thumb_size=thumb_size,
window_size=window_size,
cache_key=cache_key,
)
return self.response(
202,
Expand Down
5 changes: 5 additions & 0 deletions superset/security/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
GuestUser,
)
from superset.sql_parse import extract_tables_from_jinja_sql, Table
from superset.tasks.utils import get_current_user
from superset.utils import json
from superset.utils.core import (
DatasourceName,
Expand Down Expand Up @@ -2639,8 +2640,12 @@ def is_guest_user(user: Optional[Any] = None) -> bool:

if not is_feature_enabled("EMBEDDED_SUPERSET"):
return False

if not user:
if not get_current_user():
return False
user = g.user

return hasattr(user, "is_guest_user") and user.is_guest_user

def get_current_guest_user_if_guest(self) -> Optional[GuestUser]:
Expand Down
2 changes: 1 addition & 1 deletion superset/tasks/thumbnails.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ def cache_dashboard_screenshot( # pylint: disable=too-many-arguments
dashboard_id: int,
dashboard_url: str,
force: bool = True,
cache_key: Optional[str] = None,
guest_token: Optional[GuestToken] = None,
thumb_size: Optional[WindowSize] = None,
window_size: Optional[WindowSize] = None,
cache_key: Optional[str] = None,
) -> None:
# pylint: disable=import-outside-toplevel
from superset.models.dashboard import Dashboard
Expand Down
47 changes: 47 additions & 0 deletions superset/thumbnails/digest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@

from flask import current_app

from superset import security_manager
from superset.tasks.types import ExecutorType
from superset.tasks.utils import get_current_user, get_executor
from superset.utils.core import override_user
from superset.utils.hashing import md5_sha_from_str

if TYPE_CHECKING:
from superset.connectors.sqla.models import BaseDatasource, SqlaTable
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice

Expand All @@ -49,8 +52,46 @@ def _adjust_string_for_executor(
return unique_string


def _adjust_string_with_rls(
unique_string: str,
datasources: list[SqlaTable | None] | set[BaseDatasource],
executor: str,
) -> str:
"""
Add the RLS filters to the unique string based on current executor.
"""
user = (
security_manager.find_user(executor)
or security_manager.get_current_guest_user_if_guest()
)

if user:
stringified_rls = ""
with override_user(user):
for datasource in datasources:
if (
datasource
and hasattr(datasource, "is_rls_supported")
and datasource.is_rls_supported
):
rls_filters = datasource.get_sqla_row_level_filters()

if len(rls_filters) > 0:
stringified_rls += (
f"{str(datasource.id)}\t"
+ "\t".join([str(f) for f in rls_filters])
+ "\n"
)

if stringified_rls:
unique_string = f"{unique_string}\n{stringified_rls}"

return unique_string


def get_dashboard_digest(dashboard: Dashboard) -> str:
config = current_app.config
datasources = dashboard.datasources
executor_type, executor = get_executor(
executor_types=config["THUMBNAIL_EXECUTE_AS"],
model=dashboard,
Expand All @@ -65,19 +106,25 @@ def get_dashboard_digest(dashboard: Dashboard) -> str:
)

unique_string = _adjust_string_for_executor(unique_string, executor_type, executor)
unique_string = _adjust_string_with_rls(unique_string, datasources, executor)

return md5_sha_from_str(unique_string)


def get_chart_digest(chart: Slice) -> str:
config = current_app.config
datasource = chart.datasource
executor_type, executor = get_executor(
executor_types=config["THUMBNAIL_EXECUTE_AS"],
model=chart,
current_user=get_current_user(),
)

if func := config["THUMBNAIL_CHART_DIGEST_FUNC"]:
return func(chart, executor_type, executor)

unique_string = f"{chart.params or ''}.{executor}"
unique_string = _adjust_string_for_executor(unique_string, executor_type, executor)
unique_string = _adjust_string_with_rls(unique_string, [datasource], executor)

return md5_sha_from_str(unique_string)
Loading

0 comments on commit de3af85

Please sign in to comment.