AccessControl: Add accesscontrol metadata to datasources DTOs (#42675)

* AccessControl: Provide scope to frontend

* Covering datasources with accesscontrol metadata

* Write benchmark tests for GetResourcesMetadata

* Add accesscontrol util and interface

* Add the hasPermissionInMetadata function in the frontend access control code

* Use IsDisabled rather that performing a feature toggle check

Co-authored-by: Karl Persson <kalle.persson@grafana.com>
This commit is contained in:
Gabriel MABILLE
2021-12-15 12:08:15 +01:00
committed by GitHub
co-authored by Karl Persson
parent 2b1ed43cb2
commit c7cabdfd6f
19 changed files with 364 additions and 71 deletions
@@ -12,7 +12,8 @@ jest.mock('app/core/core', () => {
const setup = (propOverrides?: object) => {
const props: Props = {
isReadOnly: true,
canSave: false,
canDelete: false,
onSubmit: jest.fn(),
onDelete: jest.fn(),
onTest: jest.fn(),
@@ -33,7 +34,8 @@ describe('Render', () => {
it('should render with buttons enabled', () => {
const wrapper = setup({
isReadOnly: false,
canSave: true,
canDelete: true,
});
expect(wrapper).toMatchSnapshot();
@@ -8,15 +8,14 @@ import { contextSrv } from 'app/core/core';
export interface Props {
exploreUrl: string;
isReadOnly: boolean;
canSave: boolean;
canDelete: boolean;
onDelete: () => void;
onSubmit: (event: any) => void;
onTest: (event: any) => void;
}
const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit, onTest, exploreUrl }) => {
const canEditDataSources = !isReadOnly && contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
const canDeleteDataSources = !isReadOnly && contextSrv.hasPermission(AccessControlAction.DataSourcesDelete);
const ButtonRow: FC<Props> = ({ canSave, canDelete, onDelete, onSubmit, onTest, exploreUrl }) => {
const canExploreDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesExplore);
return (
@@ -30,24 +29,24 @@ const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit, onTest, exploreU
<Button
type="button"
variant="destructive"
disabled={!canDeleteDataSources}
disabled={!canDelete}
onClick={onDelete}
aria-label={selectors.pages.DataSource.delete}
>
Delete
</Button>
{canEditDataSources && (
{canSave && (
<Button
type="submit"
variant="primary"
disabled={!canEditDataSources}
disabled={!canSave}
onClick={(event) => onSubmit(event)}
aria-label={selectors.pages.DataSource.saveAndTest}
>
Save &amp; test
</Button>
)}
{!canEditDataSources && (
{!canSave && (
<Button type="submit" variant="primary" onClick={onTest}>
Test
</Button>
@@ -13,6 +13,7 @@ jest.mock('app/core/core', () => {
return {
contextSrv: {
hasPermission: () => true,
hasPermissionInMetadata: () => true,
},
};
});
@@ -167,8 +167,9 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
}
renderLoadError() {
const { loadError } = this.props;
const canDeleteDataSources = !this.isReadOnly() && contextSrv.hasPermission(AccessControlAction.DataSourcesDelete);
const { loadError, dataSource } = this.props;
const canDeleteDataSource =
!this.isReadOnly() && contextSrv.hasPermissionInMetadata(AccessControlAction.DataSourcesDelete, dataSource);
const node = {
text: loadError!,
@@ -185,7 +186,7 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
<Page.Contents isLoading={this.props.loading}>
{this.isReadOnly() && this.renderIsReadOnlyMessage()}
<div className="gf-form-button-row">
{canDeleteDataSources && (
{canDeleteDataSource && (
<Button type="submit" variant="destructive" onClick={this.onDelete}>
Delete
</Button>
@@ -230,11 +231,12 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
renderSettings() {
const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource, plugin, testingStatus } = this.props;
const canEditDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
const canWriteDataSource = contextSrv.hasPermissionInMetadata(AccessControlAction.DataSourcesWrite, dataSource);
const canDeleteDataSource = contextSrv.hasPermissionInMetadata(AccessControlAction.DataSourcesDelete, dataSource);
return (
<form onSubmit={this.onSubmit}>
{!canEditDataSources && this.renderMissingEditRightsMessage()}
{!canWriteDataSource && this.renderMissingEditRightsMessage()}
{this.isReadOnly() && this.renderIsReadOnlyMessage()}
{dataSourceMeta.state && (
<div className="gf-form">
@@ -277,7 +279,8 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
<ButtonRow
onSubmit={(event) => this.onSubmit(event)}
isReadOnly={this.isReadOnly()}
canSave={!this.isReadOnly() && canWriteDataSource}
canDelete={!this.isReadOnly() && canDeleteDataSource}
onDelete={this.onDelete}
onTest={(event) => this.onTest(event)}
exploreUrl={this.onNavigateToExplore()}
@@ -25,6 +25,7 @@ import {
testDataSourceSucceeded,
} from './reducers';
import { getDataSource, getDataSourceMeta } from './selectors';
import { addAccessControlQueryParam } from 'app/core/utils/accessControl';
export interface DataSourceTypesLoadedPayload {
plugins: DataSourcePluginMeta[];
@@ -154,7 +155,7 @@ export async function getDataSourceUsingUidOrId(uid: string | number): Promise<D
const byUid = await lastValueFrom(
getBackendSrv().fetch<DataSourceSettings>({
method: 'GET',
url: `/api/datasources/uid/${uid}`,
url: addAccessControlQueryParam(`/api/datasources/uid/${uid}`),
showErrorAlert: false,
})
);
@@ -172,7 +173,7 @@ export async function getDataSourceUsingUidOrId(uid: string | number): Promise<D
const response = await lastValueFrom(
getBackendSrv().fetch<DataSourceSettings>({
method: 'GET',
url: `/api/datasources/${id}`,
url: addAccessControlQueryParam(`/api/datasources/${id}`),
showErrorAlert: false,
})
);