mirror of
https://github.com/apache/superset.git
synced 2026-07-16 19:55:39 +00:00
committed by
GitHub
parent
dea9068647
commit
a9fb853e3e
@@ -23,7 +23,8 @@ This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
|
||||
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
|
||||
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:
|
||||
|
||||
@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### Keycloak-Specific Configuration using Flask-OIDC
|
||||
|
||||
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
|
||||
|
||||
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
|
||||
|
||||
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.manager import AUTH_OID
|
||||
from superset.security import SupersetSecurityManager
|
||||
from flask_oidc import OpenIDConnect
|
||||
from flask_appbuilder.security.views import AuthOIDView
|
||||
from flask_login import login_user
|
||||
from urllib.parse import quote
|
||||
from flask_appbuilder.views import ModelView, SimpleFormView, expose
|
||||
from flask import (
|
||||
redirect,
|
||||
request
|
||||
)
|
||||
import logging
|
||||
|
||||
class OIDCSecurityManager(SupersetSecurityManager):
|
||||
|
||||
def __init__(self, appbuilder):
|
||||
super(OIDCSecurityManager, self).__init__(appbuilder)
|
||||
if self.auth_type == AUTH_OID:
|
||||
self.oid = OpenIDConnect(self.appbuilder.get_app)
|
||||
self.authoidview = AuthOIDCView
|
||||
|
||||
class AuthOIDCView(AuthOIDView):
|
||||
|
||||
@expose('/login/', methods=['GET', 'POST'])
|
||||
def login(self, flag=True):
|
||||
sm = self.appbuilder.sm
|
||||
oidc = sm.oid
|
||||
|
||||
@self.appbuilder.sm.oid.require_login
|
||||
def handle_login():
|
||||
user = sm.auth_user_oid(oidc.user_getfield('email'))
|
||||
|
||||
if user is None:
|
||||
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
|
||||
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
|
||||
info.get('email'), sm.find_role('Gamma'))
|
||||
|
||||
login_user(user, remember=False)
|
||||
return redirect(self.appbuilder.get_url_for_index)
|
||||
|
||||
return handle_login()
|
||||
|
||||
@expose('/logout/', methods=['GET', 'POST'])
|
||||
def logout(self):
|
||||
oidc = self.appbuilder.sm.oid
|
||||
|
||||
oidc.logout()
|
||||
super(AuthOIDCView, self).logout()
|
||||
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
|
||||
|
||||
return redirect(
|
||||
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
|
||||
```
|
||||
|
||||
Then add to your `superset_config.py` file:
|
||||
|
||||
```python
|
||||
from keycloak_security_manager import OIDCSecurityManager
|
||||
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
|
||||
import os
|
||||
|
||||
AUTH_TYPE = AUTH_OID
|
||||
SECRET_KEY: 'SomethingNotEntirelySecret'
|
||||
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_OPENID_REALM: '<myRealm>'
|
||||
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
|
||||
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
|
||||
|
||||
# Will allow user self registration, allowing to create Flask users from Authorized User
|
||||
AUTH_USER_REGISTRATION = True
|
||||
|
||||
# The default user self registration role
|
||||
AUTH_USER_REGISTRATION_ROLE = 'Public'
|
||||
```
|
||||
|
||||
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"<myOpenIDProvider>": {
|
||||
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
|
||||
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
|
||||
"client_id": "https://<myKeycloakDomain>",
|
||||
"client_secret": "<myClientSecret>",
|
||||
"redirect_uris": [
|
||||
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
|
||||
],
|
||||
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### Keycloak-Specific Configuration using Flask-OIDC
|
||||
|
||||
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
|
||||
|
||||
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
|
||||
|
||||
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.manager import AUTH_OID
|
||||
from superset.security import SupersetSecurityManager
|
||||
from flask_oidc import OpenIDConnect
|
||||
from flask_appbuilder.security.views import AuthOIDView
|
||||
from flask_login import login_user
|
||||
from urllib.parse import quote
|
||||
from flask_appbuilder.views import ModelView, SimpleFormView, expose
|
||||
from flask import (
|
||||
redirect,
|
||||
request
|
||||
)
|
||||
import logging
|
||||
|
||||
class OIDCSecurityManager(SupersetSecurityManager):
|
||||
|
||||
def __init__(self, appbuilder):
|
||||
super(OIDCSecurityManager, self).__init__(appbuilder)
|
||||
if self.auth_type == AUTH_OID:
|
||||
self.oid = OpenIDConnect(self.appbuilder.get_app)
|
||||
self.authoidview = AuthOIDCView
|
||||
|
||||
class AuthOIDCView(AuthOIDView):
|
||||
|
||||
@expose('/login/', methods=['GET', 'POST'])
|
||||
def login(self, flag=True):
|
||||
sm = self.appbuilder.sm
|
||||
oidc = sm.oid
|
||||
|
||||
@self.appbuilder.sm.oid.require_login
|
||||
def handle_login():
|
||||
user = sm.auth_user_oid(oidc.user_getfield('email'))
|
||||
|
||||
if user is None:
|
||||
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
|
||||
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
|
||||
info.get('email'), sm.find_role('Gamma'))
|
||||
|
||||
login_user(user, remember=False)
|
||||
return redirect(self.appbuilder.get_url_for_index)
|
||||
|
||||
return handle_login()
|
||||
|
||||
@expose('/logout/', methods=['GET', 'POST'])
|
||||
def logout(self):
|
||||
oidc = self.appbuilder.sm.oid
|
||||
|
||||
oidc.logout()
|
||||
super(AuthOIDCView, self).logout()
|
||||
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
|
||||
|
||||
return redirect(
|
||||
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
|
||||
```
|
||||
|
||||
Then add to your `superset_config.py` file:
|
||||
|
||||
```python
|
||||
from keycloak_security_manager import OIDCSecurityManager
|
||||
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
|
||||
import os
|
||||
|
||||
AUTH_TYPE = AUTH_OID
|
||||
SECRET_KEY: 'SomethingNotEntirelySecret'
|
||||
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_OPENID_REALM: '<myRealm>'
|
||||
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
|
||||
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
|
||||
|
||||
# Will allow user self registration, allowing to create Flask users from Authorized User
|
||||
AUTH_USER_REGISTRATION = True
|
||||
|
||||
# The default user self registration role
|
||||
AUTH_USER_REGISTRATION_ROLE = 'Public'
|
||||
```
|
||||
|
||||
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"<myOpenIDProvider>": {
|
||||
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
|
||||
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
|
||||
"client_id": "https://<myKeycloakDomain>",
|
||||
"client_secret": "<myClientSecret>",
|
||||
"redirect_uris": [
|
||||
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
|
||||
],
|
||||
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
"cryptography>=42.0.4, <45.0.0",
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.2.5, <3.0.0",
|
||||
"flask-appbuilder>=4.8.1, <5.0.0",
|
||||
"flask-appbuilder>=5.0.0,<6",
|
||||
"flask-caching>=2.1.0, <3",
|
||||
"flask-compress>=1.13, <2.0",
|
||||
"flask-talisman>=1.0.0, <2.0",
|
||||
|
||||
@@ -114,11 +114,9 @@ flask==2.3.3
|
||||
# flask-session
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.8.1
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
flask-babel==2.0.0
|
||||
flask-appbuilder==5.0.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
flask-babel==3.1.0
|
||||
# via flask-appbuilder
|
||||
flask-caching==2.3.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
|
||||
@@ -208,12 +208,11 @@ flask==2.3.3
|
||||
# flask-sqlalchemy
|
||||
# flask-testing
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.8.1
|
||||
flask-appbuilder==5.0.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
flask-babel==2.0.0
|
||||
flask-babel==3.1.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-appbuilder
|
||||
|
||||
@@ -42,7 +42,7 @@ classifiers = [
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
dependencies = [
|
||||
"flask-appbuilder>=4.5.3, <5.0.0",
|
||||
"flask-appbuilder>=5.0.0,<6",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -350,7 +350,6 @@ FAB_API_SWAGGER_UI = True
|
||||
# AUTHENTICATION CONFIG
|
||||
# ----------------------------------------------------
|
||||
# The authentication type
|
||||
# AUTH_OID : Is for OpenID
|
||||
# AUTH_DB : Is for database (username/password)
|
||||
# AUTH_LDAP : Is for LDAP
|
||||
# AUTH_REMOTE_USER : Is for using REMOTE_USER from web server
|
||||
|
||||
@@ -22,7 +22,7 @@ from io import BytesIO
|
||||
from typing import Any, Callable, cast
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
|
||||
from flask import g, redirect, request, Response, send_file, url_for
|
||||
from flask import current_app, g, redirect, request, Response, send_file, url_for
|
||||
from flask_appbuilder import permission_name
|
||||
from flask_appbuilder.api import expose, protect, rison, safe
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
@@ -332,8 +332,8 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||
"""Deterministic string representation of the API instance for etag_cache."""
|
||||
# pylint: disable=consider-using-f-string
|
||||
return "Superset.dashboards.api.DashboardRestApi@v{}{}".format(
|
||||
self.appbuilder.app.config["VERSION_STRING"],
|
||||
self.appbuilder.app.config["VERSION_SHA"],
|
||||
current_app.config["VERSION_STRING"],
|
||||
current_app.config["VERSION_SHA"],
|
||||
)
|
||||
|
||||
@expose("/<id_or_slug>", methods=("GET",))
|
||||
|
||||
@@ -20,7 +20,8 @@ from typing import Any, Callable, Optional
|
||||
|
||||
import celery
|
||||
from flask import Flask
|
||||
from flask_appbuilder import AppBuilder, SQLA
|
||||
from flask_appbuilder import AppBuilder
|
||||
from flask_appbuilder.utils.legacy import get_sqla_class
|
||||
from flask_caching.backends.base import BaseCache
|
||||
from flask_migrate import Migrate
|
||||
from flask_talisman import Talisman
|
||||
@@ -123,7 +124,7 @@ async_query_manager: AsyncQueryManager = LocalProxy(
|
||||
cache_manager = CacheManager()
|
||||
celery_app = celery.Celery()
|
||||
csrf = CSRFProtect()
|
||||
db = SQLA() # pylint: disable=disallowed-name
|
||||
db = get_sqla_class()()
|
||||
_event_logger: dict[str, Any] = {}
|
||||
encrypted_field_factory = EncryptedFieldFactory()
|
||||
event_logger = LocalProxy(lambda: _event_logger.get("event_logger"))
|
||||
|
||||
@@ -25,7 +25,7 @@ from typing import Any, Callable, TYPE_CHECKING
|
||||
import wtforms_json
|
||||
from colorama import Fore, Style
|
||||
from deprecation import deprecated
|
||||
from flask import abort, Flask, redirect, request, session, url_for
|
||||
from flask import abort, current_app, Flask, redirect, request, session, url_for
|
||||
from flask_appbuilder import expose, IndexView
|
||||
from flask_appbuilder.api import safe
|
||||
from flask_appbuilder.utils.base import get_safe_redirect
|
||||
@@ -290,7 +290,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
||||
"Home",
|
||||
label=_("Home"),
|
||||
href="/superset/welcome/",
|
||||
cond=lambda: bool(appbuilder.app.config["LOGO_TARGET_PATH"]),
|
||||
cond=lambda: bool(current_app.config["LOGO_TARGET_PATH"]),
|
||||
)
|
||||
|
||||
appbuilder.add_view(
|
||||
|
||||
@@ -22,7 +22,7 @@ from alembic import context
|
||||
from alembic.operations.ops import MigrationScript
|
||||
from alembic.runtime.migration import MigrationContext
|
||||
from flask import current_app
|
||||
from flask_appbuilder import Base
|
||||
from flask_appbuilder import Model
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
@@ -45,7 +45,7 @@ if "sqlite" in DATABASE_URI:
|
||||
# Escape % chars in the database URI to avoid interpolation errors in ConfigParser
|
||||
escaped_uri = DATABASE_URI.replace("%", "%%")
|
||||
config.set_main_option("sqlalchemy.url", escaped_uri)
|
||||
target_metadata = Base.metadata # pylint: disable=no-member
|
||||
target_metadata = Model.metadata # pylint: disable=no-member
|
||||
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
|
||||
@@ -21,6 +21,7 @@ from superset.models.helpers import AuditMixinNullable
|
||||
|
||||
|
||||
class DynamicPlugin(Model, AuditMixinNullable):
|
||||
__tablename__ = "dynamic_plugin"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(Text, unique=True, nullable=False)
|
||||
# key corresponds to viz_type from static plugins
|
||||
|
||||
@@ -805,7 +805,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
user_datasources.update(
|
||||
self.get_session.query(SqlaTable)
|
||||
self.session.query(SqlaTable)
|
||||
.filter(get_dataset_access_filters(SqlaTable))
|
||||
.all()
|
||||
)
|
||||
@@ -841,7 +841,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
|
||||
def user_view_menu_names(self, permission_name: str) -> set[str]:
|
||||
base_query = (
|
||||
self.get_session.query(self.viewmenu_model.name)
|
||||
self.session.query(self.viewmenu_model.name)
|
||||
.join(self.permissionview_model)
|
||||
.join(self.permission_model)
|
||||
.join(assoc_permissionview_role)
|
||||
@@ -949,7 +949,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
# datasource_access
|
||||
if perms := self.user_view_menu_names("datasource_access"):
|
||||
tables = (
|
||||
self.get_session.query(SqlaTable.schema)
|
||||
self.session.query(SqlaTable.schema)
|
||||
.filter(SqlaTable.database_id == database.id)
|
||||
.filter(or_(SqlaTable.perm.in_(perms)))
|
||||
.distinct()
|
||||
@@ -1009,7 +1009,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
# datasource_access
|
||||
if perms := self.user_view_menu_names("datasource_access"):
|
||||
tables = (
|
||||
self.get_session.query(SqlaTable.schema)
|
||||
self.session.query(SqlaTable.schema)
|
||||
.filter(SqlaTable.database_id == database.id)
|
||||
.filter(or_(SqlaTable.perm.in_(perms)))
|
||||
.distinct()
|
||||
@@ -1152,7 +1152,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
merge_pv("catalog_access", datasource.get_catalog_perm())
|
||||
|
||||
logger.info("Creating missing database permissions.")
|
||||
databases = self.get_session.query(models.Database).all()
|
||||
databases = self.session.query(models.Database).all()
|
||||
for database in databases:
|
||||
merge_pv("database_access", database.perm)
|
||||
|
||||
@@ -1162,7 +1162,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
|
||||
logger.info("Cleaning faulty perms")
|
||||
pvms = self.get_session.query(PermissionView).filter(
|
||||
pvms = self.session.query(PermissionView).filter(
|
||||
or_(
|
||||
PermissionView.permission # pylint: disable=singleton-comparison
|
||||
== None, # noqa: E711
|
||||
@@ -1205,7 +1205,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
Gets list of all PVM
|
||||
"""
|
||||
pvms = (
|
||||
self.get_session.query(self.permissionview_model)
|
||||
self.session.query(self.permissionview_model)
|
||||
.options(
|
||||
eagerload(self.permissionview_model.permission),
|
||||
eagerload(self.permissionview_model.view_menu),
|
||||
@@ -1220,7 +1220,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
definition
|
||||
"""
|
||||
role_from_permissions_names = self.builtin_roles.get(role_name, [])
|
||||
all_pvms = self.get_session.query(PermissionView).all()
|
||||
all_pvms = self.session.query(PermissionView).all()
|
||||
role_from_permissions = []
|
||||
for pvm_regex in role_from_permissions_names:
|
||||
view_name_regex = pvm_regex[0]
|
||||
@@ -1237,7 +1237,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
Find a List of models by a list of ids, if defined applies `base_filter`
|
||||
"""
|
||||
query = self.get_session.query(self.role_model).filter(
|
||||
query = self.session.query(self.role_model).filter(
|
||||
self.role_model.id.in_(role_ids)
|
||||
)
|
||||
return query.all()
|
||||
@@ -1510,7 +1510,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
)
|
||||
# Clean database schema permissions
|
||||
schema_pvms = (
|
||||
self.get_session.query(self.permissionview_model)
|
||||
self.session.query(self.permissionview_model)
|
||||
.join(self.permission_model)
|
||||
.join(self.viewmenu_model)
|
||||
.filter(
|
||||
@@ -1609,7 +1609,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
chart_table = Slice.__table__ # pylint: disable=no-member
|
||||
new_database_name = target.database_name
|
||||
datasets = (
|
||||
self.get_session.query(SqlaTable)
|
||||
self.session.query(SqlaTable)
|
||||
.filter(SqlaTable.database_id == target.id)
|
||||
.all()
|
||||
)
|
||||
@@ -1688,7 +1688,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
logger.warning(
|
||||
"Dataset has no database will retry with database_id to set permission"
|
||||
)
|
||||
database = self.get_session.query(Database).get(target.database_id)
|
||||
database = self.session.query(Database).get(target.database_id)
|
||||
dataset_perm = self.get_dataset_perm(
|
||||
target.id, target.table_name, database.database_name
|
||||
)
|
||||
@@ -2322,7 +2322,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
client_id=shortid()[:10],
|
||||
user_id=get_user_id(),
|
||||
)
|
||||
self.get_session.expunge(query)
|
||||
self.session.expunge(query)
|
||||
|
||||
if database and table or query:
|
||||
if query:
|
||||
@@ -2439,7 +2439,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
form_data
|
||||
and (dashboard_id := form_data.get("dashboardId"))
|
||||
and (
|
||||
dashboard_ := self.get_session.query(Dashboard)
|
||||
dashboard_ := self.session.query(Dashboard)
|
||||
.filter(Dashboard.id == dashboard_id)
|
||||
.one_or_none()
|
||||
)
|
||||
@@ -2472,7 +2472,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
form_data.get("type") != "NATIVE_FILTER"
|
||||
and (slice_id := form_data.get("slice_id"))
|
||||
and (
|
||||
slc := self.get_session.query(Slice)
|
||||
slc := self.session.query(Slice)
|
||||
.filter(Slice.id == slice_id)
|
||||
.one_or_none()
|
||||
)
|
||||
@@ -2546,7 +2546,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
need to be scoped
|
||||
"""
|
||||
return (
|
||||
self.get_session.query(self.user_model)
|
||||
self.session.query(self.user_model)
|
||||
.filter(self.user_model.username == username)
|
||||
.one_or_none()
|
||||
)
|
||||
@@ -2601,7 +2601,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
|
||||
user_roles = [role.id for role in self.get_user_roles(g.user)]
|
||||
regular_filter_roles = (
|
||||
self.get_session.query(RLSFilterRoles.c.rls_filter_id)
|
||||
self.session.query(RLSFilterRoles.c.rls_filter_id)
|
||||
.join(RowLevelSecurityFilter)
|
||||
.filter(
|
||||
RowLevelSecurityFilter.filter_type == RowLevelSecurityFilterType.REGULAR
|
||||
@@ -2609,18 +2609,18 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
.filter(RLSFilterRoles.c.role_id.in_(user_roles))
|
||||
)
|
||||
base_filter_roles = (
|
||||
self.get_session.query(RLSFilterRoles.c.rls_filter_id)
|
||||
self.session.query(RLSFilterRoles.c.rls_filter_id)
|
||||
.join(RowLevelSecurityFilter)
|
||||
.filter(
|
||||
RowLevelSecurityFilter.filter_type == RowLevelSecurityFilterType.BASE
|
||||
)
|
||||
.filter(RLSFilterRoles.c.role_id.in_(user_roles))
|
||||
)
|
||||
filter_tables = self.get_session.query(RLSFilterTables.c.rls_filter_id).filter(
|
||||
filter_tables = self.session.query(RLSFilterTables.c.rls_filter_id).filter(
|
||||
RLSFilterTables.c.table_id == table.id
|
||||
)
|
||||
query = (
|
||||
self.get_session.query(
|
||||
self.session.query(
|
||||
RowLevelSecurityFilter.id,
|
||||
RowLevelSecurityFilter.group_key,
|
||||
RowLevelSecurityFilter.clause,
|
||||
@@ -2827,7 +2827,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
|
||||
if self.is_admin():
|
||||
return
|
||||
orig_resource = self.get_session.query(resource.__class__).get(resource.id)
|
||||
orig_resource = self.session.query(resource.__class__).get(resource.id)
|
||||
owners = orig_resource.owners if hasattr(orig_resource, "owners") else []
|
||||
|
||||
if g.user.is_anonymous or g.user not in owners:
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from flask import request, Response
|
||||
from flask import current_app, request, Response
|
||||
from flask_appbuilder.api import expose, protect, rison, safe
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from marshmallow import ValidationError
|
||||
@@ -146,8 +146,8 @@ class TagRestApi(BaseSupersetModelRestApi):
|
||||
"""Deterministic string representation of the API instance for etag_cache."""
|
||||
return (
|
||||
"Superset.tags.api.TagRestApi@v"
|
||||
f"{self.appbuilder.app.config['VERSION_STRING']}"
|
||||
f"{self.appbuilder.app.config['VERSION_SHA']}"
|
||||
f"{current_app.config['VERSION_STRING']}"
|
||||
f"{current_app.config['VERSION_SHA']}"
|
||||
)
|
||||
|
||||
@expose("/", methods=("POST",))
|
||||
|
||||
@@ -69,9 +69,9 @@ import sqlalchemy as sa
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.x509 import Certificate, load_pem_x509_certificate
|
||||
from flask import current_app as app, g, request
|
||||
from flask_appbuilder import SQLA
|
||||
from flask_appbuilder.security.sqla.models import User
|
||||
from flask_babel import gettext as __
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from markupsafe import Markup
|
||||
from pandas.api.types import infer_dtype
|
||||
from pandas.core.dtypes.common import is_numeric_dtype
|
||||
@@ -613,7 +613,7 @@ def readfile(file_path: str) -> str | None:
|
||||
|
||||
|
||||
def generic_find_constraint_name(
|
||||
table: str, columns: set[str], referenced: str, database: SQLA
|
||||
table: str, columns: set[str], referenced: str, database: SQLAlchemy
|
||||
) -> str | None:
|
||||
"""Utility to find a constraint name in alembic migrations"""
|
||||
tbl = sa.Table(
|
||||
|
||||
@@ -37,7 +37,7 @@ from flask import (
|
||||
)
|
||||
from flask_appbuilder import BaseView, Model, ModelView
|
||||
from flask_appbuilder.actions import action
|
||||
from flask_appbuilder.const import AUTH_OAUTH, AUTH_OID
|
||||
from flask_appbuilder.const import AUTH_OAUTH
|
||||
from flask_appbuilder.forms import DynamicForm
|
||||
from flask_appbuilder.models.sqla.filters import BaseFilter
|
||||
from flask_appbuilder.security.sqla.models import User
|
||||
@@ -471,12 +471,6 @@ def cached_common_bootstrap_data( # pylint: disable=unused-argument
|
||||
)
|
||||
frontend_config["AUTH_PROVIDERS"] = oauth_providers
|
||||
|
||||
if auth_type == AUTH_OID:
|
||||
oid_providers = []
|
||||
for provider in appbuilder.sm.openid_providers:
|
||||
oid_providers.append(provider)
|
||||
frontend_config["AUTH_PROVIDERS"] = oid_providers
|
||||
|
||||
bootstrap_data = {
|
||||
"application_root": app.config["APPLICATION_ROOT"],
|
||||
"static_assets_prefix": app.config["STATIC_ASSETS_PREFIX"],
|
||||
@@ -603,9 +597,7 @@ class DeleteMixin: # pylint: disable=too-few-public-methods
|
||||
else:
|
||||
view_menu = security_manager.find_view_menu(item.get_perm())
|
||||
pvs = (
|
||||
security_manager.get_session.query(
|
||||
security_manager.permissionview_model
|
||||
)
|
||||
db.session.query(security_manager.permissionview_model)
|
||||
.filter_by(view_menu=view_menu)
|
||||
.all()
|
||||
)
|
||||
@@ -614,10 +606,10 @@ class DeleteMixin: # pylint: disable=too-few-public-methods
|
||||
self.post_delete(item)
|
||||
|
||||
for pv in pvs:
|
||||
security_manager.get_session.delete(pv)
|
||||
db.session.delete(pv)
|
||||
|
||||
if view_menu:
|
||||
security_manager.get_session.delete(view_menu)
|
||||
db.session.delete(view_menu)
|
||||
|
||||
db.session.commit() # pylint: disable=consider-using-transaction
|
||||
|
||||
|
||||
@@ -657,15 +657,13 @@ class BaseSupersetModelRestApi(BaseSupersetApiMixin, ModelRestApi):
|
||||
# Create generic base filters with added request filter
|
||||
filters = self._get_distinct_filter(column_name, args.get("filter"))
|
||||
# Make the query
|
||||
query_count = self.appbuilder.get_session.query(
|
||||
query_count = db.session.query(
|
||||
func.count(distinct(getattr(self.datamodel.obj, column_name)))
|
||||
)
|
||||
count = self.datamodel.apply_filters(query_count, filters).scalar()
|
||||
if count == 0:
|
||||
return self.response(200, count=count, result=[])
|
||||
query = self.appbuilder.get_session.query(
|
||||
distinct(getattr(self.datamodel.obj, column_name))
|
||||
)
|
||||
query = db.session.query(distinct(getattr(self.datamodel.obj, column_name)))
|
||||
# Apply generic base filters with added request filter
|
||||
query = self.datamodel.apply_filters(query, filters)
|
||||
# Apply sort
|
||||
|
||||
@@ -55,10 +55,10 @@ class Model1Api(BaseSupersetModelRestApi):
|
||||
}
|
||||
|
||||
|
||||
appbuilder.add_api(Model1Api)
|
||||
|
||||
|
||||
class TestOpenApiSpec(SupersetTestCase):
|
||||
def setUp(self) -> None:
|
||||
appbuilder.add_api(Model1Api)
|
||||
|
||||
def test_open_api_spec(self):
|
||||
"""
|
||||
API: Test validate OpenAPI spec
|
||||
|
||||
@@ -29,7 +29,7 @@ from tests.integration_tests.dashboards.consts import DEFAULT_DASHBOARD_SLUG_TO_
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
session = appbuilder.get_session
|
||||
session = appbuilder.session
|
||||
|
||||
|
||||
def get_mock_positions(dashboard: Dashboard) -> dict[str, Any]:
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestDynamicPlugins(SupersetTestCase):
|
||||
Dynamic Plugins: Responds not found when disabled
|
||||
"""
|
||||
self.login(ADMIN_USERNAME)
|
||||
uri = "/dynamic-plugins/api"
|
||||
uri = "/dynamic-plugins/list/"
|
||||
rv = self.client.get(uri)
|
||||
assert rv.status_code == 404
|
||||
|
||||
@@ -36,6 +36,6 @@ class TestDynamicPlugins(SupersetTestCase):
|
||||
Dynamic Plugins: Responds successfully when enabled
|
||||
"""
|
||||
self.login(ADMIN_USERNAME)
|
||||
uri = "/dynamic-plugins/api"
|
||||
uri = "/dynamic-plugins/list/"
|
||||
rv = self.client.get(uri)
|
||||
assert rv.status_code == 200
|
||||
|
||||
@@ -49,9 +49,9 @@ def create_user_and_group(
|
||||
|
||||
|
||||
def cleanup(user, group):
|
||||
security_manager.get_session.delete(user)
|
||||
security_manager.get_session.delete(group)
|
||||
security_manager.get_session.commit()
|
||||
security_manager.session.delete(user)
|
||||
security_manager.session.delete(group)
|
||||
security_manager.session.commit()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"""Unit tests for Superset"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from unittest import mock
|
||||
import random
|
||||
import string
|
||||
|
||||
@@ -453,21 +452,13 @@ class TestQueryApi(SupersetTestCase):
|
||||
db.session.delete(updated_query)
|
||||
db.session.commit()
|
||||
|
||||
@mock.patch("superset.sql_lab.cancel_query")
|
||||
@mock.patch("superset.views.core.db.session")
|
||||
def test_stop_query_not_found(
|
||||
self, mock_superset_db_session, mock_sql_lab_cancel_query
|
||||
):
|
||||
def test_stop_query_not_found(self):
|
||||
"""
|
||||
Handles stop query when the DB engine spec does not
|
||||
have a cancel query method (with invalid client_id).
|
||||
"""
|
||||
form_data = {"client_id": "foo2"}
|
||||
query_mock = mock.Mock()
|
||||
query_mock.return_value = None
|
||||
self.login(ADMIN_USERNAME)
|
||||
mock_superset_db_session.query().filter_by().one_or_none = query_mock
|
||||
mock_sql_lab_cancel_query.return_value = True
|
||||
rv = self.client.post(
|
||||
"/api/v1/query/stop",
|
||||
data=json.dumps(form_data),
|
||||
@@ -478,22 +469,25 @@ class TestQueryApi(SupersetTestCase):
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert data["message"] == "Query with client_id foo2 not found"
|
||||
|
||||
@mock.patch("superset.sql_lab.cancel_query")
|
||||
@mock.patch("superset.views.core.db.session")
|
||||
def test_stop_query(self, mock_superset_db_session, mock_sql_lab_cancel_query):
|
||||
# @mock.patch("superset.sql_lab.cancel_query")
|
||||
def test_stop_query(self):
|
||||
"""
|
||||
Handles stop query when the DB engine spec does not
|
||||
have a cancel query method.
|
||||
"""
|
||||
form_data = {"client_id": "foo"}
|
||||
query_mock = mock.Mock()
|
||||
query_mock.client_id = "foo"
|
||||
query_mock.status = QueryStatus.RUNNING
|
||||
self.login(ADMIN_USERNAME)
|
||||
mock_superset_db_session.query().filter_by().one_or_none().return_value = (
|
||||
query_mock
|
||||
admin = self.get_user("admin")
|
||||
example_db = get_example_database()
|
||||
query1 = self.insert_query(
|
||||
example_db.id,
|
||||
admin.id,
|
||||
form_data["client_id"],
|
||||
sql="SELECT col1, col2 from table1",
|
||||
select_sql="SELECT col1, col2 from table1",
|
||||
executed_sql="SELECT col1, col2 from table1 LIMIT 100",
|
||||
changed_on=datetime.utcnow() - timedelta(days=1),
|
||||
)
|
||||
mock_sql_lab_cancel_query.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
rv = self.client.post(
|
||||
"/api/v1/query/stop",
|
||||
data=json.dumps(form_data),
|
||||
@@ -503,3 +497,5 @@ class TestQueryApi(SupersetTestCase):
|
||||
assert rv.status_code == 200
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert data["result"] == "OK"
|
||||
db.session.delete(query1)
|
||||
db.session.commit()
|
||||
|
||||
@@ -1959,13 +1959,11 @@ class TestSecurityManager(SupersetTestCase):
|
||||
|
||||
class TestDatasources(SupersetTestCase):
|
||||
@patch("superset.security.SupersetSecurityManager.can_access_database")
|
||||
@patch("superset.security.SupersetSecurityManager.get_session")
|
||||
def test_get_user_datasources_admin(
|
||||
self, mock_get_session, mock_can_access_database
|
||||
):
|
||||
@patch("superset.security.SupersetSecurityManager.session")
|
||||
def test_get_user_datasources_admin(self, mock_session, mock_can_access_database):
|
||||
Datasource = namedtuple("Datasource", ["database", "schema", "name"])
|
||||
mock_can_access_database.return_value = True
|
||||
mock_get_session.query.return_value.filter.return_value.all.return_value = []
|
||||
mock_session.query.return_value.filter.return_value.all.return_value = []
|
||||
|
||||
with mock.patch.object(
|
||||
SqlaTable, "get_all_datasources"
|
||||
@@ -1984,13 +1982,11 @@ class TestDatasources(SupersetTestCase):
|
||||
]
|
||||
|
||||
@patch("superset.security.SupersetSecurityManager.can_access_database")
|
||||
@patch("superset.security.SupersetSecurityManager.get_session")
|
||||
def test_get_user_datasources_gamma(
|
||||
self, mock_get_session, mock_can_access_database
|
||||
):
|
||||
@patch("superset.security.SupersetSecurityManager.session")
|
||||
def test_get_user_datasources_gamma(self, mock_session, mock_can_access_database):
|
||||
Datasource = namedtuple("Datasource", ["database", "schema", "name"])
|
||||
mock_can_access_database.return_value = False
|
||||
mock_get_session.query.return_value.filter.return_value.all.return_value = []
|
||||
mock_session.query.return_value.filter.return_value.all.return_value = []
|
||||
|
||||
with mock.patch.object(
|
||||
SqlaTable, "get_all_datasources"
|
||||
@@ -2005,14 +2001,14 @@ class TestDatasources(SupersetTestCase):
|
||||
assert datasources == []
|
||||
|
||||
@patch("superset.security.SupersetSecurityManager.can_access_database")
|
||||
@patch("superset.security.SupersetSecurityManager.get_session")
|
||||
@patch("superset.security.SupersetSecurityManager.session")
|
||||
def test_get_user_datasources_gamma_with_schema(
|
||||
self, mock_get_session, mock_can_access_database
|
||||
self, mock_session, mock_can_access_database
|
||||
):
|
||||
Datasource = namedtuple("Datasource", ["database", "schema", "name"])
|
||||
mock_can_access_database.return_value = False
|
||||
|
||||
mock_get_session.query.return_value.filter.return_value.all.return_value = [
|
||||
mock_session.query.return_value.filter.return_value.all.return_value = [
|
||||
Datasource("database1", "schema1", "table1"),
|
||||
Datasource("database1", "schema1", "table2"),
|
||||
]
|
||||
|
||||
@@ -54,7 +54,7 @@ def get_session(mocker: MockerFixture) -> Callable[[], Session]:
|
||||
|
||||
# patch session
|
||||
get_session = mocker.patch(
|
||||
"superset.security.SupersetSecurityManager.get_session",
|
||||
"superset.security.SupersetSecurityManager.session",
|
||||
)
|
||||
get_session.return_value = in_memory_session
|
||||
# FAB calls get_session.get_bind() to get a handler to the engine
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_filter_by_uuid(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -135,7 +135,7 @@ def test_password_mask(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -191,7 +191,7 @@ def test_database_connection(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -355,7 +355,7 @@ def test_update_with_password_mask(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -502,7 +502,7 @@ def test_delete_ssh_tunnel(
|
||||
from superset.databases.ssh_tunnel.models import SSHTunnel
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -580,7 +580,7 @@ def test_delete_ssh_tunnel_not_found(
|
||||
from superset.databases.ssh_tunnel.models import SSHTunnel
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -658,7 +658,7 @@ def test_apply_dynamic_database_filter(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -753,7 +753,7 @@ def test_oauth2_happy_path(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database, DatabaseUserOAuth2Tokens
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -822,7 +822,7 @@ def test_oauth2_permissions(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database, DatabaseUserOAuth2Tokens
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
@@ -888,7 +888,7 @@ def test_oauth2_multiple_tokens(
|
||||
from superset.databases.api import DatabaseRestApi
|
||||
from superset.models.core import Database, DatabaseUserOAuth2Tokens
|
||||
|
||||
DatabaseRestApi.datamodel.session = session
|
||||
DatabaseRestApi.datamodel._session = session
|
||||
|
||||
# create table for databases
|
||||
Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member
|
||||
|
||||
@@ -450,7 +450,7 @@ def test_raise_for_access_chart_for_datasource_permission(
|
||||
when the user does not have access to the chart datasource
|
||||
"""
|
||||
sm = SupersetSecurityManager(appbuilder)
|
||||
session = sm.get_session
|
||||
session = sm.session
|
||||
|
||||
engine = session.get_bind()
|
||||
Slice.metadata.create_all(engine) # pylint: disable=no-member
|
||||
@@ -510,7 +510,7 @@ def test_raise_for_access_chart_on_admin(
|
||||
from superset.utils.core import override_user
|
||||
|
||||
sm = SupersetSecurityManager(appbuilder)
|
||||
session = sm.get_session
|
||||
session = sm.session
|
||||
|
||||
engine = session.get_bind()
|
||||
Slice.metadata.create_all(engine) # pylint: disable=no-member
|
||||
@@ -547,7 +547,7 @@ def test_raise_for_access_chart_owner(
|
||||
when the user does not have access to the chart datasource
|
||||
"""
|
||||
sm = SupersetSecurityManager(appbuilder)
|
||||
session = sm.get_session
|
||||
session = sm.session
|
||||
|
||||
engine = session.get_bind()
|
||||
Slice.metadata.create_all(engine) # pylint: disable=no-member
|
||||
|
||||
Reference in New Issue
Block a user