diff --git a/pkg/api/api.go b/pkg/api/api.go index 215ff274848..9ff29328060 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -124,18 +124,19 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/live/pipeline", reqGrafanaAdmin, hs.Index) r.Get("/live/cloud", reqGrafanaAdmin, hs.Index) - r.Get("/plugins", middleware.CanAdminPlugins(hs.Cfg), hs.Index) - r.Get("/plugins/:id/", middleware.CanAdminPlugins(hs.Cfg), hs.Index) - r.Get("/plugins/:id/edit", middleware.CanAdminPlugins(hs.Cfg), hs.Index) // deprecated - r.Get("/plugins/:id/page/:page", 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.AccessControl), hs.Index) + 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.AccessControl), 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/edit/*", authorize(datasources.EditPageAccess), hs.Index) r.Get("/connections", 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) - r.Get("/connections/datasources/:id/page/:page", middleware.CanAdminPlugins(hs.Cfg), hs.Index) + // Plugin details pages + 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 appPluginIDScope := pluginaccesscontrol.ScopeProvider.GetResourceScope(ac.Parameter(":id")) diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index fccbcb409fe..d0bbe913713 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -9,6 +9,7 @@ import ( "strings" "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/authn" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -86,9 +87,10 @@ func removeForceLoginParams(str string) string { 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) { - if !pluginaccesscontrol.ReqCanAdminPlugins(cfg)(c) { + hasAccess := ac.HasAccess(accessControl, c) + if !pluginaccesscontrol.ReqCanAdminPlugins(cfg)(c) && !hasAccess(pluginaccesscontrol.AdminAccessEvaluator) { accessForbidden(c) return } diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index ace34b04ac3..c3d5602e8e4 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -17,7 +17,8 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead) 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) { configNodes = append(configNodes, &navtree.NavLink{ Text: "Plugins", diff --git a/public/app/features/plugins/admin/routes.tsx b/public/app/features/plugins/admin/routes.tsx index 86ffb65a3d2..ab7fb2d13ce 100644 --- a/public/app/features/plugins/admin/routes.tsx +++ b/public/app/features/plugins/admin/routes.tsx @@ -1,5 +1,7 @@ import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; +import { contextSrv } from 'app/core/core'; import { RouteDescriptor } from 'app/core/navigation/types'; +import { AccessControlAction } from 'app/types'; import { PluginAdminRoutes } from './types'; @@ -7,26 +9,49 @@ const DEFAULT_ROUTES = [ { path: '/plugins', navId: 'plugins', - roles: () => ['Admin', 'ServerAdmin'], + roles: evaluateAccess( + [AccessControlAction.PluginsInstall, AccessControlAction.PluginsWrite], + ['Admin', 'ServerAdmin'] + ), routeName: PluginAdminRoutes.Home, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')), }, { path: '/plugins/browse', navId: 'plugins', - roles: () => ['Admin', 'ServerAdmin'], + roles: evaluateAccess( + [AccessControlAction.PluginsInstall, AccessControlAction.PluginsWrite], + ['Admin', 'ServerAdmin'] + ), routeName: PluginAdminRoutes.Browse, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')), }, { path: '/plugins/:pluginId/', navId: 'plugins', - roles: () => ['Admin', 'ServerAdmin'], + roles: evaluateAccess( + [AccessControlAction.PluginsInstall, AccessControlAction.PluginsWrite], + ['Admin', 'ServerAdmin'] + ), routeName: PluginAdminRoutes.Details, 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[] { return DEFAULT_ROUTES; }