Access control: Pass access control metadata for api keys (#48445)

* Move ApiKeyDTO to dtos package

* Add access control filter to api keys

* pass user in GetApiKeysQuery

* Add api key metadata to DTO

* Remove scope all requirement from get api keys endpoint

* Handle api key access control metadata in frondend
This commit is contained in:
Karl Persson
2022-04-29 15:30:24 +02:00
committed by GitHub
parent e9a2a06651
commit 6c6137f45a
13 changed files with 123 additions and 39 deletions

View File

@@ -13,6 +13,15 @@ import { ApiKeysPageUnconnected, Props } from './ApiKeysPage';
import { getMultipleMockKeys } from './__mocks__/apiKeysMock';
import { setSearchQuery } from './state/reducers';
jest.mock('app/core/core', () => {
return {
contextSrv: {
hasPermission: () => true,
hasPermissionInMetadata: () => true,
},
};
});
const setup = (propOverrides: Partial<Props>) => {
const loadApiKeysMock = jest.fn();
const deleteApiKeyMock = jest.fn();
@@ -40,9 +49,7 @@ const setup = (propOverrides: Partial<Props>) => {
includeExpired: false,
includeExpiredDisabled: false,
toggleIncludeExpired: toggleIncludeExpiredMock,
canRead: true,
canCreate: true,
canDelete: true,
};
Object.assign(props, propOverrides);

View File

@@ -24,9 +24,7 @@ import { setSearchQuery } from './state/reducers';
import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './state/selectors';
function mapStateToProps(state: StoreState) {
const canRead = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysRead, true);
const canCreate = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysCreate, true);
const canDelete = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysDelete, true);
return {
navModel: getNavModel(state.navIndex, 'apikeys'),
@@ -37,9 +35,7 @@ function mapStateToProps(state: StoreState) {
timeZone: getTimeZone(state.user),
includeExpired: getIncludeExpired(state.apiKeys),
includeExpiredDisabled: getIncludeExpiredDisabled(state.apiKeys),
canRead: canRead,
canCreate: canCreate,
canDelete: canDelete,
};
}
@@ -130,9 +126,7 @@ export class ApiKeysPageUnconnected extends PureComponent<Props, State> {
timeZone,
includeExpired,
includeExpiredDisabled,
canRead,
canCreate,
canDelete,
} = this.props;
if (!hasFetched) {
@@ -181,13 +175,7 @@ export class ApiKeysPageUnconnected extends PureComponent<Props, State> {
<InlineField disabled={includeExpiredDisabled} label="Include expired keys">
<InlineSwitch id="showExpired" value={includeExpired} onChange={this.onIncludeExpiredChange} />
</InlineField>
<ApiKeysTable
apiKeys={apiKeys}
timeZone={timeZone}
onDelete={this.onDeleteApiKey}
canRead={canRead}
canDelete={canDelete}
/>
<ApiKeysTable apiKeys={apiKeys} timeZone={timeZone} onDelete={this.onDeleteApiKey} />
</VerticalGroup>
) : null}
</>

View File

@@ -3,6 +3,8 @@ import React, { FC } from 'react';
import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data';
import { DeleteButton, Icon, IconName, Tooltip, useTheme2 } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { AccessControlAction } from 'app/types';
import { ApiKey } from '../../types';
@@ -10,11 +12,9 @@ interface Props {
apiKeys: ApiKey[];
timeZone: TimeZone;
onDelete: (apiKey: ApiKey) => void;
canRead: boolean;
canDelete: boolean;
}
export const ApiKeysTable: FC<Props> = ({ apiKeys, timeZone, onDelete, canRead, canDelete }) => {
export const ApiKeysTable: FC<Props> = ({ apiKeys, timeZone, onDelete }) => {
const theme = useTheme2();
const styles = getStyles(theme);
@@ -28,7 +28,7 @@ export const ApiKeysTable: FC<Props> = ({ apiKeys, timeZone, onDelete, canRead,
<th style={{ width: '34px' }} />
</tr>
</thead>
{canRead && apiKeys.length > 0 ? (
{apiKeys.length > 0 ? (
<tbody>
{apiKeys.map((key) => {
const isExpired = Boolean(key.expiration && Date.now() > new Date(key.expiration).getTime());
@@ -51,7 +51,7 @@ export const ApiKeysTable: FC<Props> = ({ apiKeys, timeZone, onDelete, canRead,
aria-label="Delete API key"
size="sm"
onConfirm={() => onDelete(key)}
disabled={!canDelete}
disabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.ActionAPIKeysDelete, key)}
/>
</td>
</tr>

View File

@@ -16,8 +16,8 @@ export function loadApiKeys(): ThunkResult<void> {
return async (dispatch) => {
dispatch(isFetching());
const [keys, keysIncludingExpired] = await Promise.all([
getBackendSrv().get('/api/auth/keys?includeExpired=false'),
getBackendSrv().get('/api/auth/keys?includeExpired=true'),
getBackendSrv().get('/api/auth/keys?includeExpired=false&accesscontrol=true'),
getBackendSrv().get('/api/auth/keys?includeExpired=true&accesscontrol=true'),
]);
dispatch(apiKeysLoaded({ keys, keysIncludingExpired }));
};