feat: add customizable brand spinners with theme integration (#34764)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
Maxime Beauchemin
2025-09-03 08:28:59 -07:00
committed by GitHub
parent b7a193d53e
commit b0d3f0f0d4
31 changed files with 987 additions and 167 deletions

View File

@@ -23,6 +23,7 @@ from superset.themes.utils import (
_is_valid_algorithm,
_is_valid_theme_mode,
is_valid_theme,
sanitize_theme_tokens,
)
@@ -75,3 +76,40 @@ def test_is_valid_algorithm(algorithm, expected):
)
def test_is_valid_theme(theme, expected):
assert is_valid_theme(theme) is expected
def test_sanitize_theme_tokens_with_svg():
"""Test that theme tokens with SVG content get sanitized."""
theme_config = {
"token": {
"brandSpinnerSvg": (
'<svg><script>alert("xss")</script><rect width="10"/></svg>'
),
"colorPrimary": "#ff0000",
}
}
result = sanitize_theme_tokens(theme_config)
assert "script" not in result["token"]["brandSpinnerSvg"].lower()
assert result["token"]["colorPrimary"] == "#ff0000" # Other tokens unchanged
def test_sanitize_theme_tokens_with_url():
"""Test that theme tokens with URL get sanitized."""
theme_config = {
"token": {
"brandSpinnerUrl": "javascript:alert('xss')",
"colorPrimary": "#ff0000",
}
}
result = sanitize_theme_tokens(theme_config)
assert result["token"]["brandSpinnerUrl"] == "" # Blocked
assert result["token"]["colorPrimary"] == "#ff0000" # Unchanged
def test_sanitize_theme_tokens_no_spinner_tokens():
"""Test that themes without spinner tokens are unchanged."""
theme_config = {"token": {"colorPrimary": "#ff0000", "fontFamily": "Arial"}}
result = sanitize_theme_tokens(theme_config)
assert result == theme_config

View File

@@ -46,6 +46,8 @@ from superset.utils.core import (
QueryObjectFilterClause,
QuerySource,
remove_extra_adhoc_filters,
sanitize_svg_content,
sanitize_url,
)
from tests.conftest import with_config
@@ -1122,3 +1124,41 @@ def test_get_stacktrace():
except Exception:
stacktrace = get_stacktrace()
assert stacktrace is None
def test_sanitize_svg_content_safe():
"""Test that safe SVG content is preserved."""
safe_svg = '<svg><rect width="10" height="10"/></svg>'
result = sanitize_svg_content(safe_svg)
assert "svg" in result
assert "rect" in result
def test_sanitize_svg_content_removes_scripts():
"""Test that nh3 removes dangerous script content."""
malicious_svg = '<svg><script>alert("xss")</script><rect/></svg>'
result = sanitize_svg_content(malicious_svg)
assert "script" not in result.lower()
assert "alert" not in result
def test_sanitize_url_relative():
"""Test that relative URLs are allowed."""
assert sanitize_url("/static/spinner.gif") == "/static/spinner.gif"
def test_sanitize_url_safe_absolute():
"""Test that safe absolute URLs are allowed."""
assert (
sanitize_url("https://example.com/spinner.gif")
== "https://example.com/spinner.gif"
)
assert (
sanitize_url("http://localhost/spinner.png") == "http://localhost/spinner.png"
)
def test_sanitize_url_blocks_dangerous():
"""Test that dangerous URL schemes are blocked."""
assert sanitize_url("javascript:alert('xss')") == ""
assert sanitize_url("data:text/html,<script>alert(1)</script>") == ""