2023-11-14 18:11:01 -05:00
package accesscontrol
import (
"context"
2024-06-13 07:11:35 +03:00
"github.com/grafana/grafana/pkg/apimachinery/errutil"
2023-11-14 18:11:01 -05:00
"github.com/grafana/grafana/pkg/infra/db"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
2023-11-22 14:20:22 +01:00
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
2023-11-14 18:11:01 -05: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
}
func NewAuthService ( db db . DB , features featuremgmt . FeatureToggles ) * AuthService {
return & AuthService {
db : db ,
features : features ,
}
}
// 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 09:14:06 +02:00
func ( authz * AuthService ) Authorize ( ctx context . Context , query annotations . ItemQuery ) ( * AccessResources , error ) {
2024-02-21 12:30:26 +03:00
user := query . SignedInUser
2023-11-14 18:11:01 -05: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 10:34:44 +00:00
_ , canAccessOrgAnnotations := scopeTypes [ annotations . Organization . String ()]
_ , canAccessDashAnnotations := scopeTypes [ annotations . Dashboard . String ()]
if authz . features . IsEnabled ( ctx , featuremgmt . FlagAnnotationPermissionUpdate ) {
canAccessDashAnnotations = true
}
2023-11-14 18:11:01 -05:00
var visibleDashboards map [ string ] int64
var err error
2023-11-29 10:34:44 +00:00
if canAccessDashAnnotations {
2024-03-26 11:50:51 +01:00
if query . AnnotationID != 0 {
2024-09-23 17:29:29 +02:00
annotationDashboardID , err := authz . getAnnotationDashboard ( ctx , query )
2024-03-26 11:50:51 +01:00
if err != nil {
return nil , ErrAccessControlInternal . Errorf ( "failed to fetch annotations: %w" , err )
}
query . DashboardID = annotationDashboardID
}
2024-09-23 17:29:29 +02:00
visibleDashboards , err = authz . dashboardsWithVisibleAnnotations ( ctx , query )
2023-11-14 18:11:01 -05:00
if err != nil {
return nil , ErrAccessControlInternal . Errorf ( "failed to fetch dashboards: %w" , err )
}
}
return & AccessResources {
2023-11-29 10:34:44 +00:00
Dashboards : visibleDashboards ,
CanAccessDashAnnotations : canAccessDashAnnotations ,
CanAccessOrgAnnotations : canAccessOrgAnnotations ,
2023-11-14 18:11:01 -05:00
}, nil
}
2024-10-03 09:14:06 +02:00
func ( authz * AuthService ) getAnnotationDashboard ( ctx context . Context , query annotations . ItemQuery ) ( int64 , error ) {
2024-03-26 11:50:51 +01: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,
a.dashboard_id
FROM annotation as a
WHERE a.org_id = ? AND a.id = ?
`
2024-09-23 17:29:29 +02:00
params = append ( params , query . OrgID , query . AnnotationID )
2024-03-26 11:50:51 +01:00
return sess . SQL ( sql , params ... ). Find ( & items )
})
if err != nil {
return 0 , err
}
if len ( items ) == 0 {
return 0 , ErrAccessControlInternal . Errorf ( "annotation not found" )
}
return items [ 0 ]. DashboardID , nil
}
2024-10-03 09:14:06 +02:00
func ( authz * AuthService ) dashboardsWithVisibleAnnotations ( ctx context . Context , query annotations . ItemQuery ) ( map [ string ] int64 , error ) {
2023-11-14 18:11:01 -05:00
recursiveQueriesSupported , err := authz . db . RecursiveQueriesAreSupported ()
if err != nil {
return nil , err
}
2023-11-29 10:34:44 +00:00
filterType := searchstore . TypeDashboard
if authz . features . IsEnabled ( ctx , featuremgmt . FlagAnnotationPermissionUpdate ) {
filterType = searchstore . TypeAnnotation
}
2023-11-14 18:11:01 -05:00
filters := [] any {
2024-02-21 12:30:26 +03:00
permissions . NewAccessControlDashboardPermissionFilter ( query . SignedInUser , dashboardaccess . PERMISSION_VIEW , filterType , authz . features , recursiveQueriesSupported ),
2024-09-23 17:29:29 +02:00
searchstore . OrgFilter { OrgId : query . OrgID },
2023-11-14 18:11:01 -05:00
}
2024-02-21 12:30:26 +03:00
if query . DashboardUID != "" {
filters = append ( filters , searchstore . DashboardFilter {
UIDs : [] string { query . DashboardUID },
})
}
if query . DashboardID != 0 {
filters = append ( filters , searchstore . DashboardIDFilter {
IDs : [] int64 { query . DashboardID },
})
}
2023-11-14 18:11:01 -05:00
sb := & searchstore . Builder { Dialect : authz . db . GetDialect (), Filters : filters , Features : authz . features }
2024-09-23 17:29:29 +02:00
// This is a limit for a batch size, not for the end query result.
2023-11-14 18:11:01 -05:00
var limit int64 = 1000
2024-09-23 17:29:29 +02:00
if query . Page == 0 {
query . Page = 1
}
sql , params := sb . ToSQL ( limit , query . Page )
2023-11-14 18:11:01 -05:00
2024-09-23 17:29:29 +02:00
visibleDashboards := make ( map [ string ] int64 )
var res [] dashboardProjection
2023-11-14 18:11:01 -05:00
2024-09-23 17:29:29 +02:00
err = authz . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
return sess . SQL ( sql , params ... ). Find ( & res )
})
if err != nil {
return nil , err
}
2023-11-14 18:11:01 -05:00
2024-09-23 17:29:29 +02:00
for _ , p := range res {
visibleDashboards [ p . UID ] = p . ID
2023-11-14 18:11:01 -05: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
}