2023-11-15 07:11:01 +08:00
package accesscontrol
import (
"context"
2024-06-13 12:11:35 +08:00
"github.com/grafana/grafana/pkg/apimachinery/errutil"
2023-11-15 07:11:01 +08:00
"github.com/grafana/grafana/pkg/infra/db"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
2025-01-10 22:35:38 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2023-11-22 21:20:22 +08:00
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
2023-11-15 07:11:01 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
)
var (
ErrReadForbidden = errutil . NewBase (
errutil . StatusForbidden ,
"annotations.accesscontrol.read" ,
errutil . WithPublicMessage ( "User missing permissions" ) ,
)
ErrAccessControlInternal = errutil . NewBase (
errutil . StatusInternal ,
"annotations.accesscontrol.internal" ,
errutil . WithPublicMessage ( "Internal error while checking permissions" ) ,
)
)
type AuthService struct {
db db . DB
features featuremgmt . FeatureToggles
2025-01-10 22:35:38 +08:00
dashSvc dashboards . DashboardService
2023-11-15 07:11:01 +08:00
}
2025-01-10 22:35:38 +08:00
func NewAuthService ( db db . DB , features featuremgmt . FeatureToggles , dashSvc dashboards . DashboardService ) * AuthService {
2023-11-15 07:11:01 +08:00
return & AuthService {
db : db ,
features : features ,
2025-01-10 22:35:38 +08:00
dashSvc : dashSvc ,
2023-11-15 07:11:01 +08:00
}
}
// Authorize checks if the user has permission to read annotations, then returns a struct containing dashboards and scope types that the user has access to.
2024-10-03 15:14:06 +08:00
func ( authz * AuthService ) Authorize ( ctx context . Context , query annotations . ItemQuery ) ( * AccessResources , error ) {
2024-02-21 17:30:26 +08:00
user := query . SignedInUser
2023-11-15 07:11:01 +08:00
if user == nil || user . IsNil ( ) {
return nil , ErrReadForbidden . Errorf ( "missing user" )
}
scopes , has := user . GetPermissions ( ) [ ac . ActionAnnotationsRead ]
if ! has {
return nil , ErrReadForbidden . Errorf ( "user does not have permission to read annotations" )
}
scopeTypes := annotationScopeTypes ( scopes )
2023-11-29 18:34:44 +08:00
_ , canAccessOrgAnnotations := scopeTypes [ annotations . Organization . String ( ) ]
_ , canAccessDashAnnotations := scopeTypes [ annotations . Dashboard . String ( ) ]
if authz . features . IsEnabled ( ctx , featuremgmt . FlagAnnotationPermissionUpdate ) {
canAccessDashAnnotations = true
}
2023-11-15 07:11:01 +08:00
var visibleDashboards map [ string ] int64
var err error
2023-11-29 18:34:44 +08:00
if canAccessDashAnnotations {
2024-03-26 18:50:51 +08:00
if query . AnnotationID != 0 {
2025-06-14 02:59:24 +08:00
annotationDashboardUID , err := authz . getAnnotationDashboard ( ctx , query )
2024-03-26 18:50:51 +08:00
if err != nil {
return nil , ErrAccessControlInternal . Errorf ( "failed to fetch annotations: %w" , err )
}
2025-06-14 02:59:24 +08:00
query . DashboardUID = annotationDashboardUID
2024-03-26 18:50:51 +08:00
}
2024-09-23 23:29:29 +08:00
visibleDashboards , err = authz . dashboardsWithVisibleAnnotations ( ctx , query )
2023-11-15 07:11:01 +08:00
if err != nil {
return nil , ErrAccessControlInternal . Errorf ( "failed to fetch dashboards: %w" , err )
}
}
return & AccessResources {
2023-11-29 18:34:44 +08:00
Dashboards : visibleDashboards ,
CanAccessDashAnnotations : canAccessDashAnnotations ,
CanAccessOrgAnnotations : canAccessOrgAnnotations ,
2023-11-15 07:11:01 +08:00
} , nil
}
2025-06-14 02:59:24 +08:00
func ( authz * AuthService ) getAnnotationDashboard ( ctx context . Context , query annotations . ItemQuery ) ( string , error ) {
2024-03-26 18:50:51 +08:00
var items [ ] annotations . Item
params := make ( [ ] any , 0 )
err := authz . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
sql := `
SELECT
a . id ,
a . org_id ,
2025-06-14 02:59:24 +08:00
a . dashboard_uid
2024-03-26 18:50:51 +08:00
FROM annotation as a
WHERE a . org_id = ? AND a . id = ?
`
2024-09-23 23:29:29 +08:00
params = append ( params , query . OrgID , query . AnnotationID )
2024-03-26 18:50:51 +08:00
return sess . SQL ( sql , params ... ) . Find ( & items )
} )
if err != nil {
2025-06-14 02:59:24 +08:00
return "" , err
2024-03-26 18:50:51 +08:00
}
if len ( items ) == 0 {
2025-06-14 02:59:24 +08:00
return "" , ErrAccessControlInternal . Errorf ( "annotation not found" )
2024-03-26 18:50:51 +08:00
}
2025-06-14 02:59:24 +08:00
return items [ 0 ] . DashboardUID , nil
2024-03-26 18:50:51 +08:00
}
2024-10-03 15:14:06 +08:00
func ( authz * AuthService ) dashboardsWithVisibleAnnotations ( ctx context . Context , query annotations . ItemQuery ) ( map [ string ] int64 , error ) {
2023-11-15 07:11:01 +08:00
recursiveQueriesSupported , err := authz . db . RecursiveQueriesAreSupported ( )
if err != nil {
return nil , err
}
2023-11-29 18:34:44 +08:00
filterType := searchstore . TypeDashboard
if authz . features . IsEnabled ( ctx , featuremgmt . FlagAnnotationPermissionUpdate ) {
filterType = searchstore . TypeAnnotation
}
2023-11-15 07:11:01 +08:00
filters := [ ] any {
2025-03-10 19:33:52 +08:00
permissions . NewAccessControlDashboardPermissionFilter ( query . SignedInUser , dashboardaccess . PERMISSION_VIEW , filterType , authz . features , recursiveQueriesSupported , authz . db . GetDialect ( ) ) ,
2024-09-23 23:29:29 +08:00
searchstore . OrgFilter { OrgId : query . OrgID } ,
2023-11-15 07:11:01 +08:00
}
2024-02-21 17:30:26 +08:00
if query . DashboardUID != "" {
filters = append ( filters , searchstore . DashboardFilter {
UIDs : [ ] string { query . DashboardUID } ,
} )
}
2025-01-10 22:35:38 +08:00
dashs , err := authz . dashSvc . SearchDashboards ( ctx , & dashboards . FindPersistedDashboardsQuery {
OrgId : query . SignedInUser . GetOrgID ( ) ,
Filters : filters ,
SignedInUser : query . SignedInUser ,
Page : query . Page ,
Type : filterType ,
Limit : 1000 ,
2024-09-23 23:29:29 +08:00
} )
if err != nil {
return nil , err
}
2023-11-15 07:11:01 +08:00
2025-01-10 22:35:38 +08:00
visibleDashboards := make ( map [ string ] int64 )
for _ , d := range dashs {
visibleDashboards [ d . UID ] = d . ID
2023-11-15 07:11:01 +08:00
}
return visibleDashboards , nil
}
func annotationScopeTypes ( scopes [ ] string ) map [ any ] struct { } {
allScopeTypes := map [ any ] struct { } {
annotations . Dashboard . String ( ) : { } ,
annotations . Organization . String ( ) : { } ,
}
types , hasWildcardScope := ac . ParseScopes ( ac . ScopeAnnotationsProvider . GetResourceScopeType ( "" ) , scopes )
if hasWildcardScope {
types = allScopeTypes
}
return types
}