@@ -28,11 +28,13 @@ import (
2828 "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
2929 "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
3030 dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
31+ "github.com/grafana/grafana/pkg/services/datasources"
3132 "github.com/grafana/grafana/pkg/services/featuremgmt"
3233 "github.com/grafana/grafana/pkg/services/ngalert/image"
3334 "github.com/grafana/grafana/pkg/services/org"
3435 "github.com/grafana/grafana/pkg/services/queryhistory"
3536 "github.com/grafana/grafana/pkg/services/shorturls"
37+ "github.com/grafana/grafana/pkg/services/team"
3638 tempuser "github.com/grafana/grafana/pkg/services/temp_user"
3739 "github.com/grafana/grafana/pkg/setting"
3840)
@@ -58,12 +60,14 @@ type CleanUpService struct {
5860 alertRuleService AlertRuleService
5961 clientConfigProvider grafanaapiserver.RestConfigProvider
6062 orgService org.Service
63+ teamService team.Service
64+ dataSourceService datasources.DataSourceService
6165}
6266
6367func ProvideService (cfg * setting.Cfg , Features featuremgmt.FeatureToggles , serverLockService * serverlock.ServerLockService ,
6468 shortURLService shorturls.Service , sqlstore db.DB , queryHistoryService queryhistory.Service ,
6569 dashboardVersionService dashver.Service , dashSnapSvc dashboardsnapshots.Service , deleteExpiredImageService * image.DeleteExpiredService ,
66- tempUserService tempuser.Service , tracer tracing.Tracer , annotationCleaner annotations.Cleaner , service AlertRuleService , clientConfigProvider grafanaapiserver.RestConfigProvider , orgService org.Service ) * CleanUpService {
70+ tempUserService tempuser.Service , tracer tracing.Tracer , annotationCleaner annotations.Cleaner , service AlertRuleService , clientConfigProvider grafanaapiserver.RestConfigProvider , orgService org.Service , teamService team. Service , dataSourceService datasources. DataSourceService ) * CleanUpService {
6771 s := & CleanUpService {
6872 Cfg : cfg ,
6973 Features : Features ,
@@ -81,6 +85,8 @@ func ProvideService(cfg *setting.Cfg, Features featuremgmt.FeatureToggles, serve
8185 alertRuleService : service ,
8286 clientConfigProvider : clientConfigProvider ,
8387 orgService : orgService ,
88+ teamService : teamService ,
89+ dataSourceService : dataSourceService ,
8490 }
8591 return s
8692}
@@ -125,6 +131,7 @@ func (srv *CleanUpService) clean(ctx context.Context) {
125131 {"expire old user invites" , srv .expireOldUserInvites },
126132 {"delete stale query history" , srv .deleteStaleQueryHistory },
127133 {"expire old email verifications" , srv .expireOldVerifications },
134+ {"cleanup stale LBAC rules" , srv .cleanupStaleLBACRules },
128135 }
129136
130137 if srv .Cfg .ShortLinkExpiration > 0 {
@@ -418,3 +425,133 @@ func (srv *CleanUpService) cleanUpTrashAlertRules(ctx context.Context) {
418425 logger .Debug ("Cleaned up deleted alert rules" , "rows affected" , affected )
419426 }
420427}
428+
429+ // cleanupStaleLBACRules exists to clean up lbac rules that are stale from teams getting deleted as we do not have
430+ // cascading deletions on teams to delete existing lbac rules
431+ func (srv * CleanUpService ) cleanupStaleLBACRules (ctx context.Context ) {
432+ logger := srv .log .FromContext (ctx )
433+
434+ // Get all datasources
435+ allDataSources , err := srv .dataSourceService .GetAllDataSources (ctx , & datasources.GetAllDataSourcesQuery {})
436+ if err != nil {
437+ logger .Error ("Failed to get datasources for LBAC cleanup" , "error" , err )
438+ return
439+ }
440+
441+ var totalCleaned int
442+ var totalDataSources int
443+
444+ for _ , ds := range allDataSources {
445+ if ds .JsonData == nil {
446+ continue
447+ }
448+
449+ // Check if datasource has team LBAC rules
450+ teamHTTPHeaders , err := datasources .GetTeamHTTPHeaders (ds .JsonData )
451+ if err != nil || teamHTTPHeaders == nil {
452+ continue
453+ }
454+
455+ totalDataSources ++
456+
457+ // Extract team UIDs and check if teams still exist
458+ cleanedRules , removedCount := srv .getLBACRulesForTeamsStillExisting (ctx , teamHTTPHeaders , ds .OrgID )
459+
460+ if removedCount > 0 {
461+ // Update the datasource with cleaned rules
462+ err := srv .updateDataSourceLBACRules (ctx , ds , cleanedRules )
463+ if err != nil {
464+ logger .Error ("Failed to update datasource LBAC rules" ,
465+ "datasource" , ds .UID , "error" , err )
466+ } else {
467+ totalCleaned += removedCount
468+ logger .Debug ("Cleaned stale LBAC rules" ,
469+ "datasource" , ds .UID , "removed" , removedCount )
470+ }
471+ }
472+ }
473+
474+ if totalCleaned > 0 {
475+ logger .Info ("Cleaned up stale team LBAC rules" ,
476+ "datasources_processed" , totalDataSources ,
477+ "total_rules_removed" , totalCleaned )
478+ }
479+ }
480+
481+ func (srv * CleanUpService ) getLBACRulesForTeamsStillExisting (ctx context.Context , teamHeaders * datasources.TeamHTTPHeaders , orgID int64 ) (* datasources.TeamHTTPHeaders , int ) {
482+ logger := srv .log .FromContext (ctx )
483+ cleanedHeaders := & datasources.TeamHTTPHeaders {Headers : make (map [string ][]datasources.TeamHTTPHeader )}
484+ removedCount := 0
485+
486+ for teamIdentifier , headers := range teamHeaders .Headers {
487+ // Determine if this is a UID or ID
488+ var teamUID string
489+ teamID , err := strconv .ParseInt (teamIdentifier , 10 , 64 )
490+
491+ if err != nil {
492+ // It's a UID
493+ teamUID = teamIdentifier
494+ } else {
495+ // It's an ID, need to resolve to UID
496+ teamByID , err := srv .teamService .GetTeamByID (ctx , & team.GetTeamByIDQuery {
497+ OrgID : orgID ,
498+ ID : teamID ,
499+ })
500+ if err != nil {
501+ logger .Debug ("Team ID no longer exists, removing LBAC rules" ,
502+ "teamID" , teamIdentifier , "orgID" , orgID )
503+ removedCount ++
504+ continue
505+ }
506+ teamUID = teamByID .UID
507+ }
508+
509+ // Check if team still exists by UID
510+ _ , err = srv .teamService .GetTeamByID (ctx , & team.GetTeamByIDQuery {
511+ OrgID : orgID ,
512+ UID : teamUID ,
513+ })
514+
515+ if err != nil {
516+ logger .Debug ("Team UID no longer exists, removing LBAC rules" ,
517+ "teamUID" , teamUID , "orgID" , orgID )
518+ removedCount ++
519+ continue
520+ }
521+
522+ // Team exists, keep the rules
523+ cleanedHeaders .Headers [teamIdentifier ] = headers
524+ }
525+
526+ return cleanedHeaders , removedCount
527+ }
528+
529+ func (srv * CleanUpService ) updateDataSourceLBACRules (ctx context.Context , ds * datasources.DataSource , cleanedHeaders * datasources.TeamHTTPHeaders ) error {
530+ // Update JsonData with cleaned rules
531+ jsonData := ds .JsonData
532+ jsonData .Set ("teamHttpHeaders" , cleanedHeaders )
533+
534+ updateCmd := & datasources.UpdateDataSourceCommand {
535+ ID : ds .ID ,
536+ OrgID : ds .OrgID ,
537+ UID : ds .UID ,
538+ Name : ds .Name ,
539+ Type : ds .Type ,
540+ Access : ds .Access ,
541+ URL : ds .URL ,
542+ User : ds .User ,
543+ Database : ds .Database ,
544+ BasicAuth : ds .BasicAuth ,
545+ BasicAuthUser : ds .BasicAuthUser ,
546+ WithCredentials : ds .WithCredentials ,
547+ IsDefault : ds .IsDefault ,
548+ JsonData : jsonData ,
549+ AllowLBACRuleUpdates : true ,
550+ Version : ds .Version ,
551+ ReadOnly : ds .ReadOnly ,
552+ APIVersion : ds .APIVersion ,
553+ }
554+
555+ _ , err := srv .dataSourceService .UpdateDataSource (ctx , updateCmd )
556+ return err
557+ }
0 commit comments