Plugins: Add backend check for app page role access (#78269)
* add backend check for roles * tidy * fix tests * incorporate rbac * fix linter * apply PR feedback * add tests * fix logic * add comment * apply PR feedback
This commit is contained in:
@@ -10,12 +10,17 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@@ -150,6 +155,185 @@ func TestAuth_Middleware(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleAppPluginAuth(t *testing.T) {
|
||||
t.Run("Verify user's role when requesting app route which requires role", func(t *testing.T) {
|
||||
appSubURL := setting.AppSubUrl
|
||||
setting.AppSubUrl = "/grafana/"
|
||||
t.Cleanup(func() {
|
||||
setting.AppSubUrl = appSubURL
|
||||
})
|
||||
|
||||
tcs := []struct {
|
||||
roleRequired org.RoleType
|
||||
role org.RoleType
|
||||
expStatus int
|
||||
expBody string
|
||||
expLocation string
|
||||
}{
|
||||
{roleRequired: org.RoleViewer, role: org.RoleAdmin, expStatus: http.StatusOK, expBody: ""},
|
||||
{roleRequired: org.RoleAdmin, role: org.RoleAdmin, expStatus: http.StatusOK, expBody: ""},
|
||||
{roleRequired: org.RoleAdmin, role: org.RoleViewer, expStatus: http.StatusFound, expBody: "<a href=\"/grafana/\">Found</a>.\n\n", expLocation: "/grafana/"},
|
||||
{roleRequired: "", role: org.RoleViewer, expStatus: http.StatusOK, expBody: ""},
|
||||
{roleRequired: org.RoleEditor, role: "", expStatus: http.StatusFound, expBody: "<a href=\"/grafana/\">Found</a>.\n\n", expLocation: "/grafana/"},
|
||||
}
|
||||
|
||||
const path = "/a/test-app/test"
|
||||
for i, tc := range tcs {
|
||||
t.Run(fmt.Sprintf("testcase %d", i), func(t *testing.T) {
|
||||
ps := pluginstore.NewFakePluginStore(pluginstore.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Type: "page",
|
||||
Role: tc.roleRequired,
|
||||
Path: path,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
middlewareScenario(t, t.Name(), func(t *testing.T, sc *scenarioContext) {
|
||||
sc.withIdentity(&authn.Identity{
|
||||
OrgRoles: map[int64]org.RoleType{
|
||||
0: tc.role,
|
||||
},
|
||||
})
|
||||
features := featuremgmt.WithFeatures()
|
||||
logger := &logtest.Fake{}
|
||||
ac := &actest.FakeAccessControl{}
|
||||
|
||||
sc.m.Get("/a/:id/*", RoleAppPluginAuth(ac, ps, features, logger), func(c *contextmodel.ReqContext) {
|
||||
c.JSON(http.StatusOK, map[string]interface{}{})
|
||||
})
|
||||
sc.fakeReq("GET", path).exec()
|
||||
assert.Equal(t, tc.expStatus, sc.resp.Code)
|
||||
assert.Equal(t, tc.expBody, sc.resp.Body.String())
|
||||
assert.Equal(t, tc.expLocation, sc.resp.Header().Get("Location"))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// We return success in this case because the frontend takes care of rendering the 404 page
|
||||
middlewareScenario(t, "Plugin is not found returns success", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.withIdentity(&authn.Identity{
|
||||
OrgRoles: map[int64]org.RoleType{
|
||||
0: org.RoleViewer,
|
||||
},
|
||||
})
|
||||
features := featuremgmt.WithFeatures()
|
||||
logger := &logtest.Fake{}
|
||||
ac := &actest.FakeAccessControl{}
|
||||
sc.m.Get("/a/:id/*", RoleAppPluginAuth(ac, &pluginstore.FakePluginStore{}, features, logger), func(c *contextmodel.ReqContext) {
|
||||
c.JSON(http.StatusOK, map[string]interface{}{})
|
||||
})
|
||||
sc.fakeReq("GET", "/a/test-app/test").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, "", sc.resp.Body.String())
|
||||
})
|
||||
|
||||
// We return success in this case because the frontend takes care of rendering the right page based on its router
|
||||
middlewareScenario(t, "Plugin page not found returns success", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.withIdentity(&authn.Identity{
|
||||
OrgRoles: map[int64]org.RoleType{
|
||||
0: org.RoleViewer,
|
||||
},
|
||||
})
|
||||
features := featuremgmt.WithFeatures()
|
||||
logger := &logtest.Fake{}
|
||||
ac := &actest.FakeAccessControl{}
|
||||
sc.m.Get("/a/:id/*", RoleAppPluginAuth(ac, pluginstore.NewFakePluginStore(pluginstore.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Type: "page",
|
||||
Role: org.RoleViewer,
|
||||
Path: "/a/test-app/test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}), features, logger), func(c *contextmodel.ReqContext) {
|
||||
c.JSON(http.StatusOK, map[string]interface{}{})
|
||||
})
|
||||
sc.fakeReq("GET", "/a/test-app/notExistingPath").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, "", sc.resp.Body.String())
|
||||
})
|
||||
|
||||
t.Run("Plugin include with RBAC", func(t *testing.T) {
|
||||
tcs := []struct {
|
||||
name string
|
||||
evalResult bool
|
||||
evalErr error
|
||||
expStatus int
|
||||
expBody string
|
||||
expLocation string
|
||||
}{
|
||||
{
|
||||
name: "Unsuccessful RBAC eval will result in a redirect",
|
||||
evalResult: false,
|
||||
expStatus: 302,
|
||||
expBody: "<a href=\"/\">Found</a>.\n\n",
|
||||
expLocation: "/",
|
||||
},
|
||||
{
|
||||
name: "An RBAC eval error will result in a redirect",
|
||||
evalErr: errors.New("eval error"),
|
||||
expStatus: 302,
|
||||
expBody: "<a href=\"/\">Found</a>.\n\n",
|
||||
expLocation: "/",
|
||||
},
|
||||
{
|
||||
name: "Successful RBAC eval will result in a successful request",
|
||||
evalResult: true,
|
||||
expStatus: 200,
|
||||
expBody: "",
|
||||
expLocation: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
middlewareScenario(t, "Plugin include with RBAC", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.withIdentity(&authn.Identity{
|
||||
OrgRoles: map[int64]org.RoleType{
|
||||
0: org.RoleViewer,
|
||||
},
|
||||
})
|
||||
logger := &logtest.Fake{}
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
|
||||
ac := &actest.FakeAccessControl{
|
||||
ExpectedEvaluate: tc.evalResult,
|
||||
ExpectedErr: tc.evalErr,
|
||||
}
|
||||
path := "/a/test-app/test"
|
||||
ps := pluginstore.NewFakePluginStore(pluginstore.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Type: "page",
|
||||
Role: org.RoleViewer,
|
||||
Path: path,
|
||||
Action: "test-app.test:read",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
sc.m.Get("/a/:id/*", RoleAppPluginAuth(ac, ps, features, logger), func(c *contextmodel.ReqContext) {
|
||||
c.JSON(http.StatusOK, map[string]interface{}{})
|
||||
})
|
||||
sc.fakeReq("GET", path).exec()
|
||||
assert.Equal(t, tc.expStatus, sc.resp.Code)
|
||||
assert.Equal(t, tc.expBody, sc.resp.Body.String())
|
||||
assert.Equal(t, tc.expLocation, sc.resp.Header().Get("Location"))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveForceLoginparams(t *testing.T) {
|
||||
tcs := []struct {
|
||||
inp string
|
||||
|
||||
Reference in New Issue
Block a user