Query History: Prevent viewers from accessing (#88735)

* Add permissions check for viewer without viewers_can_edit

* Add test

* fix lint

* Add role checks on other handlers

* Linter and fix Go issue

* Fix conflict

* Remove invalid way of testing for error
This commit is contained in:
Kristina
2024-07-19 14:44:58 -05:00
committed by GitHub
parent 98c197e6cc
commit a0268a9ad2
4 changed files with 41 additions and 7 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
)
@ -35,6 +36,10 @@ func (s *QueryHistoryService) registerAPIEndpoints() {
// 401: unauthorisedError
// 500: internalServerError
func (s *QueryHistoryService) createHandler(c *contextmodel.ReqContext) response.Response {
if c.GetOrgRole() == org.RoleViewer && !s.Cfg.ViewersCanEdit {
return response.Error(http.StatusUnauthorized, "Failed to create query history", nil)
}
cmd := CreateQueryInQueryHistoryCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
@ -61,6 +66,10 @@ func (s *QueryHistoryService) createHandler(c *contextmodel.ReqContext) response
// 401: unauthorisedError
// 500: internalServerError
func (s *QueryHistoryService) searchHandler(c *contextmodel.ReqContext) response.Response {
if c.GetOrgRole() == org.RoleViewer && !s.Cfg.ViewersCanEdit {
return response.Error(http.StatusUnauthorized, "Failed to get query history", nil)
}
timeRange := gtime.NewTimeRange(c.Query("from"), c.Query("to"))
query := SearchInQueryHistoryQuery{
@ -93,6 +102,10 @@ func (s *QueryHistoryService) searchHandler(c *contextmodel.ReqContext) response
// 401: unauthorisedError
// 500: internalServerError
func (s *QueryHistoryService) deleteHandler(c *contextmodel.ReqContext) response.Response {
if c.GetOrgRole() == org.RoleViewer && !s.Cfg.ViewersCanEdit {
return response.Error(http.StatusUnauthorized, "Failed to delete query history", nil)
}
queryUID := web.Params(c.Req)[":uid"]
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
@ -150,6 +163,9 @@ func (s *QueryHistoryService) patchCommentHandler(c *contextmodel.ReqContext) re
// 401: unauthorisedError
// 500: internalServerError
func (s *QueryHistoryService) starHandler(c *contextmodel.ReqContext) response.Response {
if c.GetOrgRole() == org.RoleViewer && !s.Cfg.ViewersCanEdit {
return response.Error(http.StatusUnauthorized, "Failed to star query history", nil)
}
queryUID := web.Params(c.Req)[":uid"]
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
@ -174,6 +190,9 @@ func (s *QueryHistoryService) starHandler(c *contextmodel.ReqContext) response.R
// 401: unauthorisedError
// 500: internalServerError
func (s *QueryHistoryService) unstarHandler(c *contextmodel.ReqContext) response.Response {
if c.GetOrgRole() == org.RoleViewer && !s.Cfg.ViewersCanEdit {
return response.Error(http.StatusUnauthorized, "Failed to unstar query history", nil)
}
queryUID := web.Params(c.Req)[":uid"]
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
return response.Error(http.StatusNotFound, "Query in query history not found", nil)

View File

@ -12,7 +12,7 @@ func TestIntegrationCreateQueryInQueryHistory(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
testScenario(t, "When users tries to create query in query history it should succeed",
testScenario(t, "When users tries to create query in query history it should succeed", false,
func(t *testing.T, sc scenarioContext) {
command := CreateQueryInQueryHistoryCommand{
DatasourceUID: "NCzh67i",

View File

@ -12,7 +12,7 @@ func TestIntegrationGetQueriesFromQueryHistory(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
testScenario(t, "When users tries to get query in empty query history, it should return empty result",
testScenario(t, "When users tries to get query in empty query history, it should return empty result", false,
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", "test")
resp := sc.service.searchHandler(sc.reqContext)
@ -262,6 +262,13 @@ func TestIntegrationGetQueriesFromQueryHistory(t *testing.T) {
require.Equal(t, 0, response.Result.TotalCount)
})
testScenario(t, "When user is viewer, return 401", true,
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", "test")
resp := sc.service.searchHandler(sc.reqContext)
require.Equal(t, 401, resp.Status())
})
testScenarioWithMixedQueriesInQueryHistory(t, "When users tries to get queries with mixed data source it should return correct queries",
func(t *testing.T, sc scenarioContext) {
t.Skip() // This test fails a lot at the moment

View File

@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
@ -47,7 +48,7 @@ type scenarioContext struct {
initialResult QueryHistoryResponse
}
func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
func testScenario(t *testing.T, desc string, isViewer bool, fn func(t *testing.T, sc scenarioContext)) {
t.Helper()
t.Run(desc, func(t *testing.T) {
@ -72,13 +73,20 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
)
require.NoError(t, err)
var role identity.RoleType
if isViewer {
role = org.RoleViewer
} else {
role = org.RoleEditor
}
usr := user.SignedInUser{
UserID: testUserID,
Name: "Signed In User",
Login: "signed_in_user",
Email: "signed.in.user@test.com",
OrgID: testOrgID,
OrgRole: org.RoleViewer,
OrgRole: role,
LastSeenAt: service.now(),
}
@ -105,7 +113,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
func testScenarioWithQueryInQueryHistory(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
testScenario(t, desc, false, func(t *testing.T, sc scenarioContext) {
command := CreateQueryInQueryHistoryCommand{
DatasourceUID: testDsUID1,
Queries: simplejson.NewFromAny([]interface{}{
@ -124,7 +132,7 @@ func testScenarioWithQueryInQueryHistory(t *testing.T, desc string, fn func(t *t
func testScenarioWithMultipleQueriesInQueryHistory(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
testScenario(t, desc, false, func(t *testing.T, sc scenarioContext) {
start := time.Now().Add(-3 * time.Second)
sc.service.now = func() time.Time { return start }
command1 := CreateQueryInQueryHistoryCommand{
@ -185,7 +193,7 @@ func testScenarioWithMultipleQueriesInQueryHistory(t *testing.T, desc string, fn
func testScenarioWithMixedQueriesInQueryHistory(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
testScenario(t, desc, false, func(t *testing.T, sc scenarioContext) {
start := time.Now()
sc.service.now = func() time.Time { return start }
command1 := CreateQueryInQueryHistoryCommand{