diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index e1c3eed6acd..9f948866810 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -174,19 +174,15 @@ func (hs *HTTPServer) declareFixedRoles() error { DisplayName: "APIKeys writer", Description: "Gives access to add and delete api keys.", Group: "API Keys", - Permissions: []ac.Permission{ + Permissions: ac.ConcatPermissions(apikeyReaderRole.Role.Permissions, []ac.Permission{ { Action: ac.ActionAPIKeyCreate, }, - { - Action: ac.ActionAPIKeyRead, - Scope: ac.ScopeAPIKeysAll, - }, { Action: ac.ActionAPIKeyDelete, Scope: ac.ScopeAPIKeysAll, }, - }, + }), }, Grants: []string{string(models.ROLE_ADMIN)}, } diff --git a/pkg/services/serviceaccounts/manager/roles.go b/pkg/services/serviceaccounts/manager/roles.go index 7c323043a95..b0164c2319a 100644 --- a/pkg/services/serviceaccounts/manager/roles.go +++ b/pkg/services/serviceaccounts/manager/roles.go @@ -31,11 +31,7 @@ func RegisterRoles(ac accesscontrol.AccessControl) error { DisplayName: "Service accounts writer", Description: "Create, delete, read, or query service accounts.", Group: "Service accounts", - Permissions: []accesscontrol.Permission{ - { - Action: serviceaccounts.ActionRead, - Scope: serviceaccounts.ScopeAll, - }, + Permissions: accesscontrol.ConcatPermissions(saReader.Role.Permissions, []accesscontrol.Permission{ { Action: serviceaccounts.ActionWrite, Scope: serviceaccounts.ScopeAll, @@ -47,7 +43,7 @@ func RegisterRoles(ac accesscontrol.AccessControl) error { Action: serviceaccounts.ActionDelete, Scope: serviceaccounts.ScopeAll, }, - }, + }), }, Grants: []string{string(models.ROLE_ADMIN)}, } diff --git a/public/app/features/api-keys/ApiKeysForm.tsx b/public/app/features/api-keys/ApiKeysForm.tsx index 9ad93b70f2a..1bf8c97c4e2 100644 --- a/public/app/features/api-keys/ApiKeysForm.tsx +++ b/public/app/features/api-keys/ApiKeysForm.tsx @@ -15,6 +15,7 @@ interface Props { show: boolean; onClose: () => void; onKeyAdded: (apiKey: NewApiKey) => void; + disabled: boolean; } function isValidInterval(value: string): boolean { @@ -40,7 +41,7 @@ const timeRangeValidationEvents: ValidationEvents = { const tooltipText = 'The API key life duration. For example, 1d if your key is going to last for one day. Supported units are: s,m,h,d,w,M,y'; -export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded }) => { +export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded, disabled }) => { const [name, setName] = useState(''); const [role, setRole] = useState(OrgRole.Viewer); const [secondsToLive, setSecondsToLive] = useState(''); @@ -102,7 +103,7 @@ export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded }) => {
- +
diff --git a/public/app/features/api-keys/ApiKeysPage.test.tsx b/public/app/features/api-keys/ApiKeysPage.test.tsx index e7adf743e62..4b33ab79096 100644 --- a/public/app/features/api-keys/ApiKeysPage.test.tsx +++ b/public/app/features/api-keys/ApiKeysPage.test.tsx @@ -37,6 +37,9 @@ const setup = (propOverrides: Partial) => { includeExpired: false, includeExpiredDisabled: false, toggleIncludeExpired: toggleIncludeExpiredMock, + canRead: true, + canCreate: true, + canDelete: true, }; Object.assign(props, propOverrides); diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 6e3d8273275..4d78f5add1f 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; // Utils -import { ApiKey, NewApiKey, StoreState } from 'app/types'; +import { AccessControlAction, ApiKey, NewApiKey, StoreState } from 'app/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './state/selectors'; import { addApiKey, deleteApiKey, loadApiKeys, toggleIncludeExpired } from './state/actions'; @@ -19,8 +19,13 @@ import { ApiKeysActionBar } from './ApiKeysActionBar'; import { ApiKeysTable } from './ApiKeysTable'; import { ApiKeysController } from './ApiKeysController'; import { ShowModalReactEvent } from 'app/types/events'; +import { contextSrv } from 'app/core/core'; 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'), apiKeys: getApiKeys(state.apiKeys), @@ -30,6 +35,9 @@ function mapStateToProps(state: StoreState) { timeZone: getTimeZone(state.user), includeExpired: getIncludeExpired(state.apiKeys), includeExpiredDisabled: getIncludeExpiredDisabled(state.apiKeys), + canRead: canRead, + canCreate: canCreate, + canDelete: canDelete, }; } @@ -120,6 +128,9 @@ export class ApiKeysPageUnconnected extends PureComponent { timeZone, includeExpired, includeExpiredDisabled, + canRead, + canCreate, + canDelete, } = this.props; if (!hasFetched) { @@ -146,23 +157,35 @@ export class ApiKeysPageUnconnected extends PureComponent { onClick={toggleIsAdding} buttonTitle="New API key" proTip="Remember, you can provide view-only API access to other applications." + buttonDisabled={!canCreate} /> ) : null} {showTable ? ( ) : null} - + {showTable ? ( - + ) : null} diff --git a/public/app/features/api-keys/ApiKeysTable.tsx b/public/app/features/api-keys/ApiKeysTable.tsx index 06445bf09e1..ed063b1392e 100644 --- a/public/app/features/api-keys/ApiKeysTable.tsx +++ b/public/app/features/api-keys/ApiKeysTable.tsx @@ -9,9 +9,11 @@ interface Props { apiKeys: ApiKey[]; timeZone: TimeZone; onDelete: (apiKey: ApiKey) => void; + canRead: boolean; + canDelete: boolean; } -export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete }) => { +export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete, canRead, canDelete }) => { const theme = useTheme2(); const styles = getStyles(theme); @@ -25,7 +27,7 @@ export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete }) => { - {apiKeys.length > 0 ? ( + {canRead && apiKeys.length > 0 ? ( {apiKeys.map((key) => { const isExpired = Boolean(key.expiration && Date.now() > new Date(key.expiration).getTime()); @@ -44,7 +46,12 @@ export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete }) => { )} - onDelete(key)} /> + onDelete(key)} + disabled={!canDelete} + /> ); diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts index 969985ba21a..12d2341559f 100644 --- a/public/app/types/accessControl.ts +++ b/public/app/types/accessControl.ts @@ -110,6 +110,8 @@ export enum AccessControlAction { AlertingNotificationsExternalRead = 'alert.notifications.external:read', ActionAPIKeysRead = 'apikeys:read', + ActionAPIKeysCreate = 'apikeys:create', + ActionAPIKeysDelete = 'apikeys:delete', } export interface Role {