From d2464812eb90cc2c01e4361d74ce5a4cfbd56a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 28 Sep 2018 14:52:12 +0200 Subject: [PATCH 1/4] noop services poc --- pkg/cmd/grafana-server/server.go | 1 + .../datasources/datasource_service.go | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 pkg/services/datasources/datasource_service.go diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 8794d7d8338..b2f4a620208 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -32,6 +32,7 @@ import ( _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" + _ "github.com/grafana/grafana/pkg/services/datasources" _ "github.com/grafana/grafana/pkg/services/notifications" _ "github.com/grafana/grafana/pkg/services/provisioning" _ "github.com/grafana/grafana/pkg/services/rendering" diff --git a/pkg/services/datasources/datasource_service.go b/pkg/services/datasources/datasource_service.go new file mode 100644 index 00000000000..2fba0bb5b87 --- /dev/null +++ b/pkg/services/datasources/datasource_service.go @@ -0,0 +1,50 @@ +package datasources + +import ( + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" +) + +type DataSourceService interface { + GetById(id int64, user *models.SignedInUser) (*models.DataSource, error) +} + +type DataSourceServiceImpl struct { + log log.Logger + Cfg *setting.Cfg `inject:""` + Guardian DataSourceGuardian `inject:""` +} + +func init() { + registry.RegisterService(&DataSourceServiceImpl{}) + registry.RegisterService(&DataSourceGuardianNoop{}) +} + +func (srv *DataSourceServiceImpl) Init() error { + srv.log = log.New("datasources") + srv.log.Info("hello", "guardian", srv.Guardian.GetPermission(0, nil)) + return nil +} + +func (srv *DataSourceServiceImpl) GetById(id int64, user *models.SignedInUser) { + // check cache + // Get by id from db + // check permissions +} + +type DataSourceGuardian interface { + GetPermission(id int64, user *models.SignedInUser) bool +} + +type DataSourceGuardianNoop struct { +} + +func (dsg *DataSourceGuardianNoop) Init() error { + return nil +} + +func (dsg *DataSourceGuardianNoop) GetPermission(id int64, user *models.SignedInUser) bool { + return false +} From b3c78f1265d3fdd35768fd54adff85917e91531f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Oct 2018 15:38:55 +0200 Subject: [PATCH 2/4] wip: data source permissions hooks --- pkg/api/api.go | 6 +-- pkg/api/datasources.go | 34 ++++++++++--- pkg/cmd/grafana-server/server.go | 1 - pkg/models/datasource.go | 26 +++++++++- .../datasources/datasource_service.go | 50 ------------------- pkg/services/sqlstore/datasource.go | 1 + 6 files changed, 55 insertions(+), 63 deletions(-) delete mode 100644 pkg/services/datasources/datasource_service.go diff --git a/pkg/api/api.go b/pkg/api/api.go index 39b332aeb9f..dcbc3a7c58f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -234,13 +234,13 @@ func (hs *HTTPServer) registerRoutes() { datasourceRoute.Get("/", Wrap(GetDataSources)) datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), Wrap(AddDataSource)) datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), Wrap(UpdateDataSource)) - datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceByID)) + datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceById)) datasourceRoute.Delete("/name/:name", Wrap(DeleteDataSourceByName)) - datasourceRoute.Get("/:id", Wrap(GetDataSourceByID)) + datasourceRoute.Get("/:id", Wrap(GetDataSourceById)) datasourceRoute.Get("/name/:name", Wrap(GetDataSourceByName)) }, reqOrgAdmin) - apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIDByName), reqSignedIn) + apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIdByName), reqSignedIn) apiRoute.Get("/plugins", Wrap(GetPluginList)) apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID)) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 23dbb221d71..b1b13d7abfd 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -20,8 +20,8 @@ func GetDataSources(c *m.ReqContext) Response { result := make(dtos.DataSourceList, 0) for _, ds := range query.Result { dsItem := dtos.DataSourceListItemDTO{ - Id: ds.Id, OrgId: ds.OrgId, + Id: ds.Id, Name: ds.Name, Url: ds.Url, Type: ds.Type, @@ -49,7 +49,27 @@ func GetDataSources(c *m.ReqContext) Response { return JSON(200, &result) } -func GetDataSourceByID(c *m.ReqContext) Response { +func hasRequiredDatasourcePermission(dsId int64, permission m.DataSourcePermissionType, user *m.SignedInUser) Response { + query := m.HasRequiredDataSourcePermissionQuery{ + Id: dsId, + User: user, + RequiredPermission: permission, + } + + if err := bus.Dispatch(&query); err != nil { + if err == bus.ErrHandlerNotFound { + return nil + } + if err == m.ErrDataSourceAccessDenied { + return Error(403, err.Error(), nil) + } + return Error(500, "Failed to check data source permissions", err) + } + + return nil +} + +func GetDataSourceById(c *m.ReqContext) Response { query := m.GetDataSourceByIdQuery{ Id: c.ParamsInt64(":id"), OrgId: c.OrgId, @@ -68,14 +88,14 @@ func GetDataSourceByID(c *m.ReqContext) Response { return JSON(200, &dtos) } -func DeleteDataSourceByID(c *m.ReqContext) Response { +func DeleteDataSourceById(c *m.ReqContext) Response { id := c.ParamsInt64(":id") if id <= 0 { return Error(400, "Missing valid datasource id", nil) } - ds, err := getRawDataSourceByID(id, c.OrgId) + ds, err := getRawDataSourceById(id, c.OrgId) if err != nil { return Error(400, "Failed to delete datasource", nil) } @@ -186,7 +206,7 @@ func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error { return nil } - ds, err := getRawDataSourceByID(cmd.Id, cmd.OrgId) + ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId) if err != nil { return err } @@ -206,7 +226,7 @@ func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error { return nil } -func getRawDataSourceByID(id int64, orgID int64) (*m.DataSource, error) { +func getRawDataSourceById(id int64, orgID int64) (*m.DataSource, error) { query := m.GetDataSourceByIdQuery{ Id: id, OrgId: orgID, @@ -236,7 +256,7 @@ func GetDataSourceByName(c *m.ReqContext) Response { } // Get /api/datasources/id/:name -func GetDataSourceIDByName(c *m.ReqContext) Response { +func GetDataSourceIdByName(c *m.ReqContext) Response { query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId} if err := bus.Dispatch(&query); err != nil { diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index b2f4a620208..8794d7d8338 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -32,7 +32,6 @@ import ( _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" - _ "github.com/grafana/grafana/pkg/services/datasources" _ "github.com/grafana/grafana/pkg/services/notifications" _ "github.com/grafana/grafana/pkg/services/provisioning" _ "github.com/grafana/grafana/pkg/services/rendering" diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index cbdd0136f4d..d602acb3ed2 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -29,6 +29,7 @@ var ( ErrDataSourceNameExists = errors.New("Data source with same name already exists") ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource") ErrDatasourceIsReadOnly = errors.New("Data source is readonly. Can only be updated from configuration.") + ErrDataSourceAccessDenied = errors.New("Data source access denied") ) type DsAccess string @@ -165,6 +166,7 @@ type DeleteDataSourceByNameCommand struct { type GetDataSourcesQuery struct { OrgId int64 + User *SignedInUser Result []*DataSource } @@ -185,6 +187,26 @@ type GetDataSourceByNameQuery struct { } // --------------------- -// EVENTS -type DataSourceCreatedEvent struct { +// Permissions +// --------------------- + +type DataSourcePermissionType int + +const ( + DsPermissionQuery DataSourcePermissionType = 1 << iota + DsPermissionAdmin +) + +func (p DataSourcePermissionType) String() string { + names := map[int]string{ + int(DsPermissionQuery): "Query", + int(DsPermissionAdmin): "Admin", + } + return names[int(p)] +} + +type HasRequiredDataSourcePermissionQuery struct { + Id int64 + User *SignedInUser + RequiredPermission DataSourcePermissionType } diff --git a/pkg/services/datasources/datasource_service.go b/pkg/services/datasources/datasource_service.go deleted file mode 100644 index 2fba0bb5b87..00000000000 --- a/pkg/services/datasources/datasource_service.go +++ /dev/null @@ -1,50 +0,0 @@ -package datasources - -import ( - "github.com/grafana/grafana/pkg/log" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/setting" -) - -type DataSourceService interface { - GetById(id int64, user *models.SignedInUser) (*models.DataSource, error) -} - -type DataSourceServiceImpl struct { - log log.Logger - Cfg *setting.Cfg `inject:""` - Guardian DataSourceGuardian `inject:""` -} - -func init() { - registry.RegisterService(&DataSourceServiceImpl{}) - registry.RegisterService(&DataSourceGuardianNoop{}) -} - -func (srv *DataSourceServiceImpl) Init() error { - srv.log = log.New("datasources") - srv.log.Info("hello", "guardian", srv.Guardian.GetPermission(0, nil)) - return nil -} - -func (srv *DataSourceServiceImpl) GetById(id int64, user *models.SignedInUser) { - // check cache - // Get by id from db - // check permissions -} - -type DataSourceGuardian interface { - GetPermission(id int64, user *models.SignedInUser) bool -} - -type DataSourceGuardianNoop struct { -} - -func (dsg *DataSourceGuardianNoop) Init() error { - return nil -} - -func (dsg *DataSourceGuardianNoop) GetPermission(id int64, user *models.SignedInUser) bool { - return false -} diff --git a/pkg/services/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go index 00d520bcfc6..7f70e5c25fc 100644 --- a/pkg/services/sqlstore/datasource.go +++ b/pkg/services/sqlstore/datasource.go @@ -27,6 +27,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error { datasource := m.DataSource{OrgId: query.OrgId, Id: query.Id} has, err := x.Get(&datasource) + if err != nil { return err } From 162a95bff92ac3d637fb2ff87ec2458da1d25c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Oct 2018 19:31:03 +0200 Subject: [PATCH 3/4] wip: going in circles --- pkg/api/datasources.go | 19 ++++++++++++++++++- pkg/models/datasource.go | 19 +++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index b1b13d7abfd..3378fb5bf2e 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -17,6 +17,16 @@ func GetDataSources(c *m.ReqContext) Response { return Error(500, "Failed to query datasources", err) } + permissions := map[int64]m.DsPermissionType{} + permissionsQuery := m.GetDataSourcePermissionsForUserQuery{User: c.SignedInUser} + if err := bus.Dispatch(&permissionsQuery); err != nil { + if err != bus.ErrHandlerNotFound { + return Error(500, "failed to read datasource permissions", err) + } + } else { + permissions = permissionsQuery.Result + } + result := make(dtos.DataSourceList, 0) for _, ds := range query.Result { dsItem := dtos.DataSourceListItemDTO{ @@ -35,6 +45,13 @@ func GetDataSources(c *m.ReqContext) Response { ReadOnly: ds.ReadOnly, } + if permission, ok := permissions[ds.Id]; ok { + c.Logger.Info("Found permission", "permission", permission) + if permission == m.DsPermissionNoAccess { + continue + } + } + if plugin, exists := plugins.DataSources[ds.Type]; exists { dsItem.TypeLogoUrl = plugin.Info.Logos.Small } else { @@ -49,7 +66,7 @@ func GetDataSources(c *m.ReqContext) Response { return JSON(200, &result) } -func hasRequiredDatasourcePermission(dsId int64, permission m.DataSourcePermissionType, user *m.SignedInUser) Response { +func hasRequiredDatasourcePermission(dsId int64, permission m.DsPermissionType, user *m.SignedInUser) Response { query := m.HasRequiredDataSourcePermissionQuery{ Id: dsId, User: user, diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index d602acb3ed2..9a32b326a4b 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -190,17 +190,19 @@ type GetDataSourceByNameQuery struct { // Permissions // --------------------- -type DataSourcePermissionType int +type DsPermissionType int const ( - DsPermissionQuery DataSourcePermissionType = 1 << iota + DsPermissionQuery DsPermissionType = 1 << iota DsPermissionAdmin + DsPermissionNoAccess ) -func (p DataSourcePermissionType) String() string { +func (p DsPermissionType) String() string { names := map[int]string{ - int(DsPermissionQuery): "Query", - int(DsPermissionAdmin): "Admin", + int(DsPermissionQuery): "Query", + int(DsPermissionAdmin): "Admin", + int(DsPermissionNoAccess): "No Access", } return names[int(p)] } @@ -208,5 +210,10 @@ func (p DataSourcePermissionType) String() string { type HasRequiredDataSourcePermissionQuery struct { Id int64 User *SignedInUser - RequiredPermission DataSourcePermissionType + RequiredPermission DsPermissionType +} + +type GetDataSourcePermissionsForUserQuery struct { + User *SignedInUser + Result map[int64]DsPermissionType } From 6c41cfb72c3394a1e6a8513b55afdaf4d91312d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Oct 2018 16:14:29 +0200 Subject: [PATCH 4/4] wip: made sqlstore dialect accessable from outside --- pkg/services/sqlstore/sqlstore.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 5477bc7b2d1..619b6862f6c 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -53,6 +53,7 @@ type SqlStore struct { dbCfg DatabaseConfig engine *xorm.Engine log log.Logger + Dialect migrator.Dialect skipEnsureAdmin bool } @@ -125,10 +126,12 @@ func (ss *SqlStore) Init() error { } ss.engine = engine + ss.Dialect = migrator.NewDialect(ss.engine) // temporarily still set global var x = engine - dialect = migrator.NewDialect(x) + dialect = ss.Dialect + migrator := migrator.NewMigrator(x) migrations.AddMigrations(migrator) @@ -347,7 +350,11 @@ func InitTestDB(t *testing.T) *SqlStore { t.Fatalf("Failed to init test database: %v", err) } - dialect = migrator.NewDialect(engine) + sqlstore.Dialect = migrator.NewDialect(engine) + + // temp global var until we get rid of global vars + dialect = sqlstore.Dialect + if err := dialect.CleanDB(); err != nil { t.Fatalf("Failed to clean test db %v", err) }