This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch geido/fix/dashboard-pdf-download
in repository https://gitbox.apache.org/repos/asf/superset.git

commit cdbf70d9a3496e2ba1128e8d2966c7da3cf28501
Author: Diego Pucci <[email protected]>
AuthorDate: Fri Apr 26 17:48:00 2024 +0300

    Add img proxy
---
 superset-frontend/src/utils/downloadAsImage.ts |  5 ++
 superset-frontend/src/utils/downloadAsPdf.ts   |  1 +
 superset/initialization/__init__.py            |  2 +
 superset/views/img_proxy.py                    | 64 ++++++++++++++++++++++++++
 4 files changed, 72 insertions(+)

diff --git a/superset-frontend/src/utils/downloadAsImage.ts 
b/superset-frontend/src/utils/downloadAsImage.ts
index 63b65cb60b..3bc8bca130 100644
--- a/superset-frontend/src/utils/downloadAsImage.ts
+++ b/superset-frontend/src/utils/downloadAsImage.ts
@@ -72,6 +72,11 @@ export default function downloadAsImage(
       .toJpeg(elementToPrint, {
         bgcolor: supersetTheme.colors.grayscale.light4,
         filter,
+        corsImg: {
+          url: '/img_proxy/?url=#{cors}',
+          method: 'GET',
+          data: '',
+        },
       })
       .then((dataUrl: string) => {
         const link = document.createElement('a');
diff --git a/superset-frontend/src/utils/downloadAsPdf.ts 
b/superset-frontend/src/utils/downloadAsPdf.ts
index bb769d1eb1..c9fd462e06 100644
--- a/superset-frontend/src/utils/downloadAsPdf.ts
+++ b/superset-frontend/src/utils/downloadAsPdf.ts
@@ -62,6 +62,7 @@ export default function downloadAsPdf(
       image: { type: 'jpeg', quality: 1 },
       html2canvas: { scale: 2 },
       excludeClassNames: ['header-controls'],
+      proxyUrl: '/img_proxy/?url=',
     };
     return domToPdf(elementToPrint, options)
       .then(() => {
diff --git a/superset/initialization/__init__.py 
b/superset/initialization/__init__.py
index 069ed19483..af53db2826 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -174,6 +174,7 @@ class SupersetAppInitializer:  # pylint: 
disable=too-many-public-methods
         from superset.views.datasource.views import DatasetEditor, Datasource
         from superset.views.dynamic_plugins import DynamicPluginsView
         from superset.views.explore import ExplorePermalinkView, ExploreView
+        from superset.views.img_proxy import ImgProxyView
         from superset.views.key_value import KV
         from superset.views.log.api import LogRestApi
         from superset.views.log.views import LogModelView
@@ -314,6 +315,7 @@ class SupersetAppInitializer:  # pylint: 
disable=too-many-public-methods
         appbuilder.add_view_no_menu(TaggedObjectsModelView)
         appbuilder.add_view_no_menu(TagView)
         appbuilder.add_view_no_menu(ReportView)
+        appbuilder.add_view_no_menu(ImgProxyView())
 
         #
         # Add links
diff --git a/superset/views/img_proxy.py b/superset/views/img_proxy.py
new file mode 100644
index 0000000000..58ade4246a
--- /dev/null
+++ b/superset/views/img_proxy.py
@@ -0,0 +1,64 @@
+from typing import Any, Dict
+import requests
+from urllib.parse import urlparse
+from flask import g, request, Response
+from flask_appbuilder.api import expose
+from superset import event_logger
+from superset.utils.core import (
+    get_user_id,
+)
+from .base import BaseSupersetView
+
+class ImgProxyView(BaseSupersetView):
+    route_base = "/img_proxy"
+
+    @expose("/")
+    @event_logger.log_this
+    def img_proxy(self) -> Response:
+        """
+        Proxy to an external URL, to overcome CORS restrictions.
+        Returns a HTTP response containing the resource fetched from the 
external URL.
+        """
+        if not get_user_id():
+            raise Exception("User context not found")
+        
+        url = request.args.get('url')
+
+        if not url:
+            return Response("URL parameter 'url' is missing", status=400)
+
+        parsed_url = urlparse(url)
+        if parsed_url.scheme not in ['http', 'https']:
+            return Response("Invalid URL scheme", status=400)
+        
+        try:
+            response = self.fetch_resource(url)
+        except Exception:
+            return Response("Error fetching resource", status=500)
+
+        return self.build_response(response)
+
+    def fetch_resource(self, url: str) -> Any:
+        """Fetch the resource from the external server and handle errors."""
+        try:
+            response = requests.get(url)
+            response.raise_for_status()
+
+            return response
+        except requests.RequestException as e:
+            raise e
+
+    def build_response(self, response: requests.Response) -> Response:
+        """Build the HTTP response to return based on the fetched resource."""
+        allowed_content_types = ['image/']
+        content_type = response.headers.get('content-type', '')
+        
+        if not any(content_type.startswith(content_type_prefix) for 
content_type_prefix in allowed_content_types):
+            return Response("Response is not an allowed resource type", 
status=400)
+
+        headers: Dict[str, Any] = {key: value for (key, value) in 
response.headers.items()}
+        
+        return Response(response.content, response.status_code, headers)
+        
+        
+

Reply via email to