This is an automated email from the ASF dual-hosted git repository. diegopucci pushed a commit to branch diego/ch77449/dashboard-granular-permissions in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1cd12c6d443e16ca2360397ca15e7ef3f1d55a8b Author: geido <[email protected]> AuthorDate: Tue Feb 6 13:11:49 2024 +0200 Override API permissions POC --- superset/utils/decorators.py | 25 +++++++++++++++++++++++++ superset/views/datasource/views.py | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/superset/utils/decorators.py b/superset/utils/decorators.py index 43b6909caf..74eec9b4df 100644 --- a/superset/utils/decorators.py +++ b/superset/utils/decorators.py @@ -20,11 +20,14 @@ import logging import time from collections.abc import Iterator from contextlib import contextmanager +from functools import wraps from typing import Any, Callable, TYPE_CHECKING from uuid import UUID from flask import current_app, g, Response +from flask_appbuilder.security.decorators import has_access_api +from superset import security_manager from superset.utils import core as utils from superset.utils.dates import now_as_float @@ -191,3 +194,25 @@ def debounce(duration: float | int = 0.1) -> Callable[..., Any]: def on_security_exception(self: Any, ex: Exception) -> Response: return self.response(403, **{"message": utils.error_msg_from_exception(ex)}) + + +def has_api_override_permission(permissions_targets: list[tuple[str, str]]) -> Callable: + """ + Decorator to check for multiple override permissions, each tied to a specific target. + :param permissions_targets: A list of tuples where each tuple contains [permission, target_view]. + """ + + def decorator(f: Callable) -> Callable: + @wraps(f) + def decorated_function(*args, **kwargs) -> Callable: + # Iterate through the list of permission-target pairs + for permission, target_view in permissions_targets: + # Check for each custom override permission with its specific target + if security_manager.can_access(permission, target_view): + return f(*args, **kwargs) + # Fallback to the standard has_access_api decorator if none of the override permissions are present + return has_access_api(f)(*args, **kwargs) + + return decorated_function + + return decorator diff --git a/superset/views/datasource/views.py b/superset/views/datasource/views.py index 2e46faf0af..1d67c6e1a1 100644 --- a/superset/views/datasource/views.py +++ b/superset/views/datasource/views.py @@ -39,6 +39,7 @@ from superset.exceptions import SupersetException, SupersetSecurityException from superset.models.core import Database from superset.superset_typing import FlaskResponse from superset.utils.core import DatasourceType +from superset.utils.decorators import has_api_override_permission from superset.views.base import ( api, BaseSupersetView, @@ -189,7 +190,7 @@ class Datasource(BaseSupersetView): return self.json_response(external_metadata) @expose("/samples", methods=("POST",)) - @has_access_api + @has_api_override_permission([("can_drill_to_detail", "Dashboard")]) @api @handle_api_exception def samples(self) -> FlaskResponse:
