chore: remove deprecated apis and ENABLE_BROAD_ACTIVITY_ACCESS (#24400)

This commit is contained in:
Daniel Vaz Gaspar 2023-06-15 22:11:24 +01:00 committed by GitHub
parent dc042c6c3d
commit 23bb1c48a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 170 additions and 698 deletions

View File

@ -61,30 +61,21 @@
|can my queries on SqlLab|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|can log on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can schemas access for csv upload on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can user slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can favstar on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can import dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can schemas on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can sqllab history on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|can publish on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can csv on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|can fave dashboards by username on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can slice on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can sync druid source on Superset|:heavy_check_mark:|O|O|O|
|can explore on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can fave slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can slice json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can approve on Superset|:heavy_check_mark:|O|O|O|
|can explore json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can fetch datasource metadata on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can override role permissions on Superset|:heavy_check_mark:|O|O|O|
|can created dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can csrf token on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can created slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can annotation json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can fave dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can sqllab on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|can recent activity on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can select star on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can warm up cache on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|can sqllab table viz on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|

View File

@ -34,6 +34,7 @@ assists people when migrating to a new version.
### Breaking Changes
- [24400](https://github.com/apache/superset/pull/24400): Removed deprecated APIs `/superset/recent_activity/...`, `/superset/fave_dashboards_by_username/...`, `/superset/fave_dashboards/...`, `/superset/created_dashboards/...`, `/superset/user_slices/`, `/superset/created_slices/...`, `/superset/fave_slices/...`, `/superset/favstar/...`,
- [24401](https://github.com/apache/superset/pull/24401): Removes the deprecated `metrics` column (which was blossomed in [20732](https://github.com/apache/superset/pull/20732)) from the `/api/v1/dataset/` API.
- [24375](https://github.com/apache/superset/pull/24375): Removed deprecated API `/superset/get_or_create_table/...`, `/superset/sqllab_viz`
- [24360](https://github.com/apache/superset/pull/24360): Removed deprecated APIs `/superset/stop_query/...`, `/superset/queries/...`, `/superset/search_queries`

View File

@ -40,7 +40,6 @@ export enum FeatureFlag {
EMBEDDABLE_CHARTS = 'EMBEDDABLE_CHARTS',
EMBEDDED_SUPERSET = 'EMBEDDED_SUPERSET',
ENABLE_ADVANCED_DATA_TYPES = 'ENABLE_ADVANCED_DATA_TYPES',
ENABLE_BROAD_ACTIVITY_ACCESS = 'ENABLE_BROAD_ACTIVITY_ACCESS',
ENABLE_EXPLORE_DRAG_AND_DROP = 'ENABLE_EXPLORE_DRAG_AND_DROP',
ENABLE_JAVASCRIPT_CONTROLS = 'ENABLE_JAVASCRIPT_CONTROLS',
ENABLE_TEMPLATE_PROCESSING = 'ENABLE_TEMPLATE_PROCESSING',

View File

@ -710,7 +710,6 @@ export const testQuery: ISaveableDatasource = {
export const mockdatasets = [...new Array(3)].map((_, i) => ({
changed_by_name: 'user',
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
changed_by_url: 'changed_by_url',
changed_by: 'user',
changed_on: new Date().toISOString(),
database_name: `db ${i}`,

View File

@ -99,7 +99,6 @@ const dashboardInfo = {
certification_details: 'Sample certification',
changed_by: null,
changed_by_name: '',
changed_by_url: '',
changed_on: '2021-03-30T19:30:14.020942',
charts: [
'Vaccine Candidates per Country & Stage',

View File

@ -231,9 +231,6 @@ function ChartList(props: ChartListProps) {
const canExport =
hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
const enableBroadUserAccess = isFeatureEnabled(
FeatureFlag.ENABLE_BROAD_ACTIVITY_ACCESS,
);
const handleBulkChartExport = (chartsToExport: Chart[]) => {
const ids = chartsToExport.map(({ id }) => id);
handleResourceExport('chart', ids, () => {
@ -415,17 +412,9 @@ function ChartList(props: ChartListProps) {
{
Cell: ({
row: {
original: {
last_saved_by: lastSavedBy,
changed_by_url: changedByUrl,
},
original: { last_saved_by: lastSavedBy },
},
}: any) =>
enableBroadUserAccess ? (
<a href={changedByUrl}>{changedByName(lastSavedBy)}</a>
) : (
<>{changedByName(lastSavedBy)}</>
),
}: any) => <>{changedByName(lastSavedBy)}</>,
Header: t('Modified by'),
accessor: 'last_saved_by.first_name',
size: 'xl',

View File

@ -58,7 +58,6 @@ const mockDashboards = [...new Array(3)].map((_, i) => ({
url: 'url',
dashboard_title: `title ${i}`,
changed_by_name: 'user',
changed_by_url: 'changed_by_url',
changed_by_fk: 1,
published: true,
changed_on_utc: new Date().toISOString(),

View File

@ -83,7 +83,6 @@ interface DashboardListProps {
interface Dashboard {
changed_by_name: string;
changed_by_url: string;
changed_on_delta_humanized: string;
changed_by: string;
dashboard_title: string;
@ -140,9 +139,6 @@ function DashboardList(props: DashboardListProps) {
const [importingDashboard, showImportModal] = useState<boolean>(false);
const [passwordFields, setPasswordFields] = useState<string[]>([]);
const [preparingExport, setPreparingExport] = useState<boolean>(false);
const enableBroadUserAccess = isFeatureEnabled(
FeatureFlag.ENABLE_BROAD_ACTIVITY_ACCESS,
);
const [sshTunnelPasswordFields, setSSHTunnelPasswordFields] = useState<
string[]
>([]);
@ -193,7 +189,6 @@ function DashboardList(props: DashboardListProps) {
if (dashboard.id === json?.result?.id) {
const {
changed_by_name,
changed_by_url,
changed_by,
dashboard_title = '',
slug = '',
@ -208,7 +203,6 @@ function DashboardList(props: DashboardListProps) {
return {
...dashboard,
changed_by_name,
changed_by_url,
changed_by,
dashboard_title,
slug,
@ -310,17 +304,9 @@ function DashboardList(props: DashboardListProps) {
{
Cell: ({
row: {
original: {
changed_by_name: changedByName,
changed_by_url: changedByUrl,
},
original: { changed_by_name: changedByName },
},
}: any) =>
enableBroadUserAccess ? (
<a href={changedByUrl}>{changedByName}</a>
) : (
<>{changedByName}</>
),
}: any) => <>{changedByName}</>,
Header: t('Modified by'),
accessor: 'changed_by.first_name',
size: 'xl',

View File

@ -50,7 +50,6 @@ const datasetsEndpoint = 'glob:*/api/v1/dataset/?*';
const mockdatasets = [...new Array(3)].map((_, i) => ({
changed_by_name: 'user',
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
changed_by_url: 'changed_by_url',
changed_by: 'user',
changed_on: new Date().toISOString(),
database_name: `db ${i}`,

View File

@ -109,7 +109,6 @@ const Actions = styled.div`
type Dataset = {
changed_by_name: string;
changed_by_url: string;
changed_by: string;
changed_on_delta_humanized: string;
database: {

View File

@ -33,7 +33,7 @@ export default class Favorites extends React.PureComponent<FavoritesProps> {
const mutator = (payload: { result: Chart[] }) =>
payload.result.map(slice => ({
slice: <a href={slice.slice_url}>{slice.slice_name}</a>,
creator: <a href={slice.created_by_url}>{slice.created_by_name}</a>,
creator: slice.created_by_name,
favorited: moment.utc(slice.changed_on_dttm).fromNow(),
_favorited: slice.changed_on_dttm,
}));

View File

@ -31,7 +31,6 @@ export type Chart = {
slice_name: string;
slice_url: string;
created_by_name?: string;
created_by_url?: string;
changed_on_dttm: number;
};

View File

@ -20,7 +20,6 @@ import Owner from './Owner';
export default interface Dataset {
changed_by_name: string;
changed_by_url: string;
changed_by: string;
changed_on_delta_humanized: string;
database: {

View File

@ -55,7 +55,6 @@ export interface Dashboard {
certified_by?: string;
certification_details?: string;
changed_by_name: string;
changed_by_url: string;
changed_on_delta_humanized?: string;
changed_on_utc?: string;
changed_by: string;

View File

@ -157,7 +157,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
"changed_by.first_name",
"changed_by.last_name",
"changed_by_name",
"changed_by_url",
"changed_on_delta_humanized",
"changed_on_dttm",
"changed_on_utc",
@ -165,7 +164,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
"created_by.id",
"created_by.last_name",
"created_by_name",
"created_by_url",
"created_on_delta_humanized",
"datasource_id",
"datasource_name_text",

View File

@ -476,7 +476,6 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
"AVOID_COLORS_COLLISION": True,
# Set to False to only allow viewing own recent activity
# or to disallow users from viewing other users profile page
"ENABLE_BROAD_ACTIVITY_ACCESS": False,
# Do not show user info or profile in the menu
"MENU_HIDE_USER_INFO": False,
}

View File

@ -599,14 +599,6 @@ class SqlaTable(
return ""
return str(self.changed_by)
@property
def changed_by_url(self) -> str:
if not self.changed_by or not is_feature_enabled(
"ENABLE_BROAD_ACTIVITY_ACCESS"
):
return ""
return f"/superset/profile/{self.changed_by.username}"
@property
def connection(self) -> str:
return str(self.database)

View File

@ -174,7 +174,6 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"changed_by.last_name",
"changed_by.id",
"changed_by_name",
"changed_by_url",
"changed_on_utc",
"changed_on_delta_humanized",
"created_on_delta_humanized",

View File

@ -194,7 +194,6 @@ class DashboardGetResponseSchema(Schema):
metadata={"description": certification_details_description}
)
changed_by_name = fields.String()
changed_by_url = fields.String()
changed_by = fields.Nested(UserSchema(exclude=(["username"])))
changed_on = fields.DateTime()
charts = fields.List(fields.String(metadata={"description": charts_description}))

View File

@ -101,7 +101,6 @@ class DatasetRestApi(BaseSupersetModelRestApi):
"database.id",
"database.database_name",
"changed_by_name",
"changed_by_url",
"changed_by.first_name",
"changed_by.last_name",
"changed_on_utc",

View File

@ -268,14 +268,6 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
return ""
return str(self.changed_by)
@property
def changed_by_url(self) -> str:
if not self.changed_by or not is_feature_enabled(
"ENABLE_BROAD_ACTIVITY_ACCESS"
):
return ""
return f"/superset/profile/{self.changed_by.username}"
@property
def data(self) -> dict[str, Any]:
positions = self.position_json

View File

@ -25,7 +25,7 @@ from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, Text
from sqlalchemy.orm import relationship
from sqlalchemy_utils import generic_relationship
from superset import app, db, is_feature_enabled
from superset import app, db
from superset.models.helpers import AuditMixinNullable
metadata = Model.metadata # pylint: disable=no-member
@ -65,14 +65,6 @@ class FilterSet(Model, AuditMixinNullable):
return ""
return str(self.changed_by)
@property
def changed_by_url(self) -> str:
if not self.changed_by or not is_feature_enabled(
"ENABLE_BROAD_ACTIVITY_ACCESS"
):
return ""
return f"/superset/profile/{self.changed_by.username}"
def to_dict(self) -> dict[str, Any]:
return {
"id": self.id,

View File

@ -331,20 +331,6 @@ class Slice( # pylint: disable=too-many-public-methods
name = escape(self.chart)
return Markup(f'<a href="{self.url}">{name}</a>')
@property
def created_by_url(self) -> str:
if not self.created_by:
return ""
return f"/superset/profile/{self.created_by.username}"
@property
def changed_by_url(self) -> str:
if not self.changed_by or not is_feature_enabled(
"ENABLE_BROAD_ACTIVITY_ACCESS"
):
return ""
return f"/superset/profile/{self.changed_by.username}"
@property
def icons(self) -> str:
return f"""

View File

@ -1991,23 +1991,6 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
guest_rls = self.get_guest_rls_filters_str(datasource)
return guest_rls + rls_str
@staticmethod
def raise_for_user_activity_access(user_id: int) -> None:
# pylint: disable=import-outside-toplevel
from superset.extensions import feature_flag_manager
if not get_user_id() or (
not feature_flag_manager.is_feature_enabled("ENABLE_BROAD_ACTIVITY_ACCESS")
and user_id != get_user_id()
):
raise SupersetSecurityException(
SupersetError(
error_type=SupersetErrorType.USER_ACTIVITY_SECURITY_ACCESS_ERROR,
message="Access to user's activity data is restricted",
level=ErrorLevel.ERROR,
)
)
def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
"""
Raise an exception if the user cannot access the dashboard.

View File

@ -90,7 +90,6 @@ FRONTEND_CONF_KEYS = (
"SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE",
"DISABLE_DATASET_SOURCE_EDIT",
"ENABLE_JAVASCRIPT_CONTROLS",
"ENABLE_BROAD_ACTIVITY_ACCESS",
"DEFAULT_SQLLAB_LIMIT",
"DEFAULT_VIZ_TYPE",
"SQL_MAX_ROW",
@ -391,7 +390,7 @@ def menu_data(user: User) -> dict[str, Any]:
"user_login_url": appbuilder.get_url_for_login,
"user_profile_url": None
if user.is_anonymous or is_feature_enabled("MENU_HIDE_USER_INFO")
else f"/superset/profile/{user.username}",
else "/superset/profile/",
"locale": session.get("locale", "en"),
},
}

View File

@ -30,9 +30,7 @@ from flask_appbuilder.security.decorators import (
has_access_api,
permission_name,
)
from flask_appbuilder.security.sqla import models as ab_models
from flask_babel import gettext as __, lazy_gettext as _
from sqlalchemy import and_, or_
from sqlalchemy.exc import SQLAlchemyError
from superset import (
@ -57,19 +55,14 @@ from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailed
from superset.databases.dao import DatabaseDAO
from superset.datasets.commands.exceptions import DatasetNotFoundError
from superset.datasource.dao import DatasourceDAO
from superset.exceptions import (
CacheLoadError,
DatabaseNotFound,
SupersetException,
SupersetSecurityException,
)
from superset.exceptions import CacheLoadError, DatabaseNotFound, SupersetException
from superset.explore.form_data.commands.create import CreateFormDataCommand
from superset.explore.form_data.commands.get import GetFormDataCommand
from superset.explore.form_data.commands.parameters import CommandParameters
from superset.explore.permalink.commands.get import GetExplorePermalinkCommand
from superset.explore.permalink.exceptions import ExplorePermalinkGetFailedError
from superset.extensions import async_query_manager, cache_manager
from superset.models.core import Database, FavStar
from superset.models.core import Database
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
from superset.models.sql_lab import Query, TabState
@ -79,7 +72,12 @@ from superset.tasks.async_queries import load_explore_json_into_cache
from superset.utils import core as utils
from superset.utils.async_query_manager import AsyncQueryTokenException
from superset.utils.cache import etag_cache
from superset.utils.core import DatasourceType, get_user_id, ReservedUrlParameters
from superset.utils.core import (
DatasourceType,
get_user_id,
get_username,
ReservedUrlParameters,
)
from superset.views.base import (
api,
BaseSupersetView,
@ -93,7 +91,6 @@ from superset.views.base import (
json_error_response,
json_success,
)
from superset.views.log.dao import LogDAO
from superset.views.utils import (
bootstrap_user_data,
check_datasource_perms,
@ -829,237 +826,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return json_success(json.dumps(response))
@staticmethod
def get_user_activity_access_error(user_id: int) -> FlaskResponse | None:
try:
security_manager.raise_for_user_activity_access(user_id)
except SupersetSecurityException as ex:
return json_error_response(
ex.message,
status=403,
)
return None
@api
@has_access_api
@event_logger.log_this
@expose("/recent_activity/<int:user_id>/", methods=("GET",))
@deprecated(new_target="/api/v1/log/recent_activity/<user_id>/")
def recent_activity(self, user_id: int) -> FlaskResponse:
"""Recent activity (actions) for a given user"""
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
limit = request.args.get("limit")
limit = int(limit) if limit and limit.isdigit() else 100
actions = request.args.get("actions", "explore,dashboard").split(",")
# whether to get distinct subjects
distinct = request.args.get("distinct") != "false"
payload = LogDAO.get_recent_activity(user_id, actions, distinct, 0, limit)
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
@api
@has_access_api
@event_logger.log_this
@expose("/fave_dashboards_by_username/<username>/", methods=("GET",))
@deprecated(new_target="api/v1/dashboard/favorite_status/")
def fave_dashboards_by_username(self, username: str) -> FlaskResponse:
"""This lets us use a user's username to pull favourite dashboards"""
user = security_manager.find_user(username=username)
return self.fave_dashboards(user.id)
@api
@has_access_api
@event_logger.log_this
@expose("/fave_dashboards/<int:user_id>/", methods=("GET",))
@deprecated(new_target="api/v1/dashboard/favorite_status/")
def fave_dashboards(self, user_id: int) -> FlaskResponse:
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
qry = (
db.session.query(Dashboard, FavStar.dttm)
.join(
FavStar,
and_(
FavStar.user_id == int(user_id),
FavStar.class_name == "Dashboard",
Dashboard.id == FavStar.obj_id,
),
)
.order_by(FavStar.dttm.desc())
)
payload = []
for o in qry.all():
dash = {
"id": o.Dashboard.id,
"dashboard": o.Dashboard.dashboard_link(),
"title": o.Dashboard.dashboard_title,
"url": o.Dashboard.url,
"dttm": o.dttm,
}
if o.Dashboard.created_by:
user = o.Dashboard.created_by
dash["creator"] = str(user)
dash["creator_url"] = f"/superset/profile/{user.username}/"
payload.append(dash)
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
@api
@has_access_api
@event_logger.log_this
@expose("/created_dashboards/<int:user_id>/", methods=("GET",))
@deprecated(new_target="api/v1/dashboard/")
def created_dashboards(self, user_id: int) -> FlaskResponse:
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
qry = (
db.session.query(Dashboard)
.filter( # pylint: disable=comparison-with-callable
or_(
Dashboard.created_by_fk == user_id,
Dashboard.changed_by_fk == user_id,
)
)
.order_by(Dashboard.changed_on.desc())
)
payload = [
{
"id": o.id,
"dashboard": o.dashboard_link(),
"title": o.dashboard_title,
"url": o.url,
"dttm": o.changed_on,
}
for o in qry.all()
]
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
@api
@has_access_api
@event_logger.log_this
@expose("/user_slices", methods=("GET",))
@expose("/user_slices/<int:user_id>/", methods=("GET",))
@deprecated(new_target="/api/v1/chart/")
def user_slices(self, user_id: int | None = None) -> FlaskResponse:
"""List of slices a user owns, created, modified or faved"""
if not user_id:
user_id = cast(int, get_user_id())
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
owner_ids_query = (
db.session.query(Slice.id)
.join(Slice.owners)
.filter(security_manager.user_model.id == user_id)
)
qry = (
db.session.query(Slice, FavStar.dttm)
.join(
FavStar,
and_(
FavStar.user_id == user_id,
FavStar.class_name == "slice",
Slice.id == FavStar.obj_id,
),
isouter=True,
)
.filter( # pylint: disable=comparison-with-callable
or_(
Slice.id.in_(owner_ids_query),
Slice.created_by_fk == user_id,
Slice.changed_by_fk == user_id,
FavStar.user_id == user_id,
)
)
.order_by(Slice.slice_name.asc())
)
payload = [
{
"id": o.Slice.id,
"title": o.Slice.slice_name,
"url": o.Slice.slice_url,
"data": o.Slice.form_data,
"dttm": o.dttm if o.dttm else o.Slice.changed_on,
"viz_type": o.Slice.viz_type,
}
for o in qry.all()
]
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
@api
@has_access_api
@event_logger.log_this
@expose("/created_slices", methods=("GET",))
@expose("/created_slices/<int:user_id>/", methods=("GET",))
@deprecated(new_target="api/v1/chart/")
def created_slices(self, user_id: int | None = None) -> FlaskResponse:
"""List of slices created by this user"""
if not user_id:
user_id = cast(int, get_user_id())
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
qry = (
db.session.query(Slice)
.filter( # pylint: disable=comparison-with-callable
or_(Slice.created_by_fk == user_id, Slice.changed_by_fk == user_id)
)
.order_by(Slice.changed_on.desc())
)
payload = [
{
"id": o.id,
"title": o.slice_name,
"url": o.slice_url,
"dttm": o.changed_on,
"viz_type": o.viz_type,
}
for o in qry.all()
]
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
@api
@has_access_api
@event_logger.log_this
@expose("/fave_slices", methods=("GET",))
@expose("/fave_slices/<int:user_id>/", methods=("GET",))
@deprecated(new_target="api/v1/chart/")
def fave_slices(self, user_id: int | None = None) -> FlaskResponse:
"""Favorite slices for a user"""
if user_id is None:
user_id = cast(int, get_user_id())
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
qry = (
db.session.query(Slice, FavStar.dttm)
.join(
FavStar,
and_(
FavStar.user_id == user_id,
FavStar.class_name == "slice",
Slice.id == FavStar.obj_id,
),
)
.order_by(FavStar.dttm.desc())
)
payload = []
for o in qry.all():
dash = {
"id": o.Slice.id,
"title": o.Slice.slice_name,
"url": o.Slice.slice_url,
"dttm": o.dttm,
"viz_type": o.Slice.viz_type,
}
if o.Slice.created_by:
user = o.Slice.created_by
dash["creator"] = str(user)
dash["creator_url"] = f"/superset/profile/{user.username}/"
payload.append(dash)
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
@event_logger.log_this
@api
@has_access_api
@ -1158,42 +924,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return json_success(json.dumps(result))
@has_access_api
@event_logger.log_this
@expose("/favstar/<class_name>/<int:obj_id>/<action>/")
@deprecated(new_target="api/v1/dashboard|chart/<pk>/favorites/")
def favstar( # pylint: disable=no-self-use
self, class_name: str, obj_id: int, action: str
) -> FlaskResponse:
"""Toggle favorite stars on Slices and Dashboard"""
if not get_user_id():
return json_error_response("ERROR: Favstar toggling denied", status=403)
session = db.session()
count = 0
favs = (
session.query(FavStar)
.filter_by(class_name=class_name, obj_id=obj_id, user_id=get_user_id())
.all()
)
if action == "select":
if not favs:
session.add(
FavStar(
class_name=class_name,
obj_id=obj_id,
user_id=get_user_id(),
dttm=datetime.now(),
)
)
count = 1
elif action == "unselect":
for fav in favs:
session.delete(fav)
else:
count = len(favs)
session.commit()
return json_success(json.dumps({"count": count}))
@has_access
@expose("/dashboard/<dashboard_id_or_slug>/")
@event_logger.log_this_with_extra_payload
@ -1279,23 +1009,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
def log(self) -> FlaskResponse: # pylint: disable=no-self-use
return Response(status=200)
@has_access
@expose("/extra_table_metadata/<int:database_id>/<table_name>/<schema>/")
@event_logger.log_this
@deprecated(
new_target="api/v1/database/<int:pk>/table_extra/<table_name>/<schema_name>/"
)
def extra_table_metadata( # pylint: disable=no-self-use
self, database_id: int, table_name: str, schema: str
) -> FlaskResponse:
parsed_schema = utils.parse_js_uri_path_item(schema, eval_undefined=True)
table_name = utils.parse_js_uri_path_item(table_name) # type: ignore
mydb = db.session.query(Database).filter_by(id=database_id).one()
payload = mydb.db_engine_spec.extra_table_metadata(
mydb, table_name, parsed_schema
)
return json_success(json.dumps(payload))
@expose("/theme/")
def theme(self) -> FlaskResponse:
return self.render_template("superset/theme.html")
@ -1363,27 +1076,20 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@has_access
@event_logger.log_this
@expose("/profile/<username>/")
def profile(self, username: str) -> FlaskResponse:
@expose("/profile/")
def profile(self) -> FlaskResponse:
"""User profile page"""
user = (
db.session.query(ab_models.User).filter_by(username=username).one_or_none()
)
# Prevent returning 404 when user is not found to prevent username scanning
user_id = -1 if not user else user.id
# Prevent unauthorized access to other user's profiles,
# unless configured to do so with ENABLE_BROAD_ACTIVITY_ACCESS
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
user = g.user if hasattr(g, "user") and g.user else None
if not user or security_manager.is_guest_user(user) or user.is_anonymous:
abort(404)
payload = {
"user": bootstrap_user_data(user, include_perms=True),
"common": common_bootstrap_payload(g.user),
"common": common_bootstrap_payload(user),
}
return self.render_template(
"superset/basic.html",
title=_("%(user)s's profile", user=username).__str__(),
title=_("%(user)s's profile", user=get_username()).__str__(),
entry="profile",
bootstrap_data=json.dumps(
payload, default=utils.pessimistic_json_iso_dttm_ser

View File

@ -23,9 +23,11 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
import superset.models.core as models
from superset import event_logger, security_manager
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP
from superset.exceptions import SupersetSecurityException
from superset.superset_typing import FlaskResponse
from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics
from superset.views.log import LogMixin
from superset.views.log.dao import LogDAO
from superset.views.log.schemas import (
get_recent_activity_schema,
@ -33,9 +35,6 @@ from superset.views.log.schemas import (
RecentActivitySchema,
)
from ...constants import MODEL_API_RW_METHOD_PERMISSION_MAP
from . import LogMixin
class LogRestApi(LogMixin, BaseSupersetModelRestApi):
datamodel = SQLAInterface(models.Log)
@ -82,7 +81,7 @@ class LogRestApi(LogMixin, BaseSupersetModelRestApi):
return self.response(403, message=ex.message)
return None
@expose("/recent_activity/<int:user_id>/", methods=("GET",))
@expose("/recent_activity/", methods=("GET",))
@protect()
@safe
@statsd_metrics
@ -92,7 +91,7 @@ class LogRestApi(LogMixin, BaseSupersetModelRestApi):
f".recent_activity",
log_to_statsd=False,
)
def recent_activity(self, user_id: int, **kwargs: Any) -> FlaskResponse:
def recent_activity(self, **kwargs: Any) -> FlaskResponse:
"""Get recent activity data for a user
---
get:
@ -125,16 +124,11 @@ class LogRestApi(LogMixin, BaseSupersetModelRestApi):
500:
$ref: '#/components/responses/500'
"""
if error_obj := self.get_user_activity_access_error(user_id):
return error_obj
args = kwargs["rison"]
page, page_size = self._sanitize_page_args(*self._handle_page_args(args))
actions = args.get("actions", ["explore", "dashboard"])
distinct = args.get("distinct", True)
payload = LogDAO.get_recent_activity(
user_id, actions, distinct, page, page_size
)
payload = LogDAO.get_recent_activity(actions, distinct, page, page_size)
return self.response(200, result=payload)

View File

@ -26,6 +26,7 @@ from superset.dao.base import BaseDAO
from superset.models.core import Log
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
from superset.utils.core import get_user_id
from superset.utils.dates import datetime_to_epoch
@ -34,8 +35,12 @@ class LogDAO(BaseDAO):
@staticmethod
def get_recent_activity(
user_id: int, actions: list[str], distinct: bool, page: int, page_size: int
actions: list[str],
distinct: bool,
page: int,
page_size: int,
) -> list[dict[str, Any]]:
user_id = get_user_id()
has_subject_title = or_(
and_(
Dashboard.dashboard_title is not None,

View File

@ -605,54 +605,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
db.session.delete(model)
db.session.commit()
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
def test_chart_activity_access_disabled(self):
"""
Chart API: Test ENABLE_BROAD_ACTIVITY_ACCESS = False
"""
admin = self.get_user("admin")
birth_names_table_id = SupersetTestCase.get_table(name="birth_names").id
chart_id = self.insert_chart("title", [admin.id], birth_names_table_id).id
chart_data = {
"slice_name": (new_name := "title1_changed"),
}
self.login(username="admin")
uri = f"api/v1/chart/{chart_id}"
rv = self.put_assert_metric(uri, chart_data, "put")
self.assertEqual(rv.status_code, 200)
model = db.session.query(Slice).get(chart_id)
self.assertEqual(model.slice_name, new_name)
self.assertEqual(model.changed_by_url, "")
db.session.delete(model)
db.session.commit()
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
def test_chart_activity_access_enabled(self):
"""
Chart API: Test ENABLE_BROAD_ACTIVITY_ACCESS = True
"""
admin = self.get_user("admin")
birth_names_table_id = SupersetTestCase.get_table(name="birth_names").id
chart_id = self.insert_chart("title", [admin.id], birth_names_table_id).id
chart_data = {
"slice_name": (new_name := "title1_changed"),
}
self.login(username="admin")
uri = f"api/v1/chart/{chart_id}"
rv = self.put_assert_metric(uri, chart_data, "put")
self.assertEqual(rv.status_code, 200)
model = db.session.query(Slice).get(chart_id)
self.assertEqual(model.slice_name, new_name)
self.assertEqual(model.changed_by_url, "/superset/profile/admin")
db.session.delete(model)
db.session.commit()
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_chart_get_list_no_username(self):
"""

View File

@ -23,8 +23,10 @@ import json
import logging
from urllib.parse import quote
import prison
import superset.utils.database
from superset.utils.core import backend
from tests.integration_tests.fixtures.public_role import public_role_like_gamma
from tests.integration_tests.fixtures.birth_names_dashboard import (
load_birth_names_dashboard_with_slices,
load_birth_names_data,
@ -47,6 +49,7 @@ from tests.integration_tests.fixtures.energy_dashboard import (
load_energy_table_with_slice,
load_energy_table_data,
)
from tests.integration_tests.insert_chart_mixin import InsertChartMixin
from tests.integration_tests.test_app import app
import superset.views.utils
from superset import (
@ -89,7 +92,7 @@ def cleanup():
yield
class TestCore(SupersetTestCase):
class TestCore(SupersetTestCase, InsertChartMixin):
def setUp(self):
self.table_ids = {
tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all())
@ -100,6 +103,50 @@ class TestCore(SupersetTestCase):
db.session.query(Query).delete()
app.config["PREVENT_UNSAFE_DB_CONNECTIONS"] = self.original_unsafe_db_setting
def insert_dashboard_created_by(self, username: str) -> Dashboard:
user = self.get_user(username)
dashboard = self.insert_dashboard(
f"create_title_test",
f"create_slug_test",
[user.id],
created_by=user,
)
return dashboard
def insert_chart_created_by(self, username: str) -> Slice:
user = self.get_user(username)
dataset = db.session.query(SqlaTable).first()
chart = self.insert_chart(
f"create_title_test",
[user.id],
dataset.id,
created_by=user,
)
return chart
@pytest.fixture()
def insert_dashboard_created_by_admin(self):
with self.create_app().app_context():
dashboard = self.insert_dashboard_created_by("admin")
yield dashboard
db.session.delete(dashboard)
db.session.commit()
@pytest.fixture()
def insert_dashboard_created_by_gamma(self):
dashboard = self.insert_dashboard_created_by("gamma")
yield dashboard
db.session.delete(dashboard)
db.session.commit()
@pytest.fixture()
def insert_chart_created_by_admin(self):
with self.create_app().app_context():
chart = self.insert_chart_created_by("admin")
yield chart
db.session.delete(chart)
db.session.commit()
def test_login(self):
resp = self.get_resp("/login/", data=dict(username="admin", password="general"))
self.assertNotIn("User confirmation needed", resp)
@ -262,43 +309,6 @@ class TestCore(SupersetTestCase):
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_get_user_slices_for_owners(self):
self.login(username="alpha")
user = security_manager.find_user("alpha")
slice_name = "Girls"
# ensure user is not owner of any slices
url = f"/superset/user_slices/{user.id}/"
resp = self.client.get(url)
data = json.loads(resp.data)
self.assertEqual(data, [])
# make user owner of slice and verify that endpoint returns said slice
slc = self.get_slice(
slice_name=slice_name, session=db.session, expunge_from_session=False
)
slc.owners = [user]
db.session.merge(slc)
db.session.commit()
url = f"/superset/user_slices/{user.id}/"
resp = self.client.get(url)
data = json.loads(resp.data)
self.assertEqual(len(data), 1)
self.assertEqual(data[0]["title"], slice_name)
# remove ownership and ensure user no longer gets slice
slc = self.get_slice(
slice_name=slice_name, session=db.session, expunge_from_session=False
)
slc.owners = []
db.session.merge(slc)
db.session.commit()
url = f"/superset/user_slices/{user.id}/"
resp = self.client.get(url)
data = json.loads(resp.data)
self.assertEqual(data, [])
def test_get_user_slices(self):
self.login(username="admin")
userid = security_manager.find_user("admin").id
@ -483,71 +493,99 @@ class TestCore(SupersetTestCase):
for k in keys:
self.assertIn(k, resp.keys())
@staticmethod
def _get_user_activity_endpoints(user: str):
userid = security_manager.find_user(user).id
return (
f"/superset/recent_activity/{userid}/",
f"/superset/created_slices/{userid}/",
f"/superset/created_dashboards/{userid}/",
f"/superset/fave_slices/{userid}/",
f"/superset/fave_dashboards/{userid}/",
f"/superset/user_slices/{userid}/",
f"/superset/fave_dashboards_by_username/{user}/",
)
@pytest.mark.usefixtures("insert_dashboard_created_by_admin")
@pytest.mark.usefixtures("insert_chart_created_by_admin")
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_user_profile(self, username="admin"):
self.login(username=username)
slc = self.get_slice("Girls", db.session)
dashboard = db.session.query(Dashboard).filter_by(slug="births").first()
# Set a favorite dashboard
self.client.post(f"/api/v1/dashboard/{dashboard.id}/favorites/", json={})
# Set a favorite chart
self.client.post(f"/api/v1/chart/{slc.id}/favorites/", json={})
# Setting some faves
url = f"/superset/favstar/Slice/{slc.id}/select/"
resp = self.get_json_resp(url)
self.assertEqual(resp["count"], 1)
# Get favorite dashboards:
request_query = {
"columns": ["created_on_delta_humanized", "dashboard_title", "url"],
"filters": [{"col": "id", "opr": "dashboard_is_favorite", "value": True}],
"keys": ["none"],
"order_column": "changed_on",
"order_direction": "desc",
"page": 0,
"page_size": 100,
}
url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
resp = self.client.get(url)
assert resp.json["count"] == 1
assert resp.json["result"][0]["dashboard_title"] == "USA Births Names"
dash = db.session.query(Dashboard).filter_by(slug="births").first()
url = f"/superset/favstar/Dashboard/{dash.id}/select/"
resp = self.get_json_resp(url)
self.assertEqual(resp["count"], 1)
# Get Favorite Charts
request_query = {
"filters": [{"col": "id", "opr": "chart_is_favorite", "value": True}],
"order_column": "slice_name",
"order_direction": "asc",
"page": 0,
"page_size": 25,
}
url = f"api/v1/chart/?q={prison.dumps(request_query)}"
resp = self.client.get(url)
assert resp.json["count"] == 1
assert resp.json["result"][0]["id"] == slc.id
resp = self.get_resp(f"/superset/profile/{username}/")
# Get recent activity
url = "/api/v1/log/recent_activity/?q=(page_size:50)"
resp = self.client.get(url)
# TODO data for recent activity varies for sqlite, we should be able to assert
# the returned data
assert resp.status_code == 200
# Get dashboards created by the user
request_query = {
"columns": ["created_on_delta_humanized", "dashboard_title", "url"],
"filters": [
{"col": "created_by", "opr": "dashboard_created_by_me", "value": "me"}
],
"keys": ["none"],
"order_column": "changed_on",
"order_direction": "desc",
"page": 0,
"page_size": 100,
}
url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
resp = self.client.get(url)
assert resp.json["result"][0]["dashboard_title"] == "create_title_test"
# Get charts created by the user
request_query = {
"columns": ["created_on_delta_humanized", "slice_name", "url"],
"filters": [
{"col": "created_by", "opr": "chart_created_by_me", "value": "me"}
],
"keys": ["none"],
"order_column": "changed_on_delta_humanized",
"order_direction": "desc",
"page": 0,
"page_size": 100,
}
url = f"/api/v1/chart/?q={prison.dumps(request_query)}"
resp = self.client.get(url)
assert resp.json["count"] == 1
assert resp.json["result"][0]["slice_name"] == "create_title_test"
resp = self.get_resp(f"/superset/profile/")
self.assertIn('"app"', resp)
for endpoint in self._get_user_activity_endpoints(username):
data = self.get_json_resp(endpoint)
self.assertNotIn("message", data)
def test_user_profile_default_access(self):
def test_user_profile_gamma(self):
self.login(username="gamma")
resp = self.client.get(f"/superset/profile/admin/")
self.assertEqual(resp.status_code, 403)
resp = self.get_resp(f"/superset/profile/")
self.assertIn('"app"', resp)
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
def test_user_profile_broad_access(self):
self.login(username="gamma")
resp = self.client.get(f"/superset/profile/admin/")
self.assertEqual(resp.status_code, 200)
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_user_activity_default_access(self, username="gamma"):
self.login(username=username)
for user in ("admin", "gamma"):
for endpoint in self._get_user_activity_endpoints(user):
resp = self.client.get(endpoint)
expected_status_code = 200 if user == username else 403
assert resp.status_code == expected_status_code
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
def test_user_activity_broad_access(self, username="gamma"):
self.login(username=username)
for user in ("admin", "gamma"):
for endpoint in self._get_user_activity_endpoints(user):
resp = self.client.get(endpoint)
assert resp.status_code == 200
@pytest.mark.usefixtures("public_role_like_gamma")
def test_user_profile_anonymous(self):
self.logout()
resp = self.client.get("/superset/profile/")
assert resp.status_code == 404
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_slice_id_is_always_logged_correctly_on_web_request(self):
@ -1025,7 +1063,7 @@ class TestCore(SupersetTestCase):
"/superset/sqllab",
"/superset/welcome",
f"/superset/dashboard/{dash_id}/",
"/superset/profile/admin/",
"/superset/profile/",
f"/explore/?datasource_type=table&datasource_id={tbl_id}",
]
for url in urls:

View File

@ -368,7 +368,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
"certification_details": None,
"changed_by": None,
"changed_by_name": "",
"changed_by_url": "",
"charts": [],
"created_by": {
"id": 1,
@ -1326,52 +1325,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
db.session.delete(model)
db.session.commit()
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
def test_dashboard_activity_access_disabled(self):
"""
Dashboard API: Test ENABLE_BROAD_ACTIVITY_ACCESS = False
"""
admin = self.get_user("admin")
admin_role = self.get_role("Admin")
dashboard_id = self.insert_dashboard(
"title1", "slug1", [admin.id], roles=[admin_role.id]
).id
self.login(username="admin")
uri = f"api/v1/dashboard/{dashboard_id}"
dashboard_data = {"dashboard_title": "title2"}
rv = self.client.put(uri, json=dashboard_data)
self.assertEqual(rv.status_code, 200)
model = db.session.query(Dashboard).get(dashboard_id)
self.assertEqual(model.dashboard_title, "title2")
self.assertEqual(model.changed_by_url, "")
db.session.delete(model)
db.session.commit()
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
def test_dashboard_activity_access_enabled(self):
"""
Dashboard API: Test ENABLE_BROAD_ACTIVITY_ACCESS = True
"""
admin = self.get_user("admin")
admin_role = self.get_role("Admin")
dashboard_id = self.insert_dashboard(
"title1", "slug1", [admin.id], roles=[admin_role.id]
).id
self.login(username="admin")
uri = f"api/v1/dashboard/{dashboard_id}"
dashboard_data = {"dashboard_title": "title2"}
rv = self.client.put(uri, json=dashboard_data)
self.assertEqual(rv.status_code, 200)
model = db.session.query(Dashboard).get(dashboard_id)
self.assertEqual(model.dashboard_title, "title2")
self.assertEqual(model.changed_by_url, "/superset/profile/admin")
db.session.delete(model)
db.session.commit()
def test_dashboard_get_list_no_username(self):
"""
Dashboard API: Tests that no username is returned

View File

@ -207,7 +207,6 @@ class TestDatasetApi(SupersetTestCase):
expected_columns = [
"changed_by",
"changed_by_name",
"changed_by_url",
"changed_on_delta_humanized",
"changed_on_utc",
"database",
@ -1358,56 +1357,6 @@ class TestDatasetApi(SupersetTestCase):
db.session.delete(dataset)
db.session.commit()
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
def test_dataset_activity_access_enabled(self):
"""
Dataset API: Test ENABLE_BROAD_ACTIVITY_ACCESS = True
"""
if backend() == "sqlite":
return
dataset = self.insert_default_dataset()
self.login(username="admin")
table_data = {"description": "changed_description"}
uri = f"api/v1/dataset/{dataset.id}"
rv = self.client.put(uri, json=table_data)
self.assertEqual(rv.status_code, 200)
response = self.get_assert_metric("api/v1/dataset/", "get_list")
res = json.loads(response.data.decode("utf-8"))["result"]
current_dataset = [d for d in res if d["id"] == dataset.id][0]
self.assertEqual(current_dataset["description"], "changed_description")
self.assertEqual(current_dataset["changed_by_url"], "/superset/profile/admin")
db.session.delete(dataset)
db.session.commit()
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
def test_dataset_activity_access_disabled(self):
"""
Dataset API: Test ENABLE_BROAD_ACTIVITY_ACCESS = Fase
"""
if backend() == "sqlite":
return
dataset = self.insert_default_dataset()
self.login(username="admin")
table_data = {"description": "changed_description"}
uri = f"api/v1/dataset/{dataset.id}"
rv = self.put_assert_metric(uri, table_data, "put")
self.assertEqual(rv.status_code, 200)
response = self.get_assert_metric("api/v1/dataset/", "get_list")
res = json.loads(response.data.decode("utf-8"))["result"]
current_dataset = [d for d in res if d["id"] == dataset.id][0]
self.assertEqual(current_dataset["description"], "changed_description")
self.assertEqual(current_dataset["changed_by_url"], "")
db.session.delete(dataset)
db.session.commit()
def test_update_dataset_item_not_owned(self):
"""
Dataset API: Test update dataset item not owned

View File

@ -159,19 +159,6 @@ class TestLogApi(SupersetTestCase):
db.session.delete(log)
db.session.commit()
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
def test_get_recent_activity_no_broad_access(self):
"""
Log API: Test recent activity not visible for other users without
ENABLE_BROAD_ACTIVITY_ACCESS flag on
"""
admin_user = self.get_user("admin")
self.login(username="admin")
uri = f"api/v1/log/recent_activity/{admin_user.id + 1}/"
rv = self.client.get(uri)
self.assertEqual(rv.status_code, 403)
def test_get_recent_activity(self):
"""
Log API: Test recent activity endpoint
@ -182,7 +169,7 @@ class TestLogApi(SupersetTestCase):
log1 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
log2 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
uri = f"api/v1/log/recent_activity/{admin_user.id}/"
uri = f"api/v1/log/recent_activity/"
rv = self.client.get(uri)
self.assertEqual(rv.status_code, 200)
response = json.loads(rv.data.decode("utf-8"))
@ -219,7 +206,7 @@ class TestLogApi(SupersetTestCase):
log2 = self.insert_log("explore", admin_user, dashboard_id=dash.id)
arguments = {"actions": ["dashboard"]}
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
rv = self.client.get(uri)
db.session.delete(log)
@ -244,7 +231,7 @@ class TestLogApi(SupersetTestCase):
log2 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
arguments = {"distinct": False}
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
rv = self.client.get(uri)
db.session.delete(log)
@ -274,7 +261,7 @@ class TestLogApi(SupersetTestCase):
log.dttm = now - timedelta(days=2)
arguments = {"page": 0, "page_size": 2}
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
rv = self.client.get(uri)
self.assertEqual(rv.status_code, 200)
@ -304,7 +291,7 @@ class TestLogApi(SupersetTestCase):
)
arguments = {"page": 1, "page_size": 2}
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
rv = self.client.get(uri)
db.session.delete(log)

View File

@ -1350,16 +1350,12 @@ class TestRolePermission(SupersetTestCase):
# make sure that user can create slices and dashboards
self.assert_can_all("Dashboard", perm_set)
self.assert_can_all("Chart", perm_set)
self.assertIn(("can_created_dashboards", "Superset"), perm_set)
self.assertIn(("can_created_slices", "Superset"), perm_set)
self.assertIn(("can_csv", "Superset"), perm_set)
self.assertIn(("can_dashboard", "Superset"), perm_set)
self.assertIn(("can_explore", "Superset"), perm_set)
self.assertIn(("can_share_chart", "Superset"), perm_set)
self.assertIn(("can_share_dashboard", "Superset"), perm_set)
self.assertIn(("can_explore_json", "Superset"), perm_set)
self.assertIn(("can_fave_dashboards", "Superset"), perm_set)
self.assertIn(("can_fave_slices", "Superset"), perm_set)
self.assertIn(("can_explore_json", "Superset"), perm_set)
self.assertIn(("can_userinfo", "UserDBModelView"), perm_set)
self.assert_can_menu("Databases", perm_set)
@ -1525,16 +1521,12 @@ class TestRolePermission(SupersetTestCase):
self.assert_cannot_write("UserDBModelView", gamma_perm_set)
self.assert_cannot_write("RoleModelView", gamma_perm_set)
self.assertIn(("can_created_dashboards", "Superset"), gamma_perm_set)
self.assertIn(("can_created_slices", "Superset"), gamma_perm_set)
self.assertIn(("can_csv", "Superset"), gamma_perm_set)
self.assertIn(("can_dashboard", "Superset"), gamma_perm_set)
self.assertIn(("can_explore", "Superset"), gamma_perm_set)
self.assertIn(("can_share_chart", "Superset"), gamma_perm_set)
self.assertIn(("can_share_dashboard", "Superset"), gamma_perm_set)
self.assertIn(("can_explore_json", "Superset"), gamma_perm_set)
self.assertIn(("can_fave_dashboards", "Superset"), gamma_perm_set)
self.assertIn(("can_fave_slices", "Superset"), gamma_perm_set)
self.assertIn(("can_userinfo", "UserDBModelView"), gamma_perm_set)
def test_views_are_secured(self):