Plugins: Expose functions to plugins for checking RBAC permissions (#89047)

* feat(grafana-data): create rbac functions for checking permissions

* feat(grafana-runtime): pass current user to runtime

* feat(grafana-runtime): expose rbac functions to check permissions against current user

* refactor(contextsrv): use functions from grafana/data to check rbac permissions against user

* Apply suggestions from code review

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* chore(rbac): fix missing types imports

* refactor(rbac): make exposed functions return boolean

---------

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
This commit is contained in:
Jack Westbrook 2024-06-26 17:29:17 +02:00 committed by GitHub
parent 7f4faaa45b
commit 40207c53ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 89 additions and 4 deletions

View File

@ -50,3 +50,10 @@ export { CircularVector } from './vector/CircularVector';
export { vectorator } from './vector/FunctionalVector';
export { ArrayVector } from './vector/ArrayVector';
export * from './dataframe/CircularDataFrame';
export {
type CurrentUser,
userHasPermission,
userHasPermissionInMetadata,
userHasAllPermissions,
userHasAnyPermission,
} from './rbac/rbac';

View File

@ -0,0 +1,19 @@
import { CurrentUserDTO, WithAccessControlMetadata } from '../types';
export interface CurrentUser extends Omit<CurrentUserDTO, 'lightTheme'> {}
export function userHasPermission(action: string, user: CurrentUser): boolean {
return !!user.permissions?.[action];
}
export function userHasPermissionInMetadata(action: string, object: WithAccessControlMetadata): boolean {
return !!object.accessControl?.[action];
}
export function userHasAllPermissions(actions: string[], user: CurrentUser) {
return actions.every((action) => userHasPermission(action, user));
}
export function userHasAnyPermission(actions: string[], user: CurrentUser) {
return actions.some((action) => userHasPermission(action, user));
}

View File

@ -52,3 +52,4 @@ export {
export { usePluginInteractionReporter } from './analytics/plugins/usePluginInteractionReporter';
export { setReturnToPreviousHook, useReturnToPrevious } from './utils/returnToPrevious';
export { type EmbeddedDashboardProps, EmbeddedDashboard, setEmbeddedDashboard } from './components/EmbeddedDashboard';
export { hasPermission, hasPermissionInMetadata, hasAllPermissions, hasAnyPermission } from './utils/rbac';

View File

@ -33,3 +33,4 @@ export {
export { setPluginComponentHook, usePluginComponent } from './pluginExtensions/usePluginComponent';
export { isPluginExtensionLink, isPluginExtensionComponent } from './pluginExtensions/utils';
export { setCurrentUser } from './user';

View File

@ -0,0 +1,29 @@
import { CurrentUser } from '@grafana/data';
let singletonInstance: CurrentUser | null = null;
/**
* Used during startup by Grafana to set the current user so it is available
* for rbac checks.
*
* @internal
*/
export function setCurrentUser(instance: CurrentUser) {
if (singletonInstance) {
throw new Error('User should only be set once, when Grafana is starting.');
}
singletonInstance = instance;
}
/**
* Used to retrieve the current user.
*
* @internal
*
*/
export function getCurrentUser(): CurrentUser {
if (!singletonInstance) {
throw new Error('User can only be used after Grafana instance has started.');
}
return singletonInstance;
}

View File

@ -0,0 +1,18 @@
import {
userHasPermission,
userHasPermissionInMetadata,
userHasAllPermissions,
userHasAnyPermission,
WithAccessControlMetadata,
} from '@grafana/data';
import { getCurrentUser } from '../services/user';
export const hasPermission = (action: string) => userHasPermission(action, getCurrentUser());
export const hasPermissionInMetadata = (action: string, object: WithAccessControlMetadata) =>
userHasPermissionInMetadata(action, object);
export const hasAllPermissions = (actions: string[]) => userHasAllPermissions(actions, getCurrentUser());
export const hasAnyPermission = (actions: string[]) => userHasAnyPermission(actions, getCurrentUser());

View File

@ -38,6 +38,7 @@ import {
setReturnToPreviousHook,
setPluginExtensionsHook,
setPluginComponentHook,
setCurrentUser,
} from '@grafana/runtime';
import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView';
import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer';
@ -144,6 +145,7 @@ export class GrafanaApp {
setEmbeddedDashboard(EmbeddedDashboardLazy);
setTimeZoneResolver(() => config.bootData.user.timezone);
initGrafanaLive();
setCurrentUser(contextSrv.user);
initAuthConfig();

View File

@ -1,6 +1,14 @@
import { extend } from 'lodash';
import { AnalyticsSettings, OrgRole, rangeUtil, WithAccessControlMetadata } from '@grafana/data';
import {
AnalyticsSettings,
OrgRole,
rangeUtil,
WithAccessControlMetadata,
userHasPermission,
userHasPermissionInMetadata,
userHasAnyPermission,
} from '@grafana/data';
import { featureEnabled, getBackendSrv } from '@grafana/runtime';
import { getSessionExpiry } from 'app/core/utils/auth';
import { AccessControlAction, UserPermission } from 'app/types';
@ -131,12 +139,12 @@ export class ContextSrv {
// Checks whether user has required permission
hasPermissionInMetadata(action: AccessControlAction | string, object: WithAccessControlMetadata): boolean {
return !!object.accessControl?.[action];
return userHasPermissionInMetadata(action, object);
}
// Checks whether user has required permission
hasPermission(action: AccessControlAction | string): boolean {
return !!this.user.permissions?.[action];
return userHasPermission(action, this.user);
}
isGrafanaVisible() {
@ -171,7 +179,7 @@ export class ContextSrv {
// evaluates access control permissions, granting access if the user has any of them
evaluatePermission(actions: string[]) {
if (actions.some((action) => this.hasPermission(action))) {
if (userHasAnyPermission(actions, this.user)) {
return [];
}
// Hack to reject when user does not have permission