mirror of https://github.com/grafana/grafana.git
				
				
				
			Annotations: Update annotation scope resolver to resolve annotation scopes to dash and folder scopes (#78222)
* update annotation scope resolver to resolve dashboard annotation scopes to dash and folder scopes * Update annotations.go remove unwanted changes * remove unwanted change * use switch statement
This commit is contained in:
		
							parent
							
								
									36fd9040af
								
							
						
					
					
						commit
						2a5547e1b5
					
				|  | @ -14,6 +14,8 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/auth/identity" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder" | ||||
| 	"github.com/grafana/grafana/pkg/services/guardian" | ||||
| 	"github.com/grafana/grafana/pkg/services/user" | ||||
| 	"github.com/grafana/grafana/pkg/util" | ||||
|  | @ -578,7 +580,9 @@ func (hs *HTTPServer) GetAnnotationTags(c *contextmodel.ReqContext) response.Res | |||
| // AnnotationTypeScopeResolver provides an ScopeAttributeResolver able to
 | ||||
| // resolve annotation types. Scope "annotations:id:<id>" will be translated to "annotations:type:<type>,
 | ||||
| // where <type> is the type of annotation with id <id>.
 | ||||
| func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository) (string, accesscontrol.ScopeAttributeResolver) { | ||||
| // If annotationPermissionUpdate feature toggle is enabled, dashboard annotation scope will be resolved to the corresponding
 | ||||
| // dashboard and folder scopes (eg, "dashboards:uid:<annotation_dashboard_uid>", "folders:uid:<parent_folder_uid>" etc).
 | ||||
| func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository, features *featuremgmt.FeatureManager, dashSvc dashboards.DashboardService, folderSvc folder.Service) (string, accesscontrol.ScopeAttributeResolver) { | ||||
| 	prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("") | ||||
| 	return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) { | ||||
| 		scopeParts := strings.Split(initialScope, ":") | ||||
|  | @ -604,15 +608,51 @@ func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository) (string | |||
| 			}, | ||||
| 		} | ||||
| 
 | ||||
| 		if features.IsEnabled(ctx, featuremgmt.FlagAnnotationPermissionUpdate) { | ||||
| 			tempUser = &user.SignedInUser{ | ||||
| 				OrgID: orgID, | ||||
| 				Permissions: map[int64]map[string][]string{ | ||||
| 					orgID: { | ||||
| 						accesscontrol.ActionAnnotationsRead: {accesscontrol.ScopeAnnotationsTypeOrganization, dashboards.ScopeDashboardsAll}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		annotation, resp := findAnnotationByID(ctx, annotationsRepo, int64(annotationId), tempUser) | ||||
| 		if resp != nil { | ||||
| 			return nil, errors.New("could not resolve annotation type") | ||||
| 		} | ||||
| 
 | ||||
| 		if annotation.GetType() == annotations.Organization { | ||||
| 		if !features.IsEnabled(ctx, featuremgmt.FlagAnnotationPermissionUpdate) { | ||||
| 			switch annotation.GetType() { | ||||
| 			case annotations.Organization: | ||||
| 				return []string{accesscontrol.ScopeAnnotationsTypeOrganization}, nil | ||||
| 			case annotations.Dashboard: | ||||
| 				return []string{accesscontrol.ScopeAnnotationsTypeDashboard}, nil | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if annotation.DashboardID == 0 { | ||||
| 			return []string{accesscontrol.ScopeAnnotationsTypeOrganization}, nil | ||||
| 		} else { | ||||
| 			return []string{accesscontrol.ScopeAnnotationsTypeDashboard}, nil | ||||
| 			dashboard, err := dashSvc.GetDashboard(ctx, &dashboards.GetDashboardQuery{ID: annotation.DashboardID, OrgID: orgID}) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			scopes := []string{dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashboard.UID)} | ||||
| 			// Append dashboard parent scopes if dashboard is in a folder or the general scope if dashboard is not in a folder
 | ||||
| 			if dashboard.FolderUID != "" { | ||||
| 				scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(dashboard.FolderUID)) | ||||
| 				inheritedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, dashboard.FolderUID, folderSvc) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				scopes = append(scopes, inheritedScopes...) | ||||
| 			} else { | ||||
| 				scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)) | ||||
| 			} | ||||
| 			return scopes, nil | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -14,6 +14,9 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations/annotationstest" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder/foldertest" | ||||
| 	"github.com/grafana/grafana/pkg/services/guardian" | ||||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
| 	"github.com/grafana/grafana/pkg/web/webtest" | ||||
|  | @ -231,7 +234,10 @@ func TestAPI_Annotations(t *testing.T) { | |||
| 				_ = repo.Save(context.Background(), &annotations.Item{ID: 2, DashboardID: 1}) | ||||
| 				hs.annotationsRepo = repo | ||||
| 				hs.AccessControl = acimpl.ProvideAccessControl(hs.Cfg) | ||||
| 				hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo)) | ||||
| 				features := featuremgmt.WithFeatures() | ||||
| 				dashSvc := &dashboards.FakeDashboardService{} | ||||
| 				folderSvc := &foldertest.FakeService{} | ||||
| 				hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, features, dashSvc, folderSvc)) | ||||
| 			}) | ||||
| 			var body io.Reader | ||||
| 			if tt.body != "" { | ||||
|  | @ -247,60 +253,100 @@ func TestAPI_Annotations(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| func TestService_AnnotationTypeScopeResolver(t *testing.T) { | ||||
| 	rootDashUID := "root-dashboard" | ||||
| 	folderDashUID := "folder-dashboard" | ||||
| 	folderUID := "folder" | ||||
| 	dashSvc := &dashboards.FakeDashboardService{} | ||||
| 	rootDash := &dashboards.Dashboard{ID: 1, OrgID: 1, UID: rootDashUID} | ||||
| 	folderDash := &dashboards.Dashboard{ID: 2, OrgID: 1, UID: folderDashUID, FolderUID: folderUID} | ||||
| 	dashSvc.On("GetDashboard", context.Background(), &dashboards.GetDashboardQuery{ID: rootDash.ID, OrgID: 1}).Return(rootDash, nil) | ||||
| 	dashSvc.On("GetDashboard", context.Background(), &dashboards.GetDashboardQuery{ID: folderDash.ID, OrgID: 1}).Return(folderDash, nil) | ||||
| 
 | ||||
| 	rootDashboardAnnotation := annotations.Item{ID: 1, DashboardID: rootDash.ID} | ||||
| 	folderDashboardAnnotation := annotations.Item{ID: 3, DashboardID: folderDash.ID} | ||||
| 	organizationAnnotation := annotations.Item{ID: 2} | ||||
| 
 | ||||
| 	fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() | ||||
| 	_ = fakeAnnoRepo.Save(context.Background(), &rootDashboardAnnotation) | ||||
| 	_ = fakeAnnoRepo.Save(context.Background(), &folderDashboardAnnotation) | ||||
| 	_ = fakeAnnoRepo.Save(context.Background(), &organizationAnnotation) | ||||
| 
 | ||||
| 	type testCaseResolver struct { | ||||
| 		desc    string | ||||
| 		given   string | ||||
| 		want    string | ||||
| 		wantErr error | ||||
| 		desc           string | ||||
| 		given          string | ||||
| 		featureToggles []any | ||||
| 		want           []string | ||||
| 		wantErr        error | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []testCaseResolver{ | ||||
| 		{ | ||||
| 			desc:    "correctly resolves dashboard annotations", | ||||
| 			given:   "annotations:id:1", | ||||
| 			want:    accesscontrol.ScopeAnnotationsTypeDashboard, | ||||
| 			want:    []string{accesscontrol.ScopeAnnotationsTypeDashboard}, | ||||
| 			wantErr: nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:    "correctly resolves organization annotations", | ||||
| 			given:   "annotations:id:2", | ||||
| 			want:    accesscontrol.ScopeAnnotationsTypeOrganization, | ||||
| 			want:    []string{accesscontrol.ScopeAnnotationsTypeOrganization}, | ||||
| 			wantErr: nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:    "invalid annotation ID", | ||||
| 			given:   "annotations:id:123abc", | ||||
| 			want:    "", | ||||
| 			want:    []string{""}, | ||||
| 			wantErr: accesscontrol.ErrInvalidScope, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:    "malformed scope", | ||||
| 			given:   "annotations:1", | ||||
| 			want:    "", | ||||
| 			want:    []string{""}, | ||||
| 			wantErr: accesscontrol.ErrInvalidScope, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:           "correctly resolves organization annotations with feature toggle", | ||||
| 			given:          "annotations:id:2", | ||||
| 			featureToggles: []any{featuremgmt.FlagAnnotationPermissionUpdate}, | ||||
| 			want:           []string{accesscontrol.ScopeAnnotationsTypeOrganization}, | ||||
| 			wantErr:        nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:           "correctly resolves annotations from root dashboard with feature toggle", | ||||
| 			given:          "annotations:id:1", | ||||
| 			featureToggles: []any{featuremgmt.FlagAnnotationPermissionUpdate}, | ||||
| 			want: []string{ | ||||
| 				dashboards.ScopeDashboardsProvider.GetResourceScopeUID(rootDashUID), | ||||
| 				dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID), | ||||
| 			}, | ||||
| 			wantErr: nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:           "correctly resolves annotations from dashboard in a folder with feature toggle", | ||||
| 			given:          "annotations:id:3", | ||||
| 			featureToggles: []any{featuremgmt.FlagAnnotationPermissionUpdate}, | ||||
| 			want: []string{ | ||||
| 				dashboards.ScopeDashboardsProvider.GetResourceScopeUID(folderDashUID), | ||||
| 				dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID), | ||||
| 			}, | ||||
| 			wantErr: nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	dashboardAnnotation := annotations.Item{ID: 1, DashboardID: 1} | ||||
| 	organizationAnnotation := annotations.Item{ID: 2} | ||||
| 
 | ||||
| 	fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() | ||||
| 	_ = fakeAnnoRepo.Save(context.Background(), &dashboardAnnotation) | ||||
| 	_ = fakeAnnoRepo.Save(context.Background(), &organizationAnnotation) | ||||
| 
 | ||||
| 	prefix, resolver := AnnotationTypeScopeResolver(fakeAnnoRepo) | ||||
| 	require.Equal(t, "annotations:id:", prefix) | ||||
| 
 | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.desc, func(t *testing.T) { | ||||
| 			features := featuremgmt.WithFeatures(tc.featureToggles...) | ||||
| 			prefix, resolver := AnnotationTypeScopeResolver(fakeAnnoRepo, features, dashSvc, &foldertest.FakeService{}) | ||||
| 			require.Equal(t, "annotations:id:", prefix) | ||||
| 
 | ||||
| 			resolved, err := resolver.Resolve(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.Len(t, resolved, 1) | ||||
| 				require.Equal(t, tc.want, resolved[0]) | ||||
| 				require.Len(t, resolved, len(tc.want)) | ||||
| 				require.Equal(t, tc.want, resolved) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  |  | |||
|  | @ -358,7 +358,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi | |||
| 	hs.registerRoutes() | ||||
| 
 | ||||
| 	// Register access control scope resolver for annotations
 | ||||
| 	hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo)) | ||||
| 	hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, features, dashboardService, folderService)) | ||||
| 
 | ||||
| 	if err := hs.declareFixedRoles(); err != nil { | ||||
| 		return nil, err | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue