mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Fix plugins pages access-control (#76321)
* RBAC: Fix plugins pages access-control * Better comment Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Add a small comment on connections/datasources routes --------- Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
@@ -124,18 +124,19 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
r.Get("/live/pipeline", reqGrafanaAdmin, hs.Index)
|
r.Get("/live/pipeline", reqGrafanaAdmin, hs.Index)
|
||||||
r.Get("/live/cloud", reqGrafanaAdmin, hs.Index)
|
r.Get("/live/cloud", reqGrafanaAdmin, hs.Index)
|
||||||
|
|
||||||
r.Get("/plugins", middleware.CanAdminPlugins(hs.Cfg), hs.Index)
|
r.Get("/plugins", middleware.CanAdminPlugins(hs.Cfg, hs.AccessControl), hs.Index)
|
||||||
r.Get("/plugins/:id/", middleware.CanAdminPlugins(hs.Cfg), hs.Index)
|
r.Get("/plugins/:id/", middleware.CanAdminPlugins(hs.Cfg, hs.AccessControl), hs.Index)
|
||||||
r.Get("/plugins/:id/edit", middleware.CanAdminPlugins(hs.Cfg), hs.Index) // deprecated
|
r.Get("/plugins/:id/edit", middleware.CanAdminPlugins(hs.Cfg, hs.AccessControl), hs.Index) // deprecated
|
||||||
r.Get("/plugins/:id/page/:page", middleware.CanAdminPlugins(hs.Cfg), hs.Index)
|
r.Get("/plugins/:id/page/:page", middleware.CanAdminPlugins(hs.Cfg, hs.AccessControl), hs.Index)
|
||||||
|
|
||||||
r.Get("/connections/datasources", authorize(datasources.ConfigurationPageAccess), hs.Index)
|
r.Get("/connections/datasources", authorize(datasources.ConfigurationPageAccess), hs.Index)
|
||||||
r.Get("/connections/datasources/new", authorize(datasources.NewPageAccess), hs.Index)
|
r.Get("/connections/datasources/new", authorize(datasources.NewPageAccess), hs.Index)
|
||||||
r.Get("/connections/datasources/edit/*", authorize(datasources.EditPageAccess), hs.Index)
|
r.Get("/connections/datasources/edit/*", authorize(datasources.EditPageAccess), hs.Index)
|
||||||
r.Get("/connections", authorize(datasources.ConfigurationPageAccess), hs.Index)
|
r.Get("/connections", authorize(datasources.ConfigurationPageAccess), hs.Index)
|
||||||
r.Get("/connections/add-new-connection", authorize(datasources.ConfigurationPageAccess), hs.Index)
|
r.Get("/connections/add-new-connection", authorize(datasources.ConfigurationPageAccess), hs.Index)
|
||||||
r.Get("/connections/datasources/:id", middleware.CanAdminPlugins(hs.Cfg), hs.Index)
|
// Plugin details pages
|
||||||
r.Get("/connections/datasources/:id/page/:page", middleware.CanAdminPlugins(hs.Cfg), hs.Index)
|
r.Get("/connections/datasources/:id", middleware.CanAdminPlugins(hs.Cfg, hs.AccessControl), hs.Index)
|
||||||
|
r.Get("/connections/datasources/:id/page/:page", middleware.CanAdminPlugins(hs.Cfg, hs.AccessControl), hs.Index)
|
||||||
|
|
||||||
// App Root Page
|
// App Root Page
|
||||||
appPluginIDScope := pluginaccesscontrol.ScopeProvider.GetResourceScope(ac.Parameter(":id"))
|
appPluginIDScope := pluginaccesscontrol.ScopeProvider.GetResourceScope(ac.Parameter(":id"))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/middleware/cookies"
|
"github.com/grafana/grafana/pkg/middleware/cookies"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
@@ -86,9 +87,10 @@ func removeForceLoginParams(str string) string {
|
|||||||
return forceLoginParamsRegexp.ReplaceAllString(str, "")
|
return forceLoginParamsRegexp.ReplaceAllString(str, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanAdminPlugins(cfg *setting.Cfg) func(c *contextmodel.ReqContext) {
|
func CanAdminPlugins(cfg *setting.Cfg, accessControl ac.AccessControl) func(c *contextmodel.ReqContext) {
|
||||||
return func(c *contextmodel.ReqContext) {
|
return func(c *contextmodel.ReqContext) {
|
||||||
if !pluginaccesscontrol.ReqCanAdminPlugins(cfg)(c) {
|
hasAccess := ac.HasAccess(accessControl, c)
|
||||||
|
if !pluginaccesscontrol.ReqCanAdminPlugins(cfg)(c) && !hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
||||||
accessForbidden(c)
|
accessForbidden(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
|||||||
orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead)
|
orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead)
|
||||||
authConfigUIAvailable := s.license.FeatureEnabled("saml") || s.cfg.LDAPAuthEnabled
|
authConfigUIAvailable := s.license.FeatureEnabled("saml") || s.cfg.LDAPAuthEnabled
|
||||||
|
|
||||||
// FIXME: while we don't have a permissions for listing plugins the legacy check has to stay as a default
|
// FIXME: If plugin admin is disabled or externally managed, server admins still need to access the page, this is why
|
||||||
|
// while we don't have a permissions for listing plugins the legacy check has to stay as a default
|
||||||
if pluginaccesscontrol.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
if pluginaccesscontrol.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
||||||
configNodes = append(configNodes, &navtree.NavLink{
|
configNodes = append(configNodes, &navtree.NavLink{
|
||||||
Text: "Plugins",
|
Text: "Plugins",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
|
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
|
||||||
|
import { contextSrv } from 'app/core/core';
|
||||||
import { RouteDescriptor } from 'app/core/navigation/types';
|
import { RouteDescriptor } from 'app/core/navigation/types';
|
||||||
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
import { PluginAdminRoutes } from './types';
|
import { PluginAdminRoutes } from './types';
|
||||||
|
|
||||||
@@ -7,26 +9,49 @@ const DEFAULT_ROUTES = [
|
|||||||
{
|
{
|
||||||
path: '/plugins',
|
path: '/plugins',
|
||||||
navId: 'plugins',
|
navId: 'plugins',
|
||||||
roles: () => ['Admin', 'ServerAdmin'],
|
roles: evaluateAccess(
|
||||||
|
[AccessControlAction.PluginsInstall, AccessControlAction.PluginsWrite],
|
||||||
|
['Admin', 'ServerAdmin']
|
||||||
|
),
|
||||||
routeName: PluginAdminRoutes.Home,
|
routeName: PluginAdminRoutes.Home,
|
||||||
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')),
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/plugins/browse',
|
path: '/plugins/browse',
|
||||||
navId: 'plugins',
|
navId: 'plugins',
|
||||||
roles: () => ['Admin', 'ServerAdmin'],
|
roles: evaluateAccess(
|
||||||
|
[AccessControlAction.PluginsInstall, AccessControlAction.PluginsWrite],
|
||||||
|
['Admin', 'ServerAdmin']
|
||||||
|
),
|
||||||
routeName: PluginAdminRoutes.Browse,
|
routeName: PluginAdminRoutes.Browse,
|
||||||
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')),
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/plugins/:pluginId/',
|
path: '/plugins/:pluginId/',
|
||||||
navId: 'plugins',
|
navId: 'plugins',
|
||||||
roles: () => ['Admin', 'ServerAdmin'],
|
roles: evaluateAccess(
|
||||||
|
[AccessControlAction.PluginsInstall, AccessControlAction.PluginsWrite],
|
||||||
|
['Admin', 'ServerAdmin']
|
||||||
|
),
|
||||||
routeName: PluginAdminRoutes.Details,
|
routeName: PluginAdminRoutes.Details,
|
||||||
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './pages/PluginDetails')),
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './pages/PluginDetails')),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// FIXME: If plugin admin is disabled or externally managed, server admins still need to access the page, this is why
|
||||||
|
// while we don't have a permissions for listing plugins the legacy check has to stay as a default
|
||||||
|
function evaluateAccess(actions: AccessControlAction[], userRoles: string[]): () => string[] {
|
||||||
|
return () => {
|
||||||
|
if (actions.some((action) => contextSrv.hasPermission(action))) {
|
||||||
|
// If the user has any of the required actions, no need to check the role
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
// User had no action, check the org role
|
||||||
|
return userRoles;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function getRoutes(): RouteDescriptor[] {
|
export function getRoutes(): RouteDescriptor[] {
|
||||||
return DEFAULT_ROUTES;
|
return DEFAULT_ROUTES;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user