Files
Kristian Bremberg b30f501bff backendsrv: Implement optional URL path fetch sanitize (#106540)
* feat: Implement optional URL path sanitization in BackendSrv methods

* add comment

* revert

* remove namespace import from backendsrv

* change method to validatePath, remove query params and fragments

* Moved validatePath call into fetch and make it throw an error instead

* update pluginSettings tests

* prettier

* Update public/app/features/plugins/pluginSettings.ts

Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>

* change name to validatePath

* fix other tests

* rename property in backend_srv tests

* rename to validatePath in backend_srv, add extra tests

* Move path validation into parseUrlFromOptions

* fix

* Add additional check

* Add test

---------

Co-authored-by: joshhunt <josh.hunt@grafana.com>
Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
2025-06-20 11:27:53 +01:00

75 lines
2.5 KiB
TypeScript

import { AppEvents, UrlQueryMap } from '@grafana/data';
import { t } from '@grafana/i18n';
import { FetchError, getBackendSrv } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import appEvents from 'app/core/app_events';
import { Resource, ResourceList } from 'app/features/apiserver/types';
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types';
import { SaveDashboardResponseDTO, DashboardDTO } from 'app/types';
import { SaveDashboardCommand } from '../components/SaveDashboard/types';
import { DashboardAPI, ListDeletedDashboardsOptions } from './types';
export class LegacyDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
constructor() {}
saveDashboard(options: SaveDashboardCommand<Dashboard>): Promise<SaveDashboardResponseDTO> {
dashboardWatcher.ignoreNextSave();
return getBackendSrv().post<SaveDashboardResponseDTO>('/api/dashboards/db/', {
dashboard: options.dashboard,
message: options.message ?? '',
overwrite: options.overwrite ?? false,
folderUid: options.folderUid,
});
}
deleteDashboard(uid: string, showSuccessAlert: boolean): Promise<DeleteDashboardResponse> {
return getBackendSrv().delete<DeleteDashboardResponse>(`/api/dashboards/uid/${uid}`, undefined, {
showSuccessAlert,
validatePath: true,
});
}
async getDashboardDTO(uid: string, params?: UrlQueryMap) {
const result = await getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`, params, undefined, {
validatePath: true,
});
if (result.meta.isFolder) {
appEvents.emit(AppEvents.alertError, ['Dashboard not found']);
const fetchError: FetchError = {
status: 404,
config: { url: `/api/dashboards/uid/${uid}` },
data: {
message: t('dashboard.legacy-dashboard-api.fetch-error.message.dashboard-not-found', 'Dashboard not found'),
},
};
throw fetchError;
}
return result;
}
/**
* No-op for legacy API
*/
listDeletedDashboards(options: ListDeletedDashboardsOptions): Promise<ResourceList<Dashboard>> {
return Promise.resolve({
apiVersion: 'v1',
kind: 'List',
metadata: { resourceVersion: '0' },
items: [],
});
}
/**
* No-op for legacy API
*/
restoreDashboard(dashboard: Resource<Dashboard>): Promise<Resource<Dashboard>> {
return Promise.reject(new Error('Restore functionality not supported in legacy API'));
}
}