From dfbd23ba7b06b7e84052111344febd01be804e90 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Fri, 11 Mar 2022 09:41:18 +0100 Subject: [PATCH] Accesscontrol: fix data source name resolver and add uid name resolver (#46409) (#46437) * Fix data source scope resolver * Adding ds UID scope resolver * Register UID resolver * use package full name * even if it cannot be empty as of now and is also checked by store, better safe than sorry (cherry picked from commit bd918927b45c0a32b54276975f8f3ca67a5535c6) --- pkg/services/datasources/service.go | 41 ++++++++-- pkg/services/datasources/service_test.go | 100 +++++++++++++++++++++-- 2 files changed, 124 insertions(+), 17 deletions(-) diff --git a/pkg/services/datasources/service.go b/pkg/services/datasources/service.go index ca38e501cef..7edc15bd971 100644 --- a/pkg/services/datasources/service.go +++ b/pkg/services/datasources/service.go @@ -73,6 +73,7 @@ func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, secretsService secret s.Bus.AddHandler(s.GetDefaultDataSource) ac.RegisterAttributeScopeResolver(NewNameScopeResolver(store)) + ac.RegisterAttributeScopeResolver(NewUidScopeResolver(store)) return s } @@ -84,16 +85,15 @@ type DataSourceRetriever interface { // NewNameScopeResolver provides an AttributeScopeResolver able to // translate a scope prefixed with "datasources:name:" into an id based scope. func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) { + prefix := accesscontrol.Scope("datasources", "name", "") dsNameResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) { - dsNames := strings.Split(initialScope, ":") - if dsNames[0] != "datasources" || len(dsNames) != 3 { + if !strings.HasPrefix(initialScope, prefix) { return "", accesscontrol.ErrInvalidScope } - dsName := dsNames[2] - // Special wildcard case - if dsName == "*" { - return accesscontrol.Scope("datasources", "id", "*"), nil + dsName := initialScope[len(prefix):] + if dsName == "" { + return "", accesscontrol.ErrInvalidScope } query := models.GetDataSourceQuery{Name: dsName, OrgId: orgID} @@ -101,10 +101,35 @@ func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.Attribu return "", err } - return accesscontrol.Scope("datasources", "id", fmt.Sprintf("%v", query.Result.Id)), nil + return accesscontrol.Scope("datasources", "id", strconv.FormatInt(query.Result.Id, 10)), nil } - return "datasources:name:", dsNameResolver + 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 := accesscontrol.Scope("datasources", "uid", "") + dsUIDResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) { + if !strings.HasPrefix(initialScope, prefix) { + return "", accesscontrol.ErrInvalidScope + } + + dsUID := initialScope[len(prefix):] + if dsUID == "" { + return "", accesscontrol.ErrInvalidScope + } + + query := models.GetDataSourceQuery{Uid: dsUID, OrgId: orgID} + if err := db.GetDataSource(ctx, &query); err != nil { + return "", err + } + + return accesscontrol.Scope("datasources", "id", strconv.FormatInt(query.Result.Id, 10)), nil + } + + return prefix, dsUIDResolver } func (s *Service) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { diff --git a/pkg/services/datasources/service_test.go b/pkg/services/datasources/service_test.go index 136540fd516..f0073d926e4 100644 --- a/pkg/services/datasources/service_test.go +++ b/pkg/services/datasources/service_test.go @@ -69,19 +69,30 @@ func TestService(t *testing.T) { } type dataSourceMockRetriever struct { - res *models.DataSource + res []*models.DataSource } func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { - if query.Name == d.res.Name { - query.Result = d.res + for _, datasource := range d.res { + nameMatch := query.Name != "" && query.Name == datasource.Name + uidMatch := query.Uid != "" && query.Uid == datasource.Uid + if nameMatch || uidMatch { + query.Result = datasource - return nil + return nil + } } return models.ErrDataSourceNotFound } func TestService_NameScopeResolver(t *testing.T) { + retriever := &dataSourceMockRetriever{[]*models.DataSource{ + {Id: 1, Name: "test-datasource"}, + {Id: 2, Name: "*"}, + {Id: 3, Name: ":/*"}, + {Id: 4, Name: ":"}, + }} + type testCaseResolver struct { desc string given string @@ -97,9 +108,21 @@ func TestService_NameScopeResolver(t *testing.T) { wantErr: nil, }, { - desc: "correct", + desc: "asterisk in name", given: "datasources:name:*", - want: "datasources:id:*", + want: "datasources:id:2", + wantErr: nil, + }, + { + desc: "complex name", + given: "datasources:name::/*", + want: "datasources:id:3", + wantErr: nil, + }, + { + desc: "colon in name", + given: "datasources:name::", + want: "datasources:id:4", wantErr: nil, }, { @@ -114,11 +137,70 @@ func TestService_NameScopeResolver(t *testing.T) { want: "", wantErr: accesscontrol.ErrInvalidScope, }, + { + desc: "empty name scope", + given: "datasources:name:", + want: "", + wantErr: accesscontrol.ErrInvalidScope, + }, + } + prefix, resolver := NewNameScopeResolver(retriever) + require.Equal(t, "datasources:name:", prefix) + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + resolved, err := resolver(context.Background(), 1, tc.given) + if tc.wantErr != nil { + require.Error(t, err) + require.Equal(t, tc.wantErr, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.want, resolved) + } + }) + } +} + +func TestService_UIDScopeResolver(t *testing.T) { + retriever := &dataSourceMockRetriever{[]*models.DataSource{ + {Id: 1, Uid: "NnftN9Lnz"}, + }} + + type testCaseResolver struct { + desc string + given string + want string + wantErr error } - testDataSource := &models.DataSource{Id: 1, Name: "test-datasource"} - prefix, resolver := NewNameScopeResolver(&dataSourceMockRetriever{testDataSource}) - require.Equal(t, "datasources:name:", prefix) + testCases := []testCaseResolver{ + { + desc: "correct", + given: "datasources:uid:NnftN9Lnz", + want: "datasources:id:1", + wantErr: nil, + }, + { + desc: "unknown datasource", + given: "datasources:uid:unknown", + want: "", + wantErr: models.ErrDataSourceNotFound, + }, + { + desc: "malformed scope", + given: "datasources:unknown", + want: "", + wantErr: accesscontrol.ErrInvalidScope, + }, + { + desc: "empty uid scope", + given: "datasources:uid:", + want: "", + wantErr: accesscontrol.ErrInvalidScope, + }, + } + prefix, resolver := NewUidScopeResolver(retriever) + require.Equal(t, "datasources:uid:", prefix) for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) {