mirror of
https://github.com/apache/superset.git
synced 2026-06-16 04:59:17 +00:00
Compare commits
1 Commits
fix-docker
...
bump-setup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c299afa185 |
2
.github/actions/setup-backend/action.yml
vendored
2
.github/actions/setup-backend/action.yml
vendored
@@ -42,7 +42,7 @@ runs:
|
|||||||
fi
|
fi
|
||||||
echo "python-version=$RESOLVED_VERSION" >> "$GITHUB_OUTPUT"
|
echo "python-version=$RESOLVED_VERSION" >> "$GITHUB_OUTPUT"
|
||||||
- name: Set up Python ${{ steps.set-python-version.outputs.python-version }}
|
- name: Set up Python ${{ steps.set-python-version.outputs.python-version }}
|
||||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ steps.set-python-version.outputs.python-version }}
|
python-version: ${{ steps.set-python-version.outputs.python-version }}
|
||||||
cache: ${{ inputs.cache }}
|
cache: ${{ inputs.cache }}
|
||||||
|
|||||||
14
UPDATING.md
14
UPDATING.md
@@ -121,20 +121,6 @@ This change is backward compatible. The feature is off by default, and even when
|
|||||||
|
|
||||||
Disabling a user account (setting `active` to `False`, via the admin UI, REST API, or CLI) now terminates that user's outstanding sessions on their next request, instead of relying on a passive check. This works for both client-side cookie sessions and server-side session stores via a per-user invalidation epoch (`user_attribute.sessions_invalidated_at`, added by a migration). The mechanism is inert for users that were never disabled (NULL epoch), so there is no behavior change for active users. Re-enabling an account and logging in again starts a fresh, valid session. The migration backfills the epoch for accounts that are already disabled at upgrade time, so re-enabling such an account does not revive a session that predates this feature.
|
Disabling a user account (setting `active` to `False`, via the admin UI, REST API, or CLI) now terminates that user's outstanding sessions on their next request, instead of relying on a passive check. This works for both client-side cookie sessions and server-side session stores via a per-user invalidation epoch (`user_attribute.sessions_invalidated_at`, added by a migration). The mechanism is inert for users that were never disabled (NULL epoch), so there is no behavior change for active users. Re-enabling an account and logging in again starts a fresh, valid session. The migration backfills the epoch for accounts that are already disabled at upgrade time, so re-enabling such an account does not revive a session that predates this feature.
|
||||||
|
|
||||||
### Opt-in SSH tunnel server host key verification
|
|
||||||
|
|
||||||
SSH tunnels can now optionally pin the expected SSH server host key as a defense-in-depth measure against man-in-the-middle attacks. paramiko's transport performs no known-hosts checking by default, so previously the SSH server's identity was not verified. This feature is opt-in and off by default; existing tunnels are unaffected.
|
|
||||||
|
|
||||||
- A new nullable `server_host_key` column on the `ssh_tunnels` table stores the expected host key in authorized-key form (e.g. `ssh-ed25519 AAAA...`). It is a public key and is stored in plaintext. It can be set via the SSH tunnel POST/PUT payloads (`ssh_tunnel.server_host_key`).
|
|
||||||
- When a tunnel has `server_host_key` set, Superset connects to the SSH server, reads the host key it presents, and rejects the tunnel if it does not match.
|
|
||||||
- A new config flag `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING` (default `False`) controls fail-closed behavior. When `True`, every tunnel must declare a `server_host_key`; a tunnel without one is rejected.
|
|
||||||
|
|
||||||
Runbook to adopt:
|
|
||||||
|
|
||||||
1. Capture the SSH server's host key, e.g. `ssh-keyscan -t ed25519 ssh.example.com` (verify it out-of-band).
|
|
||||||
2. Set that value on the tunnel's `server_host_key` (via the database/SSH tunnel API or UI payload).
|
|
||||||
3. Optionally set `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING = True` in `superset_config.py` to require host-key verification on all tunnels.
|
|
||||||
|
|
||||||
### Dataset import validates catalog against the target connection
|
### Dataset import validates catalog against the target connection
|
||||||
|
|
||||||
Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database.
|
Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database.
|
||||||
|
|||||||
@@ -72,23 +72,20 @@ services:
|
|||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
url="http://host.docker.internal:9000/static/assets/manifest.json"
|
url="http://host.docker.internal:9000/static/assets/manifest.json"
|
||||||
max_attempts=300 # ~10 minutes at 2s intervals; first build can be slow
|
max_attempts=150 # ~5 minutes at 2s intervals
|
||||||
echo "Waiting for webpack dev server at $$url..."
|
echo "Waiting for webpack dev server at $url..."
|
||||||
attempt=0
|
attempt=0
|
||||||
until curl -sf --max-time 5 -H "Host: localhost" -o /dev/null "$$url"; do
|
until curl -sf --max-time 5 -o /dev/null "$url"; do
|
||||||
attempt=$$((attempt + 1))
|
attempt=$((attempt + 1))
|
||||||
if [ "$$attempt" -ge "$$max_attempts" ]; then
|
if [ "$attempt" -ge "$max_attempts" ]; then
|
||||||
echo "ERROR: webpack dev server did not serve $$url after $$max_attempts attempts." >&2
|
echo "ERROR: webpack dev server did not serve $url after $max_attempts attempts (~5 minutes)." >&2
|
||||||
echo "Is the dev server running? With BUILD_SUPERSET_FRONTEND_IN_DOCKER=false you must start it on the host (e.g. 'npm run dev' in superset-frontend)." >&2
|
echo "Is the dev server running? With BUILD_SUPERSET_FRONTEND_IN_DOCKER=false you must start it on the host (e.g. 'npm run dev' in superset-frontend)." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ $$((attempt % 15)) -eq 0 ]; then
|
|
||||||
echo "Still waiting for webpack dev server... ($$attempt/$$max_attempts)"
|
|
||||||
fi
|
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
echo "Webpack dev server is ready; starting nginx."
|
echo "Webpack dev server is ready; starting nginx."
|
||||||
exec /docker-entrypoint.sh nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7
|
image: redis:7
|
||||||
|
|||||||
@@ -81,17 +81,17 @@ case "${1}" in
|
|||||||
app)
|
app)
|
||||||
echo "Starting web app (using development server)..."
|
echo "Starting web app (using development server)..."
|
||||||
|
|
||||||
# Always run in Flask debug mode here: this is the dev compose entrypoint,
|
# Environment-based debugger control for security
|
||||||
# and Superset's Talisman selector keys off app.debug to serve the dev CSP
|
# Only enable Werkzeug interactive debugger when explicitly requested
|
||||||
# (which permits 'unsafe-eval' required by React Refresh / HMR).
|
# Modern Werkzeug (3.0+) includes PIN protection, but defense-in-depth approach
|
||||||
export FLASK_DEBUG=1
|
# Override FLASK_DEBUG so the effective state matches SUPERSET_DEBUG_ENABLED even
|
||||||
|
# when FLASK_DEBUG=true is inherited from docker/.env or .flaskenv
|
||||||
# Werkzeug's interactive debugger (/console) is a separate, security-sensitive
|
|
||||||
# feature and must be opted into explicitly via SUPERSET_DEBUG_ENABLED=true.
|
|
||||||
if [[ "${SUPERSET_DEBUG_ENABLED:-}" == "true" ]]; then
|
if [[ "${SUPERSET_DEBUG_ENABLED:-}" == "true" ]]; then
|
||||||
|
export FLASK_DEBUG=1
|
||||||
DEBUGGER_FLAG="--debugger"
|
DEBUGGER_FLAG="--debugger"
|
||||||
echo " ⚠️ Werkzeug debugger enabled (requires PIN for /console access)"
|
echo " ⚠️ Werkzeug debugger enabled (requires PIN for /console access)"
|
||||||
else
|
else
|
||||||
|
export FLASK_DEBUG=0
|
||||||
DEBUGGER_FLAG="--no-debugger"
|
DEBUGGER_FLAG="--no-debugger"
|
||||||
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
|
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
158
docs/yarn.lock
158
docs/yarn.lock
@@ -265,15 +265,6 @@
|
|||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
|
|
||||||
"@babel/code-frame@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
|
|
||||||
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
|
|
||||||
dependencies:
|
|
||||||
"@babel/helper-validator-identifier" "^7.29.7"
|
|
||||||
js-tokens "^4.0.0"
|
|
||||||
picocolors "^1.1.1"
|
|
||||||
|
|
||||||
"@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0":
|
"@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0":
|
||||||
version "7.28.0"
|
version "7.28.0"
|
||||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz"
|
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz"
|
||||||
@@ -284,25 +275,20 @@
|
|||||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz"
|
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz"
|
||||||
integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==
|
integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==
|
||||||
|
|
||||||
"@babel/compat-data@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
|
|
||||||
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
|
|
||||||
|
|
||||||
"@babel/core@^7.21.3", "@babel/core@^7.25.9":
|
"@babel/core@^7.21.3", "@babel/core@^7.25.9":
|
||||||
version "7.29.7"
|
version "7.28.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
|
resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz"
|
||||||
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
|
integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.29.7"
|
"@babel/code-frame" "^7.28.6"
|
||||||
"@babel/generator" "^7.29.7"
|
"@babel/generator" "^7.28.6"
|
||||||
"@babel/helper-compilation-targets" "^7.29.7"
|
"@babel/helper-compilation-targets" "^7.28.6"
|
||||||
"@babel/helper-module-transforms" "^7.29.7"
|
"@babel/helper-module-transforms" "^7.28.6"
|
||||||
"@babel/helpers" "^7.29.7"
|
"@babel/helpers" "^7.28.6"
|
||||||
"@babel/parser" "^7.29.7"
|
"@babel/parser" "^7.28.6"
|
||||||
"@babel/template" "^7.29.7"
|
"@babel/template" "^7.28.6"
|
||||||
"@babel/traverse" "^7.29.7"
|
"@babel/traverse" "^7.28.6"
|
||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.28.6"
|
||||||
"@jridgewell/remapping" "^2.3.5"
|
"@jridgewell/remapping" "^2.3.5"
|
||||||
convert-source-map "^2.0.0"
|
convert-source-map "^2.0.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
@@ -332,17 +318,6 @@
|
|||||||
"@jridgewell/trace-mapping" "^0.3.28"
|
"@jridgewell/trace-mapping" "^0.3.28"
|
||||||
jsesc "^3.0.2"
|
jsesc "^3.0.2"
|
||||||
|
|
||||||
"@babel/generator@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
|
|
||||||
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
|
|
||||||
dependencies:
|
|
||||||
"@babel/parser" "^7.29.7"
|
|
||||||
"@babel/types" "^7.29.7"
|
|
||||||
"@jridgewell/gen-mapping" "^0.3.12"
|
|
||||||
"@jridgewell/trace-mapping" "^0.3.28"
|
|
||||||
jsesc "^3.0.2"
|
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3":
|
"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3":
|
||||||
version "7.27.3"
|
version "7.27.3"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz"
|
||||||
@@ -350,7 +325,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.27.3"
|
"@babel/types" "^7.27.3"
|
||||||
|
|
||||||
"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2":
|
"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.28.6":
|
||||||
version "7.28.6"
|
version "7.28.6"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz"
|
||||||
integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==
|
integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==
|
||||||
@@ -361,17 +336,6 @@
|
|||||||
lru-cache "^5.1.1"
|
lru-cache "^5.1.1"
|
||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
|
|
||||||
"@babel/helper-compilation-targets@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
|
|
||||||
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
|
|
||||||
dependencies:
|
|
||||||
"@babel/compat-data" "^7.29.7"
|
|
||||||
"@babel/helper-validator-option" "^7.29.7"
|
|
||||||
browserslist "^4.24.0"
|
|
||||||
lru-cache "^5.1.1"
|
|
||||||
semver "^6.3.1"
|
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3":
|
"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3":
|
||||||
version "7.28.3"
|
version "7.28.3"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz"
|
||||||
@@ -410,11 +374,6 @@
|
|||||||
resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz"
|
||||||
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
|
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
|
||||||
|
|
||||||
"@babel/helper-globals@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
|
|
||||||
integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
|
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions@^7.27.1":
|
"@babel/helper-member-expression-to-functions@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz"
|
||||||
@@ -439,14 +398,6 @@
|
|||||||
"@babel/traverse" "^7.28.6"
|
"@babel/traverse" "^7.28.6"
|
||||||
"@babel/types" "^7.28.6"
|
"@babel/types" "^7.28.6"
|
||||||
|
|
||||||
"@babel/helper-module-imports@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
|
|
||||||
integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
|
|
||||||
dependencies:
|
|
||||||
"@babel/traverse" "^7.29.7"
|
|
||||||
"@babel/types" "^7.29.7"
|
|
||||||
|
|
||||||
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
|
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
|
||||||
version "7.28.6"
|
version "7.28.6"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz"
|
||||||
@@ -456,15 +407,6 @@
|
|||||||
"@babel/helper-validator-identifier" "^7.28.5"
|
"@babel/helper-validator-identifier" "^7.28.5"
|
||||||
"@babel/traverse" "^7.28.6"
|
"@babel/traverse" "^7.28.6"
|
||||||
|
|
||||||
"@babel/helper-module-transforms@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
|
|
||||||
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/helper-module-imports" "^7.29.7"
|
|
||||||
"@babel/helper-validator-identifier" "^7.29.7"
|
|
||||||
"@babel/traverse" "^7.29.7"
|
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression@^7.27.1":
|
"@babel/helper-optimise-call-expression@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz"
|
||||||
@@ -513,31 +455,16 @@
|
|||||||
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
|
||||||
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
|
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
|
||||||
|
|
||||||
"@babel/helper-string-parser@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
|
|
||||||
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
|
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.28.5":
|
"@babel/helper-validator-identifier@^7.28.5":
|
||||||
version "7.28.5"
|
version "7.28.5"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
|
||||||
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
|
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
|
|
||||||
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
|
|
||||||
|
|
||||||
"@babel/helper-validator-option@^7.27.1":
|
"@babel/helper-validator-option@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz"
|
||||||
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
|
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
|
||||||
|
|
||||||
"@babel/helper-validator-option@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
|
|
||||||
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
|
|
||||||
|
|
||||||
"@babel/helper-wrap-function@^7.27.1":
|
"@babel/helper-wrap-function@^7.27.1":
|
||||||
version "7.28.3"
|
version "7.28.3"
|
||||||
resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz"
|
resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz"
|
||||||
@@ -547,13 +474,13 @@
|
|||||||
"@babel/traverse" "^7.28.3"
|
"@babel/traverse" "^7.28.3"
|
||||||
"@babel/types" "^7.28.2"
|
"@babel/types" "^7.28.2"
|
||||||
|
|
||||||
"@babel/helpers@^7.29.7":
|
"@babel/helpers@^7.28.6":
|
||||||
version "7.29.7"
|
version "7.28.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
|
resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz"
|
||||||
integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
|
integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/template" "^7.29.7"
|
"@babel/template" "^7.28.6"
|
||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.28.6"
|
||||||
|
|
||||||
"@babel/parser@^7.28.6":
|
"@babel/parser@^7.28.6":
|
||||||
version "7.28.6"
|
version "7.28.6"
|
||||||
@@ -569,13 +496,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.29.0"
|
"@babel/types" "^7.29.0"
|
||||||
|
|
||||||
"@babel/parser@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
|
|
||||||
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/types" "^7.29.7"
|
|
||||||
|
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
|
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz"
|
resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz"
|
||||||
@@ -1252,15 +1172,6 @@
|
|||||||
"@babel/parser" "^7.28.6"
|
"@babel/parser" "^7.28.6"
|
||||||
"@babel/types" "^7.28.6"
|
"@babel/types" "^7.28.6"
|
||||||
|
|
||||||
"@babel/template@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
|
|
||||||
integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/code-frame" "^7.29.7"
|
|
||||||
"@babel/parser" "^7.29.7"
|
|
||||||
"@babel/types" "^7.29.7"
|
|
||||||
|
|
||||||
"@babel/traverse@^7.25.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.6":
|
"@babel/traverse@^7.25.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.6":
|
||||||
version "7.28.6"
|
version "7.28.6"
|
||||||
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz"
|
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz"
|
||||||
@@ -1287,19 +1198,6 @@
|
|||||||
"@babel/types" "^7.29.0"
|
"@babel/types" "^7.29.0"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
|
|
||||||
"@babel/traverse@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
|
|
||||||
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
|
|
||||||
dependencies:
|
|
||||||
"@babel/code-frame" "^7.29.7"
|
|
||||||
"@babel/generator" "^7.29.7"
|
|
||||||
"@babel/helper-globals" "^7.29.7"
|
|
||||||
"@babel/parser" "^7.29.7"
|
|
||||||
"@babel/template" "^7.29.7"
|
|
||||||
"@babel/types" "^7.29.7"
|
|
||||||
debug "^4.3.1"
|
|
||||||
|
|
||||||
"@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.4.4":
|
"@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.4.4":
|
||||||
version "7.28.6"
|
version "7.28.6"
|
||||||
resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz"
|
resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz"
|
||||||
@@ -1316,14 +1214,6 @@
|
|||||||
"@babel/helper-string-parser" "^7.27.1"
|
"@babel/helper-string-parser" "^7.27.1"
|
||||||
"@babel/helper-validator-identifier" "^7.28.5"
|
"@babel/helper-validator-identifier" "^7.28.5"
|
||||||
|
|
||||||
"@babel/types@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
|
|
||||||
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
|
|
||||||
dependencies:
|
|
||||||
"@babel/helper-string-parser" "^7.29.7"
|
|
||||||
"@babel/helper-validator-identifier" "^7.29.7"
|
|
||||||
|
|
||||||
"@braintree/sanitize-url@^7.1.1":
|
"@braintree/sanitize-url@^7.1.1":
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz#ca2035b0fefe956a8676ff0c69af73e605fcd81f"
|
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz#ca2035b0fefe956a8676ff0c69af73e605fcd81f"
|
||||||
@@ -9642,12 +9532,12 @@ latest-version@^7.0.0:
|
|||||||
package-json "^8.1.0"
|
package-json "^8.1.0"
|
||||||
|
|
||||||
launch-editor@^2.6.1:
|
launch-editor@^2.6.1:
|
||||||
version "2.14.1"
|
version "2.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.14.1.tgz#f7e0da3f58aaea03fea01074d840b5f739ed7ddc"
|
resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz"
|
||||||
integrity sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==
|
integrity sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==
|
||||||
dependencies:
|
dependencies:
|
||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
shell-quote "^1.8.4"
|
shell-quote "^1.8.3"
|
||||||
|
|
||||||
layout-base@^1.0.0:
|
layout-base@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
@@ -13599,7 +13489,7 @@ shebang-regex@^3.0.0:
|
|||||||
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
||||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
shell-quote@^1.8.4:
|
shell-quote@^1.8.3:
|
||||||
version "1.8.4"
|
version "1.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.4.tgz#2edd9a4dcefc96649e2e2cb12f637b1f1d92a190"
|
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.4.tgz#2edd9a4dcefc96649e2e2cb12f637b1f1d92a190"
|
||||||
integrity sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==
|
integrity sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==
|
||||||
|
|||||||
288
superset-embedded-sdk/package-lock.json
generated
288
superset-embedded-sdk/package-lock.json
generated
@@ -27,6 +27,19 @@
|
|||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ampproject/remapping": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/cli": {
|
"node_modules/@babel/cli": {
|
||||||
"version": "7.25.6",
|
"version": "7.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.6.tgz",
|
||||||
@@ -58,12 +71,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.29.7",
|
"@babel/helper-validator-identifier": "^7.28.5",
|
||||||
"js-tokens": "^4.0.0",
|
"js-tokens": "^4.0.0",
|
||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -72,30 +85,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/compat-data": {
|
"node_modules/@babel/compat-data": {
|
||||||
"version": "7.29.7",
|
"version": "7.25.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
|
||||||
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
"integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.29.6",
|
"version": "7.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
|
||||||
"integrity": "sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==",
|
"integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/generator": "^7.29.6",
|
"@babel/code-frame": "^7.24.7",
|
||||||
"@babel/helper-compilation-targets": "^7.28.6",
|
"@babel/generator": "^7.25.0",
|
||||||
"@babel/helper-module-transforms": "^7.28.6",
|
"@babel/helper-compilation-targets": "^7.25.2",
|
||||||
"@babel/helpers": "^7.29.2",
|
"@babel/helper-module-transforms": "^7.25.2",
|
||||||
"@babel/parser": "^7.29.3",
|
"@babel/helpers": "^7.25.0",
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/parser": "^7.25.0",
|
||||||
"@babel/traverse": "^7.29.0",
|
"@babel/template": "^7.25.0",
|
||||||
"@babel/types": "^7.29.0",
|
"@babel/traverse": "^7.25.2",
|
||||||
"@jridgewell/remapping": "^2.3.5",
|
"@babel/types": "^7.25.2",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
@@ -111,13 +126,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||||
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.29.7",
|
"@babel/parser": "^7.29.0",
|
||||||
"@babel/types": "^7.29.7",
|
"@babel/types": "^7.29.0",
|
||||||
"@jridgewell/gen-mapping": "^0.3.12",
|
"@jridgewell/gen-mapping": "^0.3.12",
|
||||||
"@jridgewell/trace-mapping": "^0.3.28",
|
"@jridgewell/trace-mapping": "^0.3.28",
|
||||||
"jsesc": "^3.0.2"
|
"jsesc": "^3.0.2"
|
||||||
@@ -154,14 +169,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-compilation-targets": {
|
"node_modules/@babel/helper-compilation-targets": {
|
||||||
"version": "7.29.7",
|
"version": "7.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
|
||||||
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
"integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/compat-data": "^7.29.7",
|
"@babel/compat-data": "^7.25.2",
|
||||||
"@babel/helper-validator-option": "^7.29.7",
|
"@babel/helper-validator-option": "^7.24.8",
|
||||||
"browserslist": "^4.24.0",
|
"browserslist": "^4.23.1",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"semver": "^6.3.1"
|
"semver": "^6.3.1"
|
||||||
},
|
},
|
||||||
@@ -366,28 +382,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.29.7",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
"version": "7.29.7",
|
"version": "7.28.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-option": {
|
"node_modules/@babel/helper-validator-option": {
|
||||||
"version": "7.29.7",
|
"version": "7.24.8",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
|
||||||
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@@ -408,25 +425,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.29.7",
|
"version": "7.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz",
|
||||||
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
"integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.29.7",
|
"@babel/template": "^7.25.0",
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.25.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.29.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@@ -1825,14 +1843,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.29.7",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/parser": "^7.29.7",
|
"@babel/parser": "^7.28.6",
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.28.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -1857,13 +1875,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.29.7",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
"@babel/helper-validator-identifier": "^7.29.7"
|
"@babel/helper-validator-identifier": "^7.28.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -2631,16 +2649,6 @@
|
|||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/remapping": {
|
|
||||||
"version": "2.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
|
||||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
@@ -7975,6 +7983,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ampproject/remapping": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/cli": {
|
"@babel/cli": {
|
||||||
"version": "7.25.6",
|
"version": "7.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.6.tgz",
|
||||||
@@ -7993,38 +8011,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.29.7",
|
"@babel/helper-validator-identifier": "^7.28.5",
|
||||||
"js-tokens": "^4.0.0",
|
"js-tokens": "^4.0.0",
|
||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/compat-data": {
|
"@babel/compat-data": {
|
||||||
"version": "7.29.7",
|
"version": "7.25.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
|
||||||
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
"integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
"version": "7.29.6",
|
"version": "7.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
|
||||||
"integrity": "sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==",
|
"integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/generator": "^7.29.6",
|
"@babel/code-frame": "^7.24.7",
|
||||||
"@babel/helper-compilation-targets": "^7.28.6",
|
"@babel/generator": "^7.25.0",
|
||||||
"@babel/helper-module-transforms": "^7.28.6",
|
"@babel/helper-compilation-targets": "^7.25.2",
|
||||||
"@babel/helpers": "^7.29.2",
|
"@babel/helper-module-transforms": "^7.25.2",
|
||||||
"@babel/parser": "^7.29.3",
|
"@babel/helpers": "^7.25.0",
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/parser": "^7.25.0",
|
||||||
"@babel/traverse": "^7.29.0",
|
"@babel/template": "^7.25.0",
|
||||||
"@babel/types": "^7.29.0",
|
"@babel/traverse": "^7.25.2",
|
||||||
"@jridgewell/remapping": "^2.3.5",
|
"@babel/types": "^7.25.2",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
@@ -8033,13 +8051,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/generator": {
|
"@babel/generator": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||||
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.29.7",
|
"@babel/parser": "^7.29.0",
|
||||||
"@babel/types": "^7.29.7",
|
"@babel/types": "^7.29.0",
|
||||||
"@jridgewell/gen-mapping": "^0.3.12",
|
"@jridgewell/gen-mapping": "^0.3.12",
|
||||||
"@jridgewell/trace-mapping": "^0.3.28",
|
"@jridgewell/trace-mapping": "^0.3.28",
|
||||||
"jsesc": "^3.0.2"
|
"jsesc": "^3.0.2"
|
||||||
@@ -8065,14 +8083,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-compilation-targets": {
|
"@babel/helper-compilation-targets": {
|
||||||
"version": "7.29.7",
|
"version": "7.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
|
||||||
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
"integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/compat-data": "^7.29.7",
|
"@babel/compat-data": "^7.25.2",
|
||||||
"@babel/helper-validator-option": "^7.29.7",
|
"@babel/helper-validator-option": "^7.24.8",
|
||||||
"browserslist": "^4.24.0",
|
"browserslist": "^4.23.1",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"semver": "^6.3.1"
|
"semver": "^6.3.1"
|
||||||
}
|
}
|
||||||
@@ -8211,21 +8229,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-string-parser": {
|
"@babel/helper-string-parser": {
|
||||||
"version": "7.29.7",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-identifier": {
|
"@babel/helper-validator-identifier": {
|
||||||
"version": "7.29.7",
|
"version": "7.28.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-option": {
|
"@babel/helper-validator-option": {
|
||||||
"version": "7.29.7",
|
"version": "7.24.8",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
|
||||||
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-wrap-function": {
|
"@babel/helper-wrap-function": {
|
||||||
@@ -8240,22 +8258,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helpers": {
|
"@babel/helpers": {
|
||||||
"version": "7.29.7",
|
"version": "7.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz",
|
||||||
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
"integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/template": "^7.29.7",
|
"@babel/template": "^7.25.0",
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.25.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.29.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||||
@@ -9139,14 +9157,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
"version": "7.29.7",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/parser": "^7.29.7",
|
"@babel/parser": "^7.28.6",
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.28.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
@@ -9165,13 +9183,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-string-parser": "^7.29.7",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
"@babel/helper-validator-identifier": "^7.29.7"
|
"@babel/helper-validator-identifier": "^7.28.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@bcoe/v8-coverage": {
|
"@bcoe/v8-coverage": {
|
||||||
@@ -9753,16 +9771,6 @@
|
|||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@jridgewell/remapping": {
|
|
||||||
"version": "2.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
|
||||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@jridgewell/resolve-uri": {
|
"@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
|
|||||||
39
superset-frontend/cypress-base/package-lock.json
generated
39
superset-frontend/cypress-base/package-lock.json
generated
@@ -4708,15 +4708,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.4",
|
"hasown": "^2.0.2",
|
||||||
"mime-types": "^2.1.35"
|
"mime-types": "^2.1.12"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -5028,9 +5029,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
@@ -10226,7 +10228,7 @@
|
|||||||
"camelcase": "^5.3.1",
|
"camelcase": "^5.3.1",
|
||||||
"find-up": "^4.1.0",
|
"find-up": "^4.1.0",
|
||||||
"get-package-type": "^0.1.0",
|
"get-package-type": "^0.1.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "4.1.1",
|
||||||
"resolve-from": "^5.0.0"
|
"resolve-from": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -10236,7 +10238,8 @@
|
|||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
},
|
},
|
||||||
"js-yaml": {
|
"js-yaml": {
|
||||||
"version": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@@ -12342,15 +12345,15 @@
|
|||||||
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
|
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
|
||||||
},
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.4",
|
"hasown": "^2.0.2",
|
||||||
"mime-types": "^2.1.35"
|
"mime-types": "^2.1.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fromentries": {
|
"fromentries": {
|
||||||
@@ -12571,9 +12574,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hasown": {
|
"hasown": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
46
superset-frontend/package-lock.json
generated
46
superset-frontend/package-lock.json
generated
@@ -11349,6 +11349,19 @@
|
|||||||
"integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==",
|
"integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/eslint": {
|
||||||
|
"version": "9.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||||
|
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*",
|
||||||
|
"@types/json-schema": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/esrecurse": {
|
"node_modules/@types/esrecurse": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||||
@@ -20669,35 +20682,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.4",
|
"hasown": "^2.0.2",
|
||||||
"mime-types": "^2.1.35"
|
"mime-types": "^2.1.12"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data/node_modules/hasown": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/format": {
|
"node_modules/format": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||||
@@ -26524,14 +26524,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/launch-editor": {
|
"node_modules/launch-editor": {
|
||||||
"version": "2.14.1",
|
"version": "2.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz",
|
||||||
"integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==",
|
"integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.0.0",
|
||||||
"shell-quote": "^1.8.4"
|
"shell-quote": "^1.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lerc": {
|
"node_modules/lerc": {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from superset.commands.database.exceptions import (
|
|||||||
from superset.commands.database.ssh_tunnel.exceptions import (
|
from superset.commands.database.ssh_tunnel.exceptions import (
|
||||||
SSHTunnelCreateFailedError,
|
SSHTunnelCreateFailedError,
|
||||||
SSHTunnelDatabasePortError,
|
SSHTunnelDatabasePortError,
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
SSHTunnelingNotEnabledError,
|
SSHTunnelingNotEnabledError,
|
||||||
SSHTunnelInvalidError,
|
SSHTunnelInvalidError,
|
||||||
)
|
)
|
||||||
@@ -76,7 +75,6 @@ class CreateDatabaseCommand(BaseCommand):
|
|||||||
SupersetErrorsException,
|
SupersetErrorsException,
|
||||||
SSHTunnelingNotEnabledError,
|
SSHTunnelingNotEnabledError,
|
||||||
SSHTunnelDatabasePortError,
|
SSHTunnelDatabasePortError,
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
) as ex:
|
) as ex:
|
||||||
event_logger.log_with_context(
|
event_logger.log_with_context(
|
||||||
action=f"db_creation_failed.{ex.__class__.__name__}",
|
action=f"db_creation_failed.{ex.__class__.__name__}",
|
||||||
|
|||||||
@@ -75,11 +75,3 @@ class SSHTunnelMissingCredentials(CommandInvalidError, SSHTunnelError): # noqa:
|
|||||||
|
|
||||||
class SSHTunnelInvalidCredentials(CommandInvalidError, SSHTunnelError): # noqa: N818
|
class SSHTunnelInvalidCredentials(CommandInvalidError, SSHTunnelError): # noqa: N818
|
||||||
message = _("Cannot have multiple credentials for the SSH Tunnel")
|
message = _("Cannot have multiple credentials for the SSH Tunnel")
|
||||||
|
|
||||||
|
|
||||||
class SSHTunnelHostKeyVerificationError(CommandInvalidError, SSHTunnelError):
|
|
||||||
"""The SSH server's host key failed opt-in verification for a tunnel."""
|
|
||||||
|
|
||||||
message = _(
|
|
||||||
"The SSH server host key could not be verified against the expected key."
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from superset.commands.database.exceptions import (
|
|||||||
)
|
)
|
||||||
from superset.commands.database.ssh_tunnel.exceptions import (
|
from superset.commands.database.ssh_tunnel.exceptions import (
|
||||||
SSHTunnelDatabasePortError,
|
SSHTunnelDatabasePortError,
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
SSHTunnelingNotEnabledError,
|
SSHTunnelingNotEnabledError,
|
||||||
)
|
)
|
||||||
from superset.commands.database.utils import ping
|
from superset.commands.database.utils import ping
|
||||||
@@ -222,11 +221,7 @@ class TestConnectionDatabaseCommand(BaseCommand):
|
|||||||
engine=engine_name,
|
engine=engine_name,
|
||||||
)
|
)
|
||||||
raise DatabaseSecurityUnsafeError(message=str(ex)) from ex
|
raise DatabaseSecurityUnsafeError(message=str(ex)) from ex
|
||||||
except (
|
except (SupersetTimeoutException, SSHTunnelingNotEnabledError) as ex:
|
||||||
SupersetTimeoutException,
|
|
||||||
SSHTunnelingNotEnabledError,
|
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
) as ex:
|
|
||||||
event_logger.log_with_context(
|
event_logger.log_with_context(
|
||||||
action=get_log_connection_action(
|
action=get_log_connection_action(
|
||||||
"test_connection_error",
|
"test_connection_error",
|
||||||
@@ -235,8 +230,7 @@ class TestConnectionDatabaseCommand(BaseCommand):
|
|||||||
),
|
),
|
||||||
engine=engine_name,
|
engine=engine_name,
|
||||||
)
|
)
|
||||||
# bubble up the exception (preserving its specific message and status)
|
# bubble up the exception to return proper status code
|
||||||
# instead of flattening it into a generic connection failure
|
|
||||||
raise
|
raise
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if not database:
|
if not database:
|
||||||
|
|||||||
@@ -895,15 +895,6 @@ SSH_TUNNEL_TIMEOUT_SEC = 10.0
|
|||||||
#: Timeout (seconds) for transport socket (``socket.settimeout``)
|
#: Timeout (seconds) for transport socket (``socket.settimeout``)
|
||||||
SSH_TUNNEL_PACKET_TIMEOUT_SEC = 1.0
|
SSH_TUNNEL_PACKET_TIMEOUT_SEC = 1.0
|
||||||
|
|
||||||
#: Opt-in defense-in-depth: when enabled, every SSH tunnel must declare an expected
|
|
||||||
#: server host key (``server_host_key`` on the tunnel) and the SSH server's presented
|
|
||||||
#: host key is verified against it before the tunnel is opened. A mismatch, or a
|
|
||||||
#: missing expected key while this flag is enabled, fails closed and the tunnel is
|
|
||||||
#: rejected. When disabled (the default), tunnels without a ``server_host_key`` open
|
|
||||||
#: without host-key verification, preserving existing behavior; tunnels that do set a
|
|
||||||
#: ``server_host_key`` are still verified regardless of this flag.
|
|
||||||
SSH_TUNNEL_STRICT_HOST_KEY_CHECKING: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
# Feature flags may also be set via 'SUPERSET_FEATURE_' prefixed environment vars.
|
# Feature flags may also be set via 'SUPERSET_FEATURE_' prefixed environment vars.
|
||||||
DEFAULT_FEATURE_FLAGS.update(
|
DEFAULT_FEATURE_FLAGS.update(
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ from superset.commands.database.importers.dispatcher import ImportDatabasesComma
|
|||||||
from superset.commands.database.oauth2 import OAuth2StoreTokenCommand
|
from superset.commands.database.oauth2 import OAuth2StoreTokenCommand
|
||||||
from superset.commands.database.ssh_tunnel.exceptions import (
|
from superset.commands.database.ssh_tunnel.exceptions import (
|
||||||
SSHTunnelDatabasePortError,
|
SSHTunnelDatabasePortError,
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
SSHTunnelingNotEnabledError,
|
SSHTunnelingNotEnabledError,
|
||||||
)
|
)
|
||||||
from superset.commands.database.sync_permissions import SyncPermissionsCommand
|
from superset.commands.database.sync_permissions import SyncPermissionsCommand
|
||||||
@@ -485,11 +484,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
return self.response_422(message=str(ex))
|
return self.response_422(message=str(ex))
|
||||||
except (
|
except (SSHTunnelingNotEnabledError, SSHTunnelDatabasePortError) as ex:
|
||||||
SSHTunnelingNotEnabledError,
|
|
||||||
SSHTunnelDatabasePortError,
|
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
) as ex:
|
|
||||||
return self.response_400(message=str(ex))
|
return self.response_400(message=str(ex))
|
||||||
except SupersetException as ex:
|
except SupersetException as ex:
|
||||||
return self.response(ex.status, message=ex.message)
|
return self.response(ex.status, message=ex.message)
|
||||||
@@ -574,11 +569,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
return self.response_422(message=str(ex))
|
return self.response_422(message=str(ex))
|
||||||
except (
|
except (SSHTunnelingNotEnabledError, SSHTunnelDatabasePortError) as ex:
|
||||||
SSHTunnelingNotEnabledError,
|
|
||||||
SSHTunnelDatabasePortError,
|
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
) as ex:
|
|
||||||
return self.response_400(message=str(ex))
|
return self.response_400(message=str(ex))
|
||||||
|
|
||||||
@expose("/<int:pk>", methods=("DELETE",))
|
@expose("/<int:pk>", methods=("DELETE",))
|
||||||
@@ -1300,11 +1291,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||||||
try:
|
try:
|
||||||
TestConnectionDatabaseCommand(item).run()
|
TestConnectionDatabaseCommand(item).run()
|
||||||
return self.response(200, message="OK")
|
return self.response(200, message="OK")
|
||||||
except (
|
except (SSHTunnelingNotEnabledError, SSHTunnelDatabasePortError) as ex:
|
||||||
SSHTunnelingNotEnabledError,
|
|
||||||
SSHTunnelDatabasePortError,
|
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
) as ex:
|
|
||||||
return self.response_400(message=str(ex))
|
return self.response_400(message=str(ex))
|
||||||
|
|
||||||
@expose("/<int:pk>/related_objects/", methods=("GET",))
|
@expose("/<int:pk>/related_objects/", methods=("GET",))
|
||||||
|
|||||||
@@ -477,22 +477,6 @@ class DatabaseSSHTunnel(Schema):
|
|||||||
private_key = fields.String(required=False, load_only=True)
|
private_key = fields.String(required=False, load_only=True)
|
||||||
private_key_password = fields.String(required=False, load_only=True)
|
private_key_password = fields.String(required=False, load_only=True)
|
||||||
|
|
||||||
# Optional expected SSH server host key in authorized-key form
|
|
||||||
# (e.g. "ssh-rsa AAAA...", "ssh-ed25519 AAAA..."). When set, the SSH server's
|
|
||||||
# presented host key is verified against it before the tunnel is opened. This is
|
|
||||||
# a public key, so it is not sensitive and is not masked.
|
|
||||||
server_host_key = fields.String(
|
|
||||||
required=False,
|
|
||||||
allow_none=True,
|
|
||||||
metadata={
|
|
||||||
"description": (
|
|
||||||
"Expected SSH server host key in authorized-key form "
|
|
||||||
"(e.g. 'ssh-ed25519 AAAA...'). When set, the server's host key is "
|
|
||||||
"verified against it before the tunnel is opened."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
@validates_schema
|
@validates_schema
|
||||||
def validate_authentication(self, data: dict[str, Any], **kwargs: Any) -> None:
|
def validate_authentication(self, data: dict[str, Any], **kwargs: Any) -> None:
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
|||||||
@@ -72,12 +72,6 @@ class SSHTunnel(AuditMixinNullable, ExtraJSONMixin, ImportExportMixin, Model):
|
|||||||
encrypted_field_factory.create(Text), nullable=True
|
encrypted_field_factory.create(Text), nullable=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Optional expected SSH server host key, in authorized-key form
|
|
||||||
# (e.g. "ssh-rsa AAAA...", "ssh-ed25519 AAAA..."). When set, the SSH server's
|
|
||||||
# presented host key is verified against this value before the tunnel is opened.
|
|
||||||
# This is a public key, so it is stored in plaintext (not encrypted).
|
|
||||||
server_host_key = sa.Column(sa.Text, nullable=True)
|
|
||||||
|
|
||||||
export_fields = [
|
export_fields = [
|
||||||
"server_address",
|
"server_address",
|
||||||
"server_port",
|
"server_port",
|
||||||
@@ -85,7 +79,6 @@ class SSHTunnel(AuditMixinNullable, ExtraJSONMixin, ImportExportMixin, Model):
|
|||||||
"password",
|
"password",
|
||||||
"private_key",
|
"private_key",
|
||||||
"private_key_password",
|
"private_key_password",
|
||||||
"server_host_key",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
extra_import_fields = [
|
extra_import_fields = [
|
||||||
@@ -100,9 +93,6 @@ class SSHTunnel(AuditMixinNullable, ExtraJSONMixin, ImportExportMixin, Model):
|
|||||||
"server_port": self.server_port,
|
"server_port": self.server_port,
|
||||||
"username": self.username,
|
"username": self.username,
|
||||||
}
|
}
|
||||||
if self.server_host_key is not None:
|
|
||||||
# public key, not sensitive: returned in cleartext
|
|
||||||
output["server_host_key"] = self.server_host_key
|
|
||||||
if self.password is not None:
|
if self.password is not None:
|
||||||
output["password"] = PASSWORD_MASK
|
output["password"] = PASSWORD_MASK
|
||||||
if self.private_key is not None:
|
if self.private_key is not None:
|
||||||
|
|||||||
@@ -15,63 +15,26 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import base64
|
|
||||||
import binascii
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import paramiko
|
|
||||||
import sshtunnel
|
import sshtunnel
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from paramiko import RSAKey
|
from paramiko import RSAKey
|
||||||
from paramiko.pkey import UnknownKeyType
|
|
||||||
|
|
||||||
from superset.commands.database.ssh_tunnel.exceptions import (
|
from superset.commands.database.ssh_tunnel.exceptions import SSHTunnelDatabasePortError
|
||||||
SSHTunnelDatabasePortError,
|
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
)
|
|
||||||
from superset.databases.utils import make_url_safe
|
from superset.databases.utils import make_url_safe
|
||||||
from superset.utils.class_utils import load_class_from_name
|
from superset.utils.class_utils import load_class_from_name
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from superset.databases.ssh_tunnel.models import SSHTunnel
|
from superset.databases.ssh_tunnel.models import SSHTunnel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_authorized_key(authorized_key: str) -> paramiko.PKey:
|
|
||||||
"""
|
|
||||||
Parse a host key in authorized-key form (``"<type> <base64>[ comment]"``) into a
|
|
||||||
:class:`paramiko.PKey`. The optional trailing comment field and surrounding
|
|
||||||
whitespace are ignored.
|
|
||||||
|
|
||||||
:raises ValueError: if the value is empty or cannot be parsed as a host key.
|
|
||||||
"""
|
|
||||||
fields = authorized_key.strip().split()
|
|
||||||
if len(fields) < 2:
|
|
||||||
raise ValueError("Host key must be in 'ssh-<type> <base64>' form")
|
|
||||||
key_type, key_b64 = fields[0], fields[1]
|
|
||||||
try:
|
|
||||||
# validate=True so malformed characters raise instead of being silently
|
|
||||||
# dropped, which could otherwise pin an unintended key value.
|
|
||||||
key_bytes = base64.b64decode(key_b64, validate=True)
|
|
||||||
except (binascii.Error, ValueError) as ex:
|
|
||||||
raise ValueError("Host key base64 payload could not be decoded") from ex
|
|
||||||
try:
|
|
||||||
return paramiko.PKey.from_type_string(key_type, key_bytes)
|
|
||||||
except (paramiko.SSHException, UnknownKeyType) as ex:
|
|
||||||
raise ValueError(f"Host key could not be parsed: {ex}") from ex
|
|
||||||
|
|
||||||
|
|
||||||
class SSHManager:
|
class SSHManager:
|
||||||
def __init__(self, app: Flask) -> None:
|
def __init__(self, app: Flask) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.local_bind_address = app.config["SSH_TUNNEL_LOCAL_BIND_ADDRESS"]
|
self.local_bind_address = app.config["SSH_TUNNEL_LOCAL_BIND_ADDRESS"]
|
||||||
self.strict_host_key_checking = app.config.get(
|
|
||||||
"SSH_TUNNEL_STRICT_HOST_KEY_CHECKING", False
|
|
||||||
)
|
|
||||||
sshtunnel.TUNNEL_TIMEOUT = app.config["SSH_TUNNEL_TIMEOUT_SEC"]
|
sshtunnel.TUNNEL_TIMEOUT = app.config["SSH_TUNNEL_TIMEOUT_SEC"]
|
||||||
sshtunnel.SSH_TIMEOUT = app.config["SSH_TUNNEL_PACKET_TIMEOUT_SEC"]
|
sshtunnel.SSH_TIMEOUT = app.config["SSH_TUNNEL_PACKET_TIMEOUT_SEC"]
|
||||||
|
|
||||||
@@ -85,87 +48,6 @@ class SSHManager:
|
|||||||
port=server.local_bind_port,
|
port=server.local_bind_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _verify_host_key(self, ssh_tunnel: "SSHTunnel") -> "paramiko.PKey | None":
|
|
||||||
"""
|
|
||||||
Opt-in defense-in-depth: verify the SSH server's host key before opening the
|
|
||||||
tunnel, to resist man-in-the-middle attacks (paramiko's ``Transport`` does no
|
|
||||||
known-hosts checking by default).
|
|
||||||
|
|
||||||
Behavior:
|
|
||||||
|
|
||||||
- If the tunnel declares an expected ``server_host_key``, connect to the SSH
|
|
||||||
server, read the host key it presents, and compare. On mismatch (or if the
|
|
||||||
expected key cannot be parsed) raise
|
|
||||||
:class:`SSHTunnelHostKeyVerificationError`.
|
|
||||||
- If no expected key is set and ``SSH_TUNNEL_STRICT_HOST_KEY_CHECKING`` is
|
|
||||||
enabled, fail closed and raise.
|
|
||||||
- If no expected key is set and strict checking is disabled, do nothing,
|
|
||||||
preserving existing (unverified) behavior.
|
|
||||||
|
|
||||||
:returns: the parsed expected host key when one is configured (so the caller
|
|
||||||
can pin it on the tunnel's own connection), or ``None`` when no key is
|
|
||||||
configured.
|
|
||||||
"""
|
|
||||||
expected_raw = ssh_tunnel.server_host_key
|
|
||||||
|
|
||||||
if not expected_raw or not expected_raw.strip():
|
|
||||||
if self.strict_host_key_checking:
|
|
||||||
raise SSHTunnelHostKeyVerificationError(
|
|
||||||
message=(
|
|
||||||
"SSH_TUNNEL_STRICT_HOST_KEY_CHECKING is enabled but no "
|
|
||||||
"expected server host key is configured for this tunnel."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
expected_key = _parse_authorized_key(expected_raw)
|
|
||||||
except ValueError as ex:
|
|
||||||
raise SSHTunnelHostKeyVerificationError(
|
|
||||||
message=f"The configured expected server host key is invalid: {ex}"
|
|
||||||
) from ex
|
|
||||||
|
|
||||||
# Build the socket ourselves with an explicit timeout so the TCP connect
|
|
||||||
# phase is bounded too. ``paramiko.Transport((host, port))`` would connect
|
|
||||||
# synchronously with no timeout, leaving ``start_client(timeout=...)`` to
|
|
||||||
# govern only the SSH handshake; an unreachable host could then block for the
|
|
||||||
# full OS-level TCP timeout.
|
|
||||||
try:
|
|
||||||
sock = socket.create_connection(
|
|
||||||
(ssh_tunnel.server_address, ssh_tunnel.server_port),
|
|
||||||
timeout=sshtunnel.SSH_TIMEOUT,
|
|
||||||
)
|
|
||||||
except OSError as ex:
|
|
||||||
raise SSHTunnelHostKeyVerificationError(
|
|
||||||
message=f"Could not connect to the SSH server: {ex}"
|
|
||||||
) from ex
|
|
||||||
|
|
||||||
transport = paramiko.Transport(sock)
|
|
||||||
try:
|
|
||||||
transport.start_client(timeout=sshtunnel.SSH_TIMEOUT)
|
|
||||||
remote_key = transport.get_remote_server_key()
|
|
||||||
except Exception as ex: # noqa: BLE001
|
|
||||||
raise SSHTunnelHostKeyVerificationError(
|
|
||||||
message=f"Could not retrieve the SSH server host key: {ex}"
|
|
||||||
) from ex
|
|
||||||
finally:
|
|
||||||
transport.close()
|
|
||||||
|
|
||||||
if remote_key != expected_key:
|
|
||||||
logger.warning(
|
|
||||||
"SSH host key mismatch for %s:%s",
|
|
||||||
ssh_tunnel.server_address,
|
|
||||||
ssh_tunnel.server_port,
|
|
||||||
)
|
|
||||||
raise SSHTunnelHostKeyVerificationError(
|
|
||||||
message=(
|
|
||||||
"The SSH server presented a host key that does not match the "
|
|
||||||
"expected server host key configured for this tunnel."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return expected_key
|
|
||||||
|
|
||||||
def create_tunnel(
|
def create_tunnel(
|
||||||
self,
|
self,
|
||||||
ssh_tunnel: "SSHTunnel",
|
ssh_tunnel: "SSHTunnel",
|
||||||
@@ -178,12 +60,6 @@ class SSHManager:
|
|||||||
port = url.port or get_default_port(backend)
|
port = url.port or get_default_port(backend)
|
||||||
if not port:
|
if not port:
|
||||||
raise SSHTunnelDatabasePortError()
|
raise SSHTunnelDatabasePortError()
|
||||||
|
|
||||||
# Opt-in host-key verification runs before the tunnel is opened. It returns
|
|
||||||
# the parsed expected key (or None) so we can also pin it on the tunnel's own
|
|
||||||
# connection below.
|
|
||||||
expected_host_key = self._verify_host_key(ssh_tunnel)
|
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"ssh_address_or_host": (ssh_tunnel.server_address, ssh_tunnel.server_port),
|
"ssh_address_or_host": (ssh_tunnel.server_address, ssh_tunnel.server_port),
|
||||||
"ssh_username": ssh_tunnel.username,
|
"ssh_username": ssh_tunnel.username,
|
||||||
@@ -192,14 +68,6 @@ class SSHManager:
|
|||||||
"debug_level": logging.getLogger("flask_appbuilder").level,
|
"debug_level": logging.getLogger("flask_appbuilder").level,
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected_host_key is not None:
|
|
||||||
# Pin the expected key on the tunnel's own connection, so paramiko verifies
|
|
||||||
# the host that actually carries traffic on the same transport. The probe
|
|
||||||
# above and the tunnel open separate connections, so verifying only the
|
|
||||||
# probe would leave a TOCTOU gap (DNS re-resolution, selective
|
|
||||||
# interception); pinning here closes it.
|
|
||||||
params["ssh_host_key"] = expected_host_key
|
|
||||||
|
|
||||||
if ssh_tunnel.password:
|
if ssh_tunnel.password:
|
||||||
params["ssh_password"] = ssh_tunnel.password
|
params["ssh_password"] = ssh_tunnel.password
|
||||||
elif ssh_tunnel.private_key:
|
elif ssh_tunnel.private_key:
|
||||||
|
|||||||
@@ -327,16 +327,7 @@ Chart Types You Can CREATE with generate_chart/generate_explore_link:
|
|||||||
- chart_type="xy", kind="scatter": Scatter plot for correlation analysis
|
- chart_type="xy", kind="scatter": Scatter plot for correlation analysis
|
||||||
- chart_type="big_number": Big Number display (single metric, header only)
|
- chart_type="big_number": Big Number display (single metric, header only)
|
||||||
- chart_type="big_number", show_trendline=True,
|
- chart_type="big_number", show_trendline=True,
|
||||||
temporal_column="<date_col>", aggregation="sum": Big Number with trendline
|
temporal_column="<date_col>": Big Number with trendline
|
||||||
(aggregation controls how the value is computed from trendline data points;
|
|
||||||
default when omitted is "LAST_VALUE" — most recent point only.
|
|
||||||
Use aggregation="sum" for all-time totals, "mean" for averages, "max"/"min" for extremes.
|
|
||||||
DIAGNOSIS: if a Big Number with Trendline shows wrong values, check
|
|
||||||
form_data["aggregation"] — missing/LAST_VALUE means the chart shows only the last data
|
|
||||||
point, not a total. Fix by calling update_chart with a complete Big Number config:
|
|
||||||
chart_type="big_number", metric=<metric>, show_trendline=True,
|
|
||||||
temporal_column=<date_col>, aggregation="sum". update_chart requires the full
|
|
||||||
config — omitting chart_type or metric causes a validation error.)
|
|
||||||
- chart_type="table": Data table for detailed views
|
- chart_type="table": Data table for detailed views
|
||||||
- chart_type="table", viz_type="ag-grid-table": Interactive AG Grid table
|
- chart_type="table", viz_type="ag-grid-table": Interactive AG Grid table
|
||||||
- chart_type="pie": Pie chart for proportional data (set donut=True for donut)
|
- chart_type="pie": Pie chart for proportional data (set donut=True for donut)
|
||||||
|
|||||||
@@ -859,9 +859,6 @@ def map_big_number_config(config: BigNumberChartConfig) -> Dict[str, Any]:
|
|||||||
if config.time_format:
|
if config.time_format:
|
||||||
form_data["time_format"] = config.time_format
|
form_data["time_format"] = config.time_format
|
||||||
|
|
||||||
if config.aggregation is not None:
|
|
||||||
form_data["aggregation"] = config.aggregation
|
|
||||||
|
|
||||||
_add_adhoc_filters(form_data, config.filters)
|
_add_adhoc_filters(form_data, config.filters)
|
||||||
|
|
||||||
return form_data
|
return form_data
|
||||||
|
|||||||
@@ -1417,32 +1417,6 @@ class BigNumberChartConfig(UnknownFieldCheckMixin):
|
|||||||
),
|
),
|
||||||
ge=1,
|
ge=1,
|
||||||
)
|
)
|
||||||
aggregation: (
|
|
||||||
Literal["LAST_VALUE", "sum", "mean", "min", "max", "median", "raw"] | None
|
|
||||||
) = Field(
|
|
||||||
None,
|
|
||||||
description=(
|
|
||||||
"How the single big-number value is computed from the trendline "
|
|
||||||
"data points. Only applies when show_trendline=True. "
|
|
||||||
"Options: "
|
|
||||||
"'sum' = Total (Sum) — add all data points; use for all-time totals. "
|
|
||||||
"'LAST_VALUE' = most recent data point "
|
|
||||||
"(frontend default when this field is absent). "
|
|
||||||
"'mean' = Average (Mean). "
|
|
||||||
"'min' = Minimum. "
|
|
||||||
"'max' = Maximum. "
|
|
||||||
"'median' = Median. "
|
|
||||||
"'raw' = Overall value — single aggregate across the full period; best for "
|
|
||||||
"non-additive metrics like ratios, averages, or distinct counts. "
|
|
||||||
"DIAGNOSIS: if a Big Number with Trendline shows an unexpectedly low value "
|
|
||||||
"(e.g. yesterday's revenue instead of all-time total), "
|
|
||||||
"inspect form_data['aggregation'] "
|
|
||||||
"— when absent or 'LAST_VALUE' the chart shows only the last data point. "
|
|
||||||
"Fix by setting aggregation='sum'. "
|
|
||||||
"IMPORTANT: when updating aggregation, always include "
|
|
||||||
"show_trendline=True and temporal_column to preserve the trendline."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
filters: list[FilterConfig] | None = Field(
|
filters: list[FilterConfig] | None = Field(
|
||||||
None,
|
None,
|
||||||
description="Filters to apply",
|
description="Filters to apply",
|
||||||
@@ -1463,13 +1437,6 @@ class BigNumberChartConfig(UnknownFieldCheckMixin):
|
|||||||
"Period comparison is only available for "
|
"Period comparison is only available for "
|
||||||
"trendline charts."
|
"trendline charts."
|
||||||
)
|
)
|
||||||
if self.aggregation and not self.show_trendline:
|
|
||||||
raise ValueError(
|
|
||||||
"aggregation requires show_trendline=True. "
|
|
||||||
"The aggregation field only applies to Big Number with "
|
|
||||||
"Trendline charts. Set show_trendline=True and provide "
|
|
||||||
"a temporal_column, or omit aggregation."
|
|
||||||
)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
|
|||||||
@@ -115,14 +115,6 @@ _CHART_EXAMPLES: Dict[str, list[Dict[str, Any]]] = {
|
|||||||
"chart_type": "big_number",
|
"chart_type": "big_number",
|
||||||
"metric": {"name": "revenue", "aggregate": "SUM"},
|
"metric": {"name": "revenue", "aggregate": "SUM"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"chart_type": "big_number",
|
|
||||||
"metric": {"name": "revenue", "aggregate": "SUM"},
|
|
||||||
"temporal_column": "order_date",
|
|
||||||
"show_trendline": True,
|
|
||||||
"aggregation": "sum",
|
|
||||||
"time_grain": "P1D",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import sqlalchemy as sa
|
|||||||
from alembic import op
|
from alembic import op
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import lazyload, Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from superset import db, security_manager
|
from superset import db, security_manager
|
||||||
from superset.db_engine_specs.base import GenericDBException
|
from superset.db_engine_specs.base import GenericDBException
|
||||||
@@ -379,15 +379,7 @@ def upgrade_catalog_perms(engines: set[str] | None = None) -> None:
|
|||||||
bind = op.get_bind()
|
bind = op.get_bind()
|
||||||
session = db.Session(bind=bind)
|
session = db.Session(bind=bind)
|
||||||
|
|
||||||
# The Database model has an eager-loaded (``lazy="joined"``) ``ssh_tunnel``
|
for database in session.query(Database).all():
|
||||||
# backref. Eager-loading it here would SELECT every column on ``ssh_tunnels``,
|
|
||||||
# including columns added by later migrations that do not yet exist at the
|
|
||||||
# revision this helper runs in (e.g. on a fresh DB upgraded in one pass). The
|
|
||||||
# catalog upgrade only needs scalar ``Database`` columns, so disable the eager
|
|
||||||
# join to keep the query schema-safe across migration revisions.
|
|
||||||
for database in (
|
|
||||||
session.query(Database).options(lazyload(Database.ssh_tunnel)).all()
|
|
||||||
):
|
|
||||||
db_engine_spec = database.db_engine_spec
|
db_engine_spec = database.db_engine_spec
|
||||||
if (
|
if (
|
||||||
engines and db_engine_spec.engine not in engines
|
engines and db_engine_spec.engine not in engines
|
||||||
@@ -584,11 +576,7 @@ def downgrade_catalog_perms(engines: set[str] | None = None) -> None:
|
|||||||
bind = op.get_bind()
|
bind = op.get_bind()
|
||||||
session = db.Session(bind=bind)
|
session = db.Session(bind=bind)
|
||||||
|
|
||||||
# See upgrade_catalog_perms: avoid eager-loading the ``ssh_tunnel`` backref so the
|
for database in session.query(Database).all():
|
||||||
# query stays schema-safe across migration revisions.
|
|
||||||
for database in (
|
|
||||||
session.query(Database).options(lazyload(Database.ssh_tunnel)).all()
|
|
||||||
):
|
|
||||||
db_engine_spec = database.db_engine_spec
|
db_engine_spec = database.db_engine_spec
|
||||||
if (
|
if (
|
||||||
engines and db_engine_spec.engine not in engines
|
engines and db_engine_spec.engine not in engines
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# 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.
|
|
||||||
"""add server_host_key to ssh_tunnels
|
|
||||||
|
|
||||||
Adds a nullable ``server_host_key`` column to the ``ssh_tunnels`` table. It stores the
|
|
||||||
expected SSH server host key in authorized-key form (e.g. "ssh-ed25519 AAAA...") so
|
|
||||||
operators can opt in to verifying the SSH server's host key before a tunnel is opened.
|
|
||||||
This is a public key and is stored in plaintext (not encrypted). The column is
|
|
||||||
nullable, so existing tunnels are unaffected.
|
|
||||||
|
|
||||||
Revision ID: 78a40c08b4be
|
|
||||||
Revises: b7c9d1e2f3a4
|
|
||||||
Create Date: 2026-06-03 10:00:00.000000
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from superset.migrations.shared.utils import add_columns, drop_columns
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = "78a40c08b4be"
|
|
||||||
down_revision = "b7c9d1e2f3a4"
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
"""Add the nullable ``server_host_key`` column to ``ssh_tunnels``."""
|
|
||||||
add_columns(
|
|
||||||
"ssh_tunnels",
|
|
||||||
sa.Column("server_host_key", sa.Text(), nullable=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
"""Drop the ``server_host_key`` column from ``ssh_tunnels``."""
|
|
||||||
drop_columns("ssh_tunnels", "server_host_key")
|
|
||||||
@@ -119,5 +119,5 @@ def test_database_filter(mocker: MockerFixture) -> None:
|
|||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
str(compiled_query)
|
str(compiled_query)
|
||||||
== "SELECT dbs.uuid, dbs.created_on, dbs.changed_on, dbs.id, dbs.verbose_name, dbs.database_name, dbs.sqlalchemy_uri, dbs.password, dbs.cache_timeout, dbs.select_as_create_table_as, dbs.expose_in_sqllab, dbs.configuration_method, dbs.allow_run_async, dbs.allow_file_upload, dbs.allow_ctas, dbs.allow_cvas, dbs.allow_dml, dbs.force_ctas_schema, dbs.extra, dbs.encrypted_extra, dbs.impersonate_user, dbs.server_cert, dbs.is_managed_externally, dbs.external_url, dbs.created_by_fk, dbs.changed_by_fk, ssh_tunnels_1.uuid AS uuid_1, ssh_tunnels_1.created_on AS created_on_1, ssh_tunnels_1.changed_on AS changed_on_1, ssh_tunnels_1.extra_json, ssh_tunnels_1.id AS id_1, ssh_tunnels_1.database_id, ssh_tunnels_1.server_address, ssh_tunnels_1.server_port, ssh_tunnels_1.username, ssh_tunnels_1.password AS password_1, ssh_tunnels_1.private_key, ssh_tunnels_1.private_key_password, ssh_tunnels_1.server_host_key, ssh_tunnels_1.created_by_fk AS created_by_fk_1, ssh_tunnels_1.changed_by_fk AS changed_by_fk_1 \nFROM dbs LEFT OUTER JOIN ssh_tunnels AS ssh_tunnels_1 ON dbs.id = ssh_tunnels_1.database_id \nWHERE '[' || dbs.database_name || '].(id:' || CAST(dbs.id AS VARCHAR) || ')' IN ('[my_db].(id:42)', '[my_other_db].(id:43)') OR dbs.database_name IN ('my_db', 'my_other_db', 'third_db')" # noqa: E501
|
== "SELECT dbs.uuid, dbs.created_on, dbs.changed_on, dbs.id, dbs.verbose_name, dbs.database_name, dbs.sqlalchemy_uri, dbs.password, dbs.cache_timeout, dbs.select_as_create_table_as, dbs.expose_in_sqllab, dbs.configuration_method, dbs.allow_run_async, dbs.allow_file_upload, dbs.allow_ctas, dbs.allow_cvas, dbs.allow_dml, dbs.force_ctas_schema, dbs.extra, dbs.encrypted_extra, dbs.impersonate_user, dbs.server_cert, dbs.is_managed_externally, dbs.external_url, dbs.created_by_fk, dbs.changed_by_fk, ssh_tunnels_1.uuid AS uuid_1, ssh_tunnels_1.created_on AS created_on_1, ssh_tunnels_1.changed_on AS changed_on_1, ssh_tunnels_1.extra_json, ssh_tunnels_1.id AS id_1, ssh_tunnels_1.database_id, ssh_tunnels_1.server_address, ssh_tunnels_1.server_port, ssh_tunnels_1.username, ssh_tunnels_1.password AS password_1, ssh_tunnels_1.private_key, ssh_tunnels_1.private_key_password, ssh_tunnels_1.created_by_fk AS created_by_fk_1, ssh_tunnels_1.changed_by_fk AS changed_by_fk_1 \nFROM dbs LEFT OUTER JOIN ssh_tunnels AS ssh_tunnels_1 ON dbs.id = ssh_tunnels_1.database_id \nWHERE '[' || dbs.database_name || '].(id:' || CAST(dbs.id AS VARCHAR) || ')' IN ('[my_db].(id:42)', '[my_other_db].(id:43)') OR dbs.database_name IN ('my_db', 'my_other_db', 'third_db')" # noqa: E501
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,44 +14,11 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import paramiko
|
|
||||||
import pytest
|
|
||||||
import sshtunnel
|
import sshtunnel
|
||||||
|
|
||||||
from superset.commands.database.ssh_tunnel.exceptions import (
|
from superset.extensions.ssh import SSHManagerFactory
|
||||||
SSHTunnelHostKeyVerificationError,
|
|
||||||
)
|
|
||||||
from superset.extensions.ssh import SSHManager, SSHManagerFactory
|
|
||||||
|
|
||||||
|
|
||||||
def _make_manager(strict: bool = False) -> SSHManager:
|
|
||||||
"""Build an ``SSHManager`` test instance with configurable strict checking."""
|
|
||||||
app = Mock()
|
|
||||||
app.config = {
|
|
||||||
"SSH_TUNNEL_MAX_RETRIES": 2,
|
|
||||||
"SSH_TUNNEL_LOCAL_BIND_ADDRESS": "127.0.0.1",
|
|
||||||
"SSH_TUNNEL_TIMEOUT_SEC": 123.0,
|
|
||||||
"SSH_TUNNEL_PACKET_TIMEOUT_SEC": 321.0,
|
|
||||||
"SSH_TUNNEL_MANAGER_CLASS": "superset.extensions.ssh.SSHManager",
|
|
||||||
"SSH_TUNNEL_STRICT_HOST_KEY_CHECKING": strict,
|
|
||||||
}
|
|
||||||
return SSHManager(app)
|
|
||||||
|
|
||||||
|
|
||||||
def _authorized_key(key: paramiko.PKey) -> str:
|
|
||||||
"""Render a paramiko key in authorized-key (``"<type> <base64>"``) form."""
|
|
||||||
return f"{key.get_name()} {key.get_base64()}"
|
|
||||||
|
|
||||||
|
|
||||||
def _ssh_tunnel(server_host_key: str | None) -> Mock:
|
|
||||||
"""Create a mocked SSH tunnel with server connection fields populated."""
|
|
||||||
tunnel = Mock()
|
|
||||||
tunnel.server_address = "ssh.example.com"
|
|
||||||
tunnel.server_port = 22
|
|
||||||
tunnel.server_host_key = server_host_key
|
|
||||||
return tunnel
|
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_tunnel_timeout_setting() -> None:
|
def test_ssh_tunnel_timeout_setting() -> None:
|
||||||
@@ -67,199 +34,3 @@ def test_ssh_tunnel_timeout_setting() -> None:
|
|||||||
factory.init_app(app)
|
factory.init_app(app)
|
||||||
assert sshtunnel.TUNNEL_TIMEOUT == 123.0
|
assert sshtunnel.TUNNEL_TIMEOUT == 123.0
|
||||||
assert sshtunnel.SSH_TIMEOUT == 321.0
|
assert sshtunnel.SSH_TIMEOUT == 321.0
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.socket.create_connection")
|
|
||||||
@patch("superset.extensions.ssh.paramiko.Transport")
|
|
||||||
def test_verify_host_key_match(
|
|
||||||
mock_transport_cls: Mock, mock_create_connection: Mock
|
|
||||||
) -> None:
|
|
||||||
"""The server presents the same key we expect: verification passes."""
|
|
||||||
server_key = paramiko.RSAKey.generate(2048)
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
tunnel = _ssh_tunnel(_authorized_key(server_key))
|
|
||||||
|
|
||||||
transport = mock_transport_cls.return_value
|
|
||||||
transport.get_remote_server_key.return_value = server_key
|
|
||||||
|
|
||||||
result = manager._verify_host_key(tunnel) # should not raise
|
|
||||||
|
|
||||||
# The TCP connect is bounded by an explicit timeout, and the resulting
|
|
||||||
# socket is handed to Transport.
|
|
||||||
mock_create_connection.assert_called_once_with(
|
|
||||||
("ssh.example.com", 22), timeout=321.0
|
|
||||||
)
|
|
||||||
mock_transport_cls.assert_called_once_with(mock_create_connection.return_value)
|
|
||||||
transport.start_client.assert_called_once()
|
|
||||||
transport.close.assert_called_once()
|
|
||||||
# The parsed expected key is returned so the caller can pin it on the tunnel.
|
|
||||||
assert result == server_key
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.socket.create_connection")
|
|
||||||
@patch("superset.extensions.ssh.paramiko.Transport")
|
|
||||||
def test_verify_host_key_mismatch_raises(
|
|
||||||
mock_transport_cls: Mock, mock_create_connection: Mock
|
|
||||||
) -> None:
|
|
||||||
"""The server presents a different key than expected: verification fails."""
|
|
||||||
expected_key = paramiko.RSAKey.generate(2048)
|
|
||||||
presented_key = paramiko.RSAKey.generate(2048)
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
tunnel = _ssh_tunnel(_authorized_key(expected_key))
|
|
||||||
|
|
||||||
transport = mock_transport_cls.return_value
|
|
||||||
transport.get_remote_server_key.return_value = presented_key
|
|
||||||
|
|
||||||
with pytest.raises(SSHTunnelHostKeyVerificationError):
|
|
||||||
manager._verify_host_key(tunnel)
|
|
||||||
|
|
||||||
mock_create_connection.assert_called_once()
|
|
||||||
transport.close.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.socket.create_connection")
|
|
||||||
def test_verify_host_key_connect_failure_raises(
|
|
||||||
mock_create_connection: Mock,
|
|
||||||
) -> None:
|
|
||||||
"""A bounded TCP connect failure surfaces as a host-key verification error."""
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
server_key = paramiko.RSAKey.generate(2048)
|
|
||||||
tunnel = _ssh_tunnel(_authorized_key(server_key))
|
|
||||||
|
|
||||||
mock_create_connection.side_effect = OSError("connection refused")
|
|
||||||
|
|
||||||
with pytest.raises(SSHTunnelHostKeyVerificationError):
|
|
||||||
manager._verify_host_key(tunnel)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.paramiko.Transport")
|
|
||||||
def test_verify_host_key_unset_non_strict_skips(mock_transport_cls: Mock) -> None:
|
|
||||||
"""Back-compat: no expected key + strict checking off => no verification at all."""
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
tunnel = _ssh_tunnel(None)
|
|
||||||
|
|
||||||
assert manager._verify_host_key(tunnel) is None # should not raise
|
|
||||||
|
|
||||||
mock_transport_cls.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.paramiko.Transport")
|
|
||||||
def test_verify_host_key_unset_strict_raises(mock_transport_cls: Mock) -> None:
|
|
||||||
"""Fail-closed: no expected key + strict checking on => reject."""
|
|
||||||
manager = _make_manager(strict=True)
|
|
||||||
tunnel = _ssh_tunnel(None)
|
|
||||||
|
|
||||||
with pytest.raises(SSHTunnelHostKeyVerificationError):
|
|
||||||
manager._verify_host_key(tunnel)
|
|
||||||
|
|
||||||
mock_transport_cls.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.socket.create_connection")
|
|
||||||
@patch("superset.extensions.ssh.paramiko.Transport")
|
|
||||||
def test_verify_host_key_match_ignores_comment_and_whitespace(
|
|
||||||
mock_transport_cls: Mock,
|
|
||||||
mock_create_connection: Mock,
|
|
||||||
) -> None:
|
|
||||||
# The stored key may carry a trailing comment and extra whitespace.
|
|
||||||
server_key = paramiko.RSAKey.generate(2048)
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
stored = f" {_authorized_key(server_key)} user@host "
|
|
||||||
tunnel = _ssh_tunnel(stored)
|
|
||||||
|
|
||||||
transport = mock_transport_cls.return_value
|
|
||||||
transport.get_remote_server_key.return_value = server_key
|
|
||||||
|
|
||||||
manager._verify_host_key(tunnel) # should not raise
|
|
||||||
|
|
||||||
# Whitespace/comment stripping must not short-circuit verification: the
|
|
||||||
# bounded TCP connect and Transport handshake still run as in the plain
|
|
||||||
# match case.
|
|
||||||
mock_create_connection.assert_called_once_with(
|
|
||||||
("ssh.example.com", 22), timeout=321.0
|
|
||||||
)
|
|
||||||
mock_transport_cls.assert_called_once_with(mock_create_connection.return_value)
|
|
||||||
transport.start_client.assert_called_once()
|
|
||||||
transport.close.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_host_key_invalid_expected_raises() -> None:
|
|
||||||
# A malformed expected key is rejected before any network connection.
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
tunnel = _ssh_tunnel("not-a-valid-key")
|
|
||||||
|
|
||||||
with pytest.raises(SSHTunnelHostKeyVerificationError):
|
|
||||||
manager._verify_host_key(tunnel)
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_host_key_unknown_key_type_raises() -> None:
|
|
||||||
"""An unsupported key type is wrapped in the verification error, not leaked."""
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
server_key = paramiko.RSAKey.generate(2048)
|
|
||||||
tunnel = _ssh_tunnel(f"ssh-bogus {server_key.get_base64()}")
|
|
||||||
|
|
||||||
with pytest.raises(SSHTunnelHostKeyVerificationError):
|
|
||||||
manager._verify_host_key(tunnel)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.sshtunnel.open_tunnel")
|
|
||||||
@patch("superset.extensions.ssh.socket.create_connection")
|
|
||||||
@patch("superset.extensions.ssh.paramiko.Transport")
|
|
||||||
def test_create_tunnel_pins_verified_host_key(
|
|
||||||
mock_transport_cls: Mock,
|
|
||||||
mock_create_connection: Mock,
|
|
||||||
mock_open_tunnel: Mock,
|
|
||||||
) -> None:
|
|
||||||
"""A verified expected key is also pinned on the tunnel's own connection.
|
|
||||||
|
|
||||||
When an expected host key is configured and verified, it is also pinned on the
|
|
||||||
tunnel's own connection (``ssh_host_key``) so paramiko verifies the host that
|
|
||||||
actually carries traffic on the same transport — closing the probe-vs-tunnel
|
|
||||||
TOCTOU gap rather than trusting only the pre-flight probe.
|
|
||||||
"""
|
|
||||||
server_key = paramiko.RSAKey.generate(2048)
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
tunnel = _ssh_tunnel(_authorized_key(server_key))
|
|
||||||
tunnel.username = "user"
|
|
||||||
tunnel.password = None
|
|
||||||
tunnel.private_key = None
|
|
||||||
|
|
||||||
mock_transport_cls.return_value.get_remote_server_key.return_value = server_key
|
|
||||||
|
|
||||||
manager.create_tunnel(tunnel, "postgresql://u:p@db:5432/ex")
|
|
||||||
|
|
||||||
_, kwargs = mock_open_tunnel.call_args
|
|
||||||
assert kwargs["ssh_host_key"] == server_key
|
|
||||||
|
|
||||||
|
|
||||||
@patch("superset.extensions.ssh.sshtunnel.open_tunnel")
|
|
||||||
def test_create_tunnel_without_host_key_does_not_pin(mock_open_tunnel: Mock) -> None:
|
|
||||||
# No expected key configured (non-strict): nothing is pinned, preserving the
|
|
||||||
# prior behavior.
|
|
||||||
manager = _make_manager(strict=False)
|
|
||||||
tunnel = _ssh_tunnel(None)
|
|
||||||
tunnel.username = "user"
|
|
||||||
tunnel.password = None
|
|
||||||
tunnel.private_key = None
|
|
||||||
|
|
||||||
manager.create_tunnel(tunnel, "postgresql://u:p@db:5432/ex")
|
|
||||||
|
|
||||||
_, kwargs = mock_open_tunnel.call_args
|
|
||||||
assert "ssh_host_key" not in kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_tunnel_schema_round_trips_server_host_key() -> None:
|
|
||||||
"""The schema accepts and preserves the public host key field."""
|
|
||||||
from superset.databases.schemas import DatabaseSSHTunnel
|
|
||||||
|
|
||||||
server_key = paramiko.RSAKey.generate(2048)
|
|
||||||
authorized = _authorized_key(server_key)
|
|
||||||
payload = {
|
|
||||||
"server_address": "ssh.example.com",
|
|
||||||
"server_port": 22,
|
|
||||||
"username": "user",
|
|
||||||
"password": "secret",
|
|
||||||
"server_host_key": authorized,
|
|
||||||
}
|
|
||||||
loaded = DatabaseSSHTunnel().load(payload)
|
|
||||||
assert loaded["server_host_key"] == authorized
|
|
||||||
|
|||||||
@@ -177,17 +177,6 @@ class TestBigNumberChartConfig:
|
|||||||
compare_lag=1,
|
compare_lag=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_aggregation_requires_trendline(self) -> None:
|
|
||||||
"""aggregation without show_trendline=True must raise ValidationError."""
|
|
||||||
with pytest.raises(
|
|
||||||
ValidationError, match="aggregation requires show_trendline"
|
|
||||||
):
|
|
||||||
BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
aggregation="sum",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_with_filters(self) -> None:
|
def test_with_filters(self) -> None:
|
||||||
config = BigNumberChartConfig(
|
config = BigNumberChartConfig(
|
||||||
chart_type="big_number",
|
chart_type="big_number",
|
||||||
@@ -199,36 +188,6 @@ class TestBigNumberChartConfig:
|
|||||||
assert config.filters is not None
|
assert config.filters is not None
|
||||||
assert len(config.filters) == 1
|
assert len(config.filters) == 1
|
||||||
|
|
||||||
def test_with_aggregation_sum(self) -> None:
|
|
||||||
"""aggregation='sum' is accepted when show_trendline=True."""
|
|
||||||
config = BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
temporal_column="ds",
|
|
||||||
show_trendline=True,
|
|
||||||
aggregation="sum",
|
|
||||||
)
|
|
||||||
assert config.aggregation == "sum"
|
|
||||||
|
|
||||||
def test_with_aggregation_last_value(self) -> None:
|
|
||||||
"""aggregation='LAST_VALUE' is accepted when show_trendline=True."""
|
|
||||||
config = BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
temporal_column="ds",
|
|
||||||
show_trendline=True,
|
|
||||||
aggregation="LAST_VALUE",
|
|
||||||
)
|
|
||||||
assert config.aggregation == "LAST_VALUE"
|
|
||||||
|
|
||||||
def test_aggregation_defaults_to_none(self) -> None:
|
|
||||||
"""aggregation field is None when omitted."""
|
|
||||||
config = BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
)
|
|
||||||
assert config.aggregation is None
|
|
||||||
|
|
||||||
def test_extra_fields_forbidden(self) -> None:
|
def test_extra_fields_forbidden(self) -> None:
|
||||||
with pytest.raises(ValueError, match="Unknown field 'unknown_field'"):
|
with pytest.raises(ValueError, match="Unknown field 'unknown_field'"):
|
||||||
BigNumberChartConfig(
|
BigNumberChartConfig(
|
||||||
@@ -348,52 +307,6 @@ class TestMapBigNumberConfig:
|
|||||||
assert "time_grain_sqla" not in form_data
|
assert "time_grain_sqla" not in form_data
|
||||||
assert "start_y_axis_at_zero" not in form_data
|
assert "start_y_axis_at_zero" not in form_data
|
||||||
|
|
||||||
def test_with_aggregation_sum(self) -> None:
|
|
||||||
"""aggregation='sum' is written to form_data for trendline charts."""
|
|
||||||
config = BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
temporal_column="order_date",
|
|
||||||
show_trendline=True,
|
|
||||||
aggregation="sum",
|
|
||||||
)
|
|
||||||
form_data = map_big_number_config(config)
|
|
||||||
assert form_data["aggregation"] == "sum"
|
|
||||||
|
|
||||||
def test_with_aggregation_last_value(self) -> None:
|
|
||||||
"""aggregation='LAST_VALUE' is written to form_data for trendline charts."""
|
|
||||||
config = BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
temporal_column="order_date",
|
|
||||||
show_trendline=True,
|
|
||||||
aggregation="LAST_VALUE",
|
|
||||||
)
|
|
||||||
form_data = map_big_number_config(config)
|
|
||||||
assert form_data["aggregation"] == "LAST_VALUE"
|
|
||||||
|
|
||||||
def test_aggregation_absent_when_not_set(self) -> None:
|
|
||||||
"""aggregation key is absent from form_data when config.aggregation is None."""
|
|
||||||
config = BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
temporal_column="order_date",
|
|
||||||
show_trendline=True,
|
|
||||||
)
|
|
||||||
form_data = map_big_number_config(config)
|
|
||||||
assert "aggregation" not in form_data
|
|
||||||
|
|
||||||
def test_aggregation_not_allowed_for_big_number_total(self) -> None:
|
|
||||||
"""aggregation is rejected when show_trendline=False (big_number_total)."""
|
|
||||||
with pytest.raises(
|
|
||||||
ValidationError, match="aggregation requires show_trendline"
|
|
||||||
):
|
|
||||||
BigNumberChartConfig(
|
|
||||||
chart_type="big_number",
|
|
||||||
metric=ColumnRef(name="revenue", aggregate="SUM"),
|
|
||||||
aggregation="sum",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestMapConfigToFormDataBigNumber:
|
class TestMapConfigToFormDataBigNumber:
|
||||||
"""Test map_config_to_form_data dispatch for big number."""
|
"""Test map_config_to_form_data dispatch for big number."""
|
||||||
|
|||||||
Reference in New Issue
Block a user