Chore: remove legacy AC fallback from frontend access control checks (#74385)

* don't use legacy AC fallback for frontend access control checks

* extend tests

* more test fixes

* more test fixes

* more test fixes

* final test fix

* team test fix

* finally fix tests
This commit is contained in:
Ieva 2023-09-06 16:07:49 +01:00 committed by GitHub
parent 12de22b771
commit d46208b28a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 129 additions and 176 deletions

View File

@ -84,7 +84,7 @@ describe('OldFolderPicker', () => {
{ title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit,
]);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
const onChangeFn = jest.fn();
render(<OldFolderPicker onChange={onChangeFn} />);
@ -105,7 +105,7 @@ describe('OldFolderPicker', () => {
{ title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit,
]);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
const onChangeFn = jest.fn();
render(<OldFolderPicker onChange={onChangeFn} showRoot={false} />);
@ -126,7 +126,7 @@ describe('OldFolderPicker', () => {
{ title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit,
]);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
const onChangeFn = jest.fn();
render(<OldFolderPicker onChange={onChangeFn} />);
@ -148,7 +148,7 @@ describe('OldFolderPicker', () => {
].filter((dash) => dash.title.indexOf(query) > -1)
);
});
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
const onChangeFn = jest.fn();
render(<OldFolderPicker onChange={onChangeFn} />);

View File

@ -104,8 +104,8 @@ export function OldFolderPicker(props: Props) {
});
const hasAccess =
contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor) ||
contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor);
contextSrv.hasPermission(AccessControlAction.DashboardsWrite) ||
contextSrv.hasPermission(AccessControlAction.DashboardsCreate);
if (hasAccess && rootName?.toLowerCase().startsWith(query.toLowerCase()) && showRoot) {
options.unshift({ label: rootName, value: '' });

View File

@ -30,6 +30,7 @@ jest.mock('./state/apis', () => ({
jest.mock('../../core/services/context_srv', () => ({
contextSrv: {
hasAccess: () => true,
hasPermission: () => true,
},
}));

View File

@ -15,11 +15,11 @@ export const ServerStats = () => {
const [isLoading, setIsLoading] = useState(false);
const styles = useStyles2(getStyles);
const hasAccessToDataSources = contextSrv.hasAccess(AccessControlAction.DataSourcesRead, contextSrv.isGrafanaAdmin);
const hasAccessToAdminUsers = contextSrv.hasAccess(AccessControlAction.UsersRead, contextSrv.isGrafanaAdmin);
const hasAccessToDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesRead);
const hasAccessToAdminUsers = contextSrv.hasPermission(AccessControlAction.UsersRead);
useEffect(() => {
if (contextSrv.hasAccess(AccessControlAction.ActionServerStatsRead, contextSrv.isGrafanaAdmin)) {
if (contextSrv.hasPermission(AccessControlAction.ActionServerStatsRead)) {
setIsLoading(true);
getServerStats().then((stats) => {
setStats(stats);
@ -28,7 +28,7 @@ export const ServerStats = () => {
}
}, []);
if (!contextSrv.hasAccess(AccessControlAction.ActionServerStatsRead, contextSrv.isGrafanaAdmin)) {
if (!contextSrv.hasPermission(AccessControlAction.ActionServerStatsRead)) {
return null;
}

View File

@ -56,7 +56,6 @@ afterEach(() => {
describe('Tabs rendering', () => {
it('should render All and Org Users tabs when user has permissions to read to org users and is admin', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
renderPage();
@ -66,7 +65,6 @@ describe('Tabs rendering', () => {
expect(screen.queryByTestId(tabsSelector.publicDashboardsUsers)).not.toBeInTheDocument();
});
it('should render All, Org and Public dashboard tabs when user has permissions to read org users, is admin and has email sharing enabled', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
enableEmailSharing();
@ -77,19 +75,8 @@ describe('Tabs rendering', () => {
expect(screen.getByTestId(tabsSelector.publicDashboardsUsers)).toBeInTheDocument();
});
describe('No permissions to read org users or not admin', () => {
[
{
hasOrgReadPermissions: false,
isAdmin: true,
},
{
hasOrgReadPermissions: true,
isAdmin: false,
},
].forEach((scenario) => {
it('should render no tabs when user has no permissions to read org users or is not admin', async () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(scenario.hasOrgReadPermissions);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(scenario.isAdmin);
it('should render no tabs when user has no permissions to read org users', async () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
renderPage();
@ -98,23 +85,9 @@ describe('Tabs rendering', () => {
expect(screen.queryByTestId(tabsSelector.publicDashboardsUsers)).not.toBeInTheDocument();
});
});
});
describe('No permissions to read org users or not admin but email sharing enabled', () => {
[
{
title: 'user has no permissions to read org users',
hasOrgReadPermissions: false,
isAdmin: true,
},
{
title: 'user is not admin',
hasOrgReadPermissions: true,
isAdmin: false,
},
].forEach((scenario) => {
it(`should render User and Public dashboard tabs when ${scenario.title} but has email sharing enabled`, async () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(scenario.hasOrgReadPermissions);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(scenario.isAdmin);
describe('No permissions to read org users but email sharing enabled', () => {
it(`should render User and Public dashboard tabs when no permissions to read org users but has email sharing enabled`, async () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
enableEmailSharing();
renderPage();
@ -126,12 +99,10 @@ describe('Tabs rendering', () => {
expect(screen.getByTestId(tabsSelector.publicDashboardsUsers)).toBeInTheDocument();
});
});
});
});
describe('Tables rendering', () => {
it('should render UserListAdminPage when user is admin', () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
renderPage();
@ -142,7 +113,6 @@ describe('Tables rendering', () => {
expect(screen.getByTestId(selectors.UserListAdminPage.container)).toBeInTheDocument();
});
it('should render UsersListPage when user is admin and has org read permissions', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
renderPage();
@ -156,8 +126,7 @@ describe('Tables rendering', () => {
expect(screen.getByTestId(selectors.UsersListPage.container)).toBeInTheDocument();
});
it('should render UsersListPage when user has org read permissions and is not admin', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
renderPage();
@ -169,8 +138,7 @@ describe('Tables rendering', () => {
expect(screen.getByTestId(selectors.UsersListPage.container)).toBeInTheDocument();
});
it('should render UserListPublicDashboardPage when user has email sharing enabled and is not admin', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
enableEmailSharing();
renderPage();
@ -186,7 +154,6 @@ describe('Tables rendering', () => {
expect(screen.getByTestId(selectors.UsersListPublicDashboardsPage.container)).toBeInTheDocument();
});
it('should render UsersListPage when user is not admin and does not have nor org read perms neither email sharing enabled', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
renderPage();

View File

@ -40,7 +40,7 @@ const TAB_PAGE_MAP: Record<TabView, React.ReactElement> = {
export default function UserListPage() {
const styles = useStyles2(getStyles);
const hasAccessToAdminUsers = contextSrv.hasAccess(AccessControlAction.UsersRead, contextSrv.isGrafanaAdmin);
const hasAccessToAdminUsers = contextSrv.hasPermission(AccessControlAction.UsersRead);
const hasAccessToOrgUsers = contextSrv.hasPermission(AccessControlAction.OrgUsersRead);
const hasEmailSharingEnabled =
Boolean(config.featureToggles.publicDashboards) &&

View File

@ -196,7 +196,6 @@ describe('NotificationPolicies', () => {
beforeEach(() => {
mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
mocks.contextSrv.hasAccess.mockImplementation(() => true);
mocks.contextSrv.hasPermission.mockImplementation(() => true);
mocks.contextSrv.evaluatePermission.mockImplementation(() => []);
mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: false });
@ -384,7 +383,7 @@ describe('NotificationPolicies', () => {
});
it('hides create and edit button if user does not have permission', async () => {
mocks.contextSrv.hasAccess.mockImplementation((action) =>
mocks.contextSrv.hasPermission.mockImplementation((action) =>
[AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsRead].includes(
action as AccessControlAction
)

View File

@ -12,7 +12,6 @@ import ConditionalWrap from 'app/features/alerting/components/ConditionalWrap';
import { RouteWithID, Receiver, ObjectMatcher, AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types';
import { ReceiversState } from 'app/types';
import { isOrgAdmin } from '../../../../plugins/admin/permissions';
import { INTEGRATION_ICONS } from '../../types/contact-points';
import { getNotificationsPermissions } from '../../utils/access-control';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
@ -75,7 +74,7 @@ const Policy: FC<PolicyComponentProps> = ({
const canEditRoutes = contextSrv.hasPermission(permissions.update);
const canDeleteRoutes = contextSrv.hasPermission(permissions.delete);
const canReadProvisioning =
contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin()) ||
contextSrv.hasPermission(permissions.provisioning.read) ||
contextSrv.hasPermission(permissions.provisioning.readSecrets);
const contactPoint = currentRoute.receiver;

View File

@ -89,7 +89,7 @@ function ViewAction({ permissions, alertManagerName, receiverName }: ActionProps
}
function ExportAction({ permissions, receiverName }: ActionProps) {
const canReadSecrets = contextSrv.hasAccess(permissions.provisioning.readSecrets, isOrgAdmin());
const canReadSecrets = contextSrv.hasPermission(permissions.provisioning.readSecrets);
return (
<Authorize actions={[permissions.provisioning.read, permissions.provisioning.readSecrets]}>
<ActionIcon
@ -308,8 +308,8 @@ export const ReceiversTable = ({ config, alertManagerName }: Props) => {
const isGrafanaAM = alertManagerName === GRAFANA_RULES_SOURCE_NAME;
const showExport =
isGrafanaAM &&
(contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin()) ||
contextSrv.hasAccess(permissions.provisioning.readSecrets, isOrgAdmin()));
(contextSrv.hasPermission(permissions.provisioning.read) ||
contextSrv.hasPermission(permissions.provisioning.readSecrets));
const onClickDeleteReceiver = (receiverName: string): void => {
if (isReceiverUsed(receiverName, config)) {

View File

@ -14,11 +14,8 @@ import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
import { NeedHelpInfo } from '../NeedHelpInfo';
function getAvailableRuleTypes() {
const canCreateGrafanaRules = contextSrv.hasAccess(
AccessControlAction.AlertingRuleCreate,
contextSrv.hasEditPermissionInFolders
);
const canCreateCloudRules = contextSrv.hasAccess(AccessControlAction.AlertingRuleExternalWrite, contextSrv.isEditor);
const canCreateGrafanaRules = contextSrv.hasPermission(AccessControlAction.AlertingRuleCreate);
const canCreateCloudRules = contextSrv.hasPermission(AccessControlAction.AlertingRuleExternalWrite);
const defaultRuleType = canCreateGrafanaRules ? RuleFormType.grafana : RuleFormType.cloudAlerting;
const enabledRuleTypes: RuleFormType[] = [];

View File

@ -291,7 +291,7 @@ function useCanSilence(rule: CombinedRule) {
return false;
}
const hasPermissions = contextSrv.hasAccess(AccessControlAction.AlertingInstanceCreate, contextSrv.isEditor);
const hasPermissions = contextSrv.hasPermission(AccessControlAction.AlertingInstanceCreate);
const interactsOnlyWithExternalAMs = amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.External;
const interactsWithAll = amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.All;

View File

@ -14,7 +14,7 @@ type Props = {
export const NoSilencesSplash = ({ alertManagerSourceName }: Props) => {
const permissions = getInstancesPermissions(alertManagerSourceName);
if (contextSrv.hasAccess(permissions.create, contextSrv.isEditor)) {
if (contextSrv.hasPermission(permissions.create)) {
return (
<EmptyListCTA
title="You haven't created any silences yet"

View File

@ -62,8 +62,8 @@ export function useIsRuleEditable(rulesSourceName: string, rule?: RulerRuleDTO):
// prom rules are only editable by users with Editor role and only if rules source supports editing
const isRulerAvailable =
Boolean(dataSources[rulesSourceName]?.result?.rulerConfig) || Boolean(dsFeatures?.rulerConfig);
const canEditCloudRules = contextSrv.hasAccess(rulePermission.update, contextSrv.isEditor);
const canRemoveCloudRules = contextSrv.hasAccess(rulePermission.delete, contextSrv.isEditor);
const canEditCloudRules = contextSrv.hasPermission(rulePermission.update);
const canRemoveCloudRules = contextSrv.hasPermission(rulePermission.delete);
return {
isEditable: canEditCloudRules && isRulerAvailable,

View File

@ -1,8 +1,7 @@
import { contextSrv } from 'app/core/services/context_srv';
import { isOrgAdmin } from 'app/features/plugins/admin/permissions';
import { AccessControlAction } from 'app/types';
import { GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from './datasource';
import { isGrafanaRulesSource } from './datasource';
type RulesSourceType = 'grafana' | 'external';
@ -116,18 +115,16 @@ export function evaluateAccess(actions: AccessControlAction[], fallBackUserRoles
export function getRulesAccess() {
return {
canCreateGrafanaRules:
contextSrv.hasAccess(AccessControlAction.FoldersRead, contextSrv.hasEditPermissionInFolders) &&
contextSrv.hasAccess(rulesPermissions.create.grafana, contextSrv.hasEditPermissionInFolders),
contextSrv.hasPermission(AccessControlAction.FoldersRead) &&
contextSrv.hasPermission(rulesPermissions.create.grafana),
canCreateCloudRules:
contextSrv.hasAccess(AccessControlAction.DataSourcesRead, contextSrv.isEditor) &&
contextSrv.hasAccess(rulesPermissions.create.external, contextSrv.isEditor),
contextSrv.hasPermission(AccessControlAction.DataSourcesRead) &&
contextSrv.hasPermission(rulesPermissions.create.external),
canEditRules: (rulesSourceName: string) => {
const permissionFallback =
rulesSourceName === GRAFANA_RULES_SOURCE_NAME ? contextSrv.hasEditPermissionInFolders : contextSrv.isEditor;
return contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, permissionFallback);
return contextSrv.hasPermission(getRulesPermissions(rulesSourceName).update);
},
canReadProvisioning:
contextSrv.hasAccess(provisioningPermissions.read, isOrgAdmin()) ||
contextSrv.hasAccess(provisioningPermissions.readSecrets, isOrgAdmin()),
contextSrv.hasPermission(provisioningPermissions.read) ||
contextSrv.hasPermission(provisioningPermissions.readSecrets),
};
}

View File

@ -16,7 +16,7 @@ import { setSearchQuery } from './state/reducers';
import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './state/selectors';
function mapStateToProps(state: StoreState) {
const canCreate = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysCreate, true);
const canCreate = contextSrv.hasPermission(AccessControlAction.ActionAPIKeysCreate);
return {
apiKeys: getApiKeys(state.apiKeys),
searchQuery: state.apiKeys.searchQuery,

View File

@ -66,8 +66,7 @@ export function HelpWizard({ panel, plugin, onClose }: Props) {
];
const hasSupportBundleAccess =
config.supportBundlesEnabled &&
contextSrv.hasAccess(AccessControlAction.ActionSupportBundlesCreate, contextSrv.isGrafanaAdmin);
config.supportBundlesEnabled && contextSrv.hasPermission(AccessControlAction.ActionSupportBundlesCreate);
return (
<Drawer

View File

@ -21,7 +21,6 @@ import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
import { contextSrv } from '../../../../../../core/services/context_srv';
import { AccessControlAction, useSelector } from '../../../../../../types';
import { DeletePublicDashboardButton } from '../../../../../manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton';
import { isOrgAdmin } from '../../../../../plugins/admin/permissions';
import { useGetPublicDashboardQuery, useUpdatePublicDashboardMutation } from '../../../../api/publicDashboardApi';
import { useIsDesktop } from '../../../../utils/screen';
import { ShareModal } from '../../ShareModal';
@ -55,7 +54,7 @@ const ConfigPublicDashboard = () => {
const isDesktop = useIsDesktop();
const { showModal, hideModal } = useContext(ModalsContext);
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
const hasEmailSharingEnabled =
!!config.featureToggles.publicDashboardsEmailSharing && featureEnabled('publicDashboardsEmailSharing');
const dashboardState = useSelector((store) => store.dashboard);

View File

@ -8,7 +8,6 @@ import { Button, Form, Spinner, useStyles2 } from '@grafana/ui/src';
import { contextSrv } from '../../../../../../core/services/context_srv';
import { AccessControlAction, useSelector } from '../../../../../../types';
import { isOrgAdmin } from '../../../../../plugins/admin/permissions';
import { useCreatePublicDashboardMutation } from '../../../../api/publicDashboardApi';
import { trackDashboardSharingActionPerType } from '../../analytics';
import { shareDashboardType } from '../../utils';
@ -29,7 +28,7 @@ export type SharePublicDashboardAcknowledgmentInputs = {
const CreatePublicDashboard = ({ isError }: { isError: boolean }) => {
const styles = useStyles2(getStyles);
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
const dashboardState = useSelector((store) => store.dashboard);
const dashboard = dashboardState.getModel()!;
const unsupportedDataSources = getUnsupportedDashboardDatasources(dashboard.panels);

View File

@ -69,7 +69,6 @@ beforeAll(() => {
beforeEach(() => {
config.featureToggles.publicDashboards = true;
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
});
@ -103,7 +102,7 @@ const getErrorPublicDashboardResponse = () =>
const alertTests = () => {
it('when user has no write permissions, warning is shown', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
await renderSharePublicDashboard();
expect(screen.queryByTestId(selectors.NoUpsertPermissionsWarningAlert)).toBeInTheDocument();
@ -251,7 +250,7 @@ describe('SharePublic - Already persisted', () => {
expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
});
it('inputs and delete button are disabled because of lack of permissions', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
await renderSharePublicDashboard();
await userEvent.click(screen.getByText('Settings'));

View File

@ -73,8 +73,8 @@ export function AddToDashboardForm(props: Props): ReactElement {
defaultValues: { saveTarget: SaveTarget.NewDashboard },
});
const canCreateDashboard = contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor);
const canWriteDashboard = contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
const canCreateDashboard = contextSrv.hasPermission(AccessControlAction.DashboardsCreate);
const canWriteDashboard = contextSrv.hasPermission(AccessControlAction.DashboardsWrite);
const saveTargets: Array<SelectableValue<SaveTarget>> = [];
if (canCreateDashboard) {

View File

@ -8,22 +8,22 @@ jest.mock('app/core/services/context_srv');
const contextSrvMock = jest.mocked(contextSrv);
describe('getAddToDashboardTitle', () => {
beforeEach(() => contextSrvMock.hasAccess.mockReset());
beforeEach(() => contextSrvMock.hasPermission.mockReset());
it('should return title ending with "dashboard" if user has full access', () => {
contextSrvMock.hasAccess.mockReturnValue(true);
contextSrvMock.hasPermission.mockReturnValue(true);
expect(getAddToDashboardTitle()).toBe('Add panel to dashboard');
});
it('should return title ending with "dashboard" if user has no access', () => {
contextSrvMock.hasAccess.mockReturnValue(false);
contextSrvMock.hasPermission.mockReturnValue(false);
expect(getAddToDashboardTitle()).toBe('Add panel to dashboard');
});
it('should return title ending with "new dashboard" if user only has access to create dashboards', () => {
contextSrvMock.hasAccess.mockImplementation((action) => {
contextSrvMock.hasPermission.mockImplementation((action) => {
return action === AccessControlAction.DashboardsCreate;
});
@ -31,7 +31,7 @@ describe('getAddToDashboardTitle', () => {
});
it('should return title ending with "existing dashboard" if user only has access to edit dashboards', () => {
contextSrvMock.hasAccess.mockImplementation((action) => {
contextSrvMock.hasPermission.mockImplementation((action) => {
return action === AccessControlAction.DashboardsWrite;
});

View File

@ -2,8 +2,8 @@ import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction } from 'app/types';
export function getAddToDashboardTitle(): string {
const canCreateDashboard = contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor);
const canWriteDashboard = contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
const canCreateDashboard = contextSrv.hasPermission(AccessControlAction.DashboardsCreate);
const canWriteDashboard = contextSrv.hasPermission(AccessControlAction.DashboardsWrite);
if (canCreateDashboard && !canWriteDashboard) {
return 'Add panel to new dashboard';

View File

@ -73,7 +73,7 @@ describe('AddToDashboardButton', () => {
beforeEach(() => {
jest.spyOn(api, 'setDashboardInLocalStorage').mockReturnValue(addToDashboardResponse);
mocks.contextSrv.hasAccess.mockImplementation(() => true);
mocks.contextSrv.hasPermission.mockImplementation(() => true);
});
afterEach(() => {
@ -283,7 +283,7 @@ describe('AddToDashboardButton', () => {
});
it('Should only show existing dashboard option with no access to create', async () => {
mocks.contextSrv.hasAccess.mockImplementation((action) => {
mocks.contextSrv.hasPermission.mockImplementation((action) => {
if (action === 'dashboards:create') {
return false;
} else {
@ -296,7 +296,7 @@ describe('AddToDashboardButton', () => {
});
it('Should only show new dashboard option with no access to write', async () => {
mocks.contextSrv.hasAccess.mockImplementation((action) => {
mocks.contextSrv.hasPermission.mockImplementation((action) => {
if (action === 'dashboards:write') {
return false;
} else {
@ -311,7 +311,7 @@ describe('AddToDashboardButton', () => {
describe('Error handling', () => {
beforeEach(() => {
mocks.contextSrv.hasAccess.mockImplementation(() => true);
mocks.contextSrv.hasPermission.mockImplementation(() => true);
});
afterEach(() => {

View File

@ -215,7 +215,7 @@ describe('ToolbarExtensionPoint', () => {
describe('without extension points', () => {
beforeAll(() => {
contextSrvMock.hasAccess.mockReturnValue(true);
contextSrvMock.hasPermission.mockReturnValue(true);
getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] });
});
@ -233,7 +233,7 @@ describe('ToolbarExtensionPoint', () => {
describe('with insufficient permissions', () => {
beforeAll(() => {
contextSrvMock.hasAccess.mockReturnValue(false);
contextSrvMock.hasPermission.mockReturnValue(false);
getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] });
});

View File

@ -35,8 +35,8 @@ export function ToolbarExtensionPoint(props: Props): ReactElement | null {
// adding a query to a dashboard.
if (extensions.length <= 1) {
const canAddPanelToDashboard =
contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor) ||
contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
contextSrv.hasPermission(AccessControlAction.DashboardsCreate) ||
contextSrv.hasPermission(AccessControlAction.DashboardsWrite);
if (!canAddPanelToDashboard) {
return null;

View File

@ -28,10 +28,10 @@ describe('getExploreExtensionConfigs', () => {
});
describe('configure function for "add to dashboard" extension', () => {
afterEach(() => contextSrvMock.hasAccess.mockRestore());
afterEach(() => contextSrvMock.hasPermission.mockRestore());
it('should return undefined if insufficient permissions', () => {
contextSrvMock.hasAccess.mockReturnValue(false);
contextSrvMock.hasPermission.mockReturnValue(false);
const extensions = getExploreExtensionConfigs();
const [extension] = extensions;
@ -40,7 +40,7 @@ describe('getExploreExtensionConfigs', () => {
});
it('should return empty object if sufficient permissions', () => {
contextSrvMock.hasAccess.mockReturnValue(true);
contextSrvMock.hasPermission.mockReturnValue(true);
const extensions = getExploreExtensionConfigs();
const [extension] = extensions;

View File

@ -21,8 +21,8 @@ export function getExploreExtensionConfigs(): PluginExtensionLinkConfig[] {
category: 'Dashboards',
configure: () => {
const canAddPanelToDashboard =
contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor) ||
contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
contextSrv.hasPermission(AccessControlAction.DashboardsCreate) ||
contextSrv.hasPermission(AccessControlAction.DashboardsWrite);
// hide option if user has insufficient permissions
if (!canAddPanelToDashboard) {

View File

@ -128,7 +128,7 @@ describe('Show table', () => {
expect(screen.queryAllByRole('listitem')).toHaveLength(0);
});
it('renders public dashboards in a good way without trashcan', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
await renderPublicDashboardTable(true);
publicDashboardListResponse.forEach((pd, idx) => {
@ -136,7 +136,7 @@ describe('Show table', () => {
});
});
it('renders public dashboards in a good way with trashcan', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
await renderPublicDashboardTable(true);
publicDashboardListResponse.forEach((pd, idx) => {
@ -147,13 +147,13 @@ describe('Show table', () => {
describe('Delete public dashboard', () => {
it('when user does not have public dashboard write permissions, then dashboards are listed without delete button', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
await renderPublicDashboardTable(true);
expect(screen.queryAllByTestId(selectors.ListItem.trashcanButton)).toHaveLength(0);
});
it('when user has public dashboard write permissions, then dashboards are listed with delete button', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
await renderPublicDashboardTable(true);
expect(screen.getAllByTestId(selectors.ListItem.trashcanButton)).toHaveLength(publicDashboardListResponse.length);
@ -171,7 +171,7 @@ describe('Orphaned public dashboard', () => {
return res(ctx.status(200), ctx.json(response));
})
);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
await renderPublicDashboardTable(true);
response.publicDashboards.forEach((pd, idx) => {

View File

@ -27,7 +27,6 @@ import {
generatePublicDashboardConfigUrl,
generatePublicDashboardUrl,
} from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
import { isOrgAdmin } from 'app/features/plugins/admin/permissions';
import { AccessControlAction } from 'app/types';
import { PublicDashboardListResponse } from '../../types';
@ -42,7 +41,7 @@ const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => {
const [update, { isLoading: isUpdateLoading }] = useUpdatePublicDashboardMutation();
const selectors = e2eSelectors.pages.PublicDashboards;
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
const isOrphaned = !pd.dashboardUid;
const onTogglePause = (pd: PublicDashboardListResponse, isPaused: boolean) => {

View File

@ -8,7 +8,6 @@ import { contextSrv } from 'app/core/core';
import { AccessControlAction } from 'app/types';
import { getExternalManageLink } from '../../helpers';
import { isGrafanaAdmin } from '../../permissions';
import { useIsRemotePluginsAvailable } from '../../state/hooks';
import { CatalogPlugin, PluginStatus, Version } from '../../types';
@ -21,7 +20,7 @@ interface Props {
export const InstallControlsWarning = ({ plugin, pluginStatus, latestCompatibleVersion }: Props) => {
const styles = useStyles2(getStyles);
const isExternallyManaged = config.pluginAdminExternalManageEnabled;
const hasPermission = contextSrv.hasAccess(AccessControlAction.PluginsInstall, isGrafanaAdmin());
const hasPermission = contextSrv.hasPermission(AccessControlAction.PluginsInstall);
const isRemotePluginsAvailable = useIsRemotePluginsAvailable();
const isCompatible = Boolean(latestCompatibleVersion);

View File

@ -5,7 +5,6 @@ import { contextSrv } from 'app/core/core';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { AccessControlAction } from 'app/types';
import { isGrafanaAdmin } from './permissions';
import { CatalogPlugin, LocalPlugin, RemotePlugin, Version } from './types';
export function mergeLocalsAndRemotes(
@ -283,7 +282,7 @@ export const hasInstallControlWarning = (
latestCompatibleVersion?: Version
) => {
const isExternallyManaged = config.pluginAdminExternalManageEnabled;
const hasPermission = contextSrv.hasAccess(AccessControlAction.PluginsInstall, isGrafanaAdmin());
const hasPermission = contextSrv.hasPermission(AccessControlAction.PluginsInstall);
const isCompatible = Boolean(latestCompatibleVersion);
return (
plugin.type === PluginType.renderer ||

View File

@ -53,6 +53,7 @@ jest.mock('../helpers.ts', () => ({
jest.mock('app/core/core', () => ({
contextSrv: {
hasAccess: (action: string, fallBack: boolean) => true,
hasPermission: (action: string) => true,
hasAccessInMetadata: (action: string, object: WithAccessControlMetadata, fallBack: boolean) => true,
},
}));

View File

@ -15,6 +15,7 @@ jest.mock('app/core/services/context_srv', () => {
...originMock.context_srv,
user: {},
hasAccess: jest.fn(() => false),
hasPermission: jest.fn(() => false),
},
};
});
@ -32,16 +33,19 @@ jest.spyOn(console, 'error').mockImplementation();
describe('ManageDashboards', () => {
beforeEach(() => {
(contextSrv.hasAccess as jest.Mock).mockClear();
(contextSrv.hasPermission as jest.Mock).mockClear();
});
it("should hide and show dashboard actions based on user's permissions", async () => {
(contextSrv.hasAccess as jest.Mock).mockReturnValue(false);
(contextSrv.hasPermission as jest.Mock).mockReturnValue(false);
const { rerender } = await setup();
expect(screen.queryByRole('button', { name: /new/i })).not.toBeInTheDocument();
(contextSrv.hasAccess as jest.Mock).mockReturnValue(true);
(contextSrv.hasPermission as jest.Mock).mockReturnValue(true);
await waitFor(() => rerender(<ManageDashboardsNew folder={{ canEdit: true } as FolderDTO} />));
expect(screen.getByRole('button', { name: /new/i })).toBeInTheDocument();

View File

@ -30,11 +30,11 @@ export const ManageDashboardsNew = React.memo(({ folder }: Props) => {
const canSave = folder?.canSave;
const { isEditor } = contextSrv;
const hasEditPermissionInFolders = folder ? canSave : contextSrv.hasEditPermissionInFolders;
const canCreateFolders = contextSrv.hasAccess(AccessControlAction.FoldersCreate, isEditor);
const canCreateFolders = contextSrv.hasPermission(AccessControlAction.FoldersCreate);
const canCreateDashboardsFallback = hasEditPermissionInFolders || !!canSave;
const canCreateDashboards = folderUid
? contextSrv.hasAccessInMetadata(AccessControlAction.DashboardsCreate, folder, canCreateDashboardsFallback)
: contextSrv.hasAccess(AccessControlAction.DashboardsCreate, canCreateDashboardsFallback);
: contextSrv.hasPermission(AccessControlAction.DashboardsCreate);
const viewActions = (folder === undefined && canCreateFolders) || canCreateDashboards;
useEffect(() => stateManager.initStateFromUrl(folder?.uid), [folder?.uid, stateManager]);

View File

@ -52,11 +52,8 @@ const SupportBundlesUnconnected = ({ supportBundles, isLoading, loadBundles, rem
}
});
const hasAccess = contextSrv.hasAccess(AccessControlAction.ActionSupportBundlesCreate, contextSrv.isGrafanaAdmin);
const hasDeleteAccess = contextSrv.hasAccess(
AccessControlAction.ActionSupportBundlesDelete,
contextSrv.isGrafanaAdmin
);
const hasAccess = contextSrv.hasPermission(AccessControlAction.ActionSupportBundlesCreate);
const hasDeleteAccess = contextSrv.hasPermission(AccessControlAction.ActionSupportBundlesDelete);
const actions = hasAccess ? NewBundleButton : undefined;

View File

@ -10,9 +10,11 @@ import { OrgRole, Team } from '../../types';
import { Props, TeamList } from './TeamList';
import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks';
jest.mock('app/core/config', () => ({
...jest.requireActual('app/core/config'),
featureToggles: { accesscontrol: false },
jest.mock('app/core/core', () => ({
contextSrv: {
hasPermission: (action: string) => true,
licensedAccessControlEnabled: () => false,
},
}));
const setup = (propOverrides?: object) => {
@ -51,9 +53,9 @@ describe('TeamList', () => {
expect(screen.getAllByRole('row')).toHaveLength(6); // 5 teams plus table header row
});
describe('when feature toggle editorsCanAdmin is turned on', () => {
describe('and signed in user is not viewer', () => {
describe('when user has access to create a team', () => {
it('should enable the new team button', () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
setup({
teams: getMultipleMockTeams(1),
totalCount: 1,
@ -69,8 +71,9 @@ describe('TeamList', () => {
});
});
describe('and signed in user is a viewer', () => {
describe('when user does not have access to create a team', () => {
it('should disable the new team button', () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
setup({
teams: getMultipleMockTeams(1),
totalCount: 1,
@ -85,12 +88,12 @@ describe('TeamList', () => {
expect(screen.getByRole('link', { name: /new team/i })).toHaveStyle('pointer-events: none');
});
});
});
});
it('should call delete team', async () => {
const mockDelete = jest.fn();
const mockTeam = getMockTeam();
jest.spyOn(contextSrv, 'hasAccessInMetadata').mockReturnValue(true);
setup({ deleteTeam: mockDelete, teams: [mockTeam], totalCount: 1, hasFetched: true });
await userEvent.click(screen.getByRole('button', { name: `Delete team ${mockTeam.name}` }));
await userEvent.click(screen.getByRole('button', { name: 'Delete' }));

View File

@ -60,7 +60,7 @@ export const TeamList = ({
}
}, []);
const canCreate = canCreateTeam(editorsCanAdmin);
const canCreate = contextSrv.hasPermission(AccessControlAction.ActionTeamsCreate);
const displayRolePicker = shouldDisplayRolePicker();
return (
@ -137,11 +137,6 @@ export const TeamList = ({
);
};
function canCreateTeam(editorsCanAdmin: boolean): boolean {
const teamAdmin = contextSrv.hasRole('Admin') || (editorsCanAdmin && contextSrv.hasRole('Editor'));
return contextSrv.hasAccess(AccessControlAction.ActionTeamsCreate, teamAdmin);
}
function shouldDisplayRolePicker(): boolean {
return (
contextSrv.licensedAccessControlEnabled() &&