diff --git a/superset-frontend/src/dashboard/util/permissionUtils.test.ts b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
index a1aa8c1ca..d19b04876 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.test.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
@@ -22,7 +22,11 @@ import {
} from 'src/types/bootstrapTypes';
import { Dashboard } from 'src/types/Dashboard';
import Owner from 'src/types/Owner';
-import { canUserEditDashboard, isUserAdmin } from './permissionUtils';
+import {
+ canUserAccessSqlLab,
+ canUserEditDashboard,
+ isUserAdmin,
+} from './permissionUtils';
const ownerUser: UserWithPermissionsAndRoles = {
createdOn: '2021-05-12T16:56:22.116839',
@@ -60,6 +64,14 @@ const owner: Owner = {
username: ownerUser.username,
};
+const sqlLabUser: UserWithPermissionsAndRoles = {
+ ...ownerUser,
+ roles: {
+ ...ownerUser.roles,
+ sql_lab: [],
+ },
+};
+
const undefinedUser: UndefinedUser = {};
describe('canUserEditDashboard', () => {
@@ -109,6 +121,10 @@ test('isUserAdmin returns true for admin user', () => {
expect(isUserAdmin(adminUser)).toEqual(true);
});
+test('isUserAdmin returns false for undefined', () => {
+ expect(isUserAdmin(undefined)).toEqual(false);
+});
+
test('isUserAdmin returns false for undefined user', () => {
expect(isUserAdmin(undefinedUser)).toEqual(false);
});
@@ -116,3 +132,23 @@ test('isUserAdmin returns false for undefined user', () => {
test('isUserAdmin returns false for non-admin user', () => {
expect(isUserAdmin(ownerUser)).toEqual(false);
});
+
+test('canUserAccessSqlLab returns true for admin user', () => {
+ expect(canUserAccessSqlLab(adminUser)).toEqual(true);
+});
+
+test('canUserAccessSqlLab returns false for undefined', () => {
+ expect(canUserAccessSqlLab(undefined)).toEqual(false);
+});
+
+test('canUserAccessSqlLab returns false for undefined user', () => {
+ expect(canUserAccessSqlLab(undefinedUser)).toEqual(false);
+});
+
+test('canUserAccessSqlLab returns false for non-sqllab role', () => {
+ expect(canUserAccessSqlLab(ownerUser)).toEqual(false);
+});
+
+test('canUserAccessSqlLab returns true for sqllab role', () => {
+ expect(canUserAccessSqlLab(sqlLabUser)).toEqual(true);
+});
diff --git a/superset-frontend/src/dashboard/util/permissionUtils.ts b/superset-frontend/src/dashboard/util/permissionUtils.ts
index 4980480ce..3ea63976b 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.ts
@@ -27,9 +27,10 @@ import { findPermission } from 'src/utils/findPermission';
// this should really be a config value,
// but is hardcoded in backend logic already, so...
const ADMIN_ROLE_NAME = 'admin';
+const SQL_LAB_ROLE = 'sql_lab';
export const isUserAdmin = (
- user: UserWithPermissionsAndRoles | UndefinedUser,
+ user?: UserWithPermissionsAndRoles | UndefinedUser,
) =>
isUserWithPermissionsAndRoles(user) &&
Object.keys(user.roles || {}).some(
@@ -50,3 +51,15 @@ export const canUserEditDashboard = (
isUserWithPermissionsAndRoles(user) &&
(isUserAdmin(user) || isUserDashboardOwner(dashboard, user)) &&
findPermission('can_write', 'Dashboard', user.roles);
+
+export function canUserAccessSqlLab(
+ user?: UserWithPermissionsAndRoles | UndefinedUser,
+) {
+ return (
+ isUserAdmin(user) ||
+ (isUserWithPermissionsAndRoles(user) &&
+ Object.keys(user.roles || {}).some(
+ role => role.toLowerCase() === SQL_LAB_ROLE,
+ ))
+ );
+}
diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
index 231300711..27b7bc568 100644
--- a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
+++ b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
@@ -20,13 +20,13 @@
import React from 'react';
import { render, screen, act, waitFor } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
-import { SupersetClient, DatasourceType } from '@superset-ui/core';
+import { DatasourceType, JsonObject, SupersetClient } from '@superset-ui/core';
import fetchMock from 'fetch-mock';
import DatasourceControl from '.';
const SupersetClientGet = jest.spyOn(SupersetClient, 'get');
-const createProps = () => ({
+const createProps = (overrides: JsonObject = {}) => ({
hovered: false,
type: 'DatasourceControl',
label: 'Datasource',
@@ -64,6 +64,7 @@ const createProps = () => ({
},
onChange: jest.fn(),
onDatasourceSave: jest.fn(),
+ ...overrides,
});
async function openAndSaveChanges(datasource: any) {
@@ -104,6 +105,52 @@ test('Should open a menu', async () => {
expect(screen.getByText('View in SQL Lab')).toBeInTheDocument();
});
+test('Should not show SQL Lab for non sql_lab role', async () => {
+ const props = createProps({
+ user: {
+ createdOn: '2021-04-27T18:12:38.952304',
+ email: 'gamma',
+ firstName: 'gamma',
+ isActive: true,
+ lastName: 'gamma',
+ permissions: {},
+ roles: { Gamma: [] },
+ userId: 2,
+ username: 'gamma',
+ },
+ });
+ render();
+
+ userEvent.click(screen.getByTestId('datasource-menu-trigger'));
+
+ expect(await screen.findByText('Edit dataset')).toBeInTheDocument();
+ expect(screen.getByText('Swap dataset')).toBeInTheDocument();
+ expect(screen.queryByText('View in SQL Lab')).not.toBeInTheDocument();
+});
+
+test('Should show SQL Lab for sql_lab role', async () => {
+ const props = createProps({
+ user: {
+ createdOn: '2021-04-27T18:12:38.952304',
+ email: 'sql',
+ firstName: 'sql',
+ isActive: true,
+ lastName: 'sql',
+ permissions: {},
+ roles: { Gamma: [], sql_lab: [] },
+ userId: 2,
+ username: 'sql',
+ },
+ });
+ render();
+
+ userEvent.click(screen.getByTestId('datasource-menu-trigger'));
+
+ expect(await screen.findByText('Edit dataset')).toBeInTheDocument();
+ expect(screen.getByText('Swap dataset')).toBeInTheDocument();
+ expect(screen.getByText('View in SQL Lab')).toBeInTheDocument();
+});
+
test('Click on Swap dataset option', async () => {
const props = createProps();
SupersetClientGet.mockImplementation(
diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
index 2dfa78363..b6adbd9ce 100644
--- a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
+++ b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
@@ -42,7 +42,10 @@ import ErrorAlert from 'src/components/ErrorMessage/ErrorAlert';
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
import { URL_PARAMS } from 'src/constants';
import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
-import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
+import {
+ canUserAccessSqlLab,
+ isUserAdmin,
+} from 'src/dashboard/util/permissionUtils';
import ModalTrigger from 'src/components/ModalTrigger';
import ViewQueryModalFooter from 'src/explore/components/controls/ViewQueryModalFooter';
import ViewQuery from 'src/explore/components/controls/ViewQuery';
@@ -279,6 +282,8 @@ class DatasourceControl extends React.PureComponent {
datasource.owners?.map(o => o.id || o.value).includes(user.userId) ||
isUserAdmin(user);
+ const canAccessSqlLab = canUserAccessSqlLab(user);
+
const editText = t('Edit dataset');
const defaultDatasourceMenu = (
@@ -303,7 +308,7 @@ class DatasourceControl extends React.PureComponent {
)}
{t('Swap dataset')}
- {datasource && (
+ {datasource && canAccessSqlLab && (
{t('View in SQL Lab')}
)}
@@ -333,7 +338,9 @@ class DatasourceControl extends React.PureComponent {
responsive
/>
- {t('View in SQL Lab')}
+ {canAccessSqlLab && (
+ {t('View in SQL Lab')}
+ )}
{t('Save as dataset')}
);
diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx
index 7edd063cb..ca2f26e61 100644
--- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx
+++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx
@@ -50,6 +50,7 @@ import copyTextToClipboard from 'src/utils/copy';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import ImportModelsModal from 'src/components/ImportModal/index';
import Icons from 'src/components/Icons';
+import { BootstrapUser } from 'src/types/bootstrapTypes';
import SavedQueryPreviewModal from './SavedQueryPreviewModal';
const PAGE_SIZE = 25;
@@ -69,9 +70,7 @@ const CONFIRM_OVERWRITE_MESSAGE = t(
interface SavedQueryListProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
- user: {
- userId: string | number;
- };
+ user: BootstrapUser;
}
const StyledTableLabel = styled.div`
diff --git a/superset-frontend/src/views/CRUD/welcome/ActivityTable.test.tsx b/superset-frontend/src/views/CRUD/welcome/ActivityTable.test.tsx
index 53f637e4e..66d521f36 100644
--- a/superset-frontend/src/views/CRUD/welcome/ActivityTable.test.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/ActivityTable.test.tsx
@@ -82,7 +82,7 @@ describe('ActivityTable', () => {
activityData: mockData,
setActiveChild: jest.fn(),
user: { userId: '1' },
- loadedCount: 3,
+ isFetchingActivityData: false,
};
let wrapper: ReactWrapper;
@@ -127,7 +127,7 @@ describe('ActivityTable', () => {
activityData: {},
setActiveChild: jest.fn(),
user: { userId: '1' },
- loadedCount: 3,
+ isFetchingActivityData: false,
};
const wrapper = mount(
diff --git a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
index 677f9e51b..d6dc3858a 100644
--- a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
@@ -74,7 +74,7 @@ interface ActivityProps {
activeChild: string;
setActiveChild: (arg0: string) => void;
activityData: ActivityData;
- loadedCount: number;
+ isFetchingActivityData: boolean;
}
const Styles = styled.div`
@@ -128,22 +128,21 @@ export default function ActivityTable({
setActiveChild,
activityData,
user,
- loadedCount,
+ isFetchingActivityData,
}: ActivityProps) {
- const [editedObjs, setEditedObjs] = useState>();
- const [loadingState, setLoadingState] = useState(false);
+ const [editedCards, setEditedCards] = useState();
+ const [isFetchingEditedCards, setIsFetchingEditedCards] = useState(false);
const getEditedCards = () => {
- setLoadingState(true);
+ setIsFetchingEditedCards(true);
getEditedObjects(user.userId).then(r => {
- setEditedObjs([...r.editedChart, ...r.editedDash]);
- setLoadingState(false);
+ setEditedCards([...r.editedChart, ...r.editedDash]);
+ setIsFetchingEditedCards(false);
});
};
useEffect(() => {
if (activeChild === TableTab.Edited) {
- setLoadingState(true);
getEditedCards();
}
}, [activeChild]);
@@ -178,9 +177,9 @@ export default function ActivityTable({
});
}
const renderActivity = () =>
- (activeChild !== TableTab.Edited
- ? activityData[activeChild]
- : editedObjs
+ (activeChild === TableTab.Edited
+ ? editedCards
+ : activityData[activeChild]
).map((entity: ActivityObject) => {
const url = getEntityUrl(entity);
const lastActionOn = getEntityLastActionOn(entity);
@@ -200,18 +199,14 @@ export default function ActivityTable({
);
});
- const doneFetching = loadedCount < 3;
-
- if ((loadingState && !editedObjs) || doneFetching) {
+ if ((isFetchingEditedCards && !editedCards) || isFetchingActivityData) {
return ;
}
return (
{activityData[activeChild]?.length > 0 ||
- (activeChild === TableTab.Edited &&
- editedObjs &&
- editedObjs.length > 0) ? (
+ (activeChild === TableTab.Edited && editedCards?.length) ? (
{renderActivity()}
diff --git a/superset-frontend/src/views/CRUD/welcome/Welcome.test.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.test.tsx
index ce688f08f..5c693ab26 100644
--- a/superset-frontend/src/views/CRUD/welcome/Welcome.test.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/Welcome.test.tsx
@@ -106,10 +106,15 @@ const mockedProps = {
userId: 5,
email: 'alpha@alpha.com',
isActive: true,
+ isAnonymous: false,
+ permissions: {},
+ roles: {
+ sql_lab: [],
+ },
},
};
-describe('Welcome', () => {
+describe('Welcome with sql role', () => {
let wrapper: ReactWrapper;
beforeAll(async () => {
@@ -122,6 +127,10 @@ describe('Welcome', () => {
});
});
+ afterAll(() => {
+ fetchMock.resetHistory();
+ });
+
it('renders', () => {
expect(wrapper).toExist();
});
@@ -142,6 +151,50 @@ describe('Welcome', () => {
});
});
+describe('Welcome without sql role', () => {
+ let wrapper: ReactWrapper;
+
+ beforeAll(async () => {
+ await act(async () => {
+ const props = {
+ ...mockedProps,
+ user: {
+ ...mockedProps.user,
+ roles: {},
+ },
+ };
+ wrapper = mount(
+
+
+ ,
+ );
+ });
+ });
+
+ afterAll(() => {
+ fetchMock.resetHistory();
+ });
+
+ it('renders', () => {
+ expect(wrapper).toExist();
+ });
+
+ it('renders all panels on the page on page load', () => {
+ expect(wrapper.find('CollapsePanel')).toHaveLength(6);
+ });
+
+ it('calls api methods in parallel on page load', () => {
+ const chartCall = fetchMock.calls(/chart\/\?q/);
+ const savedQueryCall = fetchMock.calls(/saved_query\/\?q/);
+ const recentCall = fetchMock.calls(/superset\/recent_activity\/*/);
+ const dashboardCall = fetchMock.calls(/dashboard\/\?q/);
+ expect(chartCall).toHaveLength(2);
+ expect(recentCall).toHaveLength(1);
+ expect(savedQueryCall).toHaveLength(0);
+ expect(dashboardCall).toHaveLength(2);
+ });
+});
+
async function mountAndWait(props = mockedProps) {
const wrapper = mount(
diff --git a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
index 711098ff3..1617c8b6f 100644
--- a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
@@ -47,6 +47,7 @@ import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { AntdSwitch } from 'src/components';
import getBootstrapData from 'src/utils/getBootstrapData';
import { TableTab } from 'src/views/CRUD/types';
+import { canUserAccessSqlLab } from 'src/dashboard/util/permissionUtils';
import { WelcomePageLastTab } from './types';
import ActivityTable from './ActivityTable';
import ChartTable from './ChartTable';
@@ -161,6 +162,7 @@ export const LoadingCards = ({ cover }: LoadingProps) => (
);
function Welcome({ user, addDangerToast }: WelcomeProps) {
+ const canAccessSqlLab = canUserAccessSqlLab(user);
const userid = user.userId;
const id = userid!.toString(); // confident that user is not a guest user
const recent = `/superset/recent_activity/${user.userId}/?limit=6`;
@@ -178,7 +180,7 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
const [dashboardData, setDashboardData] = useState | null>(
null,
);
- const [loadedCount, setLoadedCount] = useState(0);
+ const [isFetchingActivityData, setIsFetchingActivityData] = useState(true);
const collapseState = getItem(LocalStorageKeys.homepage_collapse_state, []);
const [activeState, setActiveState] = useState>(collapseState);
@@ -260,40 +262,46 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
value: `${id}`,
},
];
- getUserOwnedObjects(id, 'dashboard')
- .then(r => {
- setDashboardData(r);
- setLoadedCount(loadedCount => loadedCount + 1);
- })
- .catch((err: unknown) => {
- setDashboardData([]);
- setLoadedCount(loadedCount => loadedCount + 1);
- addDangerToast(
- t('There was an issue fetching your dashboards: %s', err),
- );
- });
- getUserOwnedObjects(id, 'chart')
- .then(r => {
- setChartData(r);
- setLoadedCount(loadedCount => loadedCount + 1);
- })
- .catch((err: unknown) => {
- setChartData([]);
- setLoadedCount(loadedCount => loadedCount + 1);
- addDangerToast(t('There was an issue fetching your chart: %s', err));
- });
- getUserOwnedObjects(id, 'saved_query', ownSavedQueryFilters)
- .then(r => {
- setQueryData(r);
- setLoadedCount(loadedCount => loadedCount + 1);
- })
- .catch((err: unknown) => {
- setQueryData([]);
- setLoadedCount(loadedCount => loadedCount + 1);
- addDangerToast(
- t('There was an issues fetching your saved queries: %s', err),
- );
- });
+ Promise.all([
+ getUserOwnedObjects(id, 'dashboard')
+ .then(r => {
+ setDashboardData(r);
+ return Promise.resolve();
+ })
+ .catch((err: unknown) => {
+ setDashboardData([]);
+ addDangerToast(
+ t('There was an issue fetching your dashboards: %s', err),
+ );
+ return Promise.resolve();
+ }),
+ getUserOwnedObjects(id, 'chart')
+ .then(r => {
+ setChartData(r);
+ return Promise.resolve();
+ })
+ .catch((err: unknown) => {
+ setChartData([]);
+ addDangerToast(t('There was an issue fetching your chart: %s', err));
+ return Promise.resolve();
+ }),
+ canAccessSqlLab
+ ? getUserOwnedObjects(id, 'saved_query', ownSavedQueryFilters)
+ .then(r => {
+ setQueryData(r);
+ return Promise.resolve();
+ })
+ .catch((err: unknown) => {
+ setQueryData([]);
+ addDangerToast(
+ t('There was an issue fetching your saved queries: %s', err),
+ );
+ return Promise.resolve();
+ })
+ : Promise.resolve(),
+ ]).then(() => {
+ setIsFetchingActivityData(false);
+ });
}, [otherTabFilters]);
const handleToggle = () => {
@@ -323,6 +331,7 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
const isRecentActivityLoading =
!activityData?.[TableTab.Other] && !activityData?.[TableTab.Viewed];
+
return (
{WelcomeMessageExtension && }
@@ -356,7 +365,7 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
activeChild={activeChild}
setActiveChild={setActiveChild}
activityData={activityData}
- loadedCount={loadedCount}
+ isFetchingActivityData={isFetchingActivityData}
/>
) : (
@@ -390,18 +399,20 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
/>
)}
-
- {!queryData ? (
-
- ) : (
-
- )}
-
+ {canAccessSqlLab && (
+
+ {!queryData ? (
+
+ ) : (
+
+ )}
+
+ )}
>
)}
diff --git a/superset/security/manager.py b/superset/security/manager.py
index ea6040ced..ff0309d39 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -189,7 +189,6 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
}
ADMIN_ONLY_PERMISSIONS = {
- "can_sql_json", # TODO: move can_sql_json to sql_lab role
"can_override_role_permissions",
"can_sync_druid_source",
"can_override_role_permissions",
@@ -223,11 +222,11 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
ACCESSIBLE_PERMS = {"can_userinfo", "resetmypassword"}
- SQLLAB_PERMISSION_VIEWS = {
- ("can_csv", "Superset"),
+ SQLLAB_ONLY_PERMISSIONS = {
+ ("can_my_queries", "SqlLab"),
("can_read", "SavedQuery"),
- ("can_read", "Database"),
("can_sql_json", "Superset"),
+ ("can_sqllab_history", "Superset"),
("can_sqllab_viz", "Superset"),
("can_sqllab_table_viz", "Superset"),
("can_sqllab", "Superset"),
@@ -237,6 +236,12 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
("menu_access", "Query Search"),
}
+ SQLLAB_EXTRA_PERMISSION_VIEWS = {
+ ("can_csv", "Superset"),
+ ("can_read", "Superset"),
+ ("can_read", "Database"),
+ }
+
data_access_permissions = (
"database_access",
"schema_access",
@@ -908,7 +913,9 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
"""
return not (
- self._is_user_defined_permission(pvm) or self._is_admin_only(pvm)
+ self._is_user_defined_permission(pvm)
+ or self._is_admin_only(pvm)
+ or self._is_sql_lab_only(pvm)
) or self._is_accessible_to_all(pvm)
def _is_gamma_pvm(self, pvm: PermissionView) -> bool:
@@ -924,8 +931,19 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
self._is_user_defined_permission(pvm)
or self._is_admin_only(pvm)
or self._is_alpha_only(pvm)
+ or self._is_sql_lab_only(pvm)
) or self._is_accessible_to_all(pvm)
+ def _is_sql_lab_only(self, pvm: PermissionView) -> bool:
+ """
+ Return True if the FAB permission/view is only SQL Lab related, False
+ otherwise.
+
+ :param pvm: The FAB permission/view
+ :returns: Whether the FAB object is SQL Lab related
+ """
+ return (pvm.permission.name, pvm.view_menu.name) in self.SQLLAB_ONLY_PERMISSIONS
+
def _is_sql_lab_pvm(self, pvm: PermissionView) -> bool:
"""
Return True if the FAB permission/view is SQL Lab related, False
@@ -934,7 +952,11 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
:param pvm: The FAB permission/view
:returns: Whether the FAB object is SQL Lab related
"""
- return (pvm.permission.name, pvm.view_menu.name) in self.SQLLAB_PERMISSION_VIEWS
+ return (
+ self._is_sql_lab_only(pvm)
+ or (pvm.permission.name, pvm.view_menu.name)
+ in self.SQLLAB_EXTRA_PERMISSION_VIEWS
+ )
def _is_granter_pvm( # pylint: disable=no-self-use
self, pvm: PermissionView
diff --git a/tests/integration_tests/queries/saved_queries/api_tests.py b/tests/integration_tests/queries/saved_queries/api_tests.py
index 4a615a816..91843b008 100644
--- a/tests/integration_tests/queries/saved_queries/api_tests.py
+++ b/tests/integration_tests/queries/saved_queries/api_tests.py
@@ -98,7 +98,7 @@ class TestSavedQueryApi(SupersetTestCase):
self.insert_default_saved_query(
label=f"label{SAVED_QUERIES_FIXTURE_COUNT}",
schema=f"schema{SAVED_QUERIES_FIXTURE_COUNT}",
- username="gamma",
+ username="gamma_sqllab",
)
)
@@ -157,12 +157,12 @@ class TestSavedQueryApi(SupersetTestCase):
"""
Saved Query API: Test get list saved query
"""
- gamma = self.get_user("gamma")
+ user = self.get_user("gamma_sqllab")
saved_queries = (
- db.session.query(SavedQuery).filter(SavedQuery.created_by == gamma).all()
+ db.session.query(SavedQuery).filter(SavedQuery.created_by == user).all()
)
- self.login(username="gamma")
+ self.login(username=user.username)
uri = f"api/v1/saved_query/"
rv = self.get_assert_metric(uri, "get_list")
assert rv.status_code == 200
diff --git a/tests/integration_tests/sqllab_tests.py b/tests/integration_tests/sqllab_tests.py
index b1b0480d5..a33a541a6 100644
--- a/tests/integration_tests/sqllab_tests.py
+++ b/tests/integration_tests/sqllab_tests.py
@@ -257,6 +257,22 @@ class TestSqlLab(SupersetTestCase):
db.session.commit()
self.assertLess(0, len(data["data"]))
+ def test_sqllab_has_access(self):
+ for username in ("admin", "gamma_sqllab"):
+ self.login(username)
+ for endpoint in ("/superset/sqllab/", "/superset/sqllab/history/"):
+ resp = self.client.get(endpoint)
+ self.assertEqual(200, resp.status_code)
+
+ self.logout()
+
+ def test_sqllab_no_access(self):
+ self.login("gamma")
+ for endpoint in ("/superset/sqllab/", "/superset/sqllab/history/"):
+ resp = self.client.get(endpoint)
+ # Redirects to the main page
+ self.assertEqual(302, resp.status_code)
+
def test_sql_json_schema_access(self):
examples_db = get_example_database()
db_backend = examples_db.backend