mirror of
https://github.com/apache/superset.git
synced 2026-07-08 15:55:33 +00:00
158 lines
5.8 KiB
Python
158 lines
5.8 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from superset.security.manager import SupersetSecurityManager
|
|
|
|
_DASHBOARD_RESOURCE = {"type": "dashboard", "id": "abc-uuid"}
|
|
|
|
|
|
def _token(iat: int) -> dict[str, Any]:
|
|
return {"type": "guest", "iat": iat, "resources": [_DASHBOARD_RESOURCE]}
|
|
|
|
|
|
def _embedded(revoked_before) -> MagicMock:
|
|
embedded = MagicMock()
|
|
embedded.guest_token_revoked_before = revoked_before
|
|
return embedded
|
|
|
|
|
|
def test_guest_token_not_revoked_when_no_revocation_set() -> None:
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=_embedded(None),
|
|
):
|
|
assert SupersetSecurityManager._is_guest_token_revoked(_token(1000)) is False
|
|
|
|
|
|
def test_guest_token_revoked_when_issued_before_revocation() -> None:
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=_embedded(2000),
|
|
):
|
|
# Token issued at 1000, revocation at 2000 -> revoked.
|
|
assert SupersetSecurityManager._is_guest_token_revoked(_token(1000)) is True
|
|
|
|
|
|
def test_guest_token_valid_when_issued_after_revocation() -> None:
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=_embedded(2000),
|
|
):
|
|
# Token issued at 3000, after the revocation cutoff -> still valid.
|
|
assert SupersetSecurityManager._is_guest_token_revoked(_token(3000)) is False
|
|
|
|
|
|
def test_guest_token_without_iat_is_revoked_when_cutoff_set() -> None:
|
|
# A token lacking ``iat`` cannot prove it predates the cutoff, so it
|
|
# fails closed and is treated as revoked when a cutoff is configured.
|
|
token = {"type": "guest", "resources": [_DASHBOARD_RESOURCE]}
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=_embedded(2000),
|
|
):
|
|
assert SupersetSecurityManager._is_guest_token_revoked(token) is True
|
|
|
|
|
|
def test_guest_token_without_iat_is_not_revoked_when_no_cutoff() -> None:
|
|
token = {"type": "guest", "resources": [_DASHBOARD_RESOURCE]}
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=_embedded(None),
|
|
):
|
|
assert SupersetSecurityManager._is_guest_token_revoked(token) is False
|
|
|
|
|
|
def test_guest_token_revoked_via_legacy_dashboard_id_resource() -> None:
|
|
# During the UUID migration a dashboard resource id may be a legacy
|
|
# dashboard id rather than an embedded UUID. In that case the embedded
|
|
# config is resolved via Dashboard.get(...).embedded, and its revocation
|
|
# cutoff must still be honored.
|
|
dashboard = MagicMock()
|
|
dashboard.embedded = [_embedded(2000)]
|
|
with (
|
|
patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=None,
|
|
),
|
|
patch(
|
|
"superset.models.dashboard.Dashboard.get",
|
|
return_value=dashboard,
|
|
),
|
|
):
|
|
assert SupersetSecurityManager._is_guest_token_revoked(_token(1000)) is True
|
|
|
|
|
|
def test_guest_token_not_revoked_when_resource_unresolvable() -> None:
|
|
# If neither an embedded config nor a dashboard resolves, there is no
|
|
# cutoff to enforce and the token is treated as not revoked.
|
|
with (
|
|
patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=None,
|
|
),
|
|
patch(
|
|
"superset.models.dashboard.Dashboard.get",
|
|
return_value=None,
|
|
),
|
|
):
|
|
assert SupersetSecurityManager._is_guest_token_revoked(_token(1000)) is False
|
|
|
|
|
|
def _manager() -> SupersetSecurityManager:
|
|
# Build an instance without running the (heavy) FAB __init__: we only
|
|
# exercise revoke_guest_token_access, which depends on nothing but
|
|
# _get_current_epoch_time and the EmbeddedDashboardDAO lookup.
|
|
return SupersetSecurityManager.__new__(SupersetSecurityManager)
|
|
|
|
|
|
def test_revoke_guest_token_access_uses_explicit_before() -> None:
|
|
embedded = _embedded(None)
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=embedded,
|
|
):
|
|
_manager().revoke_guest_token_access("abc-uuid", before=1234)
|
|
assert embedded.guest_token_revoked_before == 1234
|
|
|
|
|
|
def test_revoke_guest_token_access_defaults_to_ceil_of_now() -> None:
|
|
embedded = _embedded(None)
|
|
manager = _manager()
|
|
with (
|
|
patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=embedded,
|
|
),
|
|
patch.object(manager, "_get_current_epoch_time", return_value=1000.25),
|
|
):
|
|
manager.revoke_guest_token_access("abc-uuid")
|
|
# Cutoff is rounded up so fractional-``iat`` tokens issued in the same
|
|
# second are reliably revoked (fails closed).
|
|
assert embedded.guest_token_revoked_before == 1001
|
|
|
|
|
|
def test_revoke_guest_token_access_noop_when_embedded_missing() -> None:
|
|
with patch(
|
|
"superset.daos.dashboard.EmbeddedDashboardDAO.find_by_id",
|
|
return_value=None,
|
|
):
|
|
# Should simply return without raising when the UUID does not resolve.
|
|
_manager().revoke_guest_token_access("missing-uuid")
|