mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 05:21:50 +08:00
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:
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"github.com/grafana/grafana/pkg/api/routing"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
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/util"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
@ -35,6 +36,10 @@ func (s *QueryHistoryService) registerAPIEndpoints() {
|
|||||||
// 401: unauthorisedError
|
// 401: unauthorisedError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (s *QueryHistoryService) createHandler(c *contextmodel.ReqContext) response.Response {
|
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{}
|
cmd := CreateQueryInQueryHistoryCommand{}
|
||||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||||
@ -61,6 +66,10 @@ func (s *QueryHistoryService) createHandler(c *contextmodel.ReqContext) response
|
|||||||
// 401: unauthorisedError
|
// 401: unauthorisedError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (s *QueryHistoryService) searchHandler(c *contextmodel.ReqContext) response.Response {
|
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"))
|
timeRange := gtime.NewTimeRange(c.Query("from"), c.Query("to"))
|
||||||
|
|
||||||
query := SearchInQueryHistoryQuery{
|
query := SearchInQueryHistoryQuery{
|
||||||
@ -93,6 +102,10 @@ func (s *QueryHistoryService) searchHandler(c *contextmodel.ReqContext) response
|
|||||||
// 401: unauthorisedError
|
// 401: unauthorisedError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (s *QueryHistoryService) deleteHandler(c *contextmodel.ReqContext) response.Response {
|
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"]
|
queryUID := web.Params(c.Req)[":uid"]
|
||||||
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
|
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
|
||||||
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
|
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
|
// 401: unauthorisedError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (s *QueryHistoryService) starHandler(c *contextmodel.ReqContext) response.Response {
|
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"]
|
queryUID := web.Params(c.Req)[":uid"]
|
||||||
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
|
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
|
||||||
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
|
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
|
// 401: unauthorisedError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (s *QueryHistoryService) unstarHandler(c *contextmodel.ReqContext) response.Response {
|
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"]
|
queryUID := web.Params(c.Req)[":uid"]
|
||||||
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
|
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
|
||||||
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
|
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
|
||||||
|
@ -12,7 +12,7 @@ func TestIntegrationCreateQueryInQueryHistory(t *testing.T) {
|
|||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping integration test")
|
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) {
|
func(t *testing.T, sc scenarioContext) {
|
||||||
command := CreateQueryInQueryHistoryCommand{
|
command := CreateQueryInQueryHistoryCommand{
|
||||||
DatasourceUID: "NCzh67i",
|
DatasourceUID: "NCzh67i",
|
||||||
|
@ -12,7 +12,7 @@ func TestIntegrationGetQueriesFromQueryHistory(t *testing.T) {
|
|||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping integration test")
|
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) {
|
func(t *testing.T, sc scenarioContext) {
|
||||||
sc.reqContext.Req.Form.Add("datasourceUid", "test")
|
sc.reqContext.Req.Form.Add("datasourceUid", "test")
|
||||||
resp := sc.service.searchHandler(sc.reqContext)
|
resp := sc.service.searchHandler(sc.reqContext)
|
||||||
@ -262,6 +262,13 @@ func TestIntegrationGetQueriesFromQueryHistory(t *testing.T) {
|
|||||||
require.Equal(t, 0, response.Result.TotalCount)
|
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",
|
testScenarioWithMixedQueriesInQueryHistory(t, "When users tries to get queries with mixed data source it should return correct queries",
|
||||||
func(t *testing.T, sc scenarioContext) {
|
func(t *testing.T, sc scenarioContext) {
|
||||||
t.Skip() // This test fails a lot at the moment
|
t.Skip() // This test fails a lot at the moment
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"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/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
@ -47,7 +48,7 @@ type scenarioContext struct {
|
|||||||
initialResult QueryHistoryResponse
|
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.Helper()
|
||||||
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var role identity.RoleType
|
||||||
|
if isViewer {
|
||||||
|
role = org.RoleViewer
|
||||||
|
} else {
|
||||||
|
role = org.RoleEditor
|
||||||
|
}
|
||||||
|
|
||||||
usr := user.SignedInUser{
|
usr := user.SignedInUser{
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
Name: "Signed In User",
|
Name: "Signed In User",
|
||||||
Login: "signed_in_user",
|
Login: "signed_in_user",
|
||||||
Email: "signed.in.user@test.com",
|
Email: "signed.in.user@test.com",
|
||||||
OrgID: testOrgID,
|
OrgID: testOrgID,
|
||||||
OrgRole: org.RoleViewer,
|
OrgRole: role,
|
||||||
LastSeenAt: service.now(),
|
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)) {
|
func testScenarioWithQueryInQueryHistory(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
|
testScenario(t, desc, false, func(t *testing.T, sc scenarioContext) {
|
||||||
command := CreateQueryInQueryHistoryCommand{
|
command := CreateQueryInQueryHistoryCommand{
|
||||||
DatasourceUID: testDsUID1,
|
DatasourceUID: testDsUID1,
|
||||||
Queries: simplejson.NewFromAny([]interface{}{
|
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)) {
|
func testScenarioWithMultipleQueriesInQueryHistory(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
|
||||||
t.Helper()
|
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)
|
start := time.Now().Add(-3 * time.Second)
|
||||||
sc.service.now = func() time.Time { return start }
|
sc.service.now = func() time.Time { return start }
|
||||||
command1 := CreateQueryInQueryHistoryCommand{
|
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)) {
|
func testScenarioWithMixedQueriesInQueryHistory(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
|
testScenario(t, desc, false, func(t *testing.T, sc scenarioContext) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
sc.service.now = func() time.Time { return start }
|
sc.service.now = func() time.Time { return start }
|
||||||
command1 := CreateQueryInQueryHistoryCommand{
|
command1 := CreateQueryInQueryHistoryCommand{
|
||||||
|
Reference in New Issue
Block a user