diff --git a/pkg/services/queryhistory/api.go b/pkg/services/queryhistory/api.go index 51bf07e6572..d9115479da9 100644 --- a/pkg/services/queryhistory/api.go +++ b/pkg/services/queryhistory/api.go @@ -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) diff --git a/pkg/services/queryhistory/queryhistory_create_test.go b/pkg/services/queryhistory/queryhistory_create_test.go index e88bb09a9d5..c9fb59d7c77 100644 --- a/pkg/services/queryhistory/queryhistory_create_test.go +++ b/pkg/services/queryhistory/queryhistory_create_test.go @@ -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", diff --git a/pkg/services/queryhistory/queryhistory_search_test.go b/pkg/services/queryhistory/queryhistory_search_test.go index 2012562d61d..e81a216466f 100644 --- a/pkg/services/queryhistory/queryhistory_search_test.go +++ b/pkg/services/queryhistory/queryhistory_search_test.go @@ -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 diff --git a/pkg/services/queryhistory/queryhistory_test.go b/pkg/services/queryhistory/queryhistory_test.go index e1983bce26b..e546524ac72 100644 --- a/pkg/services/queryhistory/queryhistory_test.go +++ b/pkg/services/queryhistory/queryhistory_test.go @@ -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{