You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2026/09/16 23:01:40 UTC
(superset) 02/06: Merge remote-tracking branch 'origin/master' into madhu-one-time-login-tokens
This is an automated email from the ASF dual-hosted git repository.
madhushreeag pushed a commit to branch madhu-one-time-login-tokens
in repository https://gitbox.apache.org/repos/asf/superset.git
commit f79e824e7e16d6612728dde25a52d559f6d3881e
Merge: daf102f41fd eeafd65b746
Author: madhushree agarwal <ma...@apple.com>
AuthorDate: Mon Sep 14 12:44:58 2026 -0700
Merge remote-tracking branch 'origin/master' into madhu-one-time-login-tokens
.github/workflows/docker.yml | 23 +-
.../workflows/scheduled-docker-image-refresh.yml | 2 +-
.github/workflows/superset-python-unittest.yml | 13 +-
.github/workflows/tag-release.yml | 2 +-
Dockerfile | 37 ++
RESOURCES/INTHEWILD.yaml | 5 +
SECURITY.md | 4 +
UPDATING.md | 34 ++
docker-compose-image-tag.yml | 2 +-
docker-compose-light.yml | 2 +-
docker-compose-non-dev.yml | 5 +-
docker-compose.yml | 2 +-
docker/entrypoints/docker-ci.sh | 6 +-
docs/admin_docs/installation/docker-builds.mdx | 65 ++--
docs/developer_docs/extensions/quick-start.md | 2 +-
docs/package.json | 4 +-
docs/static/img/logos/savantly.png | Bin 0 -> 5787 bytes
docs/yarn.lock | 151 ++++----
pyproject.toml | 17 +-
requirements/base.txt | 6 +-
requirements/development.txt | 15 +-
superset-frontend/.storybook/main.mjs | 10 +-
superset-frontend/babel.config.js | 4 +-
superset-frontend/changelog.config.js | 2 +-
.../eslint-plugin-i18n-strings/index.ts | 4 +-
.../no-eager-t-in-config.test.ts | 8 +-
.../no-template-vars.test.ts | 6 +-
.../eslint-plugin-i18n-strings/package.json | 1 +
.../eslint-rules/eslint-plugin-icons/index.ts | 6 +-
.../eslint-plugin-icons/no-fontawesome.test.ts | 8 +-
.../eslint-rules/eslint-plugin-icons/package.json | 1 +
.../eslint-plugin-theme-colors/index.ts | 2 +-
.../eslint-plugin-theme-colors/package.json | 1 +
superset-frontend/eslint.config.minimal.js | 12 +-
superset-frontend/jest.config.js | 2 +-
superset-frontend/package-lock.json | 366 +++++++++---------
superset-frontend/package.json | 22 +-
.../plugin-chart/templates/babel.config.erb | 1 +
.../templates/test/__mocks__/mockExportString.js | 1 +
.../packages/generator-superset/package.json | 4 +-
.../superset-ui-core/__mocks__/mockExportObject.js | 3 +-
.../superset-ui-core/__mocks__/mockExportString.js | 3 +-
.../packages/superset-ui-core/package.json | 4 +-
.../components/ActionButton/ActionButton.test.tsx | 10 +-
.../src/components/Checkbox/Checkbox.test.tsx | 5 +-
.../components/ConfirmModal/ConfirmModal.test.tsx | 8 +-
.../components/DeleteModal/DeleteModal.test.tsx | 15 +-
.../src/components/Select/AsyncSelect.test.tsx | 79 +++-
.../src/components/Select/Select.test.tsx | 67 +++-
.../cell-renderers/ActionCell/ActionCell.test.tsx | 6 +-
.../src/components/TableCollection/index.tsx | 5 +-
.../TimezoneSelector.DaylightSavingTime.test.tsx | 7 +-
.../TimezoneSelector/TimezoneSelector.test.tsx | 17 +-
.../UnsavedChangesModal.test.tsx | 14 +-
.../generators/docs/docs-screenshots.spec.ts | 3 +
.../generators/docs/mobile-screenshots.spec.ts | 3 +
.../playwright/generators/playwright.config.ts | 3 +
.../tests/embedded/embedded-dashboard.spec.ts | 5 +-
.../tests/embedded/pivot-collapse-state.spec.ts | 5 +-
.../src/AgGridTable/index.tsx | 19 +
.../src/AgGridTableChart.tsx | 3 +
.../src/controlPanel.tsx | 14 +
.../src/transformProps.ts | 2 +
.../plugin-chart-ag-grid-table/src/types.ts | 2 +
.../src/utils/useColDefs.ts | 9 +-
.../test/controlPanel.test.ts | 12 +
.../test/transformProps.test.ts | 25 ++
.../test/utils/useColDefs.test.ts | 62 ++++
.../src/Gantt/transformProps.ts | 29 +-
superset-frontend/scripts/build.js | 11 +-
superset-frontend/scripts/bundle-size-summary.js | 58 +--
superset-frontend/scripts/check-custom-rules.js | 19 +-
.../scripts/check-storybook-coverage.js | 9 +-
superset-frontend/scripts/commitlint.js | 2 +-
superset-frontend/scripts/copyAssets.js | 4 +-
.../scripts/{ => internal}/bundle-size-summary.js | 8 +-
.../{ => internal}/oxlint-metrics-uploader.js | 14 +-
.../scripts/oxlint-metrics-uploader.js | 267 +------------
.../spec/__mocks__/mockExportObject.js | 3 +-
.../spec/__mocks__/mockExportString.js | 3 +-
...summary.test.js => bundle-size-summary.test.ts} | 8 +-
...der.test.js => oxlint-metrics-uploader.test.ts} | 2 +-
.../SaveDatasetModal/SaveDatasetModal.test.tsx | 26 +-
.../SqlLab/components/SaveQuery/SaveQuery.test.tsx | 48 ++-
.../AlteredSliceTag/AlteredSliceTag.test.tsx | 8 +-
.../components/Chart/DrillBy/DrillByModal.test.tsx | 28 +-
.../Chart/DrillBy/DrillBySubmenu.test.tsx | 20 +-
.../Chart/DrillDetail/DrillDetailModal.test.tsx | 6 +-
.../DrillDetail/DrillDetailTableControls.test.tsx | 8 +-
.../tests/DatasourceEditor.test.tsx | 8 +-
.../ErrorMessage/DatabaseErrorMessage.test.tsx | 16 +-
.../DatasourceSecurityAccessErrorMessage.test.tsx | 64 +++-
.../DatasourceSecurityAccessErrorMessage.tsx | 37 +-
.../ErrorMessageWithStackTrace.test.tsx | 8 +-
.../FrontendNetworkErrorMessage.test.tsx | 4 +-
.../ErrorMessage/ParameterErrorMessage.test.tsx | 12 +-
.../ErrorMessage/TimeoutErrorMessage.test.tsx | 16 +-
.../FilterableTable/FilterableTable.test.tsx | 40 +-
.../src/components/GridTable/HeaderMenu.test.tsx | 61 +--
.../src/components/ListView/CardCollection.tsx | 16 +-
.../src/components/ListView/ListView.tsx | 4 +-
superset-frontend/src/components/ListView/utils.ts | 69 ++--
.../DetailsPanel/DetailsPanel.test.tsx | 12 +-
.../FilterIndicator/FilterIndicator.test.tsx | 10 +-
.../dashboard/components/Header/Header.test.tsx | 95 ++---
.../PropertiesModal/PropertiesModal.test.tsx | 16 +-
.../PublishedStatus/PublishedStatus.test.tsx | 10 +-
.../components/SliceHeader/SliceHeader.test.tsx | 56 +--
.../SliceHeaderControls.subdirectory.test.tsx | 15 +-
.../SliceHeaderControls.test.tsx | 224 +++++------
.../components/filterscope/FilterScope.test.tsx | 34 +-
.../filterscope/FilterScopeSelector.test.tsx | 12 +-
.../gridComponents/Divider/Divider.test.tsx | 4 +-
.../components/gridComponents/Tab/Tab.test.tsx | 16 +-
.../components/gridComponents/Tabs/Tabs.test.tsx | 18 +-
.../DownloadMenuItems/DownloadMenuItems.test.tsx | 37 +-
.../dashboard/components/menu/HoverMenu.test.tsx | 6 +-
.../menu/ShareMenuItems/ShareMenuItems.test.tsx | 28 +-
.../FilterBar/ActionButtons/ActionButtons.test.tsx | 11 +-
.../FilterBar/CrossFilters/CrossFilterTag.test.tsx | 4 +-
.../ScopingModal/ChartsScopingListPanel.test.tsx | 10 +-
.../ScopingModal/ScopingModal.test.tsx | 24 +-
.../nativeFilters/FilterBar/FilterBar.test.tsx | 50 +--
.../FilterBarSettings/FilterBarSettings.test.tsx | 42 +--
.../FilterConfigurationLink.test.tsx | 4 +-
.../nativeFilters/FilterBar/Header/Header.test.tsx | 4 +-
.../nativeFilters/FilterCard/FilterCard.test.tsx | 6 +-
.../FiltersConfigModal/FilterConfigPane.test.tsx | 8 +-
.../FiltersConfigForm/CollapsibleControl.test.tsx | 5 +-
.../FiltersConfigForm/DatasetSelect.test.tsx | 30 +-
.../FiltersConfigForm/getControlItemsMap.test.tsx | 8 +-
.../FiltersConfigModal/FiltersConfigModal.test.tsx | 10 +-
.../DataTableControl/FilterInput.test.tsx | 2 +-
.../DatasourcePanel/DatasourcePanel.test.tsx | 16 +-
.../DatasourcePanel/DatasourcePanelItem.test.tsx | 8 +-
.../ExploreChartHeader/ExploreChartHeader.test.tsx | 178 ++++-----
.../ExploreChartPanel/ExploreChartPanel.test.tsx | 4 +-
.../StandaloneDownloadControl.test.tsx | 14 +-
.../ExportToCSVDropdown.test.tsx | 16 +-
.../RunQueryButton/RunQueryButton.test.tsx | 20 +-
.../AnnotationLayer.test.tsx | 58 +--
.../components/controls/BoundsControl.test.tsx | 10 +-
.../components/controls/CheckboxControl.test.tsx | 4 +-
.../CollectionControl/CollectionControl.test.tsx | 6 +-
.../ColorBreakpointOption.test.tsx | 4 +-
.../ColorBreakpointPopoverControl.test.tsx | 48 +--
.../ColorBreakpointPopoverTrigger.test.tsx | 30 +-
.../ColorBreakpointsControl.test.tsx | 22 +-
.../ControlPopover/ControlPopover.test.tsx | 20 +-
.../DateFilterControl/tests/AdvancedFrame.test.tsx | 8 +-
.../DateFilterControl/tests/CustomFrame.test.tsx | 62 ++--
.../tests/DateFilterLabel.test.tsx | 36 +-
.../ColumnSelectPopoverTrigger.test.tsx | 4 +-
.../DndColumnSelect.test.tsx | 22 +-
.../DndMetricSelect.test.tsx | 7 +-
.../DndColumnSelectControl/DndSelectLabel.test.tsx | 4 +-
.../DndColumnSelectControl/Option.test.tsx | 2 +-
...AdhocFilterEditPopoverSimpleTabContent.test.tsx | 50 +--
.../AdhocFilterEditPopoverSqlTabContent.test.tsx | 4 +-
.../AdhocFilterOption/AdhocFilterOption.test.tsx | 2 +-
.../AdhocFilterPopoverTrigger.test.tsx | 4 +-
.../FixedOrMetricControl.test.tsx | 14 +-
.../AdhocMetricEditPopover.test.tsx | 38 +-
.../AdhocMetricEditPopoverTitle.test.tsx | 7 +-
.../MetricControl/AdhocMetricOption.test.tsx | 29 +-
.../controls/MetricControl/MetricsControl.test.tsx | 18 +-
.../controls/NumberControl/NumberControl.test.tsx | 16 +-
.../SelectAsyncControl/SelectAsyncControl.test.tsx | 4 +-
.../components/controls/SelectControl.test.tsx | 29 +-
.../TimeSeriesColumnControl.test.tsx | 137 +++----
.../components/controls/ViewportControl.test.tsx | 8 +-
.../VizTypeControl/VizTypeControl.test.tsx | 32 +-
.../src/features/alerts/AlertReportModal.test.tsx | 191 +++++-----
.../alerts/components/NotificationMethod.test.tsx | 8 +-
.../databases/DatabaseModal/ModalHeader.tsx | 2 +-
.../DatabaseModal/SSHTunnelSwitch.test.tsx | 6 +-
.../databases/DatabaseModal/index.test.tsx | 136 +++----
.../src/features/databases/DatabaseModal/index.tsx | 2 +-
.../AddDataset/LeftPanel/LeftPanel.test.tsx | 28 +-
.../src/features/home/ActivityTable.test.tsx | 2 +-
.../src/features/home/SubMenu.test.tsx | 4 +-
.../HeaderReportDropdown/index.test.tsx | 14 +-
.../reports/ReportModal/ReportModal.test.tsx | 29 +-
.../features/rls/RowLevelSecurityModal.test.tsx | 30 +-
.../subjects/SubjectPicker/SubjectPicker.test.tsx | 2 +-
.../src/features/themes/ThemeModal.test.tsx | 21 +-
.../components/Range/RangeFilterPlugin.test.tsx | 10 +-
.../components/Select/SelectFilterPlugin.test.tsx | 102 ++---
.../src/pages/ArchivedList/ArchivedList.test.tsx | 26 +-
superset-frontend/src/pages/Chart/Chart.test.tsx | 189 +++++++++-
superset-frontend/src/pages/Chart/index.tsx | 44 ++-
.../src/pages/ChartCreation/ChartCreation.test.tsx | 68 ++--
.../RowLevelSecurityList.test.tsx | 2 +-
superset-frontend/src/types/Chart.ts | 8 +
superset-frontend/src/types/react-table.d.ts | 71 ++++
.../mockExportString.js => src/types/yaml.d.ts} | 11 +-
.../src/utils/downloadAsImage.test.ts | 35 +-
superset-frontend/src/utils/downloadAsImage.tsx | 15 +-
superset-frontend/src/utils/downloadAsPdf.ts | 9 +-
superset-frontend/src/utils/downloadUtils.ts | 14 +
superset-frontend/src/utils/textUtils.ts | 42 ++-
superset-frontend/src/views/CRUD/hooks.ts | 4 +-
superset-frontend/stylelint.config.js | 3 +-
superset-frontend/test-runner-jest.config.js | 33 --
.../tools/webpack.proxy-config.test.js | 14 +-
superset-frontend/webpack.config.js | 73 ++--
superset-frontend/webpack.proxy-config.js | 16 +-
superset/commands/dataset/create.py | 84 ++++-
superset/config.py | 4 +-
superset/databases/schemas.py | 12 +-
superset/datasets/api.py | 10 +-
superset/explore/api.py | 3 +
superset/mcp_service/chart/tool/get_chart_data.py | 211 +++++++----
.../migrations/shared/migrate_viz/processors.py | 8 +
superset/security/manager.py | 46 ++-
superset/sql/dialects/starrocks.py | 11 +-
superset/sql/parse.py | 412 +++++++++++++++------
superset/tasks/utils.py | 20 +-
superset/tasks/version_history_retention.py | 14 +-
superset/utils/dates.py | 22 +-
superset/utils/machine_auth.py | 11 +-
superset/versioning/api_helpers.py | 58 ++-
superset/versioning/baseline/insertion.py | 29 +-
superset/versioning/queries.py | 39 +-
superset/versioning/restore.py | 7 +
tests/integration_tests/datasource/api_tests.py | 4 +-
.../integration_tests/utils/machine_auth_tests.py | 10 +-
.../versioning/conditional_write_lock_tests.py | 225 +++++++++++
tests/unit_tests/commands/dataset/test_create.py | 180 +++++++++
tests/unit_tests/commands/report/base_test.py | 20 +-
tests/unit_tests/conftest.py | 32 ++
tests/unit_tests/databases/schema_tests.py | 21 ++
tests/unit_tests/docker_ci_entrypoint_test.py | 142 +++++++
tests/unit_tests/explore/api_test.py | 44 +++
.../mcp_service/chart/tool/test_get_chart_data.py | 329 ++++++++++++++++
tests/unit_tests/mcp_service/conftest.py | 27 ++
.../unit_tests/migrations/viz/table_v1_v2_test.py | 4 +
tests/unit_tests/security/login_token_test.py | 2 +-
.../security/test_permission_instructions_link.py | 79 +++-
tests/unit_tests/sql/parse_tests.py | 171 +++++++++
.../test_clock_reading_driver_preload.py | 57 +++
tests/unit_tests/versioning/test_lock_entity.py | 97 +++++
.../unit_tests/versioning/test_retention_races.py | 353 ++++++++++++++++++
243 files changed, 5467 insertions(+), 2605 deletions(-)
diff --cc tests/unit_tests/security/login_token_test.py
index 8957a4bfd9b,00000000000..ff044b47f17
mode 100644,000000..100644
--- a/tests/unit_tests/security/login_token_test.py
+++ b/tests/unit_tests/security/login_token_test.py
@@@ -1,189 -1,0 +1,189 @@@
+# 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 datetime import datetime, timedelta
+from typing import Any
+
+import pytest
+from flask import current_app
+from pytest_mock import MockerFixture
+from sqlalchemy.orm.session import Session
+
+from superset.key_value.models import KeyValueEntry
+from superset.security import login_token
+from superset.security.login_token import (
+ DEFAULT_LOGIN_TOKEN_TTL_SECONDS,
+ LoginTokenUserInfo,
+)
+
+USERINFO: LoginTokenUserInfo = {
+ "username": "jdoe",
+ "email": "jdoe@example.com",
+ "first_name": "Jane",
+ "last_name": "Doe",
+ "role_keys": ["Gamma"],
+}
+
+
+@pytest.fixture
+def kv_table(session: Session) -> Session:
+ """The key-value table, on the in-memory session the DAO is patched onto."""
+ KeyValueEntry.metadata.create_all(session.get_bind())
+ return session
+
+
+def _set_resolver(value: Any) -> None:
+ current_app.config["LOGIN_TOKEN_IDENTITY_RESOLVER"] = value
+
+
+def test_is_enabled_requires_flag_and_resolver(app_context: None) -> None:
+ """Both the feature flag and a resolver are needed; either alone is closed."""
+ original = current_app.config.get("LOGIN_TOKEN_IDENTITY_RESOLVER")
+ try:
+ _set_resolver(lambda request, **kwargs: USERINFO)
+ current_app.config["FEATURE_FLAGS"]["LOGIN_TOKEN"] = False
+ assert login_token.is_enabled() is False
+
+ current_app.config["FEATURE_FLAGS"]["LOGIN_TOKEN"] = True
+ assert login_token.is_enabled() is True
+
+ _set_resolver(None)
+ assert login_token.is_enabled() is False
+ finally:
+ current_app.config["FEATURE_FLAGS"]["LOGIN_TOKEN"] = False
+ _set_resolver(original)
+
+
+def test_get_ttl_seconds_falls_back(app_context: None) -> None:
+ """A missing, non-numeric or non-positive TTL falls back to the default."""
+ original = current_app.config.get("LOGIN_TOKEN_TTL_SECONDS")
+ try:
+ current_app.config["LOGIN_TOKEN_TTL_SECONDS"] = 30
+ assert login_token.get_ttl_seconds() == 30
+
+ # Strings (e.g. from an env var) are coerced rather than crashing.
- current_app.config["LOGIN_TOKEN_TTL_SECONDS"] = "45"
++ current_app.config["LOGIN_TOKEN_TTL_SECONDS"] = "45" # noqa: S105
+ assert login_token.get_ttl_seconds() == 45
+
+ for bad in ("not-a-number", 0, -1, None):
+ current_app.config["LOGIN_TOKEN_TTL_SECONDS"] = bad
+ assert login_token.get_ttl_seconds() == DEFAULT_LOGIN_TOKEN_TTL_SECONDS
+ finally:
+ current_app.config["LOGIN_TOKEN_TTL_SECONDS"] = original
+
+
+@pytest.mark.parametrize(
+ "resolver,reason",
+ [
+ (None, "no resolver configured"),
+ ("not-callable", "resolver is not callable"),
+ (lambda request, **kwargs: None, "resolver declined"),
+ (lambda request, **kwargs: {}, "resolver returned an empty dict"),
+ (lambda request, **kwargs: {"first_name": "Jane"}, "no username or email"),
+ ],
+)
+def test_resolve_identity_rejections(
+ app_context: None, resolver: Any, reason: str
+) -> None:
+ """Every failure mode resolves to None rather than a partial identity."""
+ original = current_app.config.get("LOGIN_TOKEN_IDENTITY_RESOLVER")
+ try:
+ _set_resolver(resolver)
+ assert login_token.resolve_identity(object()) is None, reason # type: ignore[arg-type]
+ finally:
+ _set_resolver(original)
+
+
+def test_resolve_identity_treats_a_raising_resolver_as_rejection(
+ app_context: None,
+) -> None:
+ """A resolver that raises must reject, never surface as a server error.
+
+ A validation failure escaping as a 500 would be indistinguishable from an
+ outage, and a caller retrying could otherwise be treated as authenticated.
+ """
+ original = current_app.config.get("LOGIN_TOKEN_IDENTITY_RESOLVER")
+
+ def boom(request: Any, **kwargs: Any) -> LoginTokenUserInfo:
+ raise ValueError("bad signature")
+
+ try:
+ _set_resolver(boom)
+ assert login_token.resolve_identity(object()) is None # type: ignore[arg-type]
+ finally:
+ _set_resolver(original)
+
+
+def test_resolve_identity_accepts_email_only(app_context: None) -> None:
+ """`email` alone is a valid identity — auth_user_oauth derives the username."""
+ original = current_app.config.get("LOGIN_TOKEN_IDENTITY_RESOLVER")
+ try:
+ _set_resolver(lambda request, **kwargs: {"email": "jdoe@example.com"})
+ assert login_token.resolve_identity(object()) == { # type: ignore[arg-type]
+ "email": "jdoe@example.com"
+ }
+ finally:
+ _set_resolver(original)
+
+
+def test_mint_then_consume_round_trip(app_context: None, kv_table: Session) -> None:
+ """A minted token returns exactly the stored userinfo."""
+ token, expires_on = login_token.mint(USERINFO)
+
+ assert token
+ assert expires_on > datetime.now()
+ assert login_token.consume(token) == USERINFO
+
+
+def test_consume_is_single_use(app_context: None, kv_table: Session) -> None:
+ """The second consume of the same token fails — the entry is deleted on use."""
+ token, _ = login_token.mint(USERINFO)
+
+ assert login_token.consume(token) == USERINFO
+ assert login_token.consume(token) is None
+
+
+def test_consume_rejects_unknown_and_malformed_tokens(
+ app_context: None, kv_table: Session
+) -> None:
+ """Garbage and well-formed-but-unknown tokens are both rejected."""
+ assert login_token.consume("") is None
+ assert login_token.consume("not-a-uuid") is None
+ assert login_token.consume("6d2b9921-2274-43b8-94d6-e5e1f05372c4") is None
+
+
+def test_consume_rejects_an_expired_token(
+ app_context: None, kv_table: Session, mocker: MockerFixture
+) -> None:
+ """An expired token is rejected and removed rather than left to the pruner."""
+ mocker.patch.object(login_token, "get_ttl_seconds", return_value=1)
+ token, _ = login_token.mint(USERINFO)
+
+ # Move the stored expiry into the past rather than sleeping.
+ entry = kv_table.query(KeyValueEntry).one()
+ entry.expires_on = datetime.now() - timedelta(seconds=1)
+
+ assert login_token.consume(token) is None
+ assert kv_table.query(KeyValueEntry).count() == 0
+
+
+def test_token_carries_no_identity_data(app_context: None, kv_table: Session) -> None:
+ """The token is an opaque handle — no claims travel in the URL."""
+ token, _ = login_token.mint(USERINFO)
+
+ for value in ("jdoe", "jdoe@example.com", "Jane", "Doe", "Gamma"):
+ assert value not in token