mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access control: expose permissions to the frontend (#32954)
* Expose user permissions to the frontend * Do not include empty scope * Extend ContextSrv with hasPermission() method * Add access control types * Fix type error (make permissions optional) * Fallback if access control disabled * Move UserPermission to types * Simplify hasPermission()
This commit is contained in:
parent
6ae73eaa22
commit
8b843eb0a6
packages
pkg
public/app
@ -46,6 +46,7 @@ export interface FeatureToggles {
|
||||
live: boolean;
|
||||
ngalert: boolean;
|
||||
panelLibrary: boolean;
|
||||
accesscontrol: boolean;
|
||||
|
||||
/**
|
||||
* @remarks
|
||||
|
@ -57,6 +57,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
ngalert: false,
|
||||
panelLibrary: false,
|
||||
reportVariables: false,
|
||||
accesscontrol: false,
|
||||
};
|
||||
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
||||
rendererAvailable = false;
|
||||
|
@ -25,24 +25,27 @@ type LoginCommand struct {
|
||||
}
|
||||
|
||||
type CurrentUser struct {
|
||||
IsSignedIn bool `json:"isSignedIn"`
|
||||
Id int64 `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
OrgCount int `json:"orgCount"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
OrgRole models.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
Timezone string `json:"timezone"`
|
||||
Locale string `json:"locale"`
|
||||
HelpFlags1 models.HelpFlags1 `json:"helpFlags1"`
|
||||
HasEditPermissionInFolders bool `json:"hasEditPermissionInFolders"`
|
||||
IsSignedIn bool `json:"isSignedIn"`
|
||||
Id int64 `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
OrgCount int `json:"orgCount"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
OrgRole models.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
Timezone string `json:"timezone"`
|
||||
Locale string `json:"locale"`
|
||||
HelpFlags1 models.HelpFlags1 `json:"helpFlags1"`
|
||||
HasEditPermissionInFolders bool `json:"hasEditPermissionInFolders"`
|
||||
Permissions UserPermissionsMap `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
type UserPermissionsMap map[string]map[string]string
|
||||
|
||||
type MetricRequest struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -427,6 +428,15 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
||||
ContentDeliveryURL: hs.Cfg.GetContentDeliveryURL(hs.License.ContentDeliveryPrefix()),
|
||||
}
|
||||
|
||||
if hs.Cfg.FeatureToggles["accesscontrol"] {
|
||||
userPermissions, err := hs.AccessControl.GetUserPermissions(c.Req.Context(), c.SignedInUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data.User.Permissions = accesscontrol.BuildPermissionsMap(userPermissions)
|
||||
}
|
||||
|
||||
if setting.DisableGravatar {
|
||||
data.User.GravatarUrl = hs.Cfg.AppSubURL + "/public/img/user_profile.png"
|
||||
}
|
||||
|
@ -16,3 +16,22 @@ type AccessControl interface {
|
||||
// Middleware checks if service disabled or not to switch to fallback authorization.
|
||||
IsDisabled() bool
|
||||
}
|
||||
|
||||
func BuildPermissionsMap(permissions []*Permission) map[string]map[string]string {
|
||||
permissionsMap := make(map[string]map[string]string)
|
||||
for _, p := range permissions {
|
||||
if item, ok := permissionsMap[p.Action]; ok {
|
||||
if _, ok := item[p.Scope]; !ok && p.Scope != "" {
|
||||
permissionsMap[p.Action][p.Scope] = p.Scope
|
||||
}
|
||||
} else {
|
||||
newItem := make(map[string]string)
|
||||
if p.Scope != "" {
|
||||
newItem[p.Scope] = p.Scope
|
||||
}
|
||||
permissionsMap[p.Action] = newItem
|
||||
}
|
||||
}
|
||||
|
||||
return permissionsMap
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import config from '../../core/config';
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { rangeUtil } from '@grafana/data';
|
||||
import { AccessControlAction, AccessControlScope, UserPermission } from 'app/types';
|
||||
|
||||
export class User {
|
||||
id: number;
|
||||
@ -17,6 +18,7 @@ export class User {
|
||||
lightTheme: boolean;
|
||||
hasEditPermissionInFolders: boolean;
|
||||
email?: string;
|
||||
permissions?: UserPermission;
|
||||
|
||||
constructor() {
|
||||
this.id = 0;
|
||||
@ -74,6 +76,16 @@ export class ContextSrv {
|
||||
return this.user.orgRole === role;
|
||||
}
|
||||
|
||||
// Checks whether user has required permission
|
||||
hasPermission(action: AccessControlAction, scope?: AccessControlScope): boolean {
|
||||
// Fallback if access control disabled
|
||||
if (!config.featureToggles['accesscontrol']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!(this.user.permissions?.[action] && (scope ? this.user.permissions[action][scope] : true));
|
||||
}
|
||||
|
||||
isGrafanaVisible() {
|
||||
return !!(document.visibilityState === undefined || document.visibilityState === 'visible');
|
||||
}
|
||||
|
38
public/app/types/accessControl.ts
Normal file
38
public/app/types/accessControl.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* UserPermission is a map storing permissions in a form of
|
||||
* {
|
||||
* action: { scope: scope }
|
||||
* }
|
||||
*/
|
||||
export type UserPermission = {
|
||||
[key: string]: { [key: string]: string };
|
||||
};
|
||||
|
||||
export interface AccessControlPermission {
|
||||
action: AccessControlAction;
|
||||
scope?: AccessControlScope;
|
||||
}
|
||||
|
||||
// Permission actions
|
||||
export enum AccessControlAction {
|
||||
UsersRead = 'users:read',
|
||||
UsersWrite = 'users:write',
|
||||
UsersTeamRead = 'users.teams:read',
|
||||
UsersAuthTokenList = 'users.authtoken:list',
|
||||
UsersAuthTokenUpdate = 'users.authtoken:update',
|
||||
UsersPasswordUpdate = 'users.password.update',
|
||||
UsersDelete = 'users:delete',
|
||||
UsersCreate = 'users:create',
|
||||
UsersEnable = 'users:enable',
|
||||
UsersDisable = 'users:disable',
|
||||
UsersPermissionsUpdate = 'users.permissions.update',
|
||||
UsersLogout = 'users:logout',
|
||||
UsersQuotasList = 'users.quotas:list',
|
||||
UsersQuotasUpdate = 'users.quotas:update',
|
||||
}
|
||||
|
||||
// Global Scopes
|
||||
export enum AccessControlScope {
|
||||
UsersAll = 'users:*',
|
||||
UsersSelf = 'users:self',
|
||||
}
|
@ -17,6 +17,7 @@ export * from './appEvent';
|
||||
export * from './angular';
|
||||
export * from './query';
|
||||
export * from './preferences';
|
||||
export * from './accessControl';
|
||||
|
||||
import * as CoreEvents from './events';
|
||||
export { CoreEvents };
|
||||
|
Loading…
Reference in New Issue
Block a user