Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change permissions used for finishing plugin setup #2242

Merged
merged 11 commits into from
Jun 26, 2023
55 changes: 38 additions & 17 deletions grafana-plugin/src/state/rootBaseStore/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { OrgRole } from '@grafana/data';
import { contextSrv } from 'grafana/app/core/core';
import { action, observable } from 'mobx';
import moment from 'moment-timezone';
import qs from 'query-string';
Expand Down Expand Up @@ -32,7 +34,6 @@ import { UserGroupStore } from 'models/user_group/user_group';
import { makeRequest } from 'network';
import { AppFeature } from 'state/features';
import PluginState from 'state/plugin';
import { isUserActionAllowed, UserActions } from 'utils/authorization';
import { GRAFANA_LICENSE_OSS } from 'utils/consts';

// ------ Dashboard ------ //
Expand Down Expand Up @@ -184,22 +185,42 @@ export class RootBaseStore {
if (!allow_signup) {
return this.setupPluginError('🚫 OnCall has temporarily disabled signup of new users. Please try again later.');
}

if (!isUserActionAllowed(UserActions.PluginsInstall)) {
return this.setupPluginError(
'🚫 An Admin in your organization must sign on and setup OnCall before it can be used'
);
}

try {
/**
* this will install AND sync the necessary data
* the sync is done automatically by the /plugin/install OnCall API endpoint
* therefore there is no need to trigger an additional/separate sync, nor poll a status
*/
await PluginState.installPlugin();
} catch (e) {
return this.setupPluginError(PluginState.getHumanReadableErrorFromOnCallError(e, this.onCallApiUrl, 'install'));
const fallback = contextSrv.user.orgRole === OrgRole.Admin && !contextSrv.accessControlEnabled();
const setupRequiredPermissions = [
'plugins:write',
'users:read',
'teams:read',
'apikeys:create',
'apikeys:delete',
];
const missingPermissions = setupRequiredPermissions.filter(function (permission) {
return !contextSrv.hasAccess(permission, fallback);
});
if (missingPermissions.length === 0) {
try {
/**
* this will install AND sync the necessary data
* the sync is done automatically by the /plugin/install OnCall API endpoint
* therefore there is no need to trigger an additional/separate sync, nor poll a status
*/
await PluginState.installPlugin();
} catch (e) {
return this.setupPluginError(
PluginState.getHumanReadableErrorFromOnCallError(e, this.onCallApiUrl, 'install')
);
}
} else {
if (contextSrv.accessControlEnabled()) {
return this.setupPluginError(
'🚫 User is missing permission(s) ' +
missingPermissions.join(', ') +
' to setup OnCall before it can be used'
);
} else {
return this.setupPluginError(
'🚫 User with Admin permissions in your organization must sign on and setup OnCall before it can be used'
);
}
mderynck marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
const syncDataResponse = await PluginState.syncDataWithOnCall(this.onCallApiUrl);
Expand Down
15 changes: 5 additions & 10 deletions grafana-plugin/src/state/rootBaseStore/rootBaseStore.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OnCallAppPluginMeta } from 'types';

import PluginState from 'state/plugin';
import { UserActions, isUserActionAllowed as isUserActionAllowedOriginal } from 'utils/authorization';
import { isUserActionAllowed as isUserActionAllowedOriginal } from 'utils/authorization';

import { RootBaseStore } from './';

Expand All @@ -10,8 +10,6 @@ jest.mock('utils/authorization');

const isUserActionAllowed = isUserActionAllowedOriginal as jest.Mock<ReturnType<typeof isUserActionAllowedOriginal>>;

const PluginInstallAction = UserActions.PluginsInstall;

const generatePluginData = (
onCallApiUrl: OnCallAppPluginMeta['jsonData']['onCallApiUrl'] = null
): OnCallAppPluginMeta =>
Expand Down Expand Up @@ -159,14 +157,13 @@ describe('rootBaseStore', () => {
expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1);
expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl);

expect(isUserActionAllowed).toHaveBeenCalledTimes(1);
expect(isUserActionAllowed).toHaveBeenCalledWith(PluginInstallAction);
// todo: add check for access

expect(PluginState.installPlugin).toHaveBeenCalledTimes(0);

expect(rootBaseStore.appLoading).toBe(false);
expect(rootBaseStore.initializationError).toEqual(
'🚫 An Admin in your organization must sign on and setup OnCall before it can be used'
'🚫 User with Admin permissions in your organization must sign on and setup OnCall before it can be used'
);
});

Expand Down Expand Up @@ -198,8 +195,7 @@ describe('rootBaseStore', () => {
expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1);
expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl);

expect(isUserActionAllowed).toHaveBeenCalledTimes(1);
expect(isUserActionAllowed).toHaveBeenCalledWith(PluginInstallAction);
// todo: add check for access

expect(PluginState.installPlugin).toHaveBeenCalledTimes(1);
expect(PluginState.installPlugin).toHaveBeenCalledWith();
Expand Down Expand Up @@ -238,8 +234,7 @@ describe('rootBaseStore', () => {
expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1);
expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl);

expect(isUserActionAllowed).toHaveBeenCalledTimes(1);
expect(isUserActionAllowed).toHaveBeenCalledWith(PluginInstallAction);
// todo: add check for access

expect(PluginState.installPlugin).toHaveBeenCalledTimes(1);
expect(PluginState.installPlugin).toHaveBeenCalledWith();
Expand Down
5 changes: 1 addition & 4 deletions grafana-plugin/src/utils/authorization/index.ts
mderynck marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export enum Action {
TEST = 'test',
EXPORT = 'export',
UPDATE_SETTINGS = 'update-settings',
INSTALL = 'install',
}

type Actions =
Expand Down Expand Up @@ -66,8 +65,7 @@ type Actions =
| 'UserSettingsAdmin'
| 'OtherSettingsRead'
| 'OtherSettingsWrite'
| 'TeamsWrite'
| 'PluginsInstall';
| 'TeamsWrite';

const roleMapping: Record<OrgRole, number> = {
[OrgRole.Admin]: 0,
Expand Down Expand Up @@ -164,5 +162,4 @@ export const UserActions: { [action in Actions]: UserAction } = {

// These are not oncall specific
TeamsWrite: constructAction(Resource.TEAMS, Action.WRITE, OrgRole.Admin, false),
PluginsInstall: constructAction(Resource.PLUGINS, Action.INSTALL, OrgRole.Admin, false),
};