oncall-engine/grafana-plugin/src/utils/authorization/index.ts
Michael Derynck dd713bac55
Change permissions used for finishing plugin setup (#2242)
Fixes issue where user not having `plugins:install`permission were
unable to complete setup of OnCall.

- Check multiple Grafana permissions to complete OnCall setup instead of
`plugins:install` since the plugin is already installed at this point
- Use the following permissions
  - `plugins:write` - Plugin setup will write to plugin config
- `users:read` - Grafana API key being granted to OnCall will be used to
read users from Grafana
- `teams:read` - Grafana API key being granted to OnCall will be used to
read teams from Grafana
- `apikeys:create` - If Grafana API key does not exist it will be
created
- `apikeys:delete` - If existing Grafana API key does not work it will
be deleted and recreated

Closes https://github.com/grafana/oncall-private/issues/1925

TODO:
- [x] Fix tests
2023-06-26 22:22:13 +00:00

164 lines
6.4 KiB
TypeScript

import { OrgRole } from '@grafana/data';
import { config } from '@grafana/runtime';
import { contextSrv } from 'grafana/app/core/core';
const ONCALL_PERMISSION_PREFIX = 'grafana-oncall-app';
export type UserAction = {
permission: string;
fallbackMinimumRoleRequired: OrgRole;
};
export enum Resource {
ALERT_GROUPS = 'alert-groups',
INTEGRATIONS = 'integrations',
ESCALATION_CHAINS = 'escalation-chains',
SCHEDULES = 'schedules',
CHATOPS = 'chatops',
OUTGOING_WEBHOOKS = 'outgoing-webhooks',
MAINTENANCE = 'maintenance',
API_KEYS = 'api-keys',
NOTIFICATIONS = 'notifications',
NOTIFICATION_SETTINGS = 'notification-settings',
USER_SETTINGS = 'user-settings',
OTHER_SETTINGS = 'other-settings',
TEAMS = 'teams',
}
export enum Action {
READ = 'read',
WRITE = 'write',
ADMIN = 'admin',
TEST = 'test',
EXPORT = 'export',
UPDATE_SETTINGS = 'update-settings',
}
type Actions =
| 'AlertGroupsRead'
| 'AlertGroupsWrite'
| 'IntegrationsRead'
| 'IntegrationsWrite'
| 'IntegrationsTest'
| 'EscalationChainsRead'
| 'EscalationChainsWrite'
| 'SchedulesRead'
| 'SchedulesWrite'
| 'SchedulesExport'
| 'ChatOpsRead'
| 'ChatOpsWrite'
| 'ChatOpsUpdateSettings'
| 'OutgoingWebhooksRead'
| 'OutgoingWebhooksWrite'
| 'MaintenanceRead'
| 'MaintenanceWrite'
| 'APIKeysRead'
| 'APIKeysWrite'
| 'NotificationsRead'
| 'NotificationSettingsRead'
| 'NotificationSettingsWrite'
| 'UserSettingsRead'
| 'UserSettingsWrite'
| 'UserSettingsAdmin'
| 'OtherSettingsRead'
| 'OtherSettingsWrite'
| 'TeamsWrite';
const roleMapping: Record<OrgRole, number> = {
[OrgRole.Admin]: 0,
[OrgRole.Editor]: 1,
[OrgRole.Viewer]: 2,
};
/**
* The logic here is:
* - an Admin should be able to do everything (including whatever an Editor and Viewer can do)
* - an Editor should be able to do things Editors and Viewers can do
* - a Viewer is only allowed to do things Viewers can do
*/
export const userHasMinimumRequiredRole = (minimumRoleRequired: OrgRole): boolean =>
roleMapping[contextSrv.user.orgRole] <= roleMapping[minimumRoleRequired];
/**
* See here for more info on the hasAccess method
* https://github.com/grafana/grafana/blob/main/public/app/core/services/context_srv.ts#L165-L170
*
* As a fallback (second argument), for cases where RBAC is not enabled for a grafana instance, rely on basic roles
*/
export const isUserActionAllowed = ({ permission, fallbackMinimumRoleRequired }: UserAction): boolean =>
config.featureToggles.accessControlOnCall
? !!contextSrv.user.permissions?.[permission]
: userHasMinimumRequiredRole(fallbackMinimumRoleRequired);
/**
* Given a `UserAction`, returns the permission or fallback-role, prefixed with "permission" or "role" respectively
* depending on whether or not RBAC is enabled/disabled
*/
export const determineRequiredAuthString = ({ permission, fallbackMinimumRoleRequired }: UserAction): string =>
config.featureToggles.accessControlOnCall ? `${permission} permission` : `${fallbackMinimumRoleRequired} role`;
/**
* Can be used to generate a user-friendly message about which permission is missing. Method is RBAC-aware
* and shows user the missing permission/basic-role depending on whether or not RBAC is enabled/disabled
*/
export const generateMissingPermissionMessage = (permission: UserAction): string =>
`You are missing the ${determineRequiredAuthString(permission)}`;
export const generatePermissionString = (resource: Resource, action: Action, includePrefix: boolean): string =>
`${includePrefix ? `${ONCALL_PERMISSION_PREFIX}.` : ''}${resource}:${action}`;
const constructAction = (
resource: Resource,
action: Action,
fallbackMinimumRoleRequired: OrgRole,
includePrefix = true
): UserAction => ({
permission: generatePermissionString(resource, action, includePrefix),
fallbackMinimumRoleRequired,
});
export const UserActions: { [action in Actions]: UserAction } = {
AlertGroupsRead: constructAction(Resource.ALERT_GROUPS, Action.READ, OrgRole.Viewer),
AlertGroupsWrite: constructAction(Resource.ALERT_GROUPS, Action.WRITE, OrgRole.Editor),
IntegrationsRead: constructAction(Resource.INTEGRATIONS, Action.READ, OrgRole.Viewer),
IntegrationsWrite: constructAction(Resource.INTEGRATIONS, Action.WRITE, OrgRole.Admin),
IntegrationsTest: constructAction(Resource.INTEGRATIONS, Action.TEST, OrgRole.Editor),
EscalationChainsRead: constructAction(Resource.ESCALATION_CHAINS, Action.READ, OrgRole.Viewer),
EscalationChainsWrite: constructAction(Resource.ESCALATION_CHAINS, Action.WRITE, OrgRole.Admin),
SchedulesRead: constructAction(Resource.SCHEDULES, Action.READ, OrgRole.Viewer),
SchedulesWrite: constructAction(Resource.SCHEDULES, Action.WRITE, OrgRole.Editor),
SchedulesExport: constructAction(Resource.SCHEDULES, Action.WRITE, OrgRole.Editor),
ChatOpsRead: constructAction(Resource.CHATOPS, Action.READ, OrgRole.Viewer),
ChatOpsWrite: constructAction(Resource.CHATOPS, Action.WRITE, OrgRole.Editor),
ChatOpsUpdateSettings: constructAction(Resource.CHATOPS, Action.UPDATE_SETTINGS, OrgRole.Admin),
OutgoingWebhooksRead: constructAction(Resource.OUTGOING_WEBHOOKS, Action.READ, OrgRole.Viewer),
OutgoingWebhooksWrite: constructAction(Resource.OUTGOING_WEBHOOKS, Action.WRITE, OrgRole.Admin),
MaintenanceRead: constructAction(Resource.MAINTENANCE, Action.READ, OrgRole.Viewer),
MaintenanceWrite: constructAction(Resource.MAINTENANCE, Action.WRITE, OrgRole.Editor),
APIKeysRead: constructAction(Resource.API_KEYS, Action.READ, OrgRole.Admin),
APIKeysWrite: constructAction(Resource.API_KEYS, Action.WRITE, OrgRole.Admin),
NotificationsRead: constructAction(Resource.NOTIFICATIONS, Action.READ, OrgRole.Editor),
NotificationSettingsRead: constructAction(Resource.NOTIFICATION_SETTINGS, Action.READ, OrgRole.Viewer),
NotificationSettingsWrite: constructAction(Resource.NOTIFICATION_SETTINGS, Action.WRITE, OrgRole.Editor),
UserSettingsRead: constructAction(Resource.USER_SETTINGS, Action.READ, OrgRole.Viewer),
UserSettingsWrite: constructAction(Resource.USER_SETTINGS, Action.WRITE, OrgRole.Editor),
UserSettingsAdmin: constructAction(Resource.USER_SETTINGS, Action.ADMIN, OrgRole.Admin),
OtherSettingsRead: constructAction(Resource.OTHER_SETTINGS, Action.READ, OrgRole.Viewer),
OtherSettingsWrite: constructAction(Resource.OTHER_SETTINGS, Action.WRITE, OrgRole.Admin),
// These are not oncall specific
TeamsWrite: constructAction(Resource.TEAMS, Action.WRITE, OrgRole.Admin, false),
};