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: