You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2024/02/06 11:12:08 UTC
(superset) 02/02: Override API permissions POC
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 <di...@gmail.com>
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: