mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user