mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
* feat(dashboard): embedded dashboard UI configuration (#17175) (#17450) * setup embedded provider * update ui configuration * fix test * feat: Guest token (for embedded dashboard auth) (#17517) * generate an embed token * improve existing tests * add some auth setup, and rename token * fix the stuff for compatibility with external request loaders * docs, standard jwt claims, tweaks * black * lint * tests, and safer token decoding * linting * type annotation * prettier * add feature flag * quiet pylint * apparently typing is a problem again * Make guest role name configurable * fake being a non-anonymous user * just one log entry * customizable algo * lint * lint again * 403 works now! * get guest token from header instead of cookie * Revert "403 works now!" This reverts commitdf2f49a6d4
. * fix tests * Revert "Revert "403 works now!"" This reverts commit883dff38f1
. * rename method * correct import * feat: entry for embedded dashboard (#17529) * create entry for embedded dashboard in webpack * add cookies * lint * token message handshake * guestTokenHeaderName * use setupClient instead of calling configure * rename the webpack chunk * simplified handshake * embedded entrypoint: render a proper app * make the embedded page accept anonymous connections * format * lint * fix test # Conflicts: # superset-frontend/src/embedded/index.tsx # superset/views/core.py * lint * Update superset-frontend/src/embedded/index.tsx Co-authored-by: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com> * comment out origins checks * move embedded for core to dashboard * pylint * isort Co-authored-by: David Aaron Suddjian <aasuddjian@gmail.com> Co-authored-by: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com> * feat: Authorizing guest access to embedded dashboards (#17757) * helper methods and dashboard access * guest token dashboard authz * adjust csrf exempt list * eums don't work that way * Remove unnecessary import * move row level security tests to their own file * a bit of refactoring * add guest token security tests * refactor tests * clean imports * variable names can be too long apparently * missing argument to get_user_roles * don't redefine builtins * remove unused imports * fix test import * default to global user when getting roles * missing import * mock it * test get_user_roles * infer g.user for ease of tests * remove redundant check * tests for guest user security manager fns * use algo to get rid of warning messages * tweaking access checks * fix guest token security tests * missing imports * more tests * more testing and also some small refactoring * move validation out of parsing * fix dashboard access check again * add more test Co-authored-by: Lily Kuang <lily@preset.io> * feat: Row Level Security rules for guest tokens (#17836) * helper methods and dashboard access * guest token dashboard authz * adjust csrf exempt list * eums don't work that way * Remove unnecessary import * move row level security tests to their own file * a bit of refactoring * add guest token security tests * refactor tests * clean imports * variable names can be too long apparently * missing argument to get_user_roles * don't redefine builtins * remove unused imports * fix test import * default to global user when getting roles * missing import * mock it * test get_user_roles * infer g.user for ease of tests * remove redundant check * tests for guest user security manager fns * use algo to get rid of warning messages * tweaking access checks * fix guest token security tests * missing imports * more tests * more testing and also some small refactoring * move validation out of parsing * fix dashboard access check again * rls rules for guest tokens * test guest token rls rules * more flexible rls rules * lint * fix tests * fix test * defaults * fix some tests * fix some tests * lint Co-authored-by: Lily Kuang <lily@preset.io> * SupersetClient guest token test * Apply suggestions from code review Co-authored-by: Lily Kuang <lily@preset.io> Co-authored-by: Lily Kuang <lily@preset.io>
211 lines
8.1 KiB
Python
211 lines
8.1 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.
|
|
"""Unit tests for Superset"""
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from flask import g
|
|
|
|
from superset import db, security_manager
|
|
from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
|
|
from superset.exceptions import SupersetSecurityException
|
|
from superset.models.dashboard import Dashboard
|
|
from superset.security.guest_token import GuestTokenResourceType
|
|
from superset.sql_parse import Table
|
|
from tests.integration_tests.base_tests import SupersetTestCase
|
|
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
|
load_birth_names_dashboard_with_slices,
|
|
load_birth_names_data,
|
|
)
|
|
|
|
|
|
@mock.patch.dict(
|
|
"superset.extensions.feature_flag_manager._feature_flags", EMBEDDED_SUPERSET=True,
|
|
)
|
|
class TestGuestUserSecurity(SupersetTestCase):
|
|
# This test doesn't use a dashboard fixture, the next test does.
|
|
# That way tests are faster.
|
|
|
|
resource_id = 42
|
|
|
|
def authorized_guest(self):
|
|
return security_manager.get_guest_user_from_token(
|
|
{"user": {}, "resources": [{"type": "dashboard", "id": self.resource_id}]}
|
|
)
|
|
|
|
def test_is_guest_user__regular_user(self):
|
|
is_guest = security_manager.is_guest_user(security_manager.find_user("admin"))
|
|
self.assertFalse(is_guest)
|
|
|
|
def test_is_guest_user__anonymous(self):
|
|
is_guest = security_manager.is_guest_user(security_manager.get_anonymous_user())
|
|
self.assertFalse(is_guest)
|
|
|
|
def test_is_guest_user__guest_user(self):
|
|
is_guest = security_manager.is_guest_user(self.authorized_guest())
|
|
self.assertTrue(is_guest)
|
|
|
|
@mock.patch.dict(
|
|
"superset.extensions.feature_flag_manager._feature_flags",
|
|
EMBEDDED_SUPERSET=False,
|
|
)
|
|
def test_is_guest_user__flag_off(self):
|
|
is_guest = security_manager.is_guest_user(self.authorized_guest())
|
|
self.assertFalse(is_guest)
|
|
|
|
def test_get_guest_user__regular_user(self):
|
|
g.user = security_manager.find_user("admin")
|
|
guest_user = security_manager.get_current_guest_user_if_guest()
|
|
self.assertIsNone(guest_user)
|
|
|
|
def test_get_guest_user__anonymous_user(self):
|
|
g.user = security_manager.get_anonymous_user()
|
|
guest_user = security_manager.get_current_guest_user_if_guest()
|
|
self.assertIsNone(guest_user)
|
|
|
|
def test_get_guest_user__guest_user(self):
|
|
g.user = self.authorized_guest()
|
|
guest_user = security_manager.get_current_guest_user_if_guest()
|
|
self.assertEqual(guest_user, g.user)
|
|
|
|
def test_has_guest_access__regular_user(self):
|
|
g.user = security_manager.find_user("admin")
|
|
has_guest_access = security_manager.has_guest_access(
|
|
GuestTokenResourceType.DASHBOARD, self.resource_id
|
|
)
|
|
self.assertFalse(has_guest_access)
|
|
|
|
def test_has_guest_access__anonymous_user(self):
|
|
g.user = security_manager.get_anonymous_user()
|
|
has_guest_access = security_manager.has_guest_access(
|
|
GuestTokenResourceType.DASHBOARD, self.resource_id
|
|
)
|
|
self.assertFalse(has_guest_access)
|
|
|
|
def test_has_guest_access__authorized_guest_user(self):
|
|
g.user = self.authorized_guest()
|
|
has_guest_access = security_manager.has_guest_access(
|
|
GuestTokenResourceType.DASHBOARD, self.resource_id
|
|
)
|
|
self.assertTrue(has_guest_access)
|
|
|
|
def test_has_guest_access__authorized_guest_user__non_zero_resource_index(self):
|
|
guest = self.authorized_guest()
|
|
guest.resources = [
|
|
{"type": "dashboard", "id": self.resource_id - 1}
|
|
] + guest.resources
|
|
g.user = guest
|
|
|
|
has_guest_access = security_manager.has_guest_access(
|
|
GuestTokenResourceType.DASHBOARD, self.resource_id
|
|
)
|
|
self.assertTrue(has_guest_access)
|
|
|
|
def test_has_guest_access__unauthorized_guest_user__different_resource_id(self):
|
|
g.user = security_manager.get_guest_user_from_token(
|
|
{
|
|
"user": {},
|
|
"resources": [{"type": "dashboard", "id": self.resource_id - 1}],
|
|
}
|
|
)
|
|
has_guest_access = security_manager.has_guest_access(
|
|
GuestTokenResourceType.DASHBOARD, self.resource_id
|
|
)
|
|
self.assertFalse(has_guest_access)
|
|
|
|
def test_has_guest_access__unauthorized_guest_user__different_resource_type(self):
|
|
g.user = security_manager.get_guest_user_from_token(
|
|
{"user": {}, "resources": [{"type": "dirt", "id": self.resource_id}]}
|
|
)
|
|
has_guest_access = security_manager.has_guest_access(
|
|
GuestTokenResourceType.DASHBOARD, self.resource_id
|
|
)
|
|
self.assertFalse(has_guest_access)
|
|
|
|
def test_get_guest_user_roles_explicit(self):
|
|
guest = self.authorized_guest()
|
|
roles = security_manager.get_user_roles(guest)
|
|
self.assertEqual(guest.roles, roles)
|
|
|
|
def test_get_guest_user_roles_implicit(self):
|
|
guest = self.authorized_guest()
|
|
g.user = guest
|
|
|
|
roles = security_manager.get_user_roles()
|
|
self.assertEqual(guest.roles, roles)
|
|
|
|
|
|
@mock.patch.dict(
|
|
"superset.extensions.feature_flag_manager._feature_flags", EMBEDDED_SUPERSET=True,
|
|
)
|
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
class TestGuestUserDashboardAccess(SupersetTestCase):
|
|
def setUp(self) -> None:
|
|
self.dash = db.session.query(Dashboard).filter_by(slug="births").first()
|
|
self.authorized_guest = security_manager.get_guest_user_from_token(
|
|
{"user": {}, "resources": [{"type": "dashboard", "id": self.dash.id}]}
|
|
)
|
|
self.unauthorized_guest = security_manager.get_guest_user_from_token(
|
|
{"user": {}, "resources": [{"type": "dashboard", "id": self.dash.id + 1}]}
|
|
)
|
|
|
|
def test_chart_raise_for_access_as_guest(self):
|
|
chart = self.dash.slices[0]
|
|
g.user = self.authorized_guest
|
|
|
|
security_manager.raise_for_access(viz=chart)
|
|
|
|
def test_chart_raise_for_access_as_unauthorized_guest(self):
|
|
chart = self.dash.slices[0]
|
|
g.user = self.unauthorized_guest
|
|
|
|
with self.assertRaises(SupersetSecurityException):
|
|
security_manager.raise_for_access(viz=chart)
|
|
|
|
def test_dataset_raise_for_access_as_guest(self):
|
|
dataset = self.dash.slices[0].datasource
|
|
g.user = self.authorized_guest
|
|
|
|
security_manager.raise_for_access(datasource=dataset)
|
|
|
|
def test_dataset_raise_for_access_as_unauthorized_guest(self):
|
|
dataset = self.dash.slices[0].datasource
|
|
g.user = self.unauthorized_guest
|
|
|
|
with self.assertRaises(SupersetSecurityException):
|
|
security_manager.raise_for_access(datasource=dataset)
|
|
|
|
def test_guest_token_does_not_grant_access_to_underlying_table(self):
|
|
sqla_table = self.dash.slices[0].table
|
|
table = Table(table=sqla_table.table_name)
|
|
|
|
g.user = self.authorized_guest
|
|
|
|
with self.assertRaises(Exception):
|
|
security_manager.raise_for_access(table=table, database=sqla_table.database)
|
|
|
|
def test_raise_for_dashboard_access_as_guest(self):
|
|
g.user = self.authorized_guest
|
|
|
|
security_manager.raise_for_dashboard_access(self.dash)
|
|
|
|
def test_raise_for_dashboard_access_as_unauthorized_guest(self):
|
|
g.user = self.unauthorized_guest
|
|
|
|
with self.assertRaises(DashboardAccessDeniedError):
|
|
security_manager.raise_for_dashboard_access(self.dash)
|