Remove bus from datasource api (#44987)

* Remove bus from datasource api

* Add DatasourcePermissionService and use it in api

* Fix wire and rename

* Fix import in wire

* Fix bug

* Rename Service to OSS service

* Roll back fix
This commit is contained in:
idafurjes 2022-02-09 14:01:32 +01:00 committed by GitHub
parent 0282d5f9c3
commit df282a42cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 273 additions and 252 deletions

View File

@ -273,17 +273,17 @@ func (hs *HTTPServer) registerRoutes() {
// Data sources
apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) {
datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)), routing.Wrap(hs.GetDataSources))
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesCreate)), quota("data_source"), routing.Wrap(AddDataSource))
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesCreate)), quota("data_source"), routing.Wrap(hs.AddDataSource))
datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesWrite, ScopeDatasourceID)), routing.Wrap(hs.UpdateDataSource))
datasourceRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceID)), routing.Wrap(hs.DeleteDataSourceById))
datasourceRoute.Delete("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceUID)), routing.Wrap(hs.DeleteDataSourceByUID))
datasourceRoute.Delete("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceName)), routing.Wrap(hs.DeleteDataSourceByName))
datasourceRoute.Get("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)), routing.Wrap(hs.GetDataSourceById))
datasourceRoute.Get("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)), routing.Wrap(hs.GetDataSourceByUID))
datasourceRoute.Get("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)), routing.Wrap(GetDataSourceByName))
datasourceRoute.Get("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)), routing.Wrap(hs.GetDataSourceByName))
})
apiRoute.Get("/datasources/id/:name", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesIDRead, ScopeDatasourceName)), routing.Wrap(GetDataSourceIdByName))
apiRoute.Get("/datasources/id/:name", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesIDRead, ScopeDatasourceName)), routing.Wrap(hs.GetDataSourceIdByName))
apiRoute.Get("/plugins", routing.Wrap(hs.GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(hs.GetPluginSettingByID))

View File

@ -0,0 +1,22 @@
package api
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type DatasourcePermissionsService interface {
FilterDatasourcesBasedOnQueryPermissions(ctx context.Context, cmd *models.DatasourcesPermissionFilterQuery) error
}
// dummy method
func (hs *OSSDatasourcePermissionsService) FilterDatasourcesBasedOnQueryPermissions(ctx context.Context, cmd *models.DatasourcesPermissionFilterQuery) error {
return nil
}
type OSSDatasourcePermissionsService struct{}
func ProvideDatasourcePermissionsService() *OSSDatasourcePermissionsService {
return &OSSDatasourcePermissionsService{}
}

View File

@ -0,0 +1,20 @@
package api
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type mockDatasourcePermissionService struct {
dsResult []*models.DataSource
}
func (m *mockDatasourcePermissionService) FilterDatasourcesBasedOnQueryPermissions(ctx context.Context, cmd *models.DatasourcesPermissionFilterQuery) error {
cmd.Result = m.dsResult
return nil
}
func newMockDatasourcePermissionService() *mockDatasourcePermissionService {
return &mockDatasourcePermissionService{}
}

View File

@ -27,11 +27,11 @@ var datasourcesLogger = log.New("datasources")
func (hs *HTTPServer) GetDataSources(c *models.ReqContext) response.Response {
query := models.GetDataSourcesQuery{OrgId: c.OrgId, DataSourceLimit: hs.Cfg.DataSourceLimit}
if err := bus.Dispatch(c.Req.Context(), &query); err != nil {
if err := hs.SQLStore.GetDataSources(c.Req.Context(), &query); err != nil {
return response.Error(500, "Failed to query datasources", err)
}
filtered, err := filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, query.Result)
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, query.Result)
if err != nil {
return response.Error(500, "Failed to query datasources", err)
}
@ -98,7 +98,7 @@ func (hs *HTTPServer) GetDataSourceById(c *models.ReqContext) response.Response
OrgId: c.OrgId,
}
if err := bus.Dispatch(c.Req.Context(), &query); err != nil {
if err := hs.SQLStore.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
@ -108,7 +108,7 @@ func (hs *HTTPServer) GetDataSourceById(c *models.ReqContext) response.Response
return response.Error(500, "Failed to query datasources", err)
}
filtered, err := filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, []*models.DataSource{query.Result})
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, []*models.DataSource{query.Result})
if err != nil || len(filtered) != 1 {
return response.Error(404, "Data source not found", err)
}
@ -135,7 +135,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon
return response.Error(400, "Missing valid datasource id", nil)
}
ds, err := getRawDataSourceById(c.Req.Context(), id, c.OrgId)
ds, err := hs.getRawDataSourceById(c.Req.Context(), id, c.OrgId)
if err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
@ -149,7 +149,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon
cmd := &models.DeleteDataSourceCommand{ID: id, OrgID: c.OrgId}
err = bus.Dispatch(c.Req.Context(), cmd)
err = hs.SQLStore.DeleteDataSource(c.Req.Context(), cmd)
if err != nil {
return response.Error(500, "Failed to delete datasource", err)
}
@ -161,7 +161,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon
// GET /api/datasources/uid/:uid
func (hs *HTTPServer) GetDataSourceByUID(c *models.ReqContext) response.Response {
ds, err := getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.OrgId)
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.OrgId)
if err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
@ -170,7 +170,7 @@ func (hs *HTTPServer) GetDataSourceByUID(c *models.ReqContext) response.Response
return response.Error(http.StatusInternalServerError, "Failed to query datasource", err)
}
filtered, err := filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, []*models.DataSource{ds})
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, []*models.DataSource{ds})
if err != nil || len(filtered) != 1 {
return response.Error(404, "Data source not found", err)
}
@ -195,7 +195,7 @@ func (hs *HTTPServer) DeleteDataSourceByUID(c *models.ReqContext) response.Respo
return response.Error(400, "Missing datasource uid", nil)
}
ds, err := getRawDataSourceByUID(c.Req.Context(), uid, c.OrgId)
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), uid, c.OrgId)
if err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
@ -209,7 +209,7 @@ func (hs *HTTPServer) DeleteDataSourceByUID(c *models.ReqContext) response.Respo
cmd := &models.DeleteDataSourceCommand{UID: uid, OrgID: c.OrgId}
err = bus.Dispatch(c.Req.Context(), cmd)
err = hs.SQLStore.DeleteDataSource(c.Req.Context(), cmd)
if err != nil {
return response.Error(500, "Failed to delete datasource", err)
}
@ -231,7 +231,7 @@ func (hs *HTTPServer) DeleteDataSourceByName(c *models.ReqContext) response.Resp
}
getCmd := &models.GetDataSourceQuery{Name: name, OrgId: c.OrgId}
if err := bus.Dispatch(c.Req.Context(), getCmd); err != nil {
if err := hs.SQLStore.GetDataSource(c.Req.Context(), getCmd); err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
@ -243,7 +243,7 @@ func (hs *HTTPServer) DeleteDataSourceByName(c *models.ReqContext) response.Resp
}
cmd := &models.DeleteDataSourceCommand{Name: name, OrgID: c.OrgId}
err := bus.Dispatch(c.Req.Context(), cmd)
err := hs.SQLStore.DeleteDataSource(c.Req.Context(), cmd)
if err != nil {
return response.Error(500, "Failed to delete datasource", err)
}
@ -265,7 +265,7 @@ func validateURL(cmdType string, url string) response.Response {
}
// POST /api/datasources/
func AddDataSource(c *models.ReqContext) response.Response {
func (hs *HTTPServer) AddDataSource(c *models.ReqContext) response.Response {
cmd := models.AddDataSourceCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
@ -279,7 +279,7 @@ func AddDataSource(c *models.ReqContext) response.Response {
}
}
if err := bus.Dispatch(c.Req.Context(), &cmd); err != nil {
if err := hs.SQLStore.AddDataSource(c.Req.Context(), &cmd); err != nil {
if errors.Is(err, models.ErrDataSourceNameExists) || errors.Is(err, models.ErrDataSourceUidExists) {
return response.Error(409, err.Error(), err)
}
@ -312,7 +312,7 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext) response.Response {
return resp
}
ds, err := getRawDataSourceById(c.Req.Context(), cmd.Id, cmd.OrgId)
ds, err := hs.getRawDataSourceById(c.Req.Context(), cmd.Id, cmd.OrgId)
if err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
@ -329,7 +329,7 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext) response.Response {
return response.Error(500, "Failed to update datasource", err)
}
err = bus.Dispatch(c.Req.Context(), &cmd)
err = hs.SQLStore.UpdateDataSource(c.Req.Context(), &cmd)
if err != nil {
if errors.Is(err, models.ErrDataSourceUpdatingOldVersion) {
return response.Error(409, "Datasource has already been updated by someone else. Please reload and try again", err)
@ -342,7 +342,7 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext) response.Response {
OrgId: c.OrgId,
}
if err := bus.Dispatch(c.Req.Context(), &query); err != nil {
if err := hs.SQLStore.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
@ -366,7 +366,7 @@ func (hs *HTTPServer) fillWithSecureJSONData(ctx context.Context, cmd *models.Up
return nil
}
ds, err := getRawDataSourceById(ctx, cmd.Id, cmd.OrgId)
ds, err := hs.getRawDataSourceById(ctx, cmd.Id, cmd.OrgId)
if err != nil {
return err
}
@ -388,26 +388,26 @@ func (hs *HTTPServer) fillWithSecureJSONData(ctx context.Context, cmd *models.Up
return nil
}
func getRawDataSourceById(ctx context.Context, id int64, orgID int64) (*models.DataSource, error) {
func (hs *HTTPServer) getRawDataSourceById(ctx context.Context, id int64, orgID int64) (*models.DataSource, error) {
query := models.GetDataSourceQuery{
Id: id,
OrgId: orgID,
}
if err := bus.Dispatch(ctx, &query); err != nil {
if err := hs.SQLStore.GetDataSource(ctx, &query); err != nil {
return nil, err
}
return query.Result, nil
}
func getRawDataSourceByUID(ctx context.Context, uid string, orgID int64) (*models.DataSource, error) {
func (hs *HTTPServer) getRawDataSourceByUID(ctx context.Context, uid string, orgID int64) (*models.DataSource, error) {
query := models.GetDataSourceQuery{
Uid: uid,
OrgId: orgID,
}
if err := bus.Dispatch(ctx, &query); err != nil {
if err := hs.SQLStore.GetDataSource(ctx, &query); err != nil {
return nil, err
}
@ -415,17 +415,17 @@ func getRawDataSourceByUID(ctx context.Context, uid string, orgID int64) (*model
}
// Get /api/datasources/name/:name
func GetDataSourceByName(c *models.ReqContext) response.Response {
func (hs *HTTPServer) GetDataSourceByName(c *models.ReqContext) response.Response {
query := models.GetDataSourceQuery{Name: web.Params(c.Req)[":name"], OrgId: c.OrgId}
if err := bus.Dispatch(c.Req.Context(), &query); err != nil {
if err := hs.SQLStore.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
return response.Error(500, "Failed to query datasources", err)
}
filtered, err := filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, []*models.DataSource{query.Result})
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, []*models.DataSource{query.Result})
if err != nil || len(filtered) != 1 {
return response.Error(404, "Data source not found", err)
}
@ -435,10 +435,10 @@ func GetDataSourceByName(c *models.ReqContext) response.Response {
}
// Get /api/datasources/id/:name
func GetDataSourceIdByName(c *models.ReqContext) response.Response {
func (hs *HTTPServer) GetDataSourceIdByName(c *models.ReqContext) response.Response {
query := models.GetDataSourceQuery{Name: web.Params(c.Req)[":name"], OrgId: c.OrgId}
if err := bus.Dispatch(c.Req.Context(), &query); err != nil {
if err := hs.SQLStore.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
@ -593,13 +593,14 @@ func (hs *HTTPServer) decryptSecureJsonDataFn() func(map[string][]byte) map[stri
}
}
func filterDatasourcesByQueryPermission(ctx context.Context, user *models.SignedInUser, datasources []*models.DataSource) ([]*models.DataSource, error) {
func (hs *HTTPServer) filterDatasourcesByQueryPermission(ctx context.Context, user *models.SignedInUser, datasources []*models.DataSource) ([]*models.DataSource, error) {
query := models.DatasourcesPermissionFilterQuery{
User: user,
Datasources: datasources,
}
query.Result = datasources
if err := bus.Dispatch(ctx, &query); err != nil {
if err := hs.DatasourcePermissionsService.FilterDatasourcesBasedOnQueryPermissions(ctx, &query); err != nil {
if !errors.Is(err, bus.ErrHandlerNotFound) {
return nil, err
}

View File

@ -2,7 +2,6 @@ package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@ -12,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
@ -28,26 +26,25 @@ const (
)
func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
mock := mockstore.NewSQLStoreMock()
mockSQLStore := mockstore.NewSQLStoreMock()
mockDatasourcePermissionService := newMockDatasourcePermissionService()
loggedInUserScenario(t, "When calling GET on", "/api/datasources/", "/api/datasources/", func(sc *scenarioContext) {
// Stubs the database query
bus.AddHandler("test", func(ctx context.Context, query *models.GetDataSourcesQuery) error {
assert.Equal(t, testOrgID, query.OrgId)
query.Result = []*models.DataSource{
{Name: "mmm"},
{Name: "ZZZ"},
{Name: "BBB"},
{Name: "aaa"},
}
return nil
})
ds := []*models.DataSource{
{Name: "mmm"},
{Name: "ZZZ"},
{Name: "BBB"},
{Name: "aaa"},
}
mockSQLStore.ExpectedDatasources = ds
mockDatasourcePermissionService.dsResult = ds
// handler func being tested
hs := &HTTPServer{
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
pluginStore: &fakePluginStore{},
SQLStore: mock,
Cfg: setting.NewCfg(),
pluginStore: &fakePluginStore{},
SQLStore: mockSQLStore,
DatasourcePermissionsService: mockDatasourcePermissionService,
}
sc.handlerFunc = hs.GetDataSources
sc.fakeReq("GET", "/api/datasources").exec()
@ -60,27 +57,27 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
assert.Equal(t, "BBB", respJSON[1]["name"])
assert.Equal(t, "mmm", respJSON[2]["name"])
assert.Equal(t, "ZZZ", respJSON[3]["name"])
}, mock)
}, mockSQLStore)
loggedInUserScenario(t, "Should be able to save a data source when calling DELETE on non-existing",
"/api/datasources/name/12345", "/api/datasources/name/:name", func(sc *scenarioContext) {
// handler func being tested
hs := &HTTPServer{
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
pluginStore: &fakePluginStore{},
}
sc.handlerFunc = hs.DeleteDataSourceByName
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 404, sc.resp.Code)
}, mock)
}, mockSQLStore)
}
// Adding data sources with invalid URLs should lead to an error.
func TestAddDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(t, "/api/datasources")
hs := &HTTPServer{
SQLStore: mockstore.NewSQLStoreMock(),
}
sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(models.AddDataSourceCommand{
@ -89,7 +86,7 @@ func TestAddDataSource_InvalidURL(t *testing.T) {
Access: "direct",
Type: "test",
})
return AddDataSource(c)
return hs.AddDataSource(c)
}))
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
@ -99,19 +96,14 @@ func TestAddDataSource_InvalidURL(t *testing.T) {
// Adding data sources with URLs not specifying protocol should work.
func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
defer bus.ClearBusHandlers()
const name = "Test"
const url = "localhost:5432"
// Stub handler
bus.AddHandler("sql", func(ctx context.Context, cmd *models.AddDataSourceCommand) error {
assert.Equal(t, name, cmd.Name)
assert.Equal(t, url, cmd.Url)
cmd.Result = &models.DataSource{}
return nil
})
mockSQLStore := mockstore.NewSQLStoreMock()
mockSQLStore.ExpectedDatasource = &models.DataSource{}
hs := &HTTPServer{
SQLStore: mockSQLStore,
}
sc := setupScenarioContext(t, "/api/datasources")
@ -122,7 +114,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
Access: "direct",
Type: "test",
})
return AddDataSource(c)
return hs.AddDataSource(c)
}))
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
@ -132,8 +124,9 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
// Updating data sources with invalid URLs should lead to an error.
func TestUpdateDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers()
hs := &HTTPServer{
SQLStore: mockstore.NewSQLStoreMock(),
}
sc := setupScenarioContext(t, "/api/datasources/1234")
sc.m.Put(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
@ -143,7 +136,7 @@ func TestUpdateDataSource_InvalidURL(t *testing.T) {
Access: "direct",
Type: "test",
})
return AddDataSource(c)
return hs.AddDataSource(c)
}))
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
@ -153,19 +146,15 @@ func TestUpdateDataSource_InvalidURL(t *testing.T) {
// Updating data sources with URLs not specifying protocol should work.
func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
defer bus.ClearBusHandlers()
const name = "Test"
const url = "localhost:5432"
mockSQLStore := mockstore.NewSQLStoreMock()
hs := &HTTPServer{
SQLStore: mockSQLStore,
}
// Stub handler
bus.AddHandler("sql", func(ctx context.Context, cmd *models.AddDataSourceCommand) error {
assert.Equal(t, name, cmd.Name)
assert.Equal(t, url, cmd.Url)
cmd.Result = &models.DataSource{}
return nil
})
mockSQLStore.ExpectedDatasource = &models.DataSource{}
sc := setupScenarioContext(t, "/api/datasources/1234")
@ -176,7 +165,7 @@ func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
Access: "direct",
Type: "test",
})
return AddDataSource(c)
return hs.AddDataSource(c)
}))
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
@ -204,44 +193,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
Access: "Proxy",
ReadOnly: true,
}
getDatasourceStub := func(ctx context.Context, query *models.GetDataSourceQuery) error {
result := testDatasource
result.Id = query.Id
result.OrgId = query.OrgId
query.Result = &result
return nil
}
getDatasourcesStub := func(ctx context.Context, cmd *models.GetDataSourcesQuery) error {
cmd.Result = []*models.DataSource{}
return nil
}
addDatasourceStub := func(ctx context.Context, cmd *models.AddDataSourceCommand) error {
cmd.Result = &testDatasource
return nil
}
updateDatasourceStub := func(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
cmd.Result = &testDatasource
return nil
}
updateDatasourceReadOnlyStub := func(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
cmd.Result = &testDatasourceReadOnly
return nil
}
getDatasourceNotFoundStub := func(ctx context.Context, cmd *models.GetDataSourceQuery) error {
cmd.Result = nil
return models.ErrDataSourceNotFound
}
getDatasourceReadOnlyStub := func(ctx context.Context, query *models.GetDataSourceQuery) error {
query.Result = &testDatasourceReadOnly
return nil
}
deleteDatasourceStub := func(ctx context.Context, cmd *models.DeleteDataSourceCommand) error {
cmd.DeletedDatasourcesCount = 1
return nil
}
addDatasourceBody := func() io.Reader {
s, _ := json.Marshal(models.AddDataSourceCommand{
Name: "test",
@ -251,6 +203,14 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
})
return bytes.NewReader(s)
}
sqlStore := mockstore.NewSQLStoreMock()
sqlStore.ExpectedDatasource = &testDatasource
dsPermissionService := newMockDatasourcePermissionService()
dsPermissionService.dsResult = []*models.DataSource{
&testDatasource,
}
updateDatasourceBody := func() io.Reader {
s, _ := json.Marshal(models.UpdateDataSourceCommand{
Name: "test",
@ -261,14 +221,14 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
return bytes.NewReader(s)
}
type acTestCaseWithHandler struct {
busStubs []bus.HandlerFunc
body func() io.Reader
body func() io.Reader
accessControlTestCase
expectedDS *models.DataSource
expectedSQLError error
}
tests := []acTestCaseWithHandler{
{
busStubs: []bus.HandlerFunc{getDatasourceNotFoundStub, updateDatasourceStub},
body: updateDatasourceBody,
body: updateDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusNotFound,
desc: "DatasourcesPut should return 404 if datasource not found",
@ -281,9 +241,9 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedSQLError: models.ErrDataSourceNotFound,
},
{
busStubs: []bus.HandlerFunc{getDatasourcesStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGet should return 200 for user with correct permissions",
@ -302,8 +262,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{addDatasourceStub},
body: addDatasourceBody,
body: addDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesPost should return 200 for user with correct permissions",
@ -311,6 +270,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodPost,
permissions: []*accesscontrol.Permission{{Action: ActionDatasourcesCreate}},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -322,8 +282,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, updateDatasourceStub},
body: updateDatasourceBody,
body: updateDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesPut should return 200 for user with correct permissions",
@ -336,6 +295,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -347,8 +307,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceReadOnlyStub, updateDatasourceReadOnlyStub},
body: updateDatasourceBody,
body: updateDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesPut should return 403 for read only datasource",
@ -361,9 +320,9 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasourceReadOnly,
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, deleteDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesDeleteByID should return 200 for user with correct permissions",
@ -376,6 +335,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -387,7 +347,6 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, deleteDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesDeleteByUID should return 200 for user with correct permissions",
@ -400,6 +359,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -411,7 +371,6 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, deleteDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesDeleteByName should return 200 for user with correct permissions",
@ -424,6 +383,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -435,7 +395,6 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetByID should return 200 for user with correct permissions",
@ -448,6 +407,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -459,7 +419,6 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetByUID should return 200 for user with correct permissions",
@ -472,6 +431,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -483,7 +443,6 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetByName should return 200 for user with correct permissions",
@ -496,6 +455,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -505,9 +465,9 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
expectedDS: &testDatasource,
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetIdByName should return 200 for user with correct permissions",
@ -520,6 +480,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
},
},
},
expectedDS: &testDatasource,
},
{
accessControlTestCase: accessControlTestCase{
@ -529,19 +490,26 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
expectedDS: &testDatasource,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
for i, handler := range test.busStubs {
bus.AddHandler(fmt.Sprintf("test_handler_%v", i), handler)
}
cfg := setting.NewCfg()
sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions)
// mock sqlStore and datasource permission service
sqlStore.ExpectedError = test.expectedSQLError
sqlStore.ExpectedDatasource = test.expectedDS
dsPermissionService.dsResult = []*models.DataSource{test.expectedDS}
if test.expectedDS == nil {
dsPermissionService.dsResult = nil
}
sc.sqlStore = sqlStore
hs.SQLStore = sqlStore
hs.DatasourcePermissionsService = dsPermissionService
// Create a middleware to pretend user is logged in
pretendSignInMiddleware := func(c *models.ReqContext) {
sc.context = c

View File

@ -23,7 +23,7 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins Enab
return nil, err
}
filtered, err := filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, query.Result)
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, query.Result)
if err != nil {
return nil, err
}

View File

@ -76,60 +76,61 @@ type HTTPServer struct {
middlewares []web.Handler
namedMiddlewares []routing.RegisterNamedMiddleware
PluginContextProvider *plugincontext.Provider
RouteRegister routing.RouteRegister
Bus bus.Bus
RenderService rendering.Service
Cfg *setting.Cfg
Features *featuremgmt.FeatureManager
SettingsProvider setting.Provider
HooksService *hooks.HooksService
CacheService *localcache.CacheService
DataSourceCache datasources.CacheService
AuthTokenService models.UserTokenService
QuotaService *quota.QuotaService
RemoteCacheService *remotecache.RemoteCache
ProvisioningService provisioning.ProvisioningService
Login login.Service
License models.Licensing
AccessControl accesscontrol.AccessControl
DataProxy *datasourceproxy.DataSourceProxyService
PluginRequestValidator models.PluginRequestValidator
pluginClient plugins.Client
pluginStore plugins.Store
pluginDashboardManager plugins.PluginDashboardManager
pluginStaticRouteResolver plugins.StaticRouteResolver
pluginErrorResolver plugins.ErrorResolver
SearchService search.Service
ShortURLService shorturls.Service
QueryHistoryService queryhistory.Service
Live *live.GrafanaLive
LivePushGateway *pushhttp.Gateway
ThumbService thumbs.Service
ContextHandler *contexthandler.ContextHandler
SQLStore sqlstore.Store
AlertEngine *alerting.AlertEngine
LoadSchemaService *schemaloader.SchemaLoaderService
AlertNG *ngalert.AlertNG
LibraryPanelService librarypanels.Service
LibraryElementService libraryelements.Service
SocialService social.Service
Listener net.Listener
EncryptionService encryption.Internal
SecretsService secrets.Service
DataSourcesService *datasources.Service
cleanUpService *cleanup.CleanUpService
tracer tracing.Tracer
grafanaUpdateChecker *updatechecker.GrafanaService
pluginsUpdateChecker *updatechecker.PluginsService
searchUsersService searchusers.Service
ldapGroups ldap.Groups
teamGuardian teamguardian.TeamGuardian
queryDataService *query.Service
serviceAccountsService serviceaccounts.Service
authInfoService login.AuthInfoService
TeamPermissionsService *resourcepermissions.Service
NotificationService *notifications.NotificationService
PluginContextProvider *plugincontext.Provider
RouteRegister routing.RouteRegister
Bus bus.Bus
RenderService rendering.Service
Cfg *setting.Cfg
Features *featuremgmt.FeatureManager
SettingsProvider setting.Provider
HooksService *hooks.HooksService
CacheService *localcache.CacheService
DataSourceCache datasources.CacheService
AuthTokenService models.UserTokenService
QuotaService *quota.QuotaService
RemoteCacheService *remotecache.RemoteCache
ProvisioningService provisioning.ProvisioningService
Login login.Service
License models.Licensing
AccessControl accesscontrol.AccessControl
DataProxy *datasourceproxy.DataSourceProxyService
PluginRequestValidator models.PluginRequestValidator
pluginClient plugins.Client
pluginStore plugins.Store
pluginDashboardManager plugins.PluginDashboardManager
pluginStaticRouteResolver plugins.StaticRouteResolver
pluginErrorResolver plugins.ErrorResolver
SearchService search.Service
ShortURLService shorturls.Service
QueryHistoryService queryhistory.Service
Live *live.GrafanaLive
LivePushGateway *pushhttp.Gateway
ThumbService thumbs.Service
ContextHandler *contexthandler.ContextHandler
SQLStore sqlstore.Store
AlertEngine *alerting.AlertEngine
LoadSchemaService *schemaloader.SchemaLoaderService
AlertNG *ngalert.AlertNG
LibraryPanelService librarypanels.Service
LibraryElementService libraryelements.Service
SocialService social.Service
Listener net.Listener
EncryptionService encryption.Internal
SecretsService secrets.Service
DataSourcesService *datasources.Service
cleanUpService *cleanup.CleanUpService
tracer tracing.Tracer
grafanaUpdateChecker *updatechecker.GrafanaService
pluginsUpdateChecker *updatechecker.PluginsService
searchUsersService searchusers.Service
ldapGroups ldap.Groups
teamGuardian teamguardian.TeamGuardian
queryDataService *query.Service
serviceAccountsService serviceaccounts.Service
authInfoService login.AuthInfoService
TeamPermissionsService *resourcepermissions.Service
NotificationService *notifications.NotificationService
DatasourcePermissionsService DatasourcePermissionsService
}
type ServerOptions struct {
@ -157,67 +158,68 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dataSourcesService *datasources.Service, secretsService secrets.Service, queryDataService *query.Service,
ldapGroups ldap.Groups, teamGuardian teamguardian.TeamGuardian, serviceaccountsService serviceaccounts.Service,
authInfoService login.AuthInfoService, resourcePermissionServices *resourceservices.ResourceServices,
notificationService *notifications.NotificationService) (*HTTPServer, error) {
notificationService *notifications.NotificationService, datasourcePermissionsService DatasourcePermissionsService) (*HTTPServer, error) {
web.Env = cfg.Env
m := web.New()
hs := &HTTPServer{
Cfg: cfg,
RouteRegister: routeRegister,
Bus: bus,
RenderService: renderService,
License: licensing,
HooksService: hooksService,
CacheService: cacheService,
SQLStore: sqlStore,
AlertEngine: alertEngine,
PluginRequestValidator: pluginRequestValidator,
pluginClient: pluginClient,
pluginStore: pluginStore,
pluginStaticRouteResolver: pluginStaticRouteResolver,
pluginDashboardManager: pluginDashboardManager,
pluginErrorResolver: pluginErrorResolver,
grafanaUpdateChecker: grafanaUpdateChecker,
pluginsUpdateChecker: pluginsUpdateChecker,
SettingsProvider: settingsProvider,
DataSourceCache: dataSourceCache,
AuthTokenService: userTokenService,
cleanUpService: cleanUpService,
ShortURLService: shortURLService,
QueryHistoryService: queryHistoryService,
Features: features,
ThumbService: thumbService,
RemoteCacheService: remoteCache,
ProvisioningService: provisioningService,
Login: loginService,
AccessControl: accessControl,
DataProxy: dataSourceProxy,
SearchService: searchService,
Live: live,
LivePushGateway: livePushGateway,
PluginContextProvider: plugCtxProvider,
ContextHandler: contextHandler,
LoadSchemaService: schemaService,
AlertNG: alertNG,
LibraryPanelService: libraryPanelService,
LibraryElementService: libraryElementService,
QuotaService: quotaService,
tracer: tracer,
log: log.New("http.server"),
web: m,
Listener: opts.Listener,
SocialService: socialService,
EncryptionService: encryptionService,
SecretsService: secretsService,
DataSourcesService: dataSourcesService,
searchUsersService: searchUsersService,
ldapGroups: ldapGroups,
teamGuardian: teamGuardian,
queryDataService: queryDataService,
serviceAccountsService: serviceaccountsService,
authInfoService: authInfoService,
TeamPermissionsService: resourcePermissionServices.GetTeamService(),
NotificationService: notificationService,
Cfg: cfg,
RouteRegister: routeRegister,
Bus: bus,
RenderService: renderService,
License: licensing,
HooksService: hooksService,
CacheService: cacheService,
SQLStore: sqlStore,
AlertEngine: alertEngine,
PluginRequestValidator: pluginRequestValidator,
pluginClient: pluginClient,
pluginStore: pluginStore,
pluginStaticRouteResolver: pluginStaticRouteResolver,
pluginDashboardManager: pluginDashboardManager,
pluginErrorResolver: pluginErrorResolver,
grafanaUpdateChecker: grafanaUpdateChecker,
pluginsUpdateChecker: pluginsUpdateChecker,
SettingsProvider: settingsProvider,
DataSourceCache: dataSourceCache,
AuthTokenService: userTokenService,
cleanUpService: cleanUpService,
ShortURLService: shortURLService,
QueryHistoryService: queryHistoryService,
Features: features,
ThumbService: thumbService,
RemoteCacheService: remoteCache,
ProvisioningService: provisioningService,
Login: loginService,
AccessControl: accessControl,
DataProxy: dataSourceProxy,
SearchService: searchService,
Live: live,
LivePushGateway: livePushGateway,
PluginContextProvider: plugCtxProvider,
ContextHandler: contextHandler,
LoadSchemaService: schemaService,
AlertNG: alertNG,
LibraryPanelService: libraryPanelService,
LibraryElementService: libraryElementService,
QuotaService: quotaService,
tracer: tracer,
log: log.New("http.server"),
web: m,
Listener: opts.Listener,
SocialService: socialService,
EncryptionService: encryptionService,
SecretsService: secretsService,
DataSourcesService: dataSourcesService,
searchUsersService: searchUsersService,
ldapGroups: ldapGroups,
teamGuardian: teamGuardian,
queryDataService: queryDataService,
serviceAccountsService: serviceaccountsService,
authInfoService: authInfoService,
TeamPermissionsService: resourcePermissionServices.GetTeamService(),
NotificationService: notificationService,
DatasourcePermissionsService: datasourcePermissionsService,
}
if hs.Listener != nil {
hs.log.Debug("Using provided listener")

View File

@ -5,6 +5,7 @@ package server
import (
"github.com/google/wire"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
@ -73,6 +74,8 @@ var wireExtsBasicSet = wire.NewSet(
wire.Bind(new(kmsproviders.Service), new(osskmsproviders.Service)),
ldap.ProvideGroupsService,
wire.Bind(new(ldap.Groups), new(*ldap.OSSGroups)),
api.ProvideDatasourcePermissionsService,
wire.Bind(new(api.DatasourcePermissionsService), new(*api.OSSDatasourcePermissionsService)),
)
var wireExtsSet = wire.NewSet(

View File

@ -28,7 +28,9 @@ type SQLStoreMock struct {
ExpectedDashboardSnapshot *models.DashboardSnapshot
ExpectedTeamsByUser []*models.TeamDTO
ExpectedSearchOrgList []*models.OrgDTO
ExpectedError error
ExpectedDatasources []*models.DataSource
ExpectedError error
}
func NewSQLStoreMock() *SQLStoreMock {
@ -483,6 +485,7 @@ func (m *SQLStoreMock) GetDataSource(ctx context.Context, query *models.GetDataS
}
func (m *SQLStoreMock) GetDataSources(ctx context.Context, query *models.GetDataSourcesQuery) error {
query.Result = m.ExpectedDatasources
return m.ExpectedError
}
@ -499,10 +502,12 @@ func (m *SQLStoreMock) DeleteDataSource(ctx context.Context, cmd *models.DeleteD
}
func (m *SQLStoreMock) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error {
cmd.Result = m.ExpectedDatasource
return m.ExpectedError
}
func (m *SQLStoreMock) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
cmd.Result = m.ExpectedDatasource
return m.ExpectedError
}