mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
DashboardSettings: Prevent Dashboard permissions from linking to folder permissions when user does not have sufficient permissions (#44212)
* user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * add tests * fix up Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com> Co-authored-by: Alexandra Vargas <alexa1866@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
This commit is contained in:
co-authored by
Ashley Harrison
Alexandra Vargas
Hugo Häggmark
parent
dc913f2311
commit
9f97f05fcc
@@ -20,6 +20,7 @@ const setup = (propOverrides?: object) => {
|
||||
hasChanged: false,
|
||||
version: 1,
|
||||
permissions: [],
|
||||
canViewFolderPermissions: true,
|
||||
},
|
||||
getFolderByUid: jest.fn(),
|
||||
setFolderTitle: mockToolkitActionCreator(setFolderTitle),
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||
import { checkFolderPermissions } from './actions';
|
||||
import { setCanViewFolderPermissions } from './reducers';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createWarningNotification } from 'app/core/copy/appNotification';
|
||||
import { FetchResponse } from '@grafana/runtime';
|
||||
|
||||
describe('folder actions', () => {
|
||||
let fetchSpy: jest.SpyInstance<Observable<FetchResponse<unknown>>>;
|
||||
|
||||
beforeAll(() => {
|
||||
fetchSpy = jest.spyOn(backendSrv, 'fetch');
|
||||
});
|
||||
afterAll(() => {
|
||||
fetchSpy.mockRestore();
|
||||
});
|
||||
|
||||
function mockFetch(resp: Observable<any>) {
|
||||
fetchSpy.mockReturnValueOnce(resp);
|
||||
}
|
||||
|
||||
const folderUid = 'abc123';
|
||||
|
||||
describe('checkFolderPermissions', () => {
|
||||
it('should dispatch true when the api call is successful', async () => {
|
||||
mockFetch(of({}));
|
||||
|
||||
const dispatchedActions = await thunkTester({})
|
||||
.givenThunk(checkFolderPermissions)
|
||||
.whenThunkIsDispatched(folderUid);
|
||||
|
||||
expect(dispatchedActions).toEqual([setCanViewFolderPermissions(true)]);
|
||||
});
|
||||
|
||||
it('should dispatch just "false" when the api call fails with 403', async () => {
|
||||
mockFetch(throwError(() => ({ status: 403, data: { message: 'Access denied' } })));
|
||||
|
||||
const dispatchedActions = await thunkTester({})
|
||||
.givenThunk(checkFolderPermissions)
|
||||
.whenThunkIsDispatched(folderUid);
|
||||
|
||||
expect(dispatchedActions).toEqual([setCanViewFolderPermissions(false)]);
|
||||
});
|
||||
|
||||
it('should also dispatch a notification when the api call fails with an error other than 403', async () => {
|
||||
mockFetch(throwError(() => ({ status: 500, data: { message: 'Server error' } })));
|
||||
|
||||
const dispatchedActions = await thunkTester({})
|
||||
.givenThunk(checkFolderPermissions)
|
||||
.whenThunkIsDispatched(folderUid);
|
||||
|
||||
const notificationAction = notifyApp(
|
||||
createWarningNotification('Error checking folder permissions', 'Server error')
|
||||
);
|
||||
notificationAction.payload.id = expect.any(String);
|
||||
|
||||
expect(dispatchedActions).toEqual([
|
||||
expect.objectContaining(notificationAction),
|
||||
setCanViewFolderPermissions(false),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,10 +3,12 @@ import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { FolderState, ThunkResult } from 'app/types';
|
||||
import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel } from 'app/types/acl';
|
||||
import { updateNavIndex } from 'app/core/actions';
|
||||
import { notifyApp, updateNavIndex } from 'app/core/actions';
|
||||
import { buildNavModel } from './navModel';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { loadFolder, loadFolderPermissions } from './reducers';
|
||||
import { loadFolder, loadFolderPermissions, setCanViewFolderPermissions } from './reducers';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { createWarningNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
export function getFolderByUid(uid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
@@ -43,6 +45,28 @@ export function getFolderPermissions(uid: string): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function checkFolderPermissions(uid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
await lastValueFrom(
|
||||
backendSrv.fetch({
|
||||
method: 'GET',
|
||||
showErrorAlert: false,
|
||||
showSuccessAlert: false,
|
||||
url: `/api/folders/${uid}/permissions`,
|
||||
})
|
||||
);
|
||||
dispatch(setCanViewFolderPermissions(true));
|
||||
} catch (err) {
|
||||
if (err.status !== 403) {
|
||||
dispatch(notifyApp(createWarningNotification('Error checking folder permissions', err.data?.message)));
|
||||
}
|
||||
|
||||
dispatch(setCanViewFolderPermissions(false));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toUpdateItem(item: DashboardAcl): DashboardAclUpdateDTO {
|
||||
return {
|
||||
userId: item.userId,
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { FolderDTO, FolderState, OrgRole, PermissionLevel } from 'app/types';
|
||||
import { folderReducer, initialState, loadFolder, loadFolderPermissions, setFolderTitle } from './reducers';
|
||||
import {
|
||||
folderReducer,
|
||||
initialState,
|
||||
loadFolder,
|
||||
loadFolderPermissions,
|
||||
setCanViewFolderPermissions,
|
||||
setFolderTitle,
|
||||
} from './reducers';
|
||||
import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
||||
|
||||
function getTestFolder(): FolderDTO {
|
||||
@@ -142,4 +149,16 @@ describe('folder reducer', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCanViewFolderPermissions', () => {
|
||||
it('should set the canViewFolderPermissions value', () => {
|
||||
reducerTester<FolderState>()
|
||||
.givenReducer(folderReducer, { ...initialState })
|
||||
.whenActionIsDispatched(setCanViewFolderPermissions(true))
|
||||
.thenStateShouldEqual({
|
||||
...initialState,
|
||||
canViewFolderPermissions: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ export const initialState: FolderState = {
|
||||
hasChanged: false,
|
||||
version: 1,
|
||||
permissions: [],
|
||||
canViewFolderPermissions: false,
|
||||
};
|
||||
|
||||
const folderSlice = createSlice({
|
||||
@@ -38,10 +39,14 @@ const folderSlice = createSlice({
|
||||
permissions: processAclItems(action.payload),
|
||||
};
|
||||
},
|
||||
setCanViewFolderPermissions: (state, action: PayloadAction<boolean>): FolderState => {
|
||||
state.canViewFolderPermissions = action.payload;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { loadFolderPermissions, loadFolder, setFolderTitle } = folderSlice.actions;
|
||||
export const { loadFolderPermissions, loadFolder, setFolderTitle, setCanViewFolderPermissions } = folderSlice.actions;
|
||||
|
||||
export const folderReducer = folderSlice.reducer;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user