diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 9115827c8be..71e18314d72 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -9,8 +9,6 @@ import ( "sort" "strconv" - "github.com/grafana/grafana/pkg/services/datasources/permissions" - "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/dtos" @@ -18,6 +16,8 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/adapters" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/datasources/permissions" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -100,7 +100,7 @@ func (hs *HTTPServer) GetDataSourceById(c *models.ReqContext) response.Response dto := convertModelToDtos(filtered[0]) // Add accesscontrol metadata - dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, "datasources:id:", strconv.FormatInt(dto.Id, 10)) + dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, datasources.ScopePrefix, dto.UID) return response.JSON(200, &dto) } @@ -159,7 +159,7 @@ func (hs *HTTPServer) GetDataSourceByUID(c *models.ReqContext) response.Response dto := convertModelToDtos(filtered[0]) // Add accesscontrol metadata - dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, "datasources:id:", strconv.FormatInt(dto.Id, 10)) + dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, datasources.ScopePrefix, dto.UID) return response.JSON(200, &dto) } diff --git a/pkg/api/docs/definitions/datasources.go b/pkg/api/docs/definitions/datasources.go index bd7a5c7bdb7..c31930756c4 100644 --- a/pkg/api/docs/definitions/datasources.go +++ b/pkg/api/docs/definitions/datasources.go @@ -46,7 +46,7 @@ import ( // encrypted fields are listed under secureJsonFields section in the response. // // If you are running Grafana Enterprise and have Fine-grained access control enabled -// you need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source). +// you need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source). // // Responses: // 200: createOrUpdateDatasourceResponse @@ -59,7 +59,7 @@ import ( // Delete an existing data source by id. // // If you are running Grafana Enterprise and have Fine-grained access control enabled -// you need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source). +// you need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source). // // Responses: // 200: okResponse @@ -101,7 +101,7 @@ import ( // Get a single data source by Id. // // If you are running Grafana Enterprise and have Fine-grained access control enabled -// you need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source). +// you need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source). // // Responses: // 200: getDatasourceResponse diff --git a/pkg/services/datasources/accesscontrol.go b/pkg/services/datasources/accesscontrol.go index 507d0a4ada6..30dc2d1d213 100644 --- a/pkg/services/datasources/accesscontrol.go +++ b/pkg/services/datasources/accesscontrol.go @@ -3,7 +3,8 @@ package datasources import "github.com/grafana/grafana/pkg/services/accesscontrol" const ( - ScopeRoot = "datasources" + ScopeRoot = "datasources" + ScopePrefix = ScopeRoot + ":uid:" ActionRead = "datasources:read" ActionQuery = "datasources:query" diff --git a/pkg/services/datasources/service/datasource_service.go b/pkg/services/datasources/service/datasource_service.go index 4b98fd64209..e48dd02ba85 100644 --- a/pkg/services/datasources/service/datasource_service.go +++ b/pkg/services/datasources/service/datasource_service.go @@ -83,7 +83,7 @@ func ProvideService( s.Bus.AddHandler(s.GetDefaultDataSource) ac.RegisterAttributeScopeResolver(NewNameScopeResolver(store)) - ac.RegisterAttributeScopeResolver(NewUidScopeResolver(store)) + ac.RegisterAttributeScopeResolver(NewIDScopeResolver(store)) return s } @@ -95,10 +95,10 @@ type DataSourceRetriever interface { } // NewNameScopeResolver provides an AttributeScopeResolver able to -// translate a scope prefixed with "datasources:name:" into an id based scope. +// translate a scope prefixed with "datasources:name:" into an uid based scope. func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) { prefix := datasources.ScopeProvider.GetResourceScopeName("") - dsNameResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) { + return prefix, func(ctx context.Context, orgID int64, initialScope string) (string, error) { if !strings.HasPrefix(initialScope, prefix) { return "", accesscontrol.ErrInvalidScope } @@ -113,35 +113,36 @@ func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.Attribu return "", err } - return datasources.ScopeProvider.GetResourceScope(strconv.FormatInt(query.Result.Id, 10)), nil + return datasources.ScopeProvider.GetResourceScopeUID(query.Result.Uid), nil } - - return prefix, dsNameResolver } -// NewUidScopeResolver provides an AttributeScopeResolver able to -// translate a scope prefixed with "datasources:uid:" into an id based scope. -func NewUidScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) { - prefix := datasources.ScopeProvider.GetResourceScopeUID("") - dsUIDResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) { +// NewIDScopeResolver provides an AttributeScopeResolver able to +// translate a scope prefixed with "datasources:id:" into an uid based scope. +func NewIDScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) { + prefix := datasources.ScopeProvider.GetResourceScope("") + return prefix, func(ctx context.Context, orgID int64, initialScope string) (string, error) { if !strings.HasPrefix(initialScope, prefix) { return "", accesscontrol.ErrInvalidScope } - dsUID := initialScope[len(prefix):] - if dsUID == "" { + id := initialScope[len(prefix):] + if id == "" { return "", accesscontrol.ErrInvalidScope } - query := models.GetDataSourceQuery{Uid: dsUID, OrgId: orgID} + dsID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + return "", accesscontrol.ErrInvalidScope + } + + query := models.GetDataSourceQuery{Id: dsID, OrgId: orgID} if err := db.GetDataSource(ctx, &query); err != nil { return "", err } - return datasources.ScopeProvider.GetResourceScope(strconv.FormatInt(query.Result.Id, 10)), nil + return datasources.ScopeProvider.GetResourceScopeUID(query.Result.Uid), nil } - - return prefix, dsUIDResolver } func (s *Service) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { @@ -180,7 +181,7 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCo if cmd.UserId != 0 { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{UserID: cmd.UserId, Permission: "Edit"}) } - if _, err := s.permissionsService.SetPermissions(ctx, cmd.OrgId, strconv.FormatInt(cmd.Result.Id, 10), permissions...); err != nil { + if _, err := s.permissionsService.SetPermissions(ctx, cmd.OrgId, cmd.Result.Uid, permissions...); err != nil { return err } } diff --git a/pkg/services/datasources/service/datasource_service_test.go b/pkg/services/datasources/service/datasource_service_test.go index 75a9abe1f97..c00fb0b89ce 100644 --- a/pkg/services/datasources/service/datasource_service_test.go +++ b/pkg/services/datasources/service/datasource_service_test.go @@ -75,9 +75,10 @@ type dataSourceMockRetriever struct { func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { for _, datasource := range d.res { - nameMatch := query.Name != "" && query.Name == datasource.Name + idMatch := query.Id != 0 && query.Id == datasource.Id uidMatch := query.Uid != "" && query.Uid == datasource.Uid - if nameMatch || uidMatch { + nameMatch := query.Name != "" && query.Name == datasource.Name + if idMatch || nameMatch || uidMatch { query.Result = datasource return nil @@ -88,10 +89,10 @@ func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *mode func TestService_NameScopeResolver(t *testing.T) { retriever := &dataSourceMockRetriever{[]*models.DataSource{ - {Id: 1, Name: "test-datasource"}, - {Id: 2, Name: "*"}, - {Id: 3, Name: ":/*"}, - {Id: 4, Name: ":"}, + {Name: "test-datasource", Uid: "1"}, + {Name: "*", Uid: "2"}, + {Name: ":/*", Uid: "3"}, + {Name: ":", Uid: "4"}, }} type testCaseResolver struct { @@ -105,25 +106,25 @@ func TestService_NameScopeResolver(t *testing.T) { { desc: "correct", given: "datasources:name:test-datasource", - want: "datasources:id:1", + want: "datasources:uid:1", wantErr: nil, }, { desc: "asterisk in name", given: "datasources:name:*", - want: "datasources:id:2", + want: "datasources:uid:2", wantErr: nil, }, { desc: "complex name", given: "datasources:name::/*", - want: "datasources:id:3", + want: "datasources:uid:3", wantErr: nil, }, { desc: "colon in name", given: "datasources:name::", - want: "datasources:id:4", + want: "datasources:uid:4", wantErr: nil, }, { @@ -162,7 +163,7 @@ func TestService_NameScopeResolver(t *testing.T) { } } -func TestService_UIDScopeResolver(t *testing.T) { +func TestService_IDScopeResolver(t *testing.T) { retriever := &dataSourceMockRetriever{[]*models.DataSource{ {Id: 1, Uid: "NnftN9Lnz"}, }} @@ -177,15 +178,15 @@ func TestService_UIDScopeResolver(t *testing.T) { testCases := []testCaseResolver{ { desc: "correct", - given: "datasources:uid:NnftN9Lnz", - want: "datasources:id:1", + given: "datasources:id:1", + want: "datasources:uid:NnftN9Lnz", wantErr: nil, }, { desc: "unknown datasource", - given: "datasources:uid:unknown", + given: "datasources:id:unknown", want: "", - wantErr: models.ErrDataSourceNotFound, + wantErr: accesscontrol.ErrInvalidScope, }, { desc: "malformed scope", @@ -195,13 +196,13 @@ func TestService_UIDScopeResolver(t *testing.T) { }, { desc: "empty uid scope", - given: "datasources:uid:", + given: "datasources:id:", want: "", wantErr: accesscontrol.ErrInvalidScope, }, } - prefix, resolver := NewUidScopeResolver(retriever) - require.Equal(t, "datasources:uid:", prefix) + prefix, resolver := NewIDScopeResolver(retriever) + require.Equal(t, "datasources:id:", prefix) for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) {