grafana/pkg/api/datasources_test.go

574 lines
18 KiB
Go
Raw Normal View History

package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"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"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
testOrgID int64 = 1
testUserID int64 = 1
testUserLogin string = "testUser"
)
func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
mock := mockstore.NewSQLStoreMock()
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
})
// handler func being tested
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-03-17 10:06:10 -05:00
hs := &HTTPServer{
Plugins: Refactor Plugin Management (#40477) * add core plugin flow * add instrumentation * move func * remove cruft * support external backend plugins * refactor + clean up * remove comments * refactor loader * simplify core plugin path arg * cleanup loggers * move signature validator to plugins package * fix sig packaging * cleanup plugin model * remove unnecessary plugin field * add start+stop for pm * fix failures * add decommissioned state * export fields just to get things flowing * fix comments * set static routes * make image loading idempotent * merge with backend plugin manager * re-use funcs * reorder imports + remove unnecessary interface * add some TODOs + remove unused func * remove unused instrumentation func * simplify client usage * remove import alias * re-use backendplugin.Plugin interface * re order funcs * improve var name * fix log statements * refactor data model * add logic for dupe check during loading * cleanup state setting * refactor loader * cleanup manager interface * add rendering flow * refactor loading + init * add renderer support * fix renderer plugin * reformat imports * track errors * fix plugin signature inheritance * name param in interface * update func comment * fix func arg name * introduce class concept * remove func * fix external plugin check * apply changes from pm-experiment * fix core plugins * fix imports * rename interface * comment API interface * add support for testdata plugin * enable alerting + use correct core plugin contracts * slim manager API * fix param name * fix filter * support static routes * fix rendering * tidy rendering * get tests compiling * fix install+uninstall * start finder test * add finder test coverage * start loader tests * add test for core plugins * load core + bundled test * add test for nested plugin loading * add test files * clean interface + fix registering some core plugins * refactoring * reformat and create sub packages * simplify core plugin init * fix ctx cancel scenario * migrate initializer * remove Init() funcs * add test starter * new logger * flesh out initializer tests * refactoring * remove unused svc * refactor rendering flow * fixup loader tests * add enabled helper func * fix logger name * fix data fetchers * fix case where plugin dir doesn't exist * improve coverage + move dupe checking to loader * remove noisy debug logs * register core plugins automagically * add support for renderer in catalog * make private func + fix req validation * use interface * re-add check for renderer in catalog * tidy up from moving to auto reg core plugins * core plugin registrar * guards * copy over core plugins for test infra * all tests green * renames * propagate new interfaces * kill old manager * get compiling * tidy up * update naming * refactor manager test + cleanup * add more cases to finder test * migrate validator to field * more coverage * refactor dupe checking * add test for plugin class * add coverage for initializer * split out rendering * move * fixup tests * fix uss test * fix frontend settings * fix grafanads test * add check when checking sig errors * fix enabled map * fixup * allow manual setup of CM * rename to cloud-monitoring * remove TODO * add installer interface for testing * loader interface returns * tests passing * refactor + add more coverage * support 'stackdriver' * fix frontend settings loading * improve naming based on package name * small tidy * refactor test * fix renderer start * make cloud-monitoring plugin ID clearer * add plugin update test * add integration tests * don't break all if sig can't be calculated * add root URL check test * add more signature verification tests * update DTO name * update enabled plugins comment * update comments * fix linter * revert fe naming change * fix errors endpoint * reset error code field name * re-order test to help verify * assert -> require * pm check * add missing entry + re-order * re-check * dump icon log * verify manager contents first * reformat * apply PR feedback * apply style changes * fix one vs all loading err * improve log output * only start when no signature error * move log * rework plugin update check * fix test * fix multi loading from cfg.PluginSettings * improve log output #2 * add error abstraction to capture errors without registering a plugin * add debug log * add unsigned warning * e2e test attempt * fix logger * set home path * prevent panic * alternate * ugh.. fix home path * return renderer even if not started * make renderer plugin managed * add fallback renderer icon, update renderer badge + prevent changes when renderer is installed * fix icon loading * rollback renderer changes * use correct field * remove unneccessary block * remove newline * remove unused func * fix bundled plugins base + module fields * remove unused field since refactor * add authorizer abstraction * loader only returns plugins expected to run * fix multi log output
2021-11-01 04:53:33 -05:00
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
pluginStore: &fakePluginStore{},
SQLStore: mock,
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-03-17 10:06:10 -05:00
}
sc.handlerFunc = hs.GetDataSources
sc.fakeReq("GET", "/api/datasources").exec()
respJSON := []map[string]interface{}{}
err := json.NewDecoder(sc.resp.Body).Decode(&respJSON)
require.NoError(t, err)
assert.Equal(t, "aaa", respJSON[0]["name"])
assert.Equal(t, "BBB", respJSON[1]["name"])
assert.Equal(t, "mmm", respJSON[2]["name"])
assert.Equal(t, "ZZZ", respJSON[3]["name"])
}, mock)
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{
Plugins: Refactor Plugin Management (#40477) * add core plugin flow * add instrumentation * move func * remove cruft * support external backend plugins * refactor + clean up * remove comments * refactor loader * simplify core plugin path arg * cleanup loggers * move signature validator to plugins package * fix sig packaging * cleanup plugin model * remove unnecessary plugin field * add start+stop for pm * fix failures * add decommissioned state * export fields just to get things flowing * fix comments * set static routes * make image loading idempotent * merge with backend plugin manager * re-use funcs * reorder imports + remove unnecessary interface * add some TODOs + remove unused func * remove unused instrumentation func * simplify client usage * remove import alias * re-use backendplugin.Plugin interface * re order funcs * improve var name * fix log statements * refactor data model * add logic for dupe check during loading * cleanup state setting * refactor loader * cleanup manager interface * add rendering flow * refactor loading + init * add renderer support * fix renderer plugin * reformat imports * track errors * fix plugin signature inheritance * name param in interface * update func comment * fix func arg name * introduce class concept * remove func * fix external plugin check * apply changes from pm-experiment * fix core plugins * fix imports * rename interface * comment API interface * add support for testdata plugin * enable alerting + use correct core plugin contracts * slim manager API * fix param name * fix filter * support static routes * fix rendering * tidy rendering * get tests compiling * fix install+uninstall * start finder test * add finder test coverage * start loader tests * add test for core plugins * load core + bundled test * add test for nested plugin loading * add test files * clean interface + fix registering some core plugins * refactoring * reformat and create sub packages * simplify core plugin init * fix ctx cancel scenario * migrate initializer * remove Init() funcs * add test starter * new logger * flesh out initializer tests * refactoring * remove unused svc * refactor rendering flow * fixup loader tests * add enabled helper func * fix logger name * fix data fetchers * fix case where plugin dir doesn't exist * improve coverage + move dupe checking to loader * remove noisy debug logs * register core plugins automagically * add support for renderer in catalog * make private func + fix req validation * use interface * re-add check for renderer in catalog * tidy up from moving to auto reg core plugins * core plugin registrar * guards * copy over core plugins for test infra * all tests green * renames * propagate new interfaces * kill old manager * get compiling * tidy up * update naming * refactor manager test + cleanup * add more cases to finder test * migrate validator to field * more coverage * refactor dupe checking * add test for plugin class * add coverage for initializer * split out rendering * move * fixup tests * fix uss test * fix frontend settings * fix grafanads test * add check when checking sig errors * fix enabled map * fixup * allow manual setup of CM * rename to cloud-monitoring * remove TODO * add installer interface for testing * loader interface returns * tests passing * refactor + add more coverage * support 'stackdriver' * fix frontend settings loading * improve naming based on package name * small tidy * refactor test * fix renderer start * make cloud-monitoring plugin ID clearer * add plugin update test * add integration tests * don't break all if sig can't be calculated * add root URL check test * add more signature verification tests * update DTO name * update enabled plugins comment * update comments * fix linter * revert fe naming change * fix errors endpoint * reset error code field name * re-order test to help verify * assert -> require * pm check * add missing entry + re-order * re-check * dump icon log * verify manager contents first * reformat * apply PR feedback * apply style changes * fix one vs all loading err * improve log output * only start when no signature error * move log * rework plugin update check * fix test * fix multi loading from cfg.PluginSettings * improve log output #2 * add error abstraction to capture errors without registering a plugin * add debug log * add unsigned warning * e2e test attempt * fix logger * set home path * prevent panic * alternate * ugh.. fix home path * return renderer even if not started * make renderer plugin managed * add fallback renderer icon, update renderer badge + prevent changes when renderer is installed * fix icon loading * rollback renderer changes * use correct field * remove unneccessary block * remove newline * remove unused func * fix bundled plugins base + module fields * remove unused field since refactor * add authorizer abstraction * loader only returns plugins expected to run * fix multi log output
2021-11-01 04:53:33 -05:00
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)
}
// 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")
sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(models.AddDataSourceCommand{
Name: "Test",
Url: "invalid:url",
Access: "direct",
Type: "test",
})
return AddDataSource(c)
}))
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 400, sc.resp.Code)
}
// 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
})
sc := setupScenarioContext(t, "/api/datasources")
sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(models.AddDataSourceCommand{
Name: name,
Url: url,
Access: "direct",
Type: "test",
})
return AddDataSource(c)
}))
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
}
// Updating data sources with invalid URLs should lead to an error.
func TestUpdateDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(t, "/api/datasources/1234")
sc.m.Put(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(models.AddDataSourceCommand{
Name: "Test",
Url: "invalid:url",
Access: "direct",
Type: "test",
})
return AddDataSource(c)
}))
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
assert.Equal(t, 400, sc.resp.Code)
}
// 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"
// 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
})
sc := setupScenarioContext(t, "/api/datasources/1234")
sc.m.Put(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(models.AddDataSourceCommand{
Name: name,
Url: url,
Access: "direct",
Type: "test",
})
return AddDataSource(c)
}))
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
}
func TestAPI_Datasources_AccessControl(t *testing.T) {
testDatasource := models.DataSource{
Id: 3,
Uid: "testUID",
OrgId: testOrgID,
Name: "test",
Url: "http://localhost:5432",
Type: "postgresql",
Access: "Proxy",
}
testDatasourceReadOnly := models.DataSource{
Id: 4,
Uid: "testUID",
OrgId: testOrgID,
Name: "test",
Url: "http://localhost:5432",
Type: "postgresql",
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",
Url: "http://localhost:5432",
Type: "postgresql",
Access: "Proxy",
})
return bytes.NewReader(s)
}
updateDatasourceBody := func() io.Reader {
s, _ := json.Marshal(models.UpdateDataSourceCommand{
Name: "test",
Url: "http://localhost:5432",
Type: "postgresql",
Access: "Proxy",
})
return bytes.NewReader(s)
}
type acTestCaseWithHandler struct {
busStubs []bus.HandlerFunc
body func() io.Reader
accessControlTestCase
}
tests := []acTestCaseWithHandler{
{
busStubs: []bus.HandlerFunc{getDatasourceNotFoundStub, updateDatasourceStub},
body: updateDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusNotFound,
desc: "DatasourcesPut should return 404 if datasource not found",
url: fmt.Sprintf("/api/datasources/%v", "12345678"),
method: http.MethodPut,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesWrite,
Scope: ScopeDatasourcesAll,
},
},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourcesStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGet should return 200 for user with correct permissions",
url: "/api/datasources/",
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: ActionDatasourcesRead, Scope: ScopeDatasourcesAll}},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesGet should return 403 for user without required permissions",
url: "/api/datasources/",
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{addDatasourceStub},
body: addDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesPost should return 200 for user with correct permissions",
url: "/api/datasources/",
method: http.MethodPost,
permissions: []*accesscontrol.Permission{{Action: ActionDatasourcesCreate}},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesPost should return 403 for user without required permissions",
url: "/api/datasources/",
method: http.MethodPost,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, updateDatasourceStub},
body: updateDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesPut should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/%v", testDatasource.Id),
method: http.MethodPut,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesWrite,
Scope: fmt.Sprintf("datasources:id:%v", testDatasource.Id),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesPut should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/%v", testDatasource.Id),
method: http.MethodPut,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceReadOnlyStub, updateDatasourceReadOnlyStub},
body: updateDatasourceBody,
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesPut should return 403 for read only datasource",
url: fmt.Sprintf("/api/datasources/%v", testDatasourceReadOnly.Id),
method: http.MethodPut,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesWrite,
Scope: fmt.Sprintf("datasources:id:%v", testDatasourceReadOnly.Id),
},
},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, deleteDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesDeleteByID should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/%v", testDatasource.Id),
method: http.MethodDelete,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesDelete,
Scope: fmt.Sprintf("datasources:id:%v", testDatasource.Id),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesDeleteByID should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/%v", testDatasource.Id),
method: http.MethodDelete,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, deleteDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesDeleteByUID should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/uid/%v", testDatasource.Uid),
method: http.MethodDelete,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesDelete,
Scope: fmt.Sprintf("datasources:uid:%v", testDatasource.Uid),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesDeleteByUID should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/uid/%v", testDatasource.Uid),
method: http.MethodDelete,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub, deleteDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesDeleteByName should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/name/%v", testDatasource.Name),
method: http.MethodDelete,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesDelete,
Scope: fmt.Sprintf("datasources:name:%v", testDatasource.Name),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesDeleteByName should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/name/%v", testDatasource.Name),
method: http.MethodDelete,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetByID should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/%v", testDatasource.Id),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesRead,
Scope: fmt.Sprintf("datasources:id:%v", testDatasource.Id),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesGetByID should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/%v", testDatasource.Id),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetByUID should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/uid/%v", testDatasource.Uid),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesRead,
Scope: fmt.Sprintf("datasources:uid:%v", testDatasource.Uid),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesGetByUID should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/uid/%v", testDatasource.Uid),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetByName should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/name/%v", testDatasource.Name),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesRead,
Scope: fmt.Sprintf("datasources:name:%v", testDatasource.Name),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesGetByName should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/name/%v", testDatasource.Name),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
{
busStubs: []bus.HandlerFunc{getDatasourceStub},
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusOK,
desc: "DatasourcesGetIdByName should return 200 for user with correct permissions",
url: fmt.Sprintf("/api/datasources/id/%v", testDatasource.Name),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{
{
Action: ActionDatasourcesIDRead,
Scope: fmt.Sprintf("datasources:name:%v", testDatasource.Name),
},
},
},
},
{
accessControlTestCase: accessControlTestCase{
expectedCode: http.StatusForbidden,
desc: "DatasourcesGetIdByName should return 403 for user without required permissions",
url: fmt.Sprintf("/api/datasources/id/%v", testDatasource.Name),
method: http.MethodGet,
permissions: []*accesscontrol.Permission{{Action: "wrong"}},
},
},
}
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)
// Create a middleware to pretend user is logged in
pretendSignInMiddleware := func(c *models.ReqContext) {
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
sc.context.Login = testUserLogin
sc.context.OrgRole = models.ROLE_VIEWER
sc.context.IsSignedIn = true
}
sc.m.Use(pretendSignInMiddleware)
sc.resp = httptest.NewRecorder()
hs.SettingsProvider = &setting.OSSImpl{Cfg: cfg}
var err error
if test.body != nil {
sc.req, err = http.NewRequest(test.method, test.url, test.body())
sc.req.Header.Add("Content-Type", "application/json")
} else {
sc.req, err = http.NewRequest(test.method, test.url, nil)
}
assert.NoError(t, err)
sc.exec()
assert.Equal(t, test.expectedCode, sc.resp.Code)
})
}
}