mirror of
https://github.com/apache/superset.git
synced 2026-04-14 13:44:46 +00:00
156 lines
6.0 KiB
Python
156 lines
6.0 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
from typing import Any
|
|
|
|
from marshmallow import fields, Schema, validates, ValidationError
|
|
|
|
from superset.themes.utils import (
|
|
is_valid_theme,
|
|
sanitize_theme_tokens,
|
|
validate_font_urls,
|
|
)
|
|
from superset.utils import json
|
|
|
|
|
|
def _sanitize_and_validate_theme_config(theme_config: dict[str, Any]) -> dict[str, Any]:
|
|
"""Sanitize and validate theme configuration.
|
|
|
|
Applies token sanitization and font URL validation.
|
|
Returns the sanitized configuration.
|
|
"""
|
|
sanitized_config = sanitize_theme_tokens(theme_config)
|
|
|
|
# Validate and sanitize fontUrls if present
|
|
if "token" in sanitized_config and isinstance(sanitized_config["token"], dict):
|
|
font_urls = sanitized_config["token"].get("fontUrls")
|
|
if font_urls is not None:
|
|
sanitized_config["token"]["fontUrls"] = validate_font_urls(font_urls)
|
|
|
|
# Validate theme structure
|
|
if not is_valid_theme(sanitized_config):
|
|
raise ValidationError("Invalid theme configuration structure")
|
|
|
|
return sanitized_config
|
|
|
|
|
|
class ImportV1ThemeSchema(Schema):
|
|
theme_name = fields.String(required=True)
|
|
json_data = fields.Raw(required=True)
|
|
uuid = fields.UUID(required=True)
|
|
version = fields.String(required=True)
|
|
|
|
@validates("json_data")
|
|
def validate_json_data(self, value: dict[str, Any]) -> None:
|
|
# Convert dict to JSON string for validation
|
|
if isinstance(value, dict):
|
|
json_str = json.dumps(value)
|
|
else:
|
|
json_str = str(value)
|
|
|
|
# Parse it back to validate it's valid JSON
|
|
try:
|
|
theme_config = json.loads(json_str) if isinstance(value, str) else value
|
|
except (TypeError, json.JSONDecodeError) as ex:
|
|
raise ValidationError("Invalid JSON configuration") from ex
|
|
|
|
# Sanitize and validate the theme configuration
|
|
sanitized_config = _sanitize_and_validate_theme_config(theme_config)
|
|
|
|
# Update the field with sanitized content for import
|
|
if sanitized_config != theme_config:
|
|
# Update the original value with sanitized content
|
|
if isinstance(value, dict):
|
|
value.clear()
|
|
value.update(sanitized_config)
|
|
else:
|
|
self.context["sanitized_json_data"] = json.dumps(sanitized_config)
|
|
|
|
|
|
class ThemePostSchema(Schema):
|
|
theme_name = fields.String(required=True, allow_none=False)
|
|
json_data = fields.String(required=True, allow_none=False)
|
|
|
|
@validates("theme_name")
|
|
def validate_theme_name(self, value: str) -> None:
|
|
if not value or not value.strip():
|
|
raise ValidationError("Theme name cannot be empty.")
|
|
|
|
@validates("json_data")
|
|
def validate_and_sanitize_json_data(self, value: str) -> None:
|
|
# Parse JSON
|
|
try:
|
|
theme_config = json.loads(value) if isinstance(value, str) else value
|
|
except (TypeError, json.JSONDecodeError) as ex:
|
|
raise ValidationError("Invalid JSON configuration") from ex
|
|
|
|
# Sanitize and validate the theme configuration
|
|
sanitized_config = _sanitize_and_validate_theme_config(theme_config)
|
|
|
|
# Update the field with sanitized content
|
|
# Note: This modifies the input data to ensure sanitized content is stored
|
|
if sanitized_config != theme_config:
|
|
# Re-serialize the sanitized config
|
|
self.context["sanitized_json_data"] = json.dumps(sanitized_config)
|
|
|
|
|
|
class ThemePutSchema(Schema):
|
|
theme_name = fields.String(required=True, allow_none=False)
|
|
json_data = fields.String(required=True, allow_none=False)
|
|
|
|
@validates("theme_name")
|
|
def validate_theme_name(self, value: str) -> None:
|
|
if not value or not value.strip():
|
|
raise ValidationError("Theme name cannot be empty.")
|
|
|
|
@validates("json_data")
|
|
def validate_and_sanitize_json_data(self, value: str) -> None:
|
|
# Parse JSON
|
|
try:
|
|
theme_config = json.loads(value) if isinstance(value, str) else value
|
|
except (TypeError, json.JSONDecodeError) as ex:
|
|
raise ValidationError("Invalid JSON configuration") from ex
|
|
|
|
# Sanitize and validate the theme configuration
|
|
sanitized_config = _sanitize_and_validate_theme_config(theme_config)
|
|
|
|
# Update the field with sanitized content
|
|
# Note: This modifies the input data to ensure sanitized content is stored
|
|
if sanitized_config != theme_config:
|
|
# Re-serialize the sanitized config
|
|
self.context["sanitized_json_data"] = json.dumps(sanitized_config)
|
|
|
|
|
|
openapi_spec_methods_override = {
|
|
"get": {"get": {"summary": "Get a theme"}},
|
|
"get_list": {
|
|
"get": {
|
|
"summary": "Get a list of themes",
|
|
"description": "Gets a list of themes, use Rison or JSON "
|
|
"query parameters for filtering, sorting,"
|
|
" pagination and for selecting specific"
|
|
" columns and metadata.",
|
|
}
|
|
},
|
|
"post": {"post": {"summary": "Create a theme"}},
|
|
"put": {"put": {"summary": "Update a theme"}},
|
|
"delete": {"delete": {"summary": "Delete a theme"}},
|
|
"info": {"get": {"summary": "Get metadata information about this API resource"}},
|
|
}
|
|
|
|
get_delete_ids_schema = {"type": "array", "items": {"type": "integer"}}
|
|
get_export_ids_schema = {"type": "array", "items": {"type": "integer"}}
|