mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Allow plugins to use scoped actions (#90946)
Co-authored-by: gamab <gabriel.mabille@grafana.com>
This commit is contained in:
@@ -19,11 +19,11 @@ import (
|
||||
glog "github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
pluginac "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/proxyutil"
|
||||
@@ -341,12 +341,12 @@ func (proxy *DataSourceProxy) hasAccessToRoute(route *plugins.Route) bool {
|
||||
ctxLogger := logger.FromContext(proxy.ctx.Req.Context())
|
||||
useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.ReqAction != ""
|
||||
if useRBAC {
|
||||
routeEval := accesscontrol.EvalPermission(route.ReqAction)
|
||||
ok := routeEval.Evaluate(proxy.ctx.GetPermissions())
|
||||
if !ok {
|
||||
routeEval := pluginac.GetDataSourceRouteEvaluator(proxy.ds.UID, route.ReqAction)
|
||||
hasAccess := routeEval.Evaluate(proxy.ctx.GetPermissions())
|
||||
if !hasAccess {
|
||||
ctxLogger.Debug("plugin route is covered by RBAC, user doesn't have access", "route", proxy.ctx.Req.URL.Path, "action", route.ReqAction, "path", route.Path, "method", route.Method)
|
||||
}
|
||||
return ok
|
||||
return hasAccess
|
||||
}
|
||||
if route.ReqRole.IsValid() {
|
||||
if hasUserRole := proxy.ctx.HasUserRole(route.ReqRole); !hasUserRole {
|
||||
|
||||
@@ -108,9 +108,18 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
Path: "mypath",
|
||||
URL: "https://example.com/api/v1/",
|
||||
},
|
||||
{
|
||||
Path: "api/rbac-home",
|
||||
ReqAction: "datasources:read",
|
||||
},
|
||||
{
|
||||
Path: "api/rbac-restricted",
|
||||
ReqAction: "test-app.settings:read",
|
||||
},
|
||||
}
|
||||
|
||||
ds := &datasources.DataSource{
|
||||
UID: "dsUID",
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"clientId": "asd",
|
||||
"dynamicUrl": "https://dynamic.grafana.com",
|
||||
@@ -249,6 +258,51 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("plugin route with RBAC protection user is allowed", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
ctx.SignedInUser.OrgID = int64(1)
|
||||
ctx.SignedInUser.OrgRole = identity.RoleNone
|
||||
ctx.SignedInUser.Permissions = map[int64]map[string][]string{1: {"test-app.settings:read": nil}}
|
||||
proxy, err := setupDSProxyTest(t, ctx, ds, routes, "api/rbac-restricted")
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("plugin route with RBAC protection user is not allowed", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
ctx.SignedInUser.OrgID = int64(1)
|
||||
ctx.SignedInUser.OrgRole = identity.RoleNone
|
||||
ctx.SignedInUser.Permissions = map[int64]map[string][]string{1: {"test-app:read": nil}}
|
||||
proxy, err := setupDSProxyTest(t, ctx, ds, routes, "api/rbac-restricted")
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("plugin route with dynamic RBAC protection user is allowed", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
ctx.SignedInUser.OrgID = int64(1)
|
||||
ctx.SignedInUser.OrgRole = identity.RoleNone
|
||||
ctx.SignedInUser.Permissions = map[int64]map[string][]string{1: {"datasources:read": {"datasources:uid:dsUID"}}}
|
||||
proxy, err := setupDSProxyTest(t, ctx, ds, routes, "api/rbac-home")
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("plugin route with dynamic RBAC protection user is not allowed", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
ctx.SignedInUser.OrgID = int64(1)
|
||||
ctx.SignedInUser.OrgRole = identity.RoleNone
|
||||
// Has access but to another app
|
||||
ctx.SignedInUser.Permissions = map[int64]map[string][]string{1: {"datasources:read": {"datasources:uid:notTheDsUID"}}}
|
||||
proxy, err := setupDSProxyTest(t, ctx, ds, routes, "api/rbac-home")
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Plugin with multiple routes for token auth", func(t *testing.T) {
|
||||
@@ -1021,7 +1075,7 @@ func setupDSProxyTest(t *testing.T, ctx *contextmodel.ReqContext, ds *datasource
|
||||
cfg := setting.NewCfg()
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(dbtest.NewFakeDB(), secretsService, log.NewNopLogger())
|
||||
features := featuremgmt.WithFeatures()
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()),
|
||||
&actest.FakePermissionsService{}, quotatest.New(false, nil), &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
||||
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
pluginac "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -130,7 +131,8 @@ func (proxy *PluginProxy) HandleRequest() {
|
||||
func (proxy *PluginProxy) hasAccessToRoute(route *plugins.Route) bool {
|
||||
useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.ReqAction != ""
|
||||
if useRBAC {
|
||||
hasAccess := ac.HasAccess(proxy.accessControl, proxy.ctx)(ac.EvalPermission(route.ReqAction))
|
||||
routeEval := pluginac.GetPluginRouteEvaluator(proxy.ps.PluginID, route.ReqAction)
|
||||
hasAccess := ac.HasAccess(proxy.accessControl, proxy.ctx)(routeEval)
|
||||
if !hasAccess {
|
||||
proxy.ctx.Logger.Debug("plugin route is covered by RBAC, user doesn't have access", "route", proxy.ctx.Req.URL.Path)
|
||||
}
|
||||
|
||||
@@ -455,7 +455,13 @@ func TestPluginProxyRoutesAccessControl(t *testing.T) {
|
||||
Path: "projects",
|
||||
Method: "GET",
|
||||
URL: "http://localhost/api/projects",
|
||||
ReqAction: "plugin-id.projects:read", // Protected by RBAC action
|
||||
ReqAction: "test-app.projects:read", // Protected by RBAC action
|
||||
},
|
||||
{
|
||||
Path: "home",
|
||||
Method: "GET",
|
||||
URL: "http://localhost/api/home",
|
||||
ReqAction: "plugins.app:access", // Protected by RBAC action with plugin scope
|
||||
},
|
||||
}
|
||||
|
||||
@@ -480,7 +486,7 @@ func TestPluginProxyRoutesAccessControl(t *testing.T) {
|
||||
},
|
||||
{
|
||||
proxyPath: "/projects",
|
||||
usrPerms: map[string][]string{"plugin-id.projects:read": {}},
|
||||
usrPerms: map[string][]string{"test-app.projects:read": {}},
|
||||
expectedURLPath: "/api/projects",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
@@ -490,6 +496,18 @@ func TestPluginProxyRoutesAccessControl(t *testing.T) {
|
||||
expectedURLPath: "/api/projects",
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
proxyPath: "/home",
|
||||
usrPerms: map[string][]string{"plugins.app:access": {"plugins:id:not-the-test-app"}},
|
||||
expectedURLPath: "/api/home",
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
proxyPath: "/home",
|
||||
usrPerms: map[string][]string{"plugins.app:access": {"plugins:id:test-app"}},
|
||||
expectedURLPath: "/api/home",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
@@ -534,6 +552,7 @@ func TestPluginProxyRoutesAccessControl(t *testing.T) {
|
||||
},
|
||||
}
|
||||
ps := &pluginsettings.DTO{
|
||||
PluginID: "test-app",
|
||||
SecureJSONData: map[string][]byte{},
|
||||
}
|
||||
cfg := &setting.Cfg{}
|
||||
|
||||
@@ -127,7 +127,7 @@ func RoleAppPluginAuth(accessControl ac.AccessControl, ps pluginstore.Store, fea
|
||||
|
||||
if normalizeIncludePath(u.Path) == path {
|
||||
useRBAC := features.IsEnabledGlobally(featuremgmt.FlagAccessControlOnCall) && i.RequiresRBACAction()
|
||||
if useRBAC && !hasAccess(ac.EvalPermission(i.Action)) {
|
||||
if useRBAC && !hasAccess(pluginaccesscontrol.GetPluginRouteEvaluator(pluginID, i.Action)) {
|
||||
logger.Debug("Plugin include is covered by RBAC, user doesn't have access", "plugin", pluginID, "include", i.Name)
|
||||
permitted = false
|
||||
break
|
||||
|
||||
@@ -269,7 +269,7 @@ func (s *ServiceImpl) hasAccessToInclude(c *contextmodel.ReqContext, pluginID st
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
return func(include *plugins.Includes) bool {
|
||||
useRBAC := s.features.IsEnabledGlobally(featuremgmt.FlagAccessControlOnCall) && include.RequiresRBACAction()
|
||||
if useRBAC && !hasAccess(ac.EvalPermission(include.Action)) {
|
||||
if useRBAC && !hasAccess(pluginaccesscontrol.GetPluginRouteEvaluator(pluginID, include.Action)) {
|
||||
s.log.Debug("plugin include is covered by RBAC, user doesn't have access",
|
||||
"plugin", pluginID,
|
||||
"include", include.Name)
|
||||
|
||||
@@ -407,20 +407,28 @@ func TestAddAppLinksAccessControl(t *testing.T) {
|
||||
ID: "test-app1", Name: "Test app1 name", Type: plugins.TypeApp,
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Name: "Catalog",
|
||||
Path: "/a/test-app1/catalog",
|
||||
Name: "Home",
|
||||
Path: "/a/test-app1/home",
|
||||
Type: "page",
|
||||
AddToNav: true,
|
||||
DefaultNav: true,
|
||||
Role: identity.RoleEditor,
|
||||
Action: catalogReadAction,
|
||||
Role: identity.RoleViewer,
|
||||
},
|
||||
{
|
||||
Name: "Page2",
|
||||
Path: "/a/test-app1/page2",
|
||||
Name: "Catalog",
|
||||
Path: "/a/test-app1/catalog",
|
||||
Type: "page",
|
||||
AddToNav: true,
|
||||
Role: identity.RoleEditor,
|
||||
Action: catalogReadAction,
|
||||
},
|
||||
{
|
||||
Name: "Announcements",
|
||||
Path: "/a/test-app1/announcements",
|
||||
Type: "page",
|
||||
AddToNav: true,
|
||||
Role: identity.RoleViewer,
|
||||
Action: pluginaccesscontrol.ActionAppAccess,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -443,77 +451,114 @@ func TestAddAppLinksAccessControl(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should not add app links when the user cannot access app plugins", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{}
|
||||
user.OrgRole = identity.RoleAdmin
|
||||
t.Run("Without plugin RBAC - Enforce role", func(t *testing.T) {
|
||||
t.Run("Should not add app links when the user cannot access app plugins", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{}
|
||||
user.OrgRole = identity.RoleAdmin
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, treeRoot.Children, 0)
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, treeRoot.Children, 0)
|
||||
})
|
||||
t.Run(" Should add all includes when the user is an editor", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleEditor
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Equal(t, "/a/test-app1/home", appsNode.Children[0].Url)
|
||||
require.Len(t, appsNode.Children[0].Children, 2)
|
||||
require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Children[0].Url)
|
||||
require.Equal(t, "/a/test-app1/announcements", appsNode.Children[0].Children[1].Url)
|
||||
})
|
||||
t.Run("Should add two includes when the user is a viewer", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleViewer
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Equal(t, "/a/test-app1/home", appsNode.Children[0].Url)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/announcements", appsNode.Children[0].Children[0].Url)
|
||||
})
|
||||
})
|
||||
t.Run("Should add both includes when the user is an editor", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleEditor
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Url)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url)
|
||||
})
|
||||
t.Run("Should add one include when the user is a viewer", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleViewer
|
||||
t.Run("With plugin RBAC - Enforce action first", func(t *testing.T) {
|
||||
t.Run("Should not see any includes with no app access", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"plugins:id:not-the-test-app1"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleNone
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url)
|
||||
})
|
||||
t.Run("Should add both includes when the user is a viewer with catalog read", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}, catalogReadAction: []string{}},
|
||||
}
|
||||
user.OrgRole = identity.RoleViewer
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, treeRoot.Children, 0)
|
||||
})
|
||||
t.Run("Should only see the announcements as a none role user with app access", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"plugins:id:test-app1"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleNone
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Url)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url)
|
||||
})
|
||||
t.Run("Should add one include when the user is an editor without catalog read", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleEditor
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/announcements", appsNode.Children[0].Children[0].Url)
|
||||
})
|
||||
t.Run("Should now see the catalog as a viewer with catalog read", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"plugins:id:test-app1"}, catalogReadAction: []string{}},
|
||||
}
|
||||
user.OrgRole = identity.RoleViewer
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url)
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Equal(t, "/a/test-app1/home", appsNode.Children[0].Url)
|
||||
require.Len(t, appsNode.Children[0].Children, 2)
|
||||
require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Children[0].Url)
|
||||
require.Equal(t, "/a/test-app1/announcements", appsNode.Children[0].Children[1].Url)
|
||||
})
|
||||
t.Run("Should not see the catalog include as an editor without catalog read", func(t *testing.T) {
|
||||
treeRoot := navtree.NavTreeRoot{}
|
||||
user.Permissions = map[int64]map[string][]string{
|
||||
1: {pluginaccesscontrol.ActionAppAccess: []string{"*"}},
|
||||
}
|
||||
user.OrgRole = identity.RoleEditor
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
|
||||
err := service.addAppLinks(&treeRoot, reqCtx)
|
||||
require.NoError(t, err)
|
||||
appsNode := treeRoot.FindById(navtree.NavIDApps)
|
||||
require.Len(t, appsNode.Children, 1)
|
||||
require.Equal(t, "Test app1 name", appsNode.Children[0].Text)
|
||||
require.Equal(t, "/a/test-app1/home", appsNode.Children[0].Url)
|
||||
require.Len(t, appsNode.Children[0].Children, 1)
|
||||
require.Equal(t, "/a/test-app1/announcements", appsNode.Children[0].Children[0].Url)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -88,3 +89,42 @@ func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg, features featuremgmt
|
||||
|
||||
return service.DeclareFixedRoles(AppPluginsReader, PluginsWriter, PluginsMaintainer)
|
||||
}
|
||||
|
||||
var datasourcesActions = map[string]bool{
|
||||
datasources.ActionIDRead: true,
|
||||
datasources.ActionQuery: true,
|
||||
datasources.ActionRead: true,
|
||||
datasources.ActionWrite: true,
|
||||
datasources.ActionDelete: true,
|
||||
datasources.ActionPermissionsRead: true,
|
||||
datasources.ActionPermissionsWrite: true,
|
||||
"datasources.caching:read": true,
|
||||
"datasources.caching:write": true,
|
||||
ac.ActionAlertingRuleExternalRead: true,
|
||||
ac.ActionAlertingRuleExternalWrite: true,
|
||||
ac.ActionAlertingInstancesExternalRead: true,
|
||||
ac.ActionAlertingInstancesExternalWrite: true,
|
||||
ac.ActionAlertingNotificationsExternalRead: true,
|
||||
ac.ActionAlertingNotificationsExternalWrite: true,
|
||||
}
|
||||
|
||||
// GetDataSourceRouteEvaluator returns an evaluator for the given data source UID and action.
|
||||
func GetDataSourceRouteEvaluator(dsUID, action string) ac.Evaluator {
|
||||
if datasourcesActions[action] {
|
||||
return ac.EvalPermission(action, "datasources:uid:"+dsUID)
|
||||
}
|
||||
return ac.EvalPermission(action)
|
||||
}
|
||||
|
||||
var pluginsActions = map[string]bool{
|
||||
ActionWrite: true,
|
||||
ActionAppAccess: true,
|
||||
}
|
||||
|
||||
// GetPluginRouteEvaluator returns an evaluator for the given plugin ID and action.
|
||||
func GetPluginRouteEvaluator(pluginID, action string) ac.Evaluator {
|
||||
if pluginsActions[action] {
|
||||
return ac.EvalPermission(action, "plugins:id:"+pluginID)
|
||||
}
|
||||
return ac.EvalPermission(action)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user